LeeT WiFiLamp code and test
Dependencies: ESP8266_WebServer mbed
Fork of WiFiLamp by
main.cpp@26:ea5e0ff46492, 2015-01-07 (annotated)
- Committer:
- leet
- Date:
- Wed Jan 07 21:36:45 2015 +0000
- Revision:
- 26:ea5e0ff46492
- Parent:
- 25:841fc9daffa5
Add favicon, robots.txt and LED Disco Function, no WebGUI yet.
Who changed what in which revision?
User | Revision | Line number | New contents of line |
---|---|---|---|
sschocke | 0:d21e3e1c0a4b | 1 | #include "mbed.h" |
sschocke | 1:f07afcffeb5a | 2 | #include "PololuLedStrip.h" |
sschocke | 7:f15c81074400 | 3 | #include "ESP8266_WebServer.h" |
sschocke | 22:6d7a72fab8ff | 4 | #include "resource.h" |
sschocke | 0:d21e3e1c0a4b | 5 | #include <string> |
sschocke | 13:1e8f27da036a | 6 | //#define DEBUG_WIFI |
sschocke | 0:d21e3e1c0a4b | 7 | |
sschocke | 8:f819de1946a7 | 8 | PololuLedStrip ledStrip(D11); |
sschocke | 1:f07afcffeb5a | 9 | |
sschocke | 8:f819de1946a7 | 10 | #define LED_COUNT 8 |
sschocke | 1:f07afcffeb5a | 11 | rgb_color colors[LED_COUNT]; |
sschocke | 1:f07afcffeb5a | 12 | |
sschocke | 0:d21e3e1c0a4b | 13 | DigitalOut wifiCHPD(D4); |
sschocke | 0:d21e3e1c0a4b | 14 | DigitalOut wifiReset(D9); |
sschocke | 0:d21e3e1c0a4b | 15 | |
sschocke | 0:d21e3e1c0a4b | 16 | int wifiOn = 0; |
sschocke | 23:3563e1699fb9 | 17 | int opMode = 0; |
sschocke | 23:3563e1699fb9 | 18 | std::string stationMAC; |
sschocke | 23:3563e1699fb9 | 19 | std::string stationIP; |
sschocke | 23:3563e1699fb9 | 20 | std::string ssid; |
sschocke | 23:3563e1699fb9 | 21 | std::string apMAC; |
sschocke | 23:3563e1699fb9 | 22 | std::string apIP; |
sschocke | 0:d21e3e1c0a4b | 23 | |
sschocke | 0:d21e3e1c0a4b | 24 | Serial wifiSerial(D8,D2); |
sschocke | 0:d21e3e1c0a4b | 25 | Serial pc(USBTX,USBRX); |
sschocke | 7:f15c81074400 | 26 | ESP8266_WebServer server(&wifiSerial); |
sschocke | 0:d21e3e1c0a4b | 27 | |
sschocke | 22:6d7a72fab8ff | 28 | char temp[200]; |
sschocke | 23:3563e1699fb9 | 29 | const char* VERSION = "0.5-alpha"; |
sschocke | 20:f5a6527bfda6 | 30 | |
sschocke | 20:f5a6527bfda6 | 31 | #ifdef DEBUG_WIFI |
sschocke | 20:f5a6527bfda6 | 32 | void pcrxint(void) { |
sschocke | 23:3563e1699fb9 | 33 | //server.debugBuffers(&pc); |
sschocke | 23:3563e1699fb9 | 34 | pc.putc('@'); |
sschocke | 20:f5a6527bfda6 | 35 | server.echoMode = true; |
sschocke | 20:f5a6527bfda6 | 36 | } |
sschocke | 20:f5a6527bfda6 | 37 | #endif |
sschocke | 20:f5a6527bfda6 | 38 | |
sschocke | 7:f15c81074400 | 39 | void rxint(void) { |
sschocke | 7:f15c81074400 | 40 | server.rxint(); |
sschocke | 0:d21e3e1c0a4b | 41 | } |
sschocke | 0:d21e3e1c0a4b | 42 | |
tomvdb | 4:4a502f72cbe3 | 43 | void setColor( uint8_t r, uint8_t g, uint8_t b ) |
tomvdb | 4:4a502f72cbe3 | 44 | { |
tomvdb | 4:4a502f72cbe3 | 45 | for ( int c = 0; c < LED_COUNT; c++ ) |
tomvdb | 4:4a502f72cbe3 | 46 | { |
tomvdb | 4:4a502f72cbe3 | 47 | colors[c].red = r; |
tomvdb | 4:4a502f72cbe3 | 48 | colors[c].green = g; |
tomvdb | 4:4a502f72cbe3 | 49 | colors[c].blue = b; |
tomvdb | 4:4a502f72cbe3 | 50 | } |
tomvdb | 4:4a502f72cbe3 | 51 | |
tomvdb | 4:4a502f72cbe3 | 52 | ledStrip.write(colors, LED_COUNT); |
tomvdb | 4:4a502f72cbe3 | 53 | } |
tomvdb | 4:4a502f72cbe3 | 54 | |
leet | 26:ea5e0ff46492 | 55 | void setDiscoColor( ) |
leet | 26:ea5e0ff46492 | 56 | { |
leet | 26:ea5e0ff46492 | 57 | for ( int c = 0; c < LED_COUNT; c++ ) |
leet | 26:ea5e0ff46492 | 58 | { |
leet | 26:ea5e0ff46492 | 59 | colors[c].red = rand()%255; |
leet | 26:ea5e0ff46492 | 60 | colors[c].green = rand()%255; |
leet | 26:ea5e0ff46492 | 61 | colors[c].blue = rand()%255; |
leet | 26:ea5e0ff46492 | 62 | } |
leet | 26:ea5e0ff46492 | 63 | |
leet | 26:ea5e0ff46492 | 64 | ledStrip.write(colors, LED_COUNT); |
leet | 26:ea5e0ff46492 | 65 | } |
leet | 26:ea5e0ff46492 | 66 | |
leet | 26:ea5e0ff46492 | 67 | Timer DiscoTimer; |
leet | 26:ea5e0ff46492 | 68 | #define DiscoDelay 100 |
leet | 26:ea5e0ff46492 | 69 | |
leet | 26:ea5e0ff46492 | 70 | int lampMode = 0; |
leet | 26:ea5e0ff46492 | 71 | |
leet | 26:ea5e0ff46492 | 72 | unsigned long heapSize() |
leet | 26:ea5e0ff46492 | 73 | { |
leet | 26:ea5e0ff46492 | 74 | char stackVariable; |
leet | 26:ea5e0ff46492 | 75 | void *heap; |
leet | 26:ea5e0ff46492 | 76 | unsigned long result; |
leet | 26:ea5e0ff46492 | 77 | heap = malloc(4); |
leet | 26:ea5e0ff46492 | 78 | result = (uint8_t*)&stackVariable - (uint8_t*)heap; |
leet | 26:ea5e0ff46492 | 79 | free(heap); |
leet | 26:ea5e0ff46492 | 80 | return result; |
leet | 26:ea5e0ff46492 | 81 | } |
leet | 26:ea5e0ff46492 | 82 | |
sschocke | 23:3563e1699fb9 | 83 | void sendConfigJSONReply(int linkID, const char* result) { |
sschocke | 23:3563e1699fb9 | 84 | sprintf(temp, jsonConfigReply, opMode, stationIP.c_str(), ssid.c_str(), result); |
sschocke | 23:3563e1699fb9 | 85 | server.SendReply(linkID, temp, mimeJSON, 0); |
sschocke | 23:3563e1699fb9 | 86 | } |
sschocke | 23:3563e1699fb9 | 87 | |
sschocke | 0:d21e3e1c0a4b | 88 | int main() { |
sschocke | 13:1e8f27da036a | 89 | pc.baud(115200); |
sschocke | 20:f5a6527bfda6 | 90 | #ifdef DEBUG_WIFI |
sschocke | 20:f5a6527bfda6 | 91 | pc.attach(&pcrxint); |
sschocke | 20:f5a6527bfda6 | 92 | #endif |
leet | 10:f48a9f923271 | 93 | |
leet | 26:ea5e0ff46492 | 94 | pc.printf("\r\nWiFi Lamp - %s...\r\n", VERSION); |
leet | 10:f48a9f923271 | 95 | |
sschocke | 13:1e8f27da036a | 96 | setColor( 25, 0, 0); |
tomvdb | 4:4a502f72cbe3 | 97 | |
sschocke | 0:d21e3e1c0a4b | 98 | wifiCHPD = 0; |
sschocke | 0:d21e3e1c0a4b | 99 | wifiReset = 0; |
sschocke | 13:1e8f27da036a | 100 | wifiSerial.baud(115200); |
sschocke | 0:d21e3e1c0a4b | 101 | wifiSerial.attach(&rxint); |
sschocke | 7:f15c81074400 | 102 | #ifdef DEBUG_WIFI |
sschocke | 7:f15c81074400 | 103 | server.debugSerial = &pc; |
leet | 26:ea5e0ff46492 | 104 | pc.printf("Debug WiFi Enabled!\r\n"); |
sschocke | 7:f15c81074400 | 105 | #endif |
sschocke | 0:d21e3e1c0a4b | 106 | wait_ms(1000); |
sschocke | 0:d21e3e1c0a4b | 107 | |
sschocke | 0:d21e3e1c0a4b | 108 | pc.printf("Powering WiFi...\r\n"); |
sschocke | 0:d21e3e1c0a4b | 109 | wifiCHPD = 1; |
sschocke | 13:1e8f27da036a | 110 | wifiReset = 1; |
sschocke | 0:d21e3e1c0a4b | 111 | wait_ms(250); |
sschocke | 0:d21e3e1c0a4b | 112 | pc.printf("Hardware Reset WiFi...\r\n"); |
sschocke | 24:849265425708 | 113 | server.ResetModule(); |
sschocke | 0:d21e3e1c0a4b | 114 | wifiOn = 1; |
sschocke | 0:d21e3e1c0a4b | 115 | |
sschocke | 23:3563e1699fb9 | 116 | std::string fwVer = server.GetFirmwareVersion(); |
sschocke | 23:3563e1699fb9 | 117 | pc.printf("ESP Firmware Version: %s\r\n", fwVer.c_str()); |
sschocke | 23:3563e1699fb9 | 118 | |
sschocke | 7:f15c81074400 | 119 | pc.printf("Starting Web Server...\r\n"); |
sschocke | 7:f15c81074400 | 120 | server.Initialize(); |
sschocke | 23:3563e1699fb9 | 121 | pc.printf("Done\r\n"); |
sschocke | 23:3563e1699fb9 | 122 | |
sschocke | 23:3563e1699fb9 | 123 | opMode = server.GetOperatingMode(); |
sschocke | 23:3563e1699fb9 | 124 | pc.printf("Operating Mode: %s(%d)\r\n", opModes[opMode], opMode); |
sschocke | 23:3563e1699fb9 | 125 | if( (opMode & OPMODE_STATION) ) { |
sschocke | 23:3563e1699fb9 | 126 | pc.printf("Waiting 5 secs for join to WiFi Network...\r\n"); |
sschocke | 23:3563e1699fb9 | 127 | wait_ms(5000); |
sschocke | 23:3563e1699fb9 | 128 | stationMAC = server.GetStationMAC(); |
sschocke | 23:3563e1699fb9 | 129 | stationIP = server.GetStationIP(); |
sschocke | 23:3563e1699fb9 | 130 | ssid = server.GetStationSSID(); |
sschocke | 23:3563e1699fb9 | 131 | pc.printf("Station MAC: %s, Station SSID: %s, Station IP: %s\r\n", stationMAC.c_str(), ssid.c_str(), stationIP.c_str()); |
sschocke | 23:3563e1699fb9 | 132 | } |
sschocke | 23:3563e1699fb9 | 133 | if( (opMode & OPMODE_SOFTAP) ) { |
sschocke | 23:3563e1699fb9 | 134 | apMAC = server.GetAPMAC(); |
sschocke | 23:3563e1699fb9 | 135 | apIP = server.GetAPIP(); |
sschocke | 23:3563e1699fb9 | 136 | pc.printf("SoftAP MAC: %s, SoftAP IP: %s\r\n", apMAC.c_str(), apIP.c_str()); |
sschocke | 23:3563e1699fb9 | 137 | } |
sschocke | 23:3563e1699fb9 | 138 | |
sschocke | 13:1e8f27da036a | 139 | setColor( 0, 25, 0); |
tomvdb | 4:4a502f72cbe3 | 140 | wait_ms(500); |
tomvdb | 4:4a502f72cbe3 | 141 | setColor( 0, 0, 0); |
tomvdb | 4:4a502f72cbe3 | 142 | |
leet | 26:ea5e0ff46492 | 143 | pc.printf("Heap Memory %d", heapSize()); |
leet | 26:ea5e0ff46492 | 144 | DiscoTimer.start(); |
leet | 26:ea5e0ff46492 | 145 | |
sschocke | 23:3563e1699fb9 | 146 | std::string httpReply; |
sschocke | 0:d21e3e1c0a4b | 147 | while(true) { |
sschocke | 9:319aeb6e0123 | 148 | ESP8266_WebRequest* request = server.GetRequest(); |
sschocke | 9:319aeb6e0123 | 149 | if( request != NULL ) { |
sschocke | 9:319aeb6e0123 | 150 | pc.printf("HTTP %s %s\r\n", request->Method.c_str(), request->URI.c_str()); |
sschocke | 9:319aeb6e0123 | 151 | for( std::map<std::string,std::string>::iterator it = request->Parameters.begin(); it!=request->Parameters.end(); ++it ) { |
sschocke | 9:319aeb6e0123 | 152 | pc.printf("HTTP Parameter %s = %s\r\n", it->first.c_str(), it->second.c_str()); |
sschocke | 9:319aeb6e0123 | 153 | } |
leet | 26:ea5e0ff46492 | 154 | if( request->URI == "/" || request->URI == "/index.html" ) { |
sschocke | 23:3563e1699fb9 | 155 | httpReply = htmlHead; |
sschocke | 23:3563e1699fb9 | 156 | httpReply += "<table><tr><td align='right'><a href='config'><img src='config.gif' /></a></td></tr>"; |
sschocke | 23:3563e1699fb9 | 157 | httpReply += "<tr><td align='center'>"; |
sschocke | 23:3563e1699fb9 | 158 | httpReply += "<img style='margin-right:2px' src='colormap.gif' usemap='#colormap' />"; |
sschocke | 23:3563e1699fb9 | 159 | httpReply += "<map id='colormap' name='colormap'>"; |
sschocke | 23:3563e1699fb9 | 160 | httpReply += "</map></td></tr><tr><td><span onClick=\"changeColor('#000000')\">Turn Off</span></td></tr></table>"; |
sschocke | 23:3563e1699fb9 | 161 | httpReply += htmlTail; |
sschocke | 23:3563e1699fb9 | 162 | server.SendReply(request->LinkID, httpReply, mimeHTML); |
sschocke | 23:3563e1699fb9 | 163 | } else if( request->URI == "/config" ) { |
sschocke | 23:3563e1699fb9 | 164 | if( (opMode & OPMODE_STATION) ) { |
sschocke | 23:3563e1699fb9 | 165 | stationIP = server.GetStationIP(); |
sschocke | 23:3563e1699fb9 | 166 | ssid = server.GetStationSSID(); |
sschocke | 23:3563e1699fb9 | 167 | } |
sschocke | 23:3563e1699fb9 | 168 | httpReply = htmlHead; |
sschocke | 23:3563e1699fb9 | 169 | httpReply += htmlConfigHTML; |
sschocke | 23:3563e1699fb9 | 170 | httpReply += htmlTail; |
sschocke | 23:3563e1699fb9 | 171 | httpReply = httpReply.replace(httpReply.find("%opmode1%"), 9, (opMode==1? " selected" : "")); |
sschocke | 23:3563e1699fb9 | 172 | httpReply = httpReply.replace(httpReply.find("%opmode2%"), 9, (opMode==2? " selected" : "")); |
sschocke | 23:3563e1699fb9 | 173 | httpReply = httpReply.replace(httpReply.find("%opmode3%"), 9, (opMode==3? " selected" : "")); |
sschocke | 23:3563e1699fb9 | 174 | httpReply = httpReply.replace(httpReply.find("%stamac%"), 8, stationMAC); |
sschocke | 23:3563e1699fb9 | 175 | httpReply = httpReply.replace(httpReply.find("%staip%"), 7, stationIP); |
sschocke | 23:3563e1699fb9 | 176 | httpReply = httpReply.replace(httpReply.find("%apmac%"), 7, apMAC); |
sschocke | 23:3563e1699fb9 | 177 | httpReply = httpReply.replace(httpReply.find("%apip%"), 6, apIP); |
sschocke | 23:3563e1699fb9 | 178 | httpReply = httpReply.replace(httpReply.find("%ssid%"), 6, ssid); |
sschocke | 23:3563e1699fb9 | 179 | server.SendReply(request->LinkID, httpReply, mimeHTML, 0); |
sschocke | 23:3563e1699fb9 | 180 | } else if( request->URI == "/updateconfig" ) { |
sschocke | 23:3563e1699fb9 | 181 | bool result = true; |
sschocke | 23:3563e1699fb9 | 182 | int newOpMode = atoi(request->Parameters["opmode"].c_str()); |
sschocke | 23:3563e1699fb9 | 183 | std::string newSSID = request->Parameters["ssid"]; |
sschocke | 23:3563e1699fb9 | 184 | std::string newPassword = request->Parameters["pwd"]; |
sschocke | 23:3563e1699fb9 | 185 | if( newOpMode != opMode ) { |
sschocke | 23:3563e1699fb9 | 186 | result = server.SetOperatingMode(newOpMode); |
sschocke | 23:3563e1699fb9 | 187 | if( result == false ) { |
sschocke | 23:3563e1699fb9 | 188 | sendConfigJSONReply(request->LinkID, "Error setting Operating Mode"); |
sschocke | 23:3563e1699fb9 | 189 | delete request; |
sschocke | 23:3563e1699fb9 | 190 | continue; |
sschocke | 22:6d7a72fab8ff | 191 | } |
sschocke | 23:3563e1699fb9 | 192 | opMode = newOpMode; |
sschocke | 23:3563e1699fb9 | 193 | } |
sschocke | 23:3563e1699fb9 | 194 | if( (opMode & OPMODE_STATION) ) { |
sschocke | 23:3563e1699fb9 | 195 | if( (ssid.compare(newSSID) != 0) || (newPassword.empty() == false) ) { |
sschocke | 23:3563e1699fb9 | 196 | result = server.SetStationSSID(newSSID, newPassword); |
sschocke | 23:3563e1699fb9 | 197 | if( result == false ) { |
sschocke | 23:3563e1699fb9 | 198 | sendConfigJSONReply(request->LinkID, "Error connecting to SSID"); |
sschocke | 23:3563e1699fb9 | 199 | delete request; |
sschocke | 23:3563e1699fb9 | 200 | continue; |
sschocke | 23:3563e1699fb9 | 201 | } |
sschocke | 23:3563e1699fb9 | 202 | // Wait for SSID to connect |
sschocke | 23:3563e1699fb9 | 203 | for( int retries=0; retries < 6; retries++) { |
sschocke | 23:3563e1699fb9 | 204 | wait_ms(5000); |
sschocke | 23:3563e1699fb9 | 205 | ssid = server.GetStationSSID(); |
sschocke | 23:3563e1699fb9 | 206 | if( ssid.compare(newSSID) == 0 ) { |
sschocke | 23:3563e1699fb9 | 207 | break; |
sschocke | 23:3563e1699fb9 | 208 | } |
sschocke | 23:3563e1699fb9 | 209 | } |
sschocke | 23:3563e1699fb9 | 210 | stationIP = server.GetStationIP(); |
sschocke | 22:6d7a72fab8ff | 211 | } |
sschocke | 22:6d7a72fab8ff | 212 | } |
sschocke | 23:3563e1699fb9 | 213 | sendConfigJSONReply(request->LinkID, "Success"); |
sschocke | 20:f5a6527bfda6 | 214 | } else if( request->URI == "/wifilamp.js" ) { |
sschocke | 20:f5a6527bfda6 | 215 | server.SendReply(request->LinkID, javascript, strlen(javascript), mimeJavaScript); |
sschocke | 20:f5a6527bfda6 | 216 | } else if( request->URI == "/wifilamp.css" ) { |
sschocke | 20:f5a6527bfda6 | 217 | server.SendReply(request->LinkID, css, strlen(css), mimeCSS); |
sschocke | 22:6d7a72fab8ff | 218 | } else if( request->URI == "/colormap.gif" ) { |
leet | 26:ea5e0ff46492 | 219 | server.SendReply(request->LinkID, (char*)colorMap, sizeof(colorMap), mimeGIF); |
sschocke | 23:3563e1699fb9 | 220 | } else if( request->URI == "/config.gif" ) { |
sschocke | 23:3563e1699fb9 | 221 | server.SendReply(request->LinkID, (char*)configIcon, sizeof(configIcon), mimeGIF); |
leet | 26:ea5e0ff46492 | 222 | } else if( request->URI == "/robtos.txt" ) { |
leet | 26:ea5e0ff46492 | 223 | server.SendReply(request->LinkID, robotsTxt, strlen(robotsTxt), mimeText); |
leet | 26:ea5e0ff46492 | 224 | } else if( request->URI == "/favicon.ico" ) { |
leet | 26:ea5e0ff46492 | 225 | server.SendReply(request->LinkID, (char*)favIcon, sizeof(favIcon), "image/x-icon"); |
leet | 26:ea5e0ff46492 | 226 | } else if( request->URI == "/disco" ) { |
leet | 26:ea5e0ff46492 | 227 | lampMode = 2; // All colours |
leet | 26:ea5e0ff46492 | 228 | DiscoTimer.reset(); |
leet | 26:ea5e0ff46492 | 229 | server.SendReply(request->LinkID, "OK", mimeText, 0); |
leet | 16:f2f2da9ef9ab | 230 | } else if( request->URI == "/setcolour" || request->URI == "/setcolor" ) { |
sschocke | 7:f15c81074400 | 231 | int r=0, g=0, b=0; |
leet | 26:ea5e0ff46492 | 232 | |
leet | 26:ea5e0ff46492 | 233 | lampMode = 1; // All LED 1 colour |
sschocke | 9:319aeb6e0123 | 234 | |
sschocke | 9:319aeb6e0123 | 235 | if(request->Parameters.count("r") > 0) r=atoi(request->Parameters["r"].c_str()); |
sschocke | 9:319aeb6e0123 | 236 | if(request->Parameters.count("g") > 0) g=atoi(request->Parameters["g"].c_str()); |
sschocke | 9:319aeb6e0123 | 237 | if(request->Parameters.count("b") > 0) b=atoi(request->Parameters["b"].c_str()); |
sschocke | 7:f15c81074400 | 238 | |
leet | 16:f2f2da9ef9ab | 239 | pc.printf( "Set colour to (%i, %i, %i)\r\n", r,g,b); |
sschocke | 0:d21e3e1c0a4b | 240 | |
sschocke | 7:f15c81074400 | 241 | setColor( r,g,b ); |
sschocke | 7:f15c81074400 | 242 | |
sschocke | 23:3563e1699fb9 | 243 | server.SendReply(request->LinkID, "OK", mimeText, 0); |
sschocke | 7:f15c81074400 | 244 | } else { |
sschocke | 11:3ab606a42227 | 245 | server.Send404Error(request->LinkID); |
sschocke | 0:d21e3e1c0a4b | 246 | } |
sschocke | 7:f15c81074400 | 247 | pc.printf("\r\nHTTP Reply Sent\r\n"); |
sschocke | 9:319aeb6e0123 | 248 | delete request; |
sschocke | 0:d21e3e1c0a4b | 249 | } |
leet | 26:ea5e0ff46492 | 250 | if( lampMode == 2 && DiscoTimer.read_ms() > DiscoDelay ) { |
leet | 26:ea5e0ff46492 | 251 | setDiscoColor(); |
leet | 26:ea5e0ff46492 | 252 | // Delay random colour change, else it just looks white |
leet | 26:ea5e0ff46492 | 253 | DiscoTimer.reset(); |
leet | 26:ea5e0ff46492 | 254 | } |
leet | 26:ea5e0ff46492 | 255 | /* |
leet | 26:ea5e0ff46492 | 256 | if( lampMode == 3 ) { |
leet | 26:ea5e0ff46492 | 257 | // strobe |
leet | 26:ea5e0ff46492 | 258 | } |
leet | 26:ea5e0ff46492 | 259 | |
leet | 26:ea5e0ff46492 | 260 | if( lampMode == 4 ) { |
leet | 26:ea5e0ff46492 | 261 | // fade |
leet | 26:ea5e0ff46492 | 262 | } |
leet | 26:ea5e0ff46492 | 263 | |
leet | 26:ea5e0ff46492 | 264 | */ |
sschocke | 0:d21e3e1c0a4b | 265 | } |
sschocke | 0:d21e3e1c0a4b | 266 | } |