Beware, this is highly unreliableā¦ it's just good enough for me ;)
/* * WebSocketClient.ino * * Created on: 24.05.2015 * */ //#include <Arduino.h> #include <M5Stack.h> #include <ArduinoJson.h> #include <WiFi.h> #include <WiFiMulti.h> #include <WiFiClientSecure.h> #include <HttpClient.h> #include <WebSocketsClient.h> WiFiMulti WiFiMulti; WebSocketsClient webSocket; HttpClient htclient; bool localTesting = true; bool genRandomVals = false; const char* OVMSUSER = "admin"; const char* OVMSPASS = "OVMSP455w0rd"; // Change this to your password const int LCDBrightnessSteps[] = {60, 120, 250}; int LCDBrightnessStep = 0; const int heightBarGraphSOC = 72; const int widthBarGraphSOC = 300; const int leftBarGraphSOC = (320-widthBarGraphSOC-4)/2; const int topBarGraphSOC = 0; const int SOCtextSize = 7; const int SOCtextWidth = M5.Lcd.textWidth("99%")*SOCtextSize; const int heightBarGraphPWR = 32; const int widthBarGraphPWR = 220; const int PWRtextSize = 3; const int PWRtextWidth = M5.Lcd.textWidth("100kW")*PWRtextSize; const int leftBarGraphPWR = (320-widthBarGraphPWR-4); const int topBarGraphPWR = 80; const int topTextLineRange = topBarGraphPWR + heightBarGraphPWR + 16; const int topTextLineConsumption = topTextLineRange + 32; const int topTextLineTemps = topTextLineConsumption + 32; const float MAXINPOWER = 100; const float MAXOUTPOWER = 200; const float MAXRANGEPWR = 300; float vbsoc = 90.5; float vbrangeideal = 402 ; float vbrangefull = 450 ; float vetemp = 13 ; float vbtemp = 15 ; float xknbinlettemp = 14 ; float vbpower = 1 ; float vbconsumption = 160 ; const uint16_t DARKERCYAN = 0x0124; const size_t jsonCapacity = JSON_OBJECT_SIZE(1) + JSON_OBJECT_SIZE(38) + 910; DynamicJsonDocument jsonDoc(jsonCapacity); DeserializationError payloadDecodeError; uint8_t M5triggerUpdate = 0; uint8_t dummyCounter = 0; unsigned long prevTimer = millis(); unsigned long displayTimer = millis(); unsigned long spentTimer = 0; bool slowRefreshTrigger = false; unsigned long slowRefreshTimer = 0; unsigned long slowRefreshMax = 5000; bool mildRefreshTrigger = false; unsigned long mildRefreshTimer = 0; unsigned long mildRefreshMax = 2000; bool fastRefreshTrigger = false; unsigned long fastRefreshTimer = 0; unsigned long fastRefreshMax = 100; #define USE_SERIAL Serial void hexdump(const void *mem, uint32_t len, uint8_t cols = 16) { const uint8_t* src = (const uint8_t*) mem; USE_SERIAL.printf("\n[HEXDUMP] Address: 0x%08X len: 0x%X (%d)", (ptrdiff_t)src, len, len); for(uint32_t i = 0; i < len; i++) { if(i % cols == 0) { USE_SERIAL.printf("\n[0x%08X] 0x%08X: ", (ptrdiff_t)src, i); } USE_SERIAL.printf("%02X ", *src); src++; } USE_SERIAL.printf("\n"); } void draw_vbsoc(float vbsoc) { M5.Lcd.setTextSize(SOCtextSize); uint16_t SOCcolor = GREEN; uint16_t SOCtextColor = 0x0720; // Slightly Darker Green uint16_t BorderColor = WHITE; uint16_t BGcolor = 0x2970; //BLUE; if(vbsoc<25){ SOCcolor = YELLOW; SOCtextColor = YELLOW; BorderColor = YELLOW; } if(vbsoc<10){ SOCcolor = RED; SOCtextColor = RED; BorderColor = RED; BGcolor = MAROON; } M5.Lcd.fillRoundRect(leftBarGraphSOC, topBarGraphSOC, widthBarGraphSOC+6, heightBarGraphSOC+4 , 6, BGcolor); //M5.Lcd.fillRect(leftBarGraphSOC, topBarGraphSOC, widthBarGraphSOC+4, heightBarGraphSOC+4 , BGcolor); M5.Lcd.drawRoundRect(leftBarGraphSOC, topBarGraphSOC, widthBarGraphSOC+4, heightBarGraphSOC+4 , 6, BorderColor); M5.Lcd.fillRoundRect(leftBarGraphSOC+2, topBarGraphSOC+2, widthBarGraphSOC*(vbsoc/100), heightBarGraphSOC , 4, SOCcolor); if(vbsoc>=50){ M5.Lcd.setTextColor(BLACK); M5.Lcd.setCursor(leftBarGraphSOC+6+3*vbsoc-SOCtextWidth-6, topBarGraphSOC+14); M5.Lcd.print(vbsoc,0); M5.Lcd.print('%'); }else{ M5.Lcd.setTextColor(SOCtextColor); M5.Lcd.setCursor(leftBarGraphSOC+6+3*vbsoc+8, topBarGraphSOC+12); M5.Lcd.print(vbsoc,0); M5.Lcd.print('%'); } //M5.Lcd.printf("%*s", 5, vbsoc); M5triggerUpdate++; } void draw_vbpower(float vbpower) { M5.Lcd.setTextSize(PWRtextSize); uint16_t PWRcolor; uint16_t BorderColor = LIGHTGREY; //WHITE; uint16_t BGcolor = 0x528A; // Draw empty full bar M5.Lcd.fillRoundRect(leftBarGraphPWR, topBarGraphPWR, widthBarGraphPWR+6, heightBarGraphPWR+4 , 6, BGcolor); //M5.Lcd.fillRect(leftBarGraphSOC, topBarGraphSOC, widthBarGraphSOC+4, heightBarGraphSOC+4 , BGcolor); M5.Lcd.drawRoundRect(leftBarGraphPWR, topBarGraphPWR, widthBarGraphPWR+4, heightBarGraphPWR+4 , 6, BorderColor); float barOffset; float barWidth; if(vbpower>0){ PWRcolor = YELLOW; if(vbpower>25) PWRcolor = RED; barWidth = (widthBarGraphPWR*vbpower)/MAXRANGEPWR; // (220*140)/220 barOffset = (widthBarGraphPWR*(float)MAXINPOWER)/MAXRANGEPWR; } else { PWRcolor = GREEN; barWidth = (widthBarGraphPWR*-vbpower)/MAXRANGEPWR; barOffset = (widthBarGraphPWR*((float)MAXINPOWER+vbpower))/MAXRANGEPWR; } // USE_SERIAL.print(barOffset); USE_SERIAL.print(" "); USE_SERIAL.println(barWidth); // Draw Power Bar //M5.Lcd.fillRoundRect(leftBarGraphPWR+2+barOffset, topBarGraphPWR+2, barWidth, heightBarGraphPWR , 4, PWRcolor); M5.Lcd.fillRect(leftBarGraphPWR+2+barOffset, topBarGraphPWR+2, barWidth, heightBarGraphPWR, PWRcolor); // Clean text M5.Lcd.fillRoundRect(4, topBarGraphPWR, PWRtextWidth, heightBarGraphPWR+4 , 6, BLACK); // Place 0 line M5.Lcd.drawLine( leftBarGraphPWR+2+(widthBarGraphPWR*(float)MAXINPOWER)/MAXRANGEPWR, topBarGraphPWR, leftBarGraphPWR+2+(widthBarGraphPWR*(float)MAXINPOWER)/MAXRANGEPWR, topBarGraphPWR+heightBarGraphPWR+2, BorderColor); M5.Lcd.setCursor(4, topBarGraphPWR+8); if(vbpower>0){ M5.Lcd.setTextColor(RED); }else{ M5.Lcd.setTextColor(GREEN); } if(vbpower > 0 && vbpower < 10 ) M5.Lcd.print(" "); if(vbpower > -10 && vbpower < 100 ) M5.Lcd.print(" "); M5.Lcd.print(vbpower,0); M5.Lcd.print("kW"); //M5.Lcd.drawFloat(vbpower, 0, 4, topBarGraphPWR+8); //M5.Lcd.drawNumber(vbpower, 4, topBarGraphPWR+8); //M5.Lcd.print('kW'); //M5.Lcd.printf("%*s", 5, vbpower); M5triggerUpdate++; } void draw_vbrange() { M5.Lcd.setTextSize(3); M5.lcd.fillRect(0, topTextLineRange, 320,32, DARKERCYAN); M5.Lcd.setCursor(4, topTextLineRange+4); M5.Lcd.setTextColor(LIGHTGREY); M5.Lcd.print("range: "); M5.Lcd.print(vbrangeideal,0); M5.Lcd.print("/"); M5.Lcd.print(vbrangefull,0); M5.Lcd.print(" km"); M5triggerUpdate++; } void draw_vbconsumption() { uint16_t textColor = GREEN; if(vbconsumption>150) textColor = YELLOW; if(vbconsumption>200) textColor = RED; M5.Lcd.setTextSize(3); M5.lcd.fillRect(0, topTextLineConsumption, 320,32, DARKERCYAN); M5.Lcd.setCursor(4, topTextLineConsumption+4); M5.Lcd.setTextColor(LIGHTGREY); M5.Lcd.print("cons: "); M5.Lcd.setTextColor(textColor); M5.Lcd.print(vbconsumption,0); M5.Lcd.setTextColor(LIGHTGREY); M5.Lcd.print(" Wh/km"); M5triggerUpdate++; } void draw_temps() { M5.Lcd.setTextSize(2); M5.lcd.fillRect(0, topTextLineTemps,320 , 24, DARKERCYAN); M5.Lcd.setCursor(4, topTextLineTemps+4); M5.Lcd.setTextColor(LIGHTGREY); M5.Lcd.print("temps ext:"); M5.Lcd.print(vetemp,0); M5.Lcd.setTextColor(LIGHTGREY); M5.Lcd.print(" in:"); M5.Lcd.print(xknbinlettemp,0); M5.Lcd.setTextColor(LIGHTGREY); M5.Lcd.print(" bt:"); M5.Lcd.print(vbtemp,0); M5triggerUpdate++; } void tickerBox(uint16_t tickerColor) { M5.lcd.fillRect(0,240-6 ,4 ,4 , tickerColor); M5triggerUpdate++; } void changeLCDBrightness() { LCDBrightnessStep++; if(LCDBrightnessStep==3) LCDBrightnessStep=0; M5.Lcd.setBrightness(LCDBrightnessSteps[LCDBrightnessStep]); if(localTesting) USE_SERIAL.printf("[LCD] Setting Brightness to : %u (step %u)\n", LCDBrightnessSteps[LCDBrightnessStep], LCDBrightnessStep); } void sendButtonA() { sendEvent("event+raise+M5.button.A"); } void sendButtonB() { sendEvent("event+raise+M5.button.B"); } void sendEvent(char* event){ M5.Lcd.fillRect(0, 220, 320, 32, BLUE); M5.Lcd.setCursor(6, 222); M5.Lcd.setTextColor(YELLOW); M5.Lcd.setTextSize(2); M5.Lcd.print(event); M5.update(); M5triggerUpdate = 0; WiFiClient client; if (!client.connect("192.168.4.1", 80 )) { Serial.println("connection failed"); return; } String url = "/api/execute?apikey="; url += OVMSPASS; url += "&command="; url += event; USE_SERIAL.print("Requesting URL: "); USE_SERIAL.println(url); client.print(String("GET ") + url + " HTTP/1.1\r\n" + "Host: 192.168.4.1\r\n" + "Connection: close\r\n\r\n"); unsigned long timeout = millis(); while (client.available() == 0) { if (millis() - timeout > 5000) { USE_SERIAL.println(">>> Client Timeout !"); client.stop(); return; } } while(client.available()) { String line = client.readStringUntil('\r'); USE_SERIAL.print(line); } Serial.println(); Serial.println("closing connection"); M5.Lcd.fillRect(0, 220, 320, 32, BLACK); M5.update(); M5triggerUpdate = 0; } void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) { // Timer control prevTimer=displayTimer; displayTimer=millis(); spentTimer=displayTimer-prevTimer; slowRefreshTimer+=spentTimer; mildRefreshTimer+=spentTimer; fastRefreshTimer+=spentTimer; slowRefreshTrigger = (slowRefreshTimer>=slowRefreshMax) ? true : false; mildRefreshTrigger = (mildRefreshTimer>=mildRefreshMax) ? true : false; fastRefreshTrigger = (fastRefreshTimer>=fastRefreshMax) ? true : false; switch(type) { case WStype_DISCONNECTED: USE_SERIAL.printf("[WSc] Disconnected!\n"); tickerBox(RED); M5.update(); break; case WStype_CONNECTED: tickerBox(YELLOW); M5.update(); if(localTesting) USE_SERIAL.printf("[WSc] Connected to url: %s\n", payload); // send message to server when Connected //webSocket.sendTXT("M5 Stack Connected"); sendEvent("event+raise+M5.websocket.connected"); break; case WStype_TEXT: //if(localTesting) USE_SERIAL.printf("[WSc] get text: %s\n", payload); payloadDecodeError = deserializeJson(jsonDoc, payload); // Test if parsing succeeds. if (payloadDecodeError) { USE_SERIAL.printf("deserializeJson() failed: \n"); USE_SERIAL.println(payloadDecodeError.c_str()); }else{ //USE_SERIAL.printf("parseObject() succeeded\n"); JsonObject metrics = jsonDoc["metrics"]; if (!metrics.isNull()) { if(localTesting && genRandomVals){ metrics["v.b.soc"] = random(0,200)/2; metrics["v.b.power"] = random(-MAXINPOWER*10,MAXOUTPOWER*10+1)/10; metrics["v.b.range.ideal"] = random(360*10,400*10)/10; metrics["v.b.range.ideal"] = random(360*10,400*10)/10; metrics["v.e.temp"] = random(12*10,17*10)/10; metrics["v.b.temp"] = random(18*10,21*10)/10; metrics["xkn.b.inlet.temp"] = random(14*10,19*10)/10; metrics["v.b.consumption"] = random(130,240); } // v.b.soc if(metrics.containsKey("v.b.soc") && metrics["v.b.soc"]!=vbsoc){ vbsoc = metrics["v.b.soc"]; //USE_SERIAL.printf("[json] v.b.soc: %f\n", vbsoc); } if(slowRefreshTrigger) draw_vbsoc(vbsoc); // v.b.range.ideal if(metrics.containsKey("v.b.range.ideal") && metrics["v.b.range.ideal"]!=vbrangeideal){ vbrangeideal = metrics["v.b.range.ideal"]; } // v.b.range.full if(metrics.containsKey("v.b.range.full") && metrics["v.b.range.full"]!=vbrangefull){ vbrangefull = metrics["v.b.range.full"]; } if(slowRefreshTrigger) draw_vbrange(); // v.e.temp if(metrics.containsKey("v.e.temp") && metrics["v.e.temp"]!=vetemp){ vetemp = metrics["v.e.temp"]; } // v.b.temp if(metrics.containsKey("v.b.temp") && metrics["v.b.temp"]!=vbtemp){ vbtemp = metrics["v.b.temp"]; } // xkn.b.inlet.temp if(metrics.containsKey("xkn.b.inlet.temp") && metrics["xkn.b.inlet.temp"]!=xknbinlettemp){ xknbinlettemp = metrics["xkn.b.inlet.temp"]; } if(mildRefreshTrigger) draw_temps(); // xkn.b.inlet.temp if(metrics.containsKey("v.b.consumption") && metrics["v.b.consumption"]!=vbconsumption){ vbconsumption = metrics["v.b.consumption"]; } if(mildRefreshTrigger) draw_vbconsumption(); // v.b.power if(metrics.containsKey("v.b.power")){ vbpower = metrics["v.b.power"]; //USE_SERIAL.printf("[json] v.b.power: %f\n", vbpower); } if(fastRefreshTrigger) draw_vbpower(vbpower); if(metrics.containsKey("m.monotonic")){ //JsonVariant _mmonotonic=metrics["m.monotonic"]; //int mmonotonic=_mmonotonic.as<int>(); int mmonotonic=metrics["m.monotonic"].as<int>(); if(mmonotonic % 2){ tickerBox(DARKGREEN); }else{ tickerBox(BLUE); } } if(M5.BtnA.wasReleased()) sendButtonA(); if(M5.BtnB.wasReleased()) sendButtonB(); if(M5.BtnC.wasReleased()) changeLCDBrightness(); } if(M5triggerUpdate){ M5.update(); M5triggerUpdate = 0; } if(slowRefreshTrigger){ slowRefreshTimer=0; //USE_SERIAL.printf("slowRefreshTimer reset\n"); } if(mildRefreshTrigger){ mildRefreshTimer=0; //USE_SERIAL.printf("mildRefreshTimer reset\n"); } if(fastRefreshTrigger){ fastRefreshTimer=0; //USE_SERIAL.printf("fastRefreshTimer reset\n"); } } // send message to server // webSocket.sendTXT("message here"); break; case WStype_BIN: USE_SERIAL.printf("[WSc] get binary length: %u\n", length); hexdump(payload, length); // send data to server // webSocket.sendBIN(payload, length); break; case WStype_ERROR: case WStype_FRAGMENT_TEXT_START: case WStype_FRAGMENT_BIN_START: case WStype_FRAGMENT: case WStype_FRAGMENT_FIN: break; } } void setup() { M5.begin(); M5.Power.begin(); M5.Lcd.setBrightness(LCDBrightnessSteps[LCDBrightnessStep+1]); // USE_SERIAL.begin(921600); // USE_SERIAL.begin(115200); USE_SERIAL.begin(230400); //Serial.setDebugOutput(true); USE_SERIAL.setDebugOutput(true); USE_SERIAL.println(); //M5.Lcd.fillScreen(BLACK); ledcDetachPin(SPEAKER_PIN); pinMode(SPEAKER_PIN, INPUT); for(uint8_t t = 5; t > 0; t--) { USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t); USE_SERIAL.flush(); delay(500); } WiFiMulti.addAP("OVMSWifi", "W1f1P4ssW0rd"); // Change This to your OVMS USE_SERIAL.printf("[Wifi] Added APs...\n"); WiFi.disconnect(); if(WiFiMulti.run() != WL_CONNECTED){ M5.Lcd.fillRect(0, 220, 320, 32, BLUE); M5.Lcd.setCursor(6, 222); M5.Lcd.setTextColor(WHITE); M5.Lcd.setTextSize(2); M5.Lcd.print("[Wifi] Connecting..."); USE_SERIAL.printf("[Wifi] Connecting...\n"); dummyCounter = 1; tickerBox(RED); while(WiFiMulti.run() != WL_CONNECTED) { USE_SERIAL.printf("[Wifi] Connecting... count %d\n",dummyCounter++); M5.Lcd.print("."); M5.update(); delay(500); if (M5.BtnC.wasReleased()) changeLCDBrightness(); } //hexdump(WiFi.SSID().c_str(), sizeof(WiFi.SSID().c_str())); USE_SERIAL.printf("[Wifi] Connected: %s\n",WiFi.SSID().c_str()); M5.Lcd.fillRect(0, 220, 320, 32, BLACK); M5.update(); } // server address, port and URL webSocket.begin("192.168.4.1", 80, "/"); // event handler webSocket.onEvent(webSocketEvent); // use HTTP Basic Authorization this is optional remove if not needed webSocket.setAuthorization(OVMSUSER, OVMSPASS); // try ever 5000 again if connection has failed webSocket.setReconnectInterval(2000); } void loop() { webSocket.loop(); if(WiFiMulti.run() != WL_CONNECTED){ M5.Lcd.fillRect(0, 220, 320, 32, BLUE); M5.Lcd.setCursor(6, 222); M5.Lcd.setTextColor(WHITE); M5.Lcd.setTextSize(2); M5.Lcd.print("[Wifi] Reconnecting..."); while(WiFiMulti.run() != WL_CONNECTED) { USE_SERIAL.printf("[Wifi] Connecting...\n"); M5.Lcd.print("."); M5.update(); delay(500); if (M5.BtnC.wasReleased()) changeLCDBrightness(); } USE_SERIAL.printf("Wifi: %s\n",WiFi.SSID().c_str()); M5.Lcd.fillRect(0, 220, 320, 32, BLUE); M5.Lcd.setCursor(6, 222); M5.Lcd.print("[Wifi] : "); M5.Lcd.print(WiFi.SSID().c_str()); M5.update(); M5.Speaker.mute(); delay(500); M5.Lcd.fillRect(0, 220, 320, 32, BLACK); M5.update(); } //M5.Lcd.fillScreen(BLACK); if(M5triggerUpdate){ M5.update(); M5triggerUpdate = 0; } //USE_SERIAL.printf("end of loop\n"); }