You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.

Smartmeter_Reader.ino 9.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. /*
  2. * Stand: 25.08.2021
  3. * Author: Julian Rico
  4. *
  5. * Software für Stromzähler-Empfänger
  6. *
  7. * Diese Software beinhaltet die Logik, um einen EMH Gen. K Stromzähler per
  8. * Infrarot-Schnittstelle auszulesen und den Gesamtverbrauch des Zählers
  9. * per MQTT an einen MQTT Broker zu schicken.
  10. */
  11. /* Includes */
  12. #include <Wire.h>
  13. #include <WiFiManager.h> /* https://github.com/tzapu/WiFiManager */
  14. #include <PubSubClient.h> /* https://github.com/knolleary/pubsubclient */
  15. /* Defines */
  16. #define BAUDRATE 9600
  17. #define BUFFERSIZE 1000
  18. #define RTC_I2C_ADDR 0x68
  19. /* Globale Variablen */
  20. int BUFFER[BUFFERSIZE]; // Buffer für Einlesen serieller
  21. // Daten
  22. int i, j, error; // Laufvariablen
  23. int shutdown = 0; // Wird gesetzt nach erfolgreichem
  24. // Einlesen des Zählstands
  25. WiFiClient espClient; // WiFi
  26. PubSubClient client(espClient); // MQTT Client
  27. const char* MQTT_BROKER = "192.168.178.102"; // MQTT Broker IP -> TODO anpassen
  28. const unsigned short MQTT_PORT = 1883; // MQTT Broker Port -> TODO prüfen
  29. char* MQTT_TOPIC_LIVE = "smartmeter"; // MQTT Topic für Stromzählerwerte
  30. char* MQTT_TOPIC_TEST = "ESP"; // MQTT Topic für Eingaben von
  31. // Teststand
  32. char* MQTT_MSG = "ESP ONLINE."; // Message von ESP wenn Setup
  33. // erfolgreich
  34. /* Functionsprototypen */
  35. // MQTT Callback: Gibt erhaltene Message aus
  36. void callback(char* topic, byte* payload, unsigned int length);
  37. // Liest serielle Daten ein, unterscheidet ob Daten von Teststand oder Stromzähler
  38. // kommen
  39. bool readTelegramm();
  40. // Versendet Zählstand per MQTT (TOPIC_LIVE)
  41. void read_smartmeter(int start, int komma, int ende);
  42. // Versendet Testdaten per MQTT (TOPIC_TEST)
  43. void read_teststation(int start);
  44. // Alarm Register und Control Bits setzen
  45. void RTC_Setup();
  46. // Ausschalten
  47. void RTC_Shutdown();
  48. /* Setup: einmalig */
  49. void setup() {
  50. // Debug LED
  51. pinMode(LED_BUILTIN, OUTPUT);
  52. digitalWrite(LED_BUILTIN, HIGH);
  53. // Serial Setup
  54. Serial.begin(BAUDRATE);
  55. Serial.flush();
  56. Serial.println("Serial init done.");
  57. // Wifi Setup
  58. WiFi.mode(WIFI_STA);
  59. WiFiManager wifiManager;
  60. bool res;
  61. res = wifiManager.autoConnect("ESP32-STROMZÄHLER", "PASSWORD");
  62. if(!res) {
  63. Serial.println("Failed to connect");
  64. }
  65. else {
  66. //if you get here you have connected to the WiFi
  67. Serial.println("connected...yeey :)");
  68. }
  69. // MQTT Setup
  70. error = 0;
  71. client.setServer(MQTT_BROKER, MQTT_PORT);
  72. while (!client.connected()) {
  73. client.connect("ESP8266Client");
  74. if (client.connected()) {
  75. Serial.println("MQTT connected");
  76. }
  77. else {
  78. Serial.print("failed, rc=");
  79. Serial.print(client.state());
  80. Serial.println("try again in 5 seconds");
  81. delay(5000);
  82. error++;
  83. if (error == 12) { // Nach 60 Sekunden Shutdown
  84. RTC_Shutdown();
  85. }
  86. }
  87. }
  88. // Das kann dann später raus, ist nur zum testen der MQTT Verbindung per Serial
  89. // Monitor
  90. client.subscribe(MQTT_TOPIC_TEST);
  91. client.setCallback(callback);
  92. client.publish(MQTT_TOPIC_TEST, MQTT_MSG);
  93. // RTC
  94. RTC_Setup();
  95. Serial.println("RTC Setup done");
  96. }
  97. /* Hauptprogramm */
  98. void loop() {
  99. // Benötigt für Empfangen von MQTT Messages -> kann später raus
  100. client.loop();
  101. // ESP kommst sonst seriell nicht hinterher
  102. delay(75);
  103. // Serielle Daten einlesen falls vorhanden
  104. if (Serial.available() > 0) {
  105. if (readTelegramm())
  106. shutdown = 1;
  107. }
  108. // Buffer für nächsten Datensatz leeren (benötigt, wenn Zählstand nicht dabei
  109. // war)
  110. if (i > 0) {
  111. for (int k = 0; k < i; k++)
  112. BUFFER[k] = 0;
  113. i = 0;
  114. }
  115. // Shutdown durch RTC vorbereiten
  116. if (shutdown)
  117. RTC_Shutdown();
  118. }
  119. /* MQTT Subscriber Callback */
  120. void callback(char* topic, byte* payload, unsigned int length) {
  121. Serial.print("New message in topic: ");
  122. Serial.println(topic);
  123. Serial.print("Message: ");
  124. for (i = 0; i < length; i++) {
  125. Serial.print((char)payload[i]);
  126. }
  127. Serial.println();
  128. Serial.println("-----------------------");
  129. Serial.println();
  130. }
  131. bool readTelegramm() {
  132. // Read D0 Telegram
  133. i = 0;
  134. do {
  135. if(i < BUFFERSIZE) {
  136. BUFFER[i] = Serial.read();
  137. /* Debug */
  138. if (BUFFER[i] < 0xF)
  139. Serial.print("0"); // Führende Null erzeugen
  140. Serial.print(BUFFER[i], HEX);
  141. Serial.print(" ");
  142. if (BUFFER[i] == 0x0A) { // 0A (hex) = LF (ASCII) => Neue Zeile
  143. Serial.println();
  144. }
  145. /* Debug Ende */
  146. i++;
  147. }
  148. } while (Serial.available());
  149. // Buffer nach Daten für Zählerstand absuchen
  150. for (j = 0; j < i; j++) { // Buffer nach Zeichen absuchen
  151. // Daten von Teststand: "¡Bitte geben Sie ... !");
  152. if (BUFFER[j] == 0xC2 && BUFFER[j+1]) { /* C2 A1 = '¡' */
  153. return false; // Shutdown für RTC nur schicken wenn Daten von Smartmeter
  154. // kommen
  155. }
  156. // http://itrona.ch/stuff/F2-2_PJM_5_Beschreibung%20SML%20Datenprotokoll%20
  157. // V1.0_28.02.2011.pdf
  158. // Daten von Stromzähler: Gesamtverbrauch herausfiltern
  159. if ( /* OBIS Kennung: 1-0.1.8.0*255 = 01 00 01 08 00
  160. FF */
  161. BUFFER[j] == 0x77 && /* 77 - SML_Message.messageBody.
  162. SML_GetList_Reponse.valList.valListEntry
  163. (Sequence) */
  164. BUFFER[j+1] == 0x07 && /* 07 - objName (TL[1] + octet-string[6] */
  165. BUFFER[j+2] == 0x01 && /* 01 - objName Teil A */
  166. BUFFER[j+3] == 0x00 && /* 00 - objName Teil B */
  167. BUFFER[j+4] == 0x01 && /* 01 - objName Teil C */
  168. BUFFER[j+5] == 0x08 && /* 08 - objName Teil D */
  169. BUFFER[j+6] == 0x00 && /* 00 - objName Teil E */
  170. BUFFER[j+7] == 0xFF) /* FF - objName Teil F */
  171. /* xx - status */
  172. /* xx - valTime */
  173. /* xx - unit */
  174. /* xx - scaler */
  175. {
  176. j = j+8;
  177. // status, valTime, unit und scaler überspringen
  178. while (BUFFER[j] != 0x59) { j++; } /* 59 - value (TL[1] + 64 Bit Integer */
  179. // Zahl aus SML in Variable überführen
  180. // 64 Bit: 2 x 32 Bit Variablen -> mWh
  181. long long mWh = ((long long)BUFFER[j+1]) << 56 |
  182. ((long long)BUFFER[j+2]) << 48 |
  183. ((long long)BUFFER[j+3]) << 40 |
  184. ((long long)BUFFER[j+4]) << 32 |
  185. ((long long)BUFFER[j+5]) << 24 |
  186. ((long long)BUFFER[j+6]) << 16 |
  187. ((long long)BUFFER[j+7]) << 8 |
  188. ((long long)BUFFER[j+8]);
  189. // Debug
  190. Serial.println();
  191. Serial.print("mWh:"); Serial.println(mWh);
  192. mWh = mWh / 10000; // mWh -> kWh
  193. int kWh = (int) mWh;
  194. // Debug
  195. Serial.print("Gesamtverbrauch: ");Serial.println(kWh);
  196. // Zählstand an MQTT Broker schicken
  197. send_MQTT(kWh);
  198. // Wenn Gesamtverbrauch in SML gefunden: Signal für Shutdown geben
  199. return true;
  200. } // Ende if (OBIS Kennung)
  201. j++;
  202. } // Ende for-Schleife
  203. // Hier return falls keine gültige SML Nachricht erkannt wurde => ESP nicht
  204. // ausschalten, sondern auf nächste warten
  205. return false;
  206. }
  207. void send_MQTT(int kWh) {
  208. String temp = String(kWh);
  209. client.publish(MQTT_TOPIC_LIVE, temp.c_str());
  210. }
  211. void RTC_Setup() {
  212. /* Set Alarm 1 on seconds = 0, minutes = 0, hours = 0 */
  213. Wire.beginTransmission(RTC_I2C_ADDR);
  214. Wire.write(0x07); // Address of A1M1
  215. Wire.write(0x00); // A1M1 = 0, alarm value seconds = 0
  216. Wire.write(0x80); // A1M2 = 0, alarm value minutes = 0
  217. Wire.write(0x80); // A1M3 = 0, alarm value hours = 0
  218. Wire.write(0x80); // A1M4 = 1, rest X
  219. Wire.endTransmission();
  220. /* Set alarm 2 on minutes = 0, hours = 12 */
  221. Wire.beginTransmission(RTC_I2C_ADDR);
  222. Wire.write(0x0B); // Address of A2M2
  223. Wire.write(0x00); // A2M2 = 0, alarm value minutes = 0
  224. Wire.write(0x12); // A2M3 = 0, alarm value hours = 12
  225. Wire.endTransmission();
  226. /* Set A1E & A2E control bits */
  227. Wire.beginTransmission(RTC_I2C_ADDR);
  228. Wire.write(0x0e); // Control byte
  229. Wire.write(0x1C | 3); // Default | A1E | A2E
  230. Wire.endTransmission();
  231. }
  232. void RTC_Shutdown() {
  233. Wire.beginTransmission(RTC_I2C_ADDR);
  234. Wire.write(0x0F); // Address of control/status register
  235. Wire.endTransmission();
  236. Wire.requestFrom(RTC_I2C_ADDR, 1); // Read the current value of the register
  237. unsigned char reg_val = Wire.read();
  238. Wire.beginTransmission(RTC_I2C_ADDR);
  239. Wire.write(0x0F); // Address of control/status register
  240. Wire.write(reg_val & ~0x03); // Write the old value with A1F&A2F flags
  241. // cleared
  242. Wire.endTransmission(); // -> this resets the latching ~INT Pin
  243. }