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 8.9KB

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