├── .gitignore ├── ESP_OTA.py ├── readme.md ├── teleinfo-esp8266_bb.png └── teleinfo_historique_mqtt_deepsleep.ino /.gitignore: -------------------------------------------------------------------------------- 1 | localconfig.h 2 | -------------------------------------------------------------------------------- /ESP_OTA.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # 4 | # The MIT License (MIT) 5 | # Copyright (c) 2015-2017 François GUILLIER 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | # THE SOFTWARE. 21 | 22 | # The arduino software must be loaded and sketch compiled 23 | # before lauching this software ! 24 | 25 | 26 | # http://esp8266.github.io/Arduino/versions/2.3.0/doc/ota_updates/readme.html 27 | 28 | from http.server import BaseHTTPRequestHandler, HTTPServer 29 | from urllib import parse 30 | import hashlib 31 | import os 32 | import sys 33 | 34 | # HTTPRequestHandler class 35 | class RequestHandler(BaseHTTPRequestHandler): 36 | 37 | # GET 38 | def do_GET(self): 39 | parsed_path = parse.urlparse(self.path) 40 | print(parsed_path, self.path) 41 | 42 | m = hashlib.md5() 43 | try: 44 | with open('/tmp/{}{}'.format(build_dir, parsed_path.path), 'rb') as f: 45 | firmware = f.read() 46 | 47 | m.update(firmware); 48 | print('local firmware: ' + m.hexdigest()) 49 | except: 50 | print("""Can't load local firmware""") 51 | return 52 | 53 | 54 | for name, value in self.headers.items(): 55 | #print(name, value) 56 | if name[:10] == 'x-ESP8266-': 57 | print(name[10:], '->', value) 58 | 59 | if self.headers.get('x-ESP8266-version') == m.hexdigest(): 60 | self.send_response(304) 61 | return 62 | 63 | # Send response status code 64 | self.send_response(200) 65 | 66 | # Send headers 67 | self.send_header('Content-type','application/octet-stream') 68 | self.send_header('Content-Disposition','attachment; filename=firmware.ino') 69 | self.send_header('Content-Length', len(firmware)) 70 | self.end_headers() 71 | 72 | self.wfile.write(firmware) 73 | return 74 | 75 | build_dir = "" 76 | for d in os.listdir('/tmp'): 77 | if d[:14] == 'arduino_build_' and d > build_dir: 78 | build_dir = d 79 | 80 | if len(build_dir) == 0: 81 | print("""Can't find Arduino Build Directory""") 82 | sys.exit(1) 83 | 84 | server_address = ('0.0.0.0', 8888) 85 | httpd = HTTPServer(server_address, RequestHandler) 86 | print('running server...') 87 | httpd.serve_forever() 88 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Teleinfo to MQTT 2 | 3 | Code is AS-IS without any warranty of any kind... 4 | 5 | Hardware used : 6 | * Nodemcu 1.0 (but any ESP8266 breakout with enough pins should do) 7 | * Optocoupler SFH620A (but LTV-814 could be even better) 8 | * 2N7000 or BS170 9 | * 2 * 10k + 1 * 4.7k resistors 10 | 11 | ![Schematics](teleinfo-esp8266_bb.png) 12 | 13 | * Version "Historique" (i.e. compatible with older meters) only for the moment 14 | * Version "Standard" (a.k.a new Linky version) to be written 15 | 16 | The python OTA helper can be used to upgrade the software on the ESP8266 17 | 18 | Corresponding blog entry: http://www.guillier.org/blog/2017/10/teleinfo-with-wifi-on-the-linky/ 19 | -------------------------------------------------------------------------------- /teleinfo-esp8266_bb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guillier/Teleinfo_MQTT/b8b28476eae84f4cabf4cffcb817548123c698ee/teleinfo-esp8266_bb.png -------------------------------------------------------------------------------- /teleinfo_historique_mqtt_deepsleep.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * Copyright (c) 2015-2017 François GUILLIER 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * The above copyright notice and this permission notice shall be included in 11 | * all copies or substantial portions of the Software. 12 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 13 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 14 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 15 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 16 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 17 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 18 | * THE SOFTWARE. 19 | */ 20 | #include 21 | #include 22 | #include 23 | #include "localconfig.h" // Empty file by default 24 | 25 | /* 26 | * 27 | * cm/teleinfo-049767880615/data/index {"value": "000073370"} 28 | * cm/teleinfo-049767880615/data/rms_current {"value": "001"} 29 | * cm/teleinfo-049767880615/data/apparent_power {"value": "00320"} 30 | */ 31 | 32 | // if teleinfo is not working after 60s then go to sleep for 5 minutes 33 | #ifndef TIMEOUT_IF_NO_TELEINFO 34 | #define TIMEOUT_IF_NO_TELEINFO 60000 35 | #endif 36 | 37 | #ifndef WIFI_SSID 38 | #define WIFI_SSID "DEMO_SSID" 39 | #endif 40 | 41 | #ifndef WIFI_PASSWORD 42 | #define WIFI_PASSWORD "DEMO_PASSPHRASE" 43 | #endif 44 | 45 | #ifndef MQTT_TOPIC_BASE_DEBUG 46 | #define MQTT_TOPIC_BASE_DEBUG "exp/teleinfo-esp8266-1/data/" 47 | #endif 48 | 49 | #ifndef MQTT_SERVER 50 | #define MQTT_SERVER MQTTserver(192, 168, 99, 99); 51 | #endif 52 | 53 | #ifndef UPDATE_URL 54 | #define UPDATE_URL "http://192.168.99.99:8888/teleinfo_historique_mqtt_deepsleep.ino.bin" 55 | #endif 56 | 57 | const char* ssid = WIFI_SSID; 58 | const char* password = WIFI_PASSWORD; 59 | String mqtt_topic_base_debug = MQTT_TOPIC_BASE_DEBUG; 60 | IPAddress MQTT_SERVER; 61 | WiFiClient espClient; 62 | PubSubClient mqtt_client(espClient); 63 | 64 | #ifdef DEBUG 65 | String debug_val; 66 | #endif 67 | String label; 68 | String value; 69 | char checksum1; 70 | char checksum2; 71 | String sn; 72 | String meter_index; 73 | String current; 74 | String power; 75 | unsigned char state = 0; 76 | 77 | // Connect to Wifi and report IP address 78 | void start_Wifi() 79 | { 80 | #ifdef DEBUG 81 | Serial.println("Starting Wifi"); 82 | #endif 83 | WiFi.mode(WIFI_STA); 84 | WiFi.persistent(false); 85 | WiFi.begin(ssid, password); 86 | int counter = 0; 87 | while ((WiFi.status() != WL_CONNECTED) && (counter < 240)) 88 | { 89 | delay(500); 90 | counter ++; 91 | #if defined(DEBUG) 92 | Serial.print("."); 93 | #endif 94 | } 95 | if (counter >= 240) 96 | { 97 | #ifdef DEBUG 98 | Serial.println("Connection Failed! Rebooting..."); 99 | #endif 100 | ESP.restart(); 101 | } 102 | #ifdef DEBUG 103 | Serial.println(""); 104 | Serial.println("WiFi connected to "); 105 | Serial.println(ssid); 106 | Serial.print("IP address: "); 107 | Serial.println(WiFi.localIP()); 108 | #endif 109 | } 110 | 111 | void setup() 112 | { 113 | Serial.begin(1200, SERIAL_7E1); 114 | 115 | #ifdef DEBUG 116 | Serial.println("starting"); 117 | #endif 118 | 119 | start_Wifi(); 120 | 121 | delay(1000); 122 | 123 | pinMode(D1, INPUT_PULLUP); 124 | if (digitalRead(D1) == 0) 125 | { 126 | #ifdef DEBUG 127 | Serial.println("Update attempt"); 128 | #endif 129 | 130 | ESPhttpUpdate.update(UPDATE_URL, ESP.getSketchMD5()); 131 | 132 | #ifdef DEBUG 133 | Serial.println("Continuing"); 134 | #endif 135 | } 136 | 137 | mqtt_client.setServer(MQTTserver, 1883); 138 | } 139 | 140 | void publish (String topic, String payload) 141 | { 142 | if (!mqtt_client.connected()) 143 | { 144 | mqtt_client.connect("arduinoClient"); 145 | #ifdef DEBUG 146 | Serial.println("MQTT Connect"); 147 | #endif 148 | } 149 | 150 | mqtt_client.publish(topic.c_str(), payload.c_str()); 151 | } 152 | 153 | void publish_value(String sn, String name, String value, bool number=false) 154 | { 155 | if (value != "") 156 | { 157 | if (number) 158 | value = String(value.toInt()); // Double convertion to remove leading zeros etc... 159 | else 160 | value = "\"" + value + "\""; 161 | publish("cm/teleinfo-" + sn + "/data/" + name, "{\"value\": " + value + "}"); 162 | } 163 | } 164 | 165 | void loop() 166 | { 167 | if (Serial.available() > 0) 168 | { 169 | char c = Serial.read(); 170 | switch (state) 171 | { 172 | // Start of Frame 173 | case 0: 174 | if (c == 0x02) 175 | { 176 | state = 1; 177 | sn = ""; 178 | meter_index = ""; 179 | #ifdef DEBUG 180 | debug_val = "{"; 181 | #endif 182 | } 183 | break; 184 | 185 | // End of Frame 186 | case 6: 187 | if (c == 0x03) 188 | { 189 | #ifdef DEBUG 190 | publish(mqtt_topic_base_debug + "debug", debug_val + "\"firmware_md5\": \"" + ESP.getSketchMD5() + "\"}"); 191 | #endif 192 | 193 | if (sn != "") 194 | { 195 | publish_value(sn, "index", meter_index, true); 196 | publish_value(sn, "rms_current", current, true); 197 | publish_value(sn, "apparent_power", power, true); 198 | 199 | #ifdef DEBUG 200 | Serial.println("Going to sleep..."); 201 | #endif 202 | ESP.deepSleep(65e6 - micros()); // ~ 60 secondes 203 | // Wake-up is done by a hardware reset 204 | #ifdef DEBUG 205 | Serial.println("This should not be printed"); 206 | #endif 207 | } 208 | state = 0; 209 | break; 210 | } 211 | // NO BREAK HERE ! 212 | // If frame is not yet finished then we carry on with a new group 213 | 214 | // Start of Group 215 | case 1: 216 | if (c != 0x0A) 217 | { 218 | #ifdef DEBUG 219 | publish(mqtt_topic_base_debug + "error1", String(c)); 220 | #endif 221 | state = 0; 222 | } else 223 | { 224 | state = 2; 225 | label = ""; 226 | checksum1 = 0; 227 | value = ""; 228 | } 229 | break; 230 | 231 | // Label 232 | case 2: 233 | checksum1 += c; 234 | if ((c == 0x20) || (c == 0x09)) 235 | state = 3; 236 | else 237 | label += String(c); 238 | break; 239 | 240 | // Value 241 | case 3: 242 | if ((c == 0x20) || (c == 0x09)) 243 | { 244 | checksum2 += c; 245 | state = 4; 246 | } 247 | else 248 | { 249 | checksum1 += c; 250 | value += String(c); 251 | } 252 | break; 253 | 254 | // Checksum 255 | case 4: 256 | checksum1 = (checksum1 & 0x3F) + 0x20; 257 | checksum2 = (checksum2 & 0x3F) + 0x20; 258 | if ((checksum1 != c) && (checksum2 != c)) 259 | { 260 | #ifdef DEBUG 261 | publish(mqtt_topic_base_debug + "checksum", String(checksum1) + ' ' + String(checksum2) + ' ' + String(c) + ' ' + label + ' ' + value); 262 | #endif 263 | state = 0; 264 | } else 265 | state = 5; 266 | break; 267 | 268 | // End of Group 269 | case 5: 270 | if (c == 0x0D) 271 | { 272 | #ifdef DEBUG 273 | debug_val += "\"" + label + "\": \"" + value + "\", "; 274 | #endif 275 | state = 6; 276 | if (label == "ADCO") 277 | sn = value; 278 | if (label == "BASE") 279 | meter_index = value; 280 | if (label == "IINST") 281 | current = value; 282 | if (label == "PAPP") 283 | power = value; 284 | } 285 | else 286 | { 287 | #ifdef DEBUG 288 | publish(mqtt_topic_base_debug + "error5", ""); 289 | #endif 290 | state = 0; 291 | } 292 | break; 293 | } 294 | } else if (millis() > TIMEOUT_IF_NO_TELEINFO) 295 | { 296 | #ifdef DEBUG 297 | Serial.println("No reception from meter... Going to sleep..."); 298 | #endif 299 | ESP.deepSleep(600e6 - micros()); // 600 secondes 300 | } 301 | } 302 | --------------------------------------------------------------------------------