├── README.md ├── .gitattributes ├── .gitignore └── OpenEVSE_RAPI_WiFi_ESP8266.ino /README.md: -------------------------------------------------------------------------------- 1 | # OpenEVSE_RAPI_WiFi_ESP8266 2 | OpenEVSE RAPI client/Server for ESP8266 WiFi Module and Open Energy Monitor EmonCMS 3 | 4 | Master is stable branch currently shippping on WiFi Modules. 5 | 6 | Development Branch is under active development, contains many enhancements and could possibly be unstable. 7 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | applet 3 | application.linux32 4 | application.linux64 5 | application.windows32 6 | application.windows64 7 | application.macosx 8 | 9 | # ========================= 10 | # Operating System Files 11 | # ========================= 12 | 13 | # OSX 14 | # ========================= 15 | 16 | .DS_Store 17 | .AppleDouble 18 | .LSOverride 19 | 20 | # Thumbnails 21 | ._* 22 | 23 | # Files that might appear on external disk 24 | .Spotlight-V100 25 | .Trashes 26 | 27 | # Directories potentially created on remote AFP share 28 | .AppleDB 29 | .AppleDesktop 30 | Network Trash Folder 31 | Temporary Items 32 | .apdisk 33 | 34 | # Windows 35 | # ========================= 36 | 37 | # Windows image file caches 38 | Thumbs.db 39 | ehthumbs.db 40 | 41 | # Folder config file 42 | Desktop.ini 43 | 44 | # Recycle Bin used on file shares 45 | $RECYCLE.BIN/ 46 | 47 | # Windows Installer files 48 | *.cab 49 | *.msi 50 | *.msm 51 | *.msp 52 | 53 | # Windows shortcuts 54 | *.lnk 55 | -------------------------------------------------------------------------------- /OpenEVSE_RAPI_WiFi_ESP8266.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Chris Howell 3 | * 4 | * This file is part of Open EVSE. 5 | * Open EVSE is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 3, or (at your option) 8 | * any later version. 9 | * Open EVSE is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * You should have received a copy of the GNU General Public License 14 | * along with Open EVSE; see the file COPYING. If not, write to the 15 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 16 | * Boston, MA 02111-1307, USA. 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | ESP8266WebServer server(80); 28 | ESP8266HTTPUpdateServer httpUpdater; 29 | 30 | //Default SSID and PASSWORD for AP Access Point Mode 31 | const char* ssid = "OpenEVSE"; 32 | const char* password = "openevse"; 33 | const char* www_username = "admin"; 34 | const char* www_password = "openevse"; 35 | String st; 36 | String privateKey = ""; 37 | String node = ""; 38 | 39 | 40 | //SERVER strings and interfers for OpenEVSE Energy Monotoring 41 | const char* host = "data.openevse.com"; 42 | const int httpsPort = 443; 43 | const char* e_url = "/emoncms/input/post.json?node="; 44 | const char* inputID_AMP = "OpenEVSE_AMP:"; 45 | const char* inputID_VOLT = "OpenEVSE_VOLT:"; 46 | const char* inputID_TEMP1 = "OpenEVSE_TEMP1:"; 47 | const char* inputID_TEMP2 = "OpenEVSE_TEMP2:"; 48 | const char* inputID_TEMP3 = "OpenEVSE_TEMP3:"; 49 | const char* inputID_PILOT = "OpenEVSE_PILOT:"; 50 | 51 | //Server strings for Ohm Connect 52 | const char* ohm_host = "login.ohmconnect.com"; 53 | const char* ohm_url = "/verify-ohm-hour/"; 54 | String ohm_key = ""; 55 | const int ohm_httpsPort = 443; 56 | const char* ohm_fingerprint = "6B 39 04 A4 BB E0 87 B2 EB B6 FE 77 CD D5 F6 A7 22 4B 3B ED"; 57 | int evse_sleep = 0; 58 | 59 | int amp = 0; 60 | int volt = 0; 61 | int temp1 = 0; 62 | int temp2 = 0; 63 | int temp3 = 0; 64 | int pilot = 0; 65 | 66 | int wifi_mode = 0; 67 | int buttonState = 0; 68 | int clientTimeout = 0; 69 | int i = 0; 70 | unsigned long Timer; 71 | unsigned long Timer2; 72 | 73 | void ResetEEPROM(){ 74 | //Serial.println("Erasing EEPROM"); 75 | for (int i = 0; i < 512; ++i) { 76 | EEPROM.write(i, 0); 77 | //Serial.print("#"); 78 | } 79 | EEPROM.commit(); 80 | } 81 | 82 | void setup() { 83 | delay(1000); 84 | Serial.begin(115200); 85 | EEPROM.begin(512); 86 | pinMode(0, INPUT); 87 | char tmpStr[40]; 88 | String esid; 89 | String epass = ""; 90 | 91 | for (int i = 0; i < 32; ++i){ 92 | esid += char(EEPROM.read(i)); 93 | } 94 | for (int i = 32; i < 96; ++i){ 95 | epass += char(EEPROM.read(i)); 96 | } 97 | for (int i = 96; i < 128; ++i){ 98 | privateKey += char(EEPROM.read(i)); 99 | } 100 | node += char(EEPROM.read(129)); 101 | for (int i = 130; i < 138; ++i){ 102 | ohm_key += char(EEPROM.read(i)); 103 | } 104 | 105 | if ( esid != 0 ) { 106 | //Serial.println(" "); 107 | //Serial.print("Connecting as Wifi Client to: "); 108 | //Serial.println(esid.c_str()); 109 | WiFi.mode(WIFI_STA); 110 | WiFi.disconnect(); 111 | WiFi.begin(esid.c_str(), epass.c_str()); 112 | delay(50); 113 | int t = 0; 114 | int attempt = 0; 115 | while (WiFi.status() != WL_CONNECTED){ 116 | // test esid 117 | //Serial.print("#"); 118 | delay(500); 119 | t++; 120 | if (t >= 20){ 121 | //Serial.println(" "); 122 | //Serial.println("Trying Again..."); 123 | delay(2000); 124 | WiFi.disconnect(); 125 | WiFi.begin(esid.c_str(), epass.c_str()); 126 | t = 0; 127 | attempt++; 128 | if (attempt >= 5){ 129 | //Serial.println(); 130 | //Serial.print("Configuring access point..."); 131 | WiFi.mode(WIFI_STA); 132 | WiFi.disconnect(); 133 | delay(100); 134 | int n = WiFi.scanNetworks(); 135 | //Serial.print(n); 136 | //Serial.println(" networks found"); 137 | st = "
    "; 138 | for (int i = 0; i < n; ++i){ 139 | st += "
  • "; 140 | st += WiFi.SSID(i); 141 | st += "
  • "; 142 | } 143 | st += "
"; 144 | delay(100); 145 | WiFi.softAP(ssid, password); 146 | IPAddress myIP = WiFi.softAPIP(); 147 | //Serial.print("AP IP address: "); 148 | //Serial.println(myIP); 149 | Serial.println("$FP 0 0 SSID...OpenEVSE."); 150 | delay(100); 151 | Serial.println("$FP 0 1 PASS...openevse."); 152 | delay(5000); 153 | Serial.println("$FP 0 0 IP_Address......"); 154 | delay(100); 155 | sprintf(tmpStr,"$FP 0 1 %d.%d.%d.%d",myIP[0],myIP[1],myIP[2],myIP[3]); 156 | Serial.println(tmpStr); 157 | wifi_mode = 1; 158 | break; 159 | } 160 | } 161 | } 162 | } 163 | else { 164 | //Serial.println(); 165 | //Serial.print("Configuring access point..."); 166 | WiFi.mode(WIFI_STA); 167 | WiFi.disconnect(); 168 | delay(100); 169 | int n = WiFi.scanNetworks(); 170 | st = "
    "; 171 | for (int i = 0; i < n; ++i){ 172 | st += "
  • "; 173 | st += WiFi.SSID(i); 174 | st += "
  • "; 175 | } 176 | st += "
"; 177 | delay(100); 178 | WiFi.softAP(ssid, password); 179 | IPAddress myIP = WiFi.softAPIP(); 180 | //Serial.print("AP IP address: "); 181 | //Serial.println(myIP); 182 | Serial.println("$FP 0 0 SSID...OpenEVSE."); 183 | delay(100); 184 | Serial.println("$FP 0 1 PASS...openevse."); 185 | delay(5000); 186 | Serial.println("$FP 0 0 IP_Address......"); 187 | delay(100); 188 | sprintf(tmpStr,"$FP 0 1 %d.%d.%d.%d",myIP[0],myIP[1],myIP[2],myIP[3]); 189 | Serial.println(tmpStr); 190 | 191 | 192 | wifi_mode = 2; //AP mode with no SSID in EEPROM 193 | } 194 | 195 | if (wifi_mode == 0){ 196 | //Serial.println(" "); 197 | //Serial.println("Connected as a Client"); 198 | IPAddress myAddress = WiFi.localIP(); 199 | //Serial.println(myAddress); 200 | Serial.println("$FP 0 0 Client-IP......."); 201 | delay(100); 202 | sprintf(tmpStr,"$FP 0 1 %d.%d.%d.%d",myAddress[0],myAddress[1],myAddress[2],myAddress[3]); 203 | Serial.println(tmpStr); 204 | } 205 | 206 | ArduinoOTA.begin(); 207 | 208 | server.on("/", [] () { 209 | if(!server.authenticate(www_username, www_password)) 210 | return server.requestAuthentication(); 211 | String s; 212 | s = "OpenEVSE

Open Source Hardware

Wireless Configuration

Networks Found:

"; 213 | //s += ipStr; 214 | s += "

"; 215 | s += st; 216 | s += "

"; 217 | s += "

"; 218 | s += "Status RAPI

"; 219 | s += "

Firmware Update

"; 220 | s += ""; 221 | s += "\r\n\r\n"; 222 | server.send(200, "text/html", s); 223 | }); 224 | 225 | server.on("/a", [] () { 226 | if(!server.authenticate(www_username, www_password)) 227 | return server.requestAuthentication(); 228 | String s; 229 | String qsid = server.arg("ssid"); 230 | String qpass = server.arg("pass"); 231 | String qkey = server.arg("ekey"); 232 | String qnode = server.arg("node"); 233 | String qohm = server.arg("ohm"); 234 | qpass.replace("%21", "!"); 235 | // qpass.replace("%22", '"'); 236 | qpass.replace("%23", "#"); 237 | qpass.replace("%24", "$"); 238 | qpass.replace("%25", "%"); 239 | qpass.replace("%26", "&"); 240 | qpass.replace("%27", "'"); 241 | qpass.replace("%28", "("); 242 | qpass.replace("%29", ")"); 243 | qpass.replace("%2A", "*"); 244 | qpass.replace("%2B", "+"); 245 | qpass.replace("%2C", ","); 246 | qpass.replace("%2D", "-"); 247 | qpass.replace("%2E", "."); 248 | qpass.replace("%2F", "/"); 249 | qpass.replace("%3A", ":"); 250 | qpass.replace("%3B", ";"); 251 | qpass.replace("%3C", "<"); 252 | qpass.replace("%3D", "="); 253 | qpass.replace("%3E", ">"); 254 | qpass.replace("%3F", "?"); 255 | qpass.replace("%40", "@"); 256 | qpass.replace("%5B", "["); 257 | qpass.replace("%5C", "'\'"); 258 | qpass.replace("%5D", "]"); 259 | qpass.replace("%5E", "^"); 260 | qpass.replace("%5F", "_"); 261 | qpass.replace("%60", "`"); 262 | qpass.replace("%7B", "{"); 263 | qpass.replace("%7C", "|"); 264 | qpass.replace("%7D", "}"); 265 | qpass.replace("%7E", "~"); 266 | qpass.replace('+', ' '); 267 | qsid.replace("%21", "!"); 268 | // qsid.replace("%22", '"'); 269 | qsid.replace("%23", "#"); 270 | qsid.replace("%24", "$"); 271 | qsid.replace("%25", "%"); 272 | qsid.replace("%26", "&"); 273 | qsid.replace("%27", "'"); 274 | qsid.replace("%28", "("); 275 | qsid.replace("%29", ")"); 276 | qsid.replace("%2A", "*"); 277 | qsid.replace("%2B", "+"); 278 | qsid.replace("%2C", ","); 279 | qsid.replace("%2D", "-"); 280 | qsid.replace("%2E", "."); 281 | qsid.replace("%2F", "/"); 282 | qsid.replace("%3A", ":"); 283 | qsid.replace("%3B", ";"); 284 | qsid.replace("%3C", "<"); 285 | qsid.replace("%3D", "="); 286 | qsid.replace("%3E", ">"); 287 | qsid.replace("%3F", "?"); 288 | qsid.replace("%40", "@"); 289 | qsid.replace("%5B", "["); 290 | qsid.replace("%5C", "'\'"); 291 | qsid.replace("%5D", "]"); 292 | qsid.replace("%5E", "^"); 293 | qsid.replace("%5F", "_"); 294 | qsid.replace("%60", "`"); 295 | qsid.replace("%7B", "{"); 296 | qsid.replace("%7C", "|"); 297 | qsid.replace("%7D", "}"); 298 | qsid.replace("%7E", "~"); 299 | qsid.replace('+', ' '); 300 | if (qsid != 0){ 301 | ResetEEPROM(); 302 | for (int i = 0; i < qsid.length(); ++i){ 303 | EEPROM.write(i, qsid[i]); 304 | } 305 | //Serial.println("Writing Password to Memory:"); 306 | for (int i = 0; i < qpass.length(); ++i){ 307 | EEPROM.write(32+i, qpass[i]); 308 | } 309 | //Serial.println("Writing EMON Key to Memory:"); 310 | for (int i = 0; i < qkey.length(); ++i){ 311 | EEPROM.write(96+i, qkey[i]); 312 | } 313 | EEPROM.write(129, qnode[i]); 314 | for (int i = 0; i < qohm.length(); ++i){ 315 | EEPROM.write(130+i, qohm[i]); 316 | } 317 | EEPROM.commit(); 318 | s = "OpenEVSE

Open Source Hardware

Wireless Configuration

SSID and Password

"; 319 | //s += req; 320 | s += "

Saved to Memory...

Wifi will reset to join your network\r\n\r\n"; 321 | server.send(200, "text/html", s); 322 | delay(2000); 323 | WiFi.disconnect(); 324 | ESP.reset(); 325 | } 326 | else { 327 | s = "OpenEVSE

Open Source Hardware

Wireless Configuration

Networks Found:

"; 328 | //s += ipStr; 329 | s += "

"; 330 | s += st; 331 | s += "

"; 332 | s += "

SSID Required

"; 333 | s += "\r\n\r\n"; 334 | server.send(200, "text/html", s); 335 | } 336 | }); 337 | 338 | server.on("/r", [] () { 339 | if(!server.authenticate(www_username, www_password)) 340 | return server.requestAuthentication(); 341 | String s; 342 | String rapiString; 343 | String rapi = server.arg("rapi"); 344 | rapi.replace("%24", "$"); 345 | rapi.replace("+", " "); 346 | Serial.flush(); 347 | Serial.println(rapi); 348 | delay(100); 349 | while(Serial.available()) { 350 | rapiString = Serial.readStringUntil('\r'); 351 | } 352 | s = "OpenEVSE

Open Source Hardware

RAPI Command Sent

Common Commands:

Set Current - $SC XX

Set Service Level - $SL 1 - $SL 2 - $SL A

Get Real-time Current - $GG

Get Temperatures - $GP

"; 353 | s += "

"; 354 | s += "

"; 355 | s += rapi; 356 | s += "

>"; 357 | s += rapiString; 358 | s += "

\r\n\r\n"; 359 | server.send(200, "text/html", s); 360 | }); 361 | 362 | server.on("/reset", [] () { 363 | if(!server.authenticate(www_username, www_password)) 364 | return server.requestAuthentication(); 365 | String s; 366 | s = "OpenEVSE

Open Source Hardware

Wireless Configuration

Reset to Defaults:

"; 367 | s += "

Clearing the EEPROM

"; 368 | s += "\r\n\r\n"; 369 | ResetEEPROM(); 370 | EEPROM.commit(); 371 | server.send(200, "text/html", s); 372 | WiFi.disconnect(); 373 | delay(1000); 374 | ESP.reset(); 375 | }); 376 | 377 | server.on("/status", [] () { 378 | if(!server.authenticate(www_username, www_password)) 379 | return server.requestAuthentication(); 380 | String s; 381 | s = ""; 382 | s += "\r\n\r\n"; 383 | server.send(200, "text/html", s); 384 | }); 385 | 386 | server.on("/rapi", [] () { 387 | if(!server.authenticate(www_username, www_password)) 388 | return server.requestAuthentication(); 389 | String s; 390 | s = "OpenEVSE

Open Source Hardware

Send RAPI Command

Common Commands:

Set Current - $SC XX

Set Service Level - $SL 1 - $SL 2 - $SL A

Get Real-time Current - $GG

Get Temperatures - $GP

"; 391 | s += "

"; 392 | s += "

"; 393 | s += "\r\n\r\n"; 394 | server.send(200, "text/html", s); 395 | }); 396 | 397 | httpUpdater.setup(&server); 398 | server.begin(); 399 | httpUpdater.setup(&server); 400 | //Serial.println("HTTP server started"); 401 | delay(100); 402 | Timer = millis(); 403 | } 404 | 405 | 406 | 407 | void loop() { 408 | ArduinoOTA.handle(); 409 | server.handleClient(); 410 | 411 | int erase = 0; 412 | buttonState = digitalRead(0); 413 | while (buttonState == LOW) { 414 | buttonState = digitalRead(0); 415 | erase++; 416 | if (erase >= 5000) { 417 | ResetEEPROM(); 418 | int erase = 0; 419 | WiFi.disconnect(); 420 | Serial.print("Finished..."); 421 | delay(2000); 422 | ESP.reset(); 423 | } 424 | } 425 | // Remain in AP mode for 5 Minutes before resetting 426 | if (wifi_mode == 1){ 427 | if ((millis() - Timer) >= 300000){ 428 | ESP.reset(); 429 | } 430 | } 431 | if (wifi_mode == 0 && ohm_key != 0){ 432 | if ((millis() - Timer2) >= 60000){ 433 | Timer2 = millis(); 434 | WiFiClientSecure client; 435 | if (!client.connect(ohm_host, ohm_httpsPort)) { 436 | Serial.println("connection failed"); 437 | return; 438 | } 439 | if (client.verify(ohm_fingerprint, ohm_host)) { 440 | client.print(String("GET ") + ohm_url + ohm_key + " HTTP/1.1\r\n" + 441 | "Host: " + ohm_host + "\r\n" + 442 | "User-Agent: OpenEVSE\r\n" + 443 | "Connection: close\r\n\r\n"); 444 | String line = client.readString(); 445 | if(line.indexOf("False") > 0) { 446 | //Serial.println("It is not an Ohm Hour"); 447 | if (evse_sleep == 1){ 448 | evse_sleep = 0; 449 | Serial.println("$FE*AF"); 450 | } 451 | } 452 | if(line.indexOf("True") > 0) { 453 | //Serial.println("Ohm Hour"); 454 | if (evse_sleep == 0){ 455 | evse_sleep = 1; 456 | Serial.println("$FS*BD"); 457 | } 458 | } 459 | //Serial.println(line); 460 | } 461 | else { 462 | Serial.println("Certificate Invalid"); 463 | } 464 | } 465 | } 466 | if (wifi_mode == 0 && privateKey != 0){ 467 | if ((millis() - Timer) >= 30000){ 468 | Timer = millis(); 469 | Serial.flush(); 470 | Serial.println("$GE*B0"); 471 | delay(100); 472 | while(Serial.available()) { 473 | String rapiString = Serial.readStringUntil('\r'); 474 | if ( rapiString.startsWith("$OK ") ) { 475 | String qrapi; 476 | qrapi = rapiString.substring(rapiString.indexOf(' ')); 477 | pilot = qrapi.toInt(); 478 | } 479 | } 480 | 481 | delay(100); 482 | Serial.flush(); 483 | Serial.println("$GG*B2"); 484 | delay(100); 485 | while(Serial.available()) { 486 | String rapiString = Serial.readStringUntil('\r'); 487 | if ( rapiString.startsWith("$OK") ) { 488 | String qrapi; 489 | qrapi = rapiString.substring(rapiString.indexOf(' ')); 490 | amp = qrapi.toInt(); 491 | String qrapi1; 492 | qrapi1 = rapiString.substring(rapiString.lastIndexOf(' ')); 493 | volt = qrapi1.toInt(); 494 | } 495 | } 496 | delay(100); 497 | Serial.flush(); 498 | Serial.println("$GP*BB"); 499 | delay(100); 500 | while(Serial.available()) { 501 | String rapiString = Serial.readStringUntil('\r'); 502 | if (rapiString.startsWith("$OK") ) { 503 | String qrapi; 504 | qrapi = rapiString.substring(rapiString.indexOf(' ')); 505 | temp1 = qrapi.toInt(); 506 | String qrapi1; 507 | int firstRapiCmd = rapiString.indexOf(' '); 508 | qrapi1 = rapiString.substring(rapiString.indexOf(' ', firstRapiCmd + 1 )); 509 | temp2 = qrapi1.toInt(); 510 | String qrapi2; 511 | qrapi2 = rapiString.substring(rapiString.lastIndexOf(' ')); 512 | temp3 = qrapi2.toInt(); 513 | } 514 | } 515 | 516 | // Use WiFiClientSecure to create HTTPS connections 517 | WiFiClientSecure client; 518 | if (!client.connect(host, httpsPort)) { 519 | return; 520 | } 521 | 522 | 523 | // We now create a URL for OpenEVSE RAPI data upload request 524 | String url = e_url; 525 | String url_amp = inputID_AMP; 526 | url_amp += amp; 527 | url_amp += ","; 528 | String url_volt = inputID_VOLT; 529 | url_volt += volt; 530 | url_volt += ","; 531 | String url_temp1 = inputID_TEMP1; 532 | url_temp1 += temp1; 533 | url_temp1 += ","; 534 | String url_temp2 = inputID_TEMP2; 535 | url_temp2 += temp2; 536 | url_temp2 += ","; 537 | String url_temp3 = inputID_TEMP3; 538 | url_temp3 += temp3; 539 | url_temp3 += ","; 540 | String url_pilot = inputID_PILOT; 541 | url_pilot += pilot; 542 | url += node; 543 | url += "&json={"; 544 | url += url_amp; 545 | if (volt <= 0) { 546 | url += url_volt; 547 | } 548 | if (temp1 != 0) { 549 | url += url_temp1; 550 | } 551 | if (temp2 != 0) { 552 | url += url_temp2; 553 | } 554 | if (temp3 != 0) { 555 | url += url_temp3; 556 | } 557 | url += url_pilot; 558 | url += "}&devicekey="; 559 | url += privateKey.c_str(); 560 | 561 | // This will send the request to the server 562 | client.print(String("GET ") + url + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "User-Agent: OpenEVSE\r\n" + "Connection: close\r\n\r\n"); 563 | delay(10); 564 | while(client.available()){ 565 | String line = client.readStringUntil('\r'); 566 | } 567 | //Serial.println(host); 568 | //Serial.println(url); 569 | 570 | } 571 | } 572 | 573 | } 574 | --------------------------------------------------------------------------------