Components and supplies
VMA 304 SD card shield
5 mm LED: Yellow
Level shifter, bidirectional, 5V 3.3V
5 mm LED: Red
BC337-25 Transistor, NPN
IR Phototransistor, NPN, SFH309FA
DC Power Connector, Jack
Arduino Nano Every
Resistor 1M ohm
VMA 301 RTC shield
Custom PCB
LED, Blue
Resistor 22.1k ohm
2 wire cable (phototransistor PCB)
Through Hole Resistor, 220 ohm
Tools and machines
Soldering iron (generic)
Solder Wire, Lead Free
Project description
Code
eHZ-IW8E2A5_EMLogger_ArdNanoEvery_TMG_09.ino
arduino
Recording of 2 eHZ readings to an SD card incl. supply voltage monitoring and recording
1/* 2 * Read output of 2 electricity meters of type eHZ-IW8E2A5 and write data to SD card. 3 * 4 * Written by Torsten Geppert (2020/2021) based on ideas/parts of code from 5 * - https://www.volkszaehler.org/ 6 * - and hardware / software by Volker Wegert https://sourceforge.net/p/ehzy/wiki/Hardware/ 7 * - and "set_clock.ino" by Matt Sparks, 2009, http://quadpoint.org/projects/arduino-ds1302 (seems to be one of the very few FUNCTIONAL DS1302 libraries out there in the www) 8 * 9 * Major changes/adjustments 10 * 1) Hardware modified. Replaced resistors 11 * - between IR diode and GND: 15 kOhm --> 1 MOhm (470 kOhm also sufficient). Necessary to bring BC337(-25) transistor to achieve the necessary gain (probably because of lower gain of BC337-25 as compared to BC337-40 or maybe signal of IR sender of eHZ too weak or alingment IR sender to IR receiver not precise enough 12 * - between collector of transistor and VCC: 15 kOhm --> 22 kOhm 13 * 2) w/ resepect to Volker Wegert's code: 14 * - Changes in syntax of PROGMEM related texts and corresponding array necessary (probably due to other version of IDE as compared to Volker Wegert back in in 2012) 15 * - in the end removal of parts of code (e.g piezo control),I did not use in my final code (Nevertheless, those things were really helpfull for debugging during development phase) 16 * 3) Change in Arduino related Hardware 17 * - Use Arduino Nano Every + DS1302 based RTC module and SD card shield (instead of Arduino Uno + SD card shield incl. RTC). 18 * Reason: Limitation of Arduino Uno's RAM, making code run unstable w/ already 1 electricity meter (of type eHZ-IW8E2A5). It was not possible to realize readout of 2 eHZs using Arduino UNO (at least my experience/impression) 19 * - HW used: 20 * - RTC: VMA301 by Velleman (e.g., from https://www.conrad.de/de/p/makerfactory-echtzeit-uhr-vma301-passend-fuer-arduino-boards-arduino-arduino-uno-fayaduino-freeduino-seeeduino-1612764.html) 21 * - SD shield: VMA304 by Velleman (e.g., https://www.conrad.de/de/p/makerfactory-sd-karten-logging-shield-fuer-arduino-2-tlg-1612765.html) 22 * - Level shifter (needed to adjust output from Arduino (5V) to SD shield (3.3V). In case NOT used the SD (shield ?) card might be irreversibly damaged !!! 23 * - added 3 LEDs to show SUCCESS or FAILURE of SD card initialization and data aqcuisition 24 * - added piezo speaker (accoustic warning in case supply voiltage - e.g., from battery) drops below defineable limit 25 * - additional volatge display (manually triggered by push-button (NOT connected to/controlled by Arduino) 26 * 4) Time of RTC shield can be set in section "RTC setup". Should be sufficient to do this initially (i.e., when you record your data for the first time and have to set the actual time) or in case RTC time deviates too much from real time (e.g., due to drift of oscillator in RC module due to missing temperature compensatioen or similar) 27 * see comment below "Uncomment in case current time should be written to RTC shield" 28 * 5) Ouptut into 1 file, at defined interval, for two meters type eHZ-IW8E2A5. (Unfortunately - and currently not understood - sometimes data is written into several files. However, due to naming (increasing number) it is stil possible to bring those files in (temporal) order. In addition time stamp is written into file for each datapoint aacquired. 29 * Output data: OBIS Kennzahl 1.8.0 (at Position 9x16+12 = 156 of SML protocol (In buffer w/ 1st element being number 0: position 155). 4 bytes (right after HEX value "56" at position 155 ## ## ## ## ##) 30 * To understand the SML protocol (and where to find the OBIS data I was interested in) Stefan Weigerts explanation is highly appreciated: http://www.stefan-weigert.de/php_loader/sml.php 31 */ 32 33/* ======================================================================================================= 34 * Include Libraries and alike 35 * ======================================================================================================= */ 36 37#include <stdarg.h> 38#include <stdio.h> 39#include <avr/pgmspace.h> 40#include <SoftwareSerial.h> // Serial interfaces realized by software (to allow, e.g., reading of more than just 1 sensor/eHz) 41#include <SD.h> // e.g., to write files to SD card etc. 42#include <SPI.h> // needed for SD.h 43#include <DS1302.h> // needed for RTC shield 44 45/*************************************************************************************************** 46 * CONSTANTS and alike 47 ***************************************************************************************************/ 48 49// Software Serial stuff ============================================================================================= 50// Serial Data from source 1 (IR diode of 1st eHZ) 51#define IR_RX_PIN_1 A0 // Pin 14 on Arduino Nano Every 52#define IR_TX_PIN_1 A2 // not used, but required for SoftwareSerial; Pin 16 on Arduino Nano Every 53// Serial Data from source 2 (IR diode of 2nd eHZ) 54#define IR_RX_PIN_2 A1 // Pin 14 on Arduino Nano Every 55#define IR_TX_PIN_2 A2 // not used, but required for SoftwareSerial; Pin 16 on Arduino Nano Every 56// Dummy software serial 57#define DUMMY_RX_PIN 9 // not connected 58#define DUMMY_TX_PIN A3 // not connected 59 60// SD card shield VMA304 stuff ============================================================================================= 61#define SD_CS_PIN 8 // Pin for SD card selection.Typically use hardware SS pin. (Pin 10 on most Arduino boards; however pin 8=A12 on Arduino Nano Every) 62#define SD_SS_PIN 10 // ###really needed ? Check if can be deleted ### 63 64// RTC stuff shield VMA301 stuff =========================================================================================== 65// uses Pins A3, A4, A5 on Arduino Nano Every (Physical Pins 7, 8, 9; aka ???, SDA, SCL) 66const int kCePin = 17; // Chip Enable 67const int kIoPin = 18; // Input/Output 68const int kSclkPin = 19; // Serial Clock 69 70// Other stuff ============================================================================================= 71#define loggingInterval 3600000 // time between readings in milli-seconds, e.g., 72 // 5 min = 5 * 60 * 1000 = 300 000 73 // 60 min = 60 * 60 * 1000 = 3 600 000 74// supply voltage monitoring stuff =========================================================================================== 75#define buzzPin 3 // Buzzer connected to PWM Pin D3 76#define supplVoltPin A2 // center contact of voltage divider (GND <-> supply Voltage (NOT 5V ! but V_in)) 77#define operatVolt 5 // operating voltage of Arduino (used as reference for ADC) 78#define buzzTimeEarly 8 // earliest time (hour of day; e.g., 8 AM = 8; 10PM -> 22) buzzer is triggered in case supply voltage below limit 79#define buzzTimeLate 20 // latest time (hour of day) buzzer is triggered in case supply voltage below limit 80float supplVoltLim = 5.6; // absolute supply voltage limit; serves as trigger for buzzer 81 82int numberSMLBytesRead = 0; // used to restart reading of SML data in case less than specified number of bytes (typically 397 for my electricity meter) read 83 84const int LED_WriteWarning = 4; // Pin D4 (physical pin no. 22): LED to signalize approaching or ongoing write to SD card 85const int LED_SML_Acquisition_eHZ1 = 5; // Pin PD1 (phyiscal pin no. 10): LED to signalize status of SML data acquisition for eHZ1 (OK: flashiung @ 1Hz; NOK: flashing @4 Hz*) 86const int LED_SML_Acquisition_eHZ2 = 6; // Pin A13 (phyical pin no. 24): LED to signalize status of SML data acquisition for eHZ1 (OK: flashiung @ 1Hz; NOK: flashing @4 Hz*); for whatever reason does NOT work w/ originally intended Pin D7 (phys pin 3; AVR Pin 39) 87// *frequency can be changed below to, e.g. 2 Hz by setting on time to 500 88int eHZ1Blink_ON=1000; // time LED for eHZ1 is turned on 89int eHZ2Blink_ON=1000; // time LED for eHZ2 is turned on 90int eHZ1Blink_OFF=250; // time LED for eHZ1 is turned off 91int eHZ2Blink_OFF=250; // time LED for eHZ2 is turned off 92bool SML_DataAcquisition_eHZ1 = false; // TRUE: SML data for eHZ1 acquired successfully; FALSE: error (e.g., buffer overflow, wrong header) during SML data acquisition for eHZ1 93bool SML_DataAcquisition_eHZ2 = false; // TRUE: SML data for eHZ2 acquired successfully; FALSE: error (e.g., buffer overflow, wrong header) during SML data acquisition for eHZ2 94const int eHZ1_ID = 1; 95const int eHZ2_ID = 2; 96unsigned long startTime; 97 98/** 99 * size of the SML message buffer = meximum number of bytes that can be received 100 */ 101#define SML_MSG_BUFFER_SIZE 400 // eHZ-IW8E2A5 delivers SML telegram of 396 bytes 102 103/** 104 * size of a filename (8.3 + terminating 0x00 = 13 chars) 105 */ 106#define FILENAME_SIZE 13 107 108/** 109 * maximum number of files that can be stored 110 */ 111#define MAX_FILE_NUMBER 99999999 112 113/** 114 * maximum time to wait for the end of a data packet received via IR 115 */ 116#define SERIAL_READ_TIMEOUT_MS 500 117 118/*************************************************************************************************** 119 * MESSAGES 120 ***************************************************************************************************/ 121 122/** 123 * maximum length of a message (entire text after variable substitution!) 124 */ 125#define MAX_MESSAGE_LENGTH 50 126 127/** 128 * message numbers 129 */ 130#define MSG_PROGRAM_STOPPED 0 131#define MSG_NEXT_FILENAME 1 132#define MSG_NO_FILE_NAMES_LEFT 2 133#define MSG_INIT_HARDWARE 3 134#define MSG_INIT_SD_CARD 4 135#define MSG_INIT_SD_CARD_ERROR 5 136#define MSG_BYTE_READ 6 137#define MSG_BUFFER_OVERFLOW 7 138#define MSG_SERIAL_OVERFLOW 8 139#define MSG_INVALID_HEADER 9 140#define MSG_FILE_OPEN_FAILED 10 141#define MSG_FILE_WRITTEN 11 142#define MSG_FREE_MEMORY 12 143#define MSG_NUM_BYTES_READ 13 144 145/** 146 * actual message texts - caution, adapt MAX_MESSAGE_LENGTH if required! 147 * ....+....1....+....2....+....3....+....4....+....5 148 */ 149const char msgText00[] PROGMEM = "Program stopped."; 150const char msgText01[] PROGMEM = "Next output file name is '%s'"; 151const char msgText02[] PROGMEM = "No more file names left"; 152const char msgText03[] PROGMEM = "Initializing Hardware..."; 153const char msgText04[] PROGMEM = "Initializing SD Card..."; 154const char msgText05[] PROGMEM = "SD Card initialization failed"; 155const char msgText06[] PROGMEM = "Read byte %02hhu from IR receiver"; 156const char msgText07[] PROGMEM = "Message buffer overflow"; 157const char msgText08[] PROGMEM = "Serial buffer overflow"; 158const char msgText09[] PROGMEM = "Invalid escape sequence"; 159const char msgText10[] PROGMEM = "Unable to open output file"; 160const char msgText11[] PROGMEM = "%u bytes of data written to file '%s'"; 161const char msgText12[] PROGMEM = "%u bytes of memory available"; 162const char msgText13[] PROGMEM = "%u bytes read"; 163 164/** 165 * table for easier access to the message texts 166 */ 167const char *const msgTextTable[] PROGMEM = { 168 msgText00, 169 msgText01, 170 msgText02, 171 msgText03, 172 msgText04, 173 msgText05, 174 msgText06, 175 msgText07, 176 msgText08, 177 msgText09, 178 msgText10, 179 msgText11, 180 msgText12, 181 msgText13 182}; 183 184/*************************************************************************************************** 185 * GLOBAL VARIABLES (YUCK!) 186 ***************************************************************************************************/ 187 188/** 189 * instance of the SoftwareSerial library to handle the IR communication 190 */ 191SoftwareSerial mySerial_1(IR_RX_PIN_1, IR_TX_PIN_1); // used for readout of SML data of eHZ1 192SoftwareSerial mySerial_2(IR_RX_PIN_2, IR_TX_PIN_2); // used for readout of SML data of eHZ2 193SoftwareSerial dummySerial(DUMMY_RX_PIN, DUMMY_TX_PIN); 194 195// Create a RTC DS1302 object. 196DS1302 rtc(kCePin, kIoPin, kSclkPin); 197 198/** 199 * variables to keep track of the name of the next file to be written 200 */ 201unsigned long nextFileNumber = 0; 202char nextFileName[FILENAME_SIZE]; 203 204/** 205 * the global buffer to store the SML message currently being read 206 */ 207unsigned char buffer[SML_MSG_BUFFER_SIZE]; 208 209/** 210 * Readings of eHZ 1 and 2 (after extraction from SML data) and corresponding timestamps 211 */ 212unsigned long eHZ_1_Reading; 213unsigned long eHZ_2_Reading; 214char eHZ_1_Reading_timeStamp[50]; 215char eHZ_2_Reading_timeStamp[50]; 216 217/** 218 * Supply voltage 219 */ 220float supplVolt; 221 222/*************************************************************************************************** 223 * SUBROUTINES 224 ***************************************************************************************************/ 225 226/** 227 * printMessage - reads a message text from the PROGMEM, performs variable substitution and 228 * writes the resulting text to the serial console. Use MSG_* constants for 229 * messageNumber. 230 */ 231void printMessage(int messageNumber, ...) { 232 va_list args; 233 char format[MAX_MESSAGE_LENGTH]; 234 char buffer[MAX_MESSAGE_LENGTH]; 235 va_start(args, messageNumber); 236 strncpy_P(format, (char*)pgm_read_word(&(msgTextTable[messageNumber])), MAX_MESSAGE_LENGTH); 237 vsnprintf(buffer, MAX_MESSAGE_LENGTH, format, args); 238 Serial.println(buffer); 239 va_end(args); 240} 241 242/** 243 * reportFreeMemory - determines the amount of free memory and logs it to the serial console. 244 */ 245void reportFreeMemory() { 246 int freeMemory; 247 uint8_t * heapptr, * stackptr; 248 stackptr = (uint8_t *) malloc(4); // use stackptr temporarily 249 heapptr = stackptr; // save value of heap pointer 250 free(stackptr); // free up the memory again (sets stackptr to 0) 251 stackptr = (uint8_t *) (SP); // save value of stack pointer 252 freeMemory = stackptr - heapptr; 253 printMessage(MSG_FREE_MEMORY, freeMemory); 254} 255 256/** 257 * stop - stops program execution in a controlled fashion (endless loop). 258 */ 259void stop() { 260 printMessage(MSG_PROGRAM_STOPPED); 261 while(1); 262} 263 264/** 265 * findNextFileNumber - determines the number and name of the next file available. This will change 266 * the global variables nextFileName and nextFileNumber. This routine will 267 * stop the entire program if no free filenames can be found. 268 */ 269void findNextFileNumber() { 270 do { 271 sprintf(nextFileName, "%08lu.CSV", nextFileNumber); 272 if (!SD.exists(nextFileName)) { 273 printMessage(MSG_NEXT_FILENAME, nextFileName); 274 return; 275 } else { 276 nextFileNumber += 1; 277 if (nextFileNumber > MAX_FILE_NUMBER) { 278 printMessage(MSG_NO_FILE_NAMES_LEFT); 279// errorSound(ERR_NO_FILE_NAMES_LEFT); 280 stop(); 281 } 282 } 283 } while (true); 284} 285 286void createTimeStamp(int eHZ_Number) { 287 // Get the current time and date from the chip. 288 Time t = rtc.time(); 289 // Format the time and date and insert into the temporary buffer. 290 char currDAndT_buf[50]; 291 snprintf(currDAndT_buf, sizeof(currDAndT_buf), "%04d-%02d-%02d %02d:%02d:%02d;", 292 t.yr, t.mon, t.date, 293 t.hr, t.min, t.sec); 294 295 // assign determined timestamp to either eHZ1 or eHz2 296 if ( eHZ_Number == eHZ1_ID ) { 297 for (int i = 0; i<50; i++) { 298 eHZ_1_Reading_timeStamp[i]=currDAndT_buf[i]; 299 } 300//Serial.println("eHZ_1 Timestamp copied"); 301 } 302 if ( eHZ_Number == eHZ2_ID ) { 303 for (int i = 0; i<50; i++) { 304 eHZ_2_Reading_timeStamp[i]=currDAndT_buf[i]; 305 } 306//Serial.println("eHZ_2 Timestamp copied"); 307 } 308} 309 310void writeTimeAndReadingToFile() { 311 Serial.println("Writing data to SD card..."); 312 313 File outputFile; 314 315 outputFile = SD.open(nextFileName, FILE_WRITE); 316 if (!outputFile) { 317 printMessage(MSG_FILE_OPEN_FAILED); 318 } 319 else { 320 outputFile.print(eHZ_1_Reading_timeStamp); 321 outputFile.print(eHZ_1_Reading); 322 outputFile.print(";"); 323 outputFile.print(eHZ_2_Reading_timeStamp); 324 outputFile.print(eHZ_2_Reading); 325 outputFile.print(";"); 326 outputFile.print(eHZ_2_Reading_timeStamp); 327 outputFile.println(supplVolt); 328 outputFile.close(); 329 Serial.println("...done..."); 330 } 331} 332 333void getSMLData(SoftwareSerial mySoftSer, int eHZ_Number) { 334 unsigned int nextBufferPosition = 0; 335 unsigned long lastReadTime = 0; 336 337 // clear the message buffer 338 memset(buffer, 0, sizeof(buffer)); 339 340 // initialize data acquisition LEDs assuming everything is fine 341 if ( eHZ_Number == eHZ1_ID ) { // remember NO error during SML data acquisition for eHZ1 342 SML_DataAcquisition_eHZ1 = true; 343 } 344 if ( eHZ_Number == eHZ2_ID ) { // remember NO error during SML data acquisition for eHZ2 345 SML_DataAcquisition_eHZ2 = true; 346 } 347 348 // remove a pending overflow flag (might have been left over from a previous run) 349 // and ensure that the SoftwareSerial library is listening 350 mySoftSer.overflow(); 351 mySoftSer.listen(); 352 353 // wait until actual data is available 354 while (!mySoftSer.available()); 355 // Next 2 lines ("Serial.print...") not necessary for algorithm, however might be useful for information purpose on serial monitor. 356 // In addition reading of serial data SEEMs to be more stable (less errors such as "invalid escape sequence"). Maybe 357 // something like "delay(200);" might also work... 358 Serial.print("Number of bytes available at SW serial:"); 359 Serial.println(mySoftSer.available()); 360 361 // keep reading data until either the message buffer is filled or no more data was 362 // received for SERIAL_READ_TIMEOUT_MS ms 363 364 lastReadTime = millis(); 365 while (millis() - lastReadTime < SERIAL_READ_TIMEOUT_MS) { 366 if (mySoftSer.available()) { 367 buffer[nextBufferPosition] = mySoftSer.read(); 368 lastReadTime = millis(); 369 if (nextBufferPosition >= SML_MSG_BUFFER_SIZE) { 370 dummySerial.listen(); // disable further IR input by switching software serial input to a dummy instance 371 printMessage(MSG_BUFFER_OVERFLOW); 372 373 if ( eHZ_Number == eHZ1_ID ) { // remember error during SML data acquisition for eHZ1 374 SML_DataAcquisition_eHZ1 = false; 375 } 376 if ( eHZ_Number == eHZ2_ID ) { // remember error during SML data acquisition for eHZ2 377 SML_DataAcquisition_eHZ2 = false; 378 } 379 return; 380 } 381 nextBufferPosition += 1; 382 numberSMLBytesRead = nextBufferPosition+1; // used to restart reading of SML data in case less than specified number of bytes (typically 397 for my electricity meter) read 383 } 384 } 385 386 // report an error if an overflow condition was encountered - the data received is useless 387 // in this case :-( 388 if (mySoftSer.overflow()) { 389 dummySerial.listen(); // disable further IR input 390 printMessage(MSG_SERIAL_OVERFLOW); 391 392 if ( eHZ_Number == eHZ1_ID ) { // remember error during SML data acquisition for eHZ1 393 SML_DataAcquisition_eHZ1 = false; 394 } 395 if ( eHZ_Number == eHZ2_ID ) { // remember error during SML data acquisition for eHZ2 396 SML_DataAcquisition_eHZ2 = false; 397 } 398 } 399 else { 400 dummySerial.listen(); // disable further IR input 401 // check the header 402 printMessage(MSG_NUM_BYTES_READ, nextBufferPosition + 1); 403 if (!isValidHeader()) { 404 // not a valid header - notify the user... 405 printMessage(MSG_INVALID_HEADER); 406 // ...and empty the receiver buffer (wait for the end of the current data stream 407 while (mySoftSer.available() > 0) { 408 mySoftSer.read(); 409 } 410 if ( eHZ_Number == eHZ1_ID ) { // remember error during SML data acquisition for eHZ1 411 SML_DataAcquisition_eHZ1 = false; 412 } 413 if ( eHZ_Number == eHZ2_ID ) { // remember error during SML data acquisition for eHZ2 414 SML_DataAcquisition_eHZ2 = false; 415 } 416 } 417 } 418} 419 420unsigned long getElectricityMeterReading(char SML_buff) { 421 /* Returns electricity meter reading in kWh 422 * Electricity meter reading (for eHZ-IW8E2A5) is represented by the five bytes at buffer positions 155-159. 423 * Make those 5 bytes to 1 HEX value, convert to DEC, divide by 10 (due to sacler in eHZ), divide by 1000 to get kWh 424 * However, use only lowest 4 bytes (i.e., meaningful data start at position 156). Highest number represented by these four bytes is 425 * in HEX FF FF FF FF or in DEC 4294967295 (= 2^32). This would correspond to approx. 429 496 kWh. 426 * Assuming an (realistic) average energy consumption of about 5000 kWh per year for the heat pump, this would allow 427 * to store the meter reading for about 85 years ! 428 * Therefore use data type unsigned long (allows values from 0 ... 2^32-1 = 0 ... 4 294 967 295) 429 */ 430 unsigned char EMReading_buf[4]; 431 unsigned long EMReading; 432 433 // clear the EMReading buffer 434 memset(EMReading_buf, 0, sizeof(EMReading_buf)); 435 436 // get 4 bytes representing the electricity meter reading and write them in opposite order to new array. Then create unsigned long. 437 // Algorhithm see https://forum.arduino.cc/index.php?topic=108865.0 438 for(int i=3; i>=0; i--) { 439 EMReading_buf[3-i] = (buffer[156+i]); 440 } 441 EMReading = *((unsigned long *) (& EMReading_buf[0])); 442 EMReading = EMReading/10; // consider scaler in SML data -> divide by 10 to get Wh 443 return EMReading; 444} 445 446/** 447 * isValidHeader - returns true if the global message buffer begins with a valid SML escape sequence. 448 */ 449inline boolean isValidHeader() { 450 return ((buffer[0] == 0x1b) && 451 (buffer[1] == 0x1b) && 452 (buffer[2] == 0x1b) && 453 (buffer[3] == 0x1b) && 454 (buffer[4] == 0x01) && 455 (buffer[5] == 0x01) && 456 (buffer[6] == 0x01) && 457 (buffer[7] == 0x01)); 458} 459 460/** 461 * for DS1302 based RTC time. Not necessarily (to be) used for 462 */ 463namespace { 464 465String dayAsString(const Time::Day day) { 466 switch (day) { 467 case Time::kSunday: return "Sunday"; 468 case Time::kMonday: return "Monday"; 469 case Time::kTuesday: return "Tuesday"; 470 case Time::kWednesday: return "Wednesday"; 471 case Time::kThursday: return "Thursday"; 472 case Time::kFriday: return "Friday"; 473 case Time::kSaturday: return "Saturday"; 474 } 475 return "(unknown day)"; 476} 477 478} // namespace 479 480 481/*************************************************************************************************** 482 * MAIN ROUTINES 483 ***************************************************************************************************/ 484 485/** ######################################################################################################## 486 * SETUP 487 ######################################################################################################## 488 */ 489void setup() { 490 // Serial communication 491 // ===================================================================================================== 492 Serial.begin(9600); 493 494 Serial.println("===== Starting setup ====="); 495 // Pin configuration 496 // ===================================================================================================== 497 printMessage(MSG_INIT_HARDWARE); 498 pinMode(IR_RX_PIN_1, INPUT); 499 pinMode(IR_TX_PIN_1, OUTPUT); 500 pinMode(IR_RX_PIN_2, INPUT); 501 pinMode(IR_TX_PIN_2, OUTPUT); 502 pinMode(SD_CS_PIN, OUTPUT); 503 pinMode(SD_SS_PIN, OUTPUT); 504 pinMode(DUMMY_RX_PIN, INPUT); 505 pinMode(DUMMY_TX_PIN, OUTPUT); 506 Serial.println("...done !... pinMode set !"); 507 508 // RTC setup 509 // ===================================================================================================== 510 // Initialize a new chip by turning off write protection and clearing the clock halt flag. These methods needn't always be called. See the DS1302 datasheet for details. 511 rtc.writeProtect(false); 512 rtc.halt(false); 513/* // Uncomment in case current time should be written to RTC shield 514 // Make a new time object to set the date and time and set the time 515 // Format: year, moth, day, hour, minute, second, day of the week (1=Sunday)) 516 Time t(2021, 3, 03, 21, 48, 0, 4); 517 rtc.time(t); 518*/ 519 520 // Setup of supply voltage monitoring 521 // ===================================================================================================== 522 Serial.println("Testing LEDs & piezo speaker..."); 523 pinMode(buzzPin, OUTPUT); 524 pinMode(supplVoltPin, INPUT); 525 // quick sound check to prove piezo speaker works (only during day time...) 526 527 Time t2 = rtc.time(); 528 if( t2.hr > buzzTimeEarly && t2.hr < buzzTimeLate ) { 529 for (int i = 0; i <5; i++) { 530 tone(buzzPin, 1000, 250); 531 delay(500); 532 tone(buzzPin, 5000, 250); 533 delay(500); 534 } 535 } 536 537 // Setup of data acquisition and diagnosis / warning LEDs 538 // ===================================================================================================== 539 pinMode(LED_SML_Acquisition_eHZ1, OUTPUT); 540 digitalWrite(LED_SML_Acquisition_eHZ1, LOW); 541 pinMode(LED_SML_Acquisition_eHZ2, OUTPUT); 542 digitalWrite(LED_SML_Acquisition_eHZ2, LOW); 543 pinMode(LED_WriteWarning, OUTPUT); 544 digitalWrite(LED_WriteWarning, LOW); 545 546 // ...let blink 5 times to proove the're working 547 //------------------------------------------------------------------------------------------------------- 548 Serial.println("Data acquisition LED 1 (BLUE): ...blink 5 times"); 549 for(byte i=0; i<5; i++) { 550 digitalWrite(LED_SML_Acquisition_eHZ1, HIGH); 551 delay(250); 552 digitalWrite(LED_SML_Acquisition_eHZ1, LOW); 553 delay(250); 554 } 555 Serial.println("...done !"); 556 Serial.println("Data acquisition LED 2 (YELLOW): ...blink 5 times"); 557 for(byte i=0; i<5; i++) { 558 digitalWrite(LED_SML_Acquisition_eHZ2, HIGH); 559 delay(250); 560 digitalWrite(LED_SML_Acquisition_eHZ2, LOW); 561 delay(250); 562 } 563 Serial.println("...done !"); 564 Serial.println("SD status LED (RED): ...blink 5 times"); 565 for(byte i=0; i<5; i++) { 566 digitalWrite(LED_WriteWarning, HIGH); 567 delay(250); 568 digitalWrite(LED_WriteWarning, LOW); 569 delay(250); 570 } 571 Serial.println("...done !"); 572 573 // SD card setup 574 // ===================================================================================================== 575 Serial.println("Initializing SD card..."); 576 if (!SD.begin(SD_CS_PIN)) { 577 printMessage(MSG_INIT_SD_CARD_ERROR); 578// errorSound(ERR_INIT_SD_CARD_ERROR); 579 stop(); 580 } 581 else { 582 Serial.println("...done !"); 583 Time t2 = rtc.time(); 584 if( t2.hr > buzzTimeEarly && t2.hr < buzzTimeLate ) { 585 for (int i = 0; i <10; i++) { 586 tone(buzzPin, 250, 250); 587 delay(500); 588 } 589 } 590 } 591 592 // Sofware Serial setup 593 // ===================================================================================================== 594 dummySerial.begin(9600); 595 mySerial_1.begin(9600); // eHZ sends at fix rate of 9600 Baud 596 mySerial_2.begin(9600); // eHZ sends at fix rate of 9600 Baud 597 598 // determine the first file name to use 599 findNextFileNumber(); 600 601} 602 603/** ######################################################################################################## 604 * END of SETUP 605 ######################################################################################################## 606 */ 607 608void loop() { 609 // turn off all LEDs 610 digitalWrite(LED_WriteWarning, LOW); 611 digitalWrite(LED_SML_Acquisition_eHZ1, LOW); 612 digitalWrite(LED_SML_Acquisition_eHZ2, LOW); 613// reportFreeMemory(); // for information purposes only 614 615 numberSMLBytesRead = 0; 616 while(numberSMLBytesRead!=397 || SML_DataAcquisition_eHZ1 == false){ // in case SML data is plausible (i.e., SML header is OK and also no buffer overflow) it still happens that eHZ reading is derived from this data. Examining this showed that in those cases typically less than 397 bytes SML data were read. This while loop/condition should prevent this. 617 getSMLData(mySerial_1, eHZ1_ID); // get SML data from 1st electricity meter (Input: A0), store it into (global) buffer 618 } 619 createTimeStamp(eHZ1_ID); 620 Serial.println("Time stamp eHZ_1 created..."); 621 eHZ_1_Reading = getElectricityMeterReading(buffer); // extract OBIS 1.8.0. in Wh x 10 and store it in (global) variable 622 Serial.print("eHZ_1 Reading: "); 623 Serial.println(eHZ_1_Reading); 624 625 numberSMLBytesRead = 0; 626 while(numberSMLBytesRead!=397 || SML_DataAcquisition_eHZ2 == false){ // in case SML data is plausible (i.e., SML header is OK and also no buffer overflow) it still happens that eHZ reading is derived from this data. Examining this showed that in those cases typically less than 397 bytes SML data were read. This while loop/condition should prevent this. 627 getSMLData(mySerial_2, eHZ2_ID); // get SML data from 2nd electricity meter (Input: A1), store it into (global) buffer 628 } 629 createTimeStamp(eHZ2_ID); 630 Serial.println("Time stamp eHZ_2 created..."); 631 eHZ_2_Reading = getElectricityMeterReading(buffer); // extract OBIS 1.8.0. in Wh x 10 and store it in (global) variable 632 Serial.print("eHZ_2 Reading: "); 633 Serial.println(eHZ_2_Reading); 634 635 supplVolt = analogRead(supplVoltPin) * 2 / 1024.0 * operatVolt; // get actual supply voltage in V (multiply analog reading by 2 due to 1:1 voltage divider 636 637 Serial.println(); 638 639 // turn ON red LED to show that writing is beeing performed and SD card should NOT be removed. Prewarn by blinking for about 3s. 640 // ----------------------------------------------------------------------------------------------------------------------------- 641 for(byte i=0; i<3; i++) { 642 digitalWrite(LED_WriteWarning, HIGH); 643 delay(500); 644 digitalWrite(LED_WriteWarning, LOW); 645 delay(500); 646 } 647 // Write data to SD card 648 // --------------------- 649 digitalWrite(LED_WriteWarning, HIGH); 650 // write data to file (time 1; reading 1; time 2; reading 2; time 2; supply voltage) 651 writeTimeAndReadingToFile(); 652 // turn OFF red LED to show that writing is FINISHED and SD card CAN be removed 653 digitalWrite(LED_WriteWarning, LOW); 654 delay(1000); 655 656 // Show result (SUCCESS/FAIL) of data acquisition for each channel 657 // --------------------------------------------------------------- 658 // set blinking behaviour of LED depending on result of SML data acquisition of eHZ1 659 if(SML_DataAcquisition_eHZ1 == true) { 660 eHZ1Blink_ON = 1000; 661 } 662 if(SML_DataAcquisition_eHZ1 == false) { 663 eHZ1Blink_ON = 250; 664 } 665 // set blinking behaviour of LED depending on result of SML data acquisition of eHZ1 666 if(SML_DataAcquisition_eHZ2 == true) { 667 eHZ2Blink_ON = 1000; 668 } 669 if(SML_DataAcquisition_eHZ2 == false) { 670 eHZ2Blink_ON = 250; 671 } 672 673 // wait for specified time interval until next data acquisition to be performed 674 startTime = millis(); 675 // show result of data acquisition by blinking slow (success) or fast (fail). slow/fast defined above. 676 while(millis()-startTime < loggingInterval){ 677 delay(60000); // only blink once a minute (to save battery energy) 678 for(int i=0; i<3; i++) { 679 digitalWrite(LED_SML_Acquisition_eHZ1, HIGH); 680 delay(eHZ1Blink_ON); 681 digitalWrite(LED_SML_Acquisition_eHZ1, LOW); 682 delay(eHZ1Blink_OFF); 683 } 684 for(int i=0; i<3; i++) { 685 digitalWrite(LED_SML_Acquisition_eHZ2, HIGH); 686 delay(eHZ2Blink_ON); 687 digitalWrite(LED_SML_Acquisition_eHZ2, LOW); 688 delay(eHZ2Blink_OFF); 689 } 690 // Sound buzzer in case supply voltage drops below defined limit; trigger alarm only during specified time slots. 691 Time t2 = rtc.time(); 692Serial.print("supplyVolt: "); 693Serial.println(supplVolt); 694 if( supplVolt < supplVoltLim && t2.hr > buzzTimeEarly && t2.hr < buzzTimeLate ) { 695 for (int i = 0; i <5; i++) { 696 tone(buzzPin, 1000, 250); 697 delay(750); 698 } 699 } 700 } 701 702} 703
eHZ-IW8E2A5_EMLogger_ArdNanoEvery_TMG_06.ino
arduino
Recording of 2 eHZ readings to an SD card.
1/* 2 * Read output of 2 electricity meters of type eHZ-IW8E2A5 and write data to SD card. 3 * 4 * Written by Torsten Geppert (2020) based on ideas/parts of code from 5 * - https://www.volkszaehler.org/ 6 * - and hardware / software by Volker Wegert https://sourceforge.net/p/ehzy/wiki/Hardware/ 7 * - and "set_clock.ino" by Matt Sparks, 2009, http://quadpoint.org/projects/arduino-ds1302 (seems to be one of the very few FUNCTIONAL DS1302 libraries out there in the www) 8 * 9 * Major changes/adjustments 10 * 1) Hardware modified. Replaced resistors 11 * - between IR diode and GND: 15 kOhm --> 1 MOhm (470 kOhm also sufficient). Necessary to bring BC337(-25) transistor to achieve the necessary gain (probably because of lower gain of BC337-25 as compared to BC337-40 or maybe signal of IR sender of eHZ too weak or alingment IR sender to IR receiver not precise enough 12 * - between collector of transistor and VCC: 15 kOhm --> 22 kOhm 13 * 2) w/ resepect to Volker Wegert's code: 14 * - Changes in syntax of PROGMEM related texts and corresponding array necessary (probably due to other version of IDE as compared to Volker Wegert back in in 2012) 15 * - in the end removal of parts of code (e.g piezo control),I did not use in my final code (Nevertheless, those things were really helpfull for debugging during development phase) 16 * 3) Change in Arduino related Hardware 17 * - Use Arduino Nano Every + DS1302 based RTC module and SD card shield (instead of Arduino Uno + SD card shield incl. RTC). 18 * Reason: Limitation of Arduino Uno's RAM, making code run unstable w/ already 1 electricity meter (of type eHZ-IW8E2A5). It was not possible to realize readout of 2 eHZs using Arduino UNO (at least my experience/impression) 19 * - HW used: 20 * - RTC: VMA301 by Velleman (e.g., from https://www.conrad.de/de/p/makerfactory-echtzeit-uhr-vma301-passend-fuer-arduino-boards-arduino-arduino-uno-fayaduino-freeduino-seeeduino-1612764.html) 21 * - SD shield: VMA304 by Velleman (e.g., https://www.conrad.de/de/p/makerfactory-sd-karten-logging-shield-fuer-arduino-2-tlg-1612765.html) 22 * - Level shifter (needed to adjust output from Arduino (5V) to SD shield (3.3V). In case NOT used the SD (shield ?) card might be irreversibly damaged !!! 23 * 4) Time of RTC shield can be set in section "RTC setup". Should be sufficient to do this initially (i.e., when you record your data for the first time and have to set the actual time) or in case RTC time deviates too much from real time (e.g., due to drift of oscillator in RC module due to missing temperature compensatioen or similar) 24 * see comment below "Uncomment in case current time should be written to RTC shield" 25 * 5) Ouptut into 1 file, at defined interval, for two meters type eHZ-IW8E2A5. (Unfortunately - and currently not understood - sometimes data is written into several files. However, due to naming (increasing number) it is stil possible to bring those files in (temporal) order. In addition time stamp is written into file for each datapoint aacquired. 26 * Output data: OBIS Kennzahl 1.8.0 (at Position 9x16+12 = 156 of SML protocol (In buffer w/ 1st element being number 0: position 155). 4 bytes (right after HEX value "56" at position 155 ## ## ## ## ##) 27 * To understand the SML protocol (and where to find the OBIS data I was interested in) Stefan Weigerts explanation is highly appreciated: http://www.stefan-weigert.de/php_loader/sml.php 28 */ 29 30/* ======================================================================================================= 31 * Include Libraries and alike 32 * ======================================================================================================= */ 33 34#include <stdarg.h> 35#include <stdio.h> 36#include <avr/pgmspace.h> 37#include <SoftwareSerial.h> // Serial interfaces realized by software (to allow, e.g., reading of more than just 1 sensor/eHz) 38#include <SD.h> // e.g., to write files to SD card etc. 39#include <DS1302.h> 40 41/*************************************************************************************************** 42 * CONSTANTS and alike 43 ***************************************************************************************************/ 44 45// Software Serial stuff ============================================================================================= 46// Serial Data from source 1 (IR diode of 1st eHZ) 47#define IR_RX_PIN_1 A0 // Pin 14 on Arduino Nano Every 48#define IR_TX_PIN_1 A2 // not used, but required for SoftwareSerial; Pin 16 on Arduino Nano Every 49// Serial Data from source 2 (IR diode of 2nd eHZ) 50#define IR_RX_PIN_2 A1 // Pin 14 on Arduino Nano Every 51#define IR_TX_PIN_2 A2 // not used, but required for SoftwareSerial; Pin 16 on Arduino Nano Every 52// Dummy software serial 53#define DUMMY_RX_PIN 9 // not connected 54#define DUMMY_TX_PIN A3 // not connected 55 56// SD card shield VMA304 stuff ============================================================================================= 57#define SD_CS_PIN 8 // Pin for SD card selection.Typically use hardware SS pin. (Pin 10 on most Arduino boards; however pin 8=A12 on Arduino Nano Every) 58#define SD_SS_PIN 10 // ###really needed ? Check if can be deleted ### 59 60// RTC stuff shield VMA301 stuff =========================================================================================== 61// uses Pins A3, A4, A5 on Arduino Nano Every (Physical Pins 7, 8, 9; aka ???, SDA, SCL) 62const int kCePin = 17; // Chip Enable 63const int kIoPin = 18; // Input/Output 64const int kSclkPin = 19; // Serial Clock 65 66// Other stuff ============================================================================================= 67#define loggingInterval 300000 // time between readings in milli-seconds (e.g., 5 min = 5 * 60 * 1000 = 300 000) 68 69int numberSMLBytesRead = 0; // used to restart reading of SML data in case less than specified number of bytes (typically 397 for my electricity meter) read 70 71const int LED_WriteWarning = 4; // Pin D4 (physical pin no. 22): LED to signalize approaching or ongoing write to SD card 72const int LED_SML_Acquisition_eHZ1 = 16; // Pin PD1 (phyiscal pin no. 10): LED to signalize status of SML data acquisition for eHZ1 (OK: flashiung @ 1Hz; NOK: flashing @4 Hz*) 73const int LED_SML_Acquisition_eHZ2 = 6; // Pin A13 (phyical pin no. 24): LED to signalize status of SML data acquisition for eHZ1 (OK: flashiung @ 1Hz; NOK: flashing @4 Hz*); for whatever reason does NOT work w/ originally intended Pin D7 (phys pin 3; AVR Pin 39) 74// *frequency can be changed below to, e.g. 2 Hz by setting on time to 500 75int eHZ1Blink_ON=1000; // time LED for eHZ1 is turned on 76int eHZ2Blink_ON=1000; // time LED for eHZ2 is turned on 77int eHZ1Blink_OFF=250; // time LED for eHZ1 is turned off 78int eHZ2Blink_OFF=250; // time LED for eHZ2 is turned off 79bool SML_DataAcquisition_eHZ1 = false; // TRUE: SML data for eHZ1 acquired successfully; FALSE: error (e.g., buffer overflow, wrong header) during SML data acquisition for eHZ1 80bool SML_DataAcquisition_eHZ2 = false; // TRUE: SML data for eHZ2 acquired successfully; FALSE: error (e.g., buffer overflow, wrong header) during SML data acquisition for eHZ2 81const int eHZ1_ID = 1; 82const int eHZ2_ID = 2; 83unsigned long startTime; 84 85/** 86 * size of the SML message buffer = meximum number of bytes that can be received 87 */ 88#define SML_MSG_BUFFER_SIZE 400 // eHZ-IW8E2A5 delivers SML telegram of 396 bytes 89 90/** 91 * size of a filename (8.3 + terminating 0x00 = 13 chars) 92 */ 93#define FILENAME_SIZE 13 94 95/** 96 * maximum number of files that can be stored 97 */ 98#define MAX_FILE_NUMBER 99999999 99 100/** 101 * maximum time to wait for the end of a data packet received via IR 102 */ 103#define SERIAL_READ_TIMEOUT_MS 500 104 105/*************************************************************************************************** 106 * MESSAGES 107 ***************************************************************************************************/ 108 109/** 110 * maximum length of a message (entire text after variable substitution!) 111 */ 112#define MAX_MESSAGE_LENGTH 50 113 114/** 115 * message numbers 116 */ 117#define MSG_PROGRAM_STOPPED 0 118#define MSG_NEXT_FILENAME 1 119#define MSG_NO_FILE_NAMES_LEFT 2 120#define MSG_INIT_HARDWARE 3 121#define MSG_INIT_SD_CARD 4 122#define MSG_INIT_SD_CARD_ERROR 5 123#define MSG_BYTE_READ 6 124#define MSG_BUFFER_OVERFLOW 7 125#define MSG_SERIAL_OVERFLOW 8 126#define MSG_INVALID_HEADER 9 127#define MSG_FILE_OPEN_FAILED 10 128#define MSG_FILE_WRITTEN 11 129#define MSG_FREE_MEMORY 12 130#define MSG_NUM_BYTES_READ 13 131 132/** 133 * actual message texts - caution, adapt MAX_MESSAGE_LENGTH if required! 134 * ....+....1....+....2....+....3....+....4....+....5 135 */ 136const char msgText00[] PROGMEM = "Program stopped."; 137const char msgText01[] PROGMEM = "Next output file name is '%s'"; 138const char msgText02[] PROGMEM = "No more file names left"; 139const char msgText03[] PROGMEM = "Initializing Hardware..."; 140const char msgText04[] PROGMEM = "Initializing SD Card..."; 141const char msgText05[] PROGMEM = "SD Card initialization failed"; 142const char msgText06[] PROGMEM = "Read byte %02hhu from IR receiver"; 143const char msgText07[] PROGMEM = "Message buffer overflow"; 144const char msgText08[] PROGMEM = "Serial buffer overflow"; 145const char msgText09[] PROGMEM = "Invalid escape sequence"; 146const char msgText10[] PROGMEM = "Unable to open output file"; 147const char msgText11[] PROGMEM = "%u bytes of data written to file '%s'"; 148const char msgText12[] PROGMEM = "%u bytes of memory available"; 149const char msgText13[] PROGMEM = "%u bytes read"; 150 151/** 152 * table for easier access to the message texts 153 */ 154const char *const msgTextTable[] PROGMEM = { 155 msgText00, 156 msgText01, 157 msgText02, 158 msgText03, 159 msgText04, 160 msgText05, 161 msgText06, 162 msgText07, 163 msgText08, 164 msgText09, 165 msgText10, 166 msgText11, 167 msgText12, 168 msgText13 169}; 170 171/*************************************************************************************************** 172 * GLOBAL VARIABLES (YUCK!) 173 ***************************************************************************************************/ 174 175/** 176 * instance of the SoftwareSerial library to handle the IR communication 177 */ 178SoftwareSerial mySerial_1(IR_RX_PIN_1, IR_TX_PIN_1); // used for readout of SML data of eHZ1 179SoftwareSerial mySerial_2(IR_RX_PIN_2, IR_TX_PIN_2); // used for readout of SML data of eHZ2 180SoftwareSerial dummySerial(DUMMY_RX_PIN, DUMMY_TX_PIN); 181 182// Create a RTC DS1302 object. 183DS1302 rtc(kCePin, kIoPin, kSclkPin); 184 185/** 186 * variables to keep track of the name of the next file to be written 187 */ 188unsigned long nextFileNumber = 0; 189char nextFileName[FILENAME_SIZE]; 190 191/** 192 * the global buffer to store the SML message currently being read 193 */ 194unsigned char buffer[SML_MSG_BUFFER_SIZE]; 195 196/** 197 * Readings of eHZ 1 and 2 (after extraction from SML data) and corresponding timestamps 198 */ 199unsigned long eHZ_1_Reading; 200unsigned long eHZ_2_Reading; 201char eHZ_1_Reading_timeStamp[50]; 202char eHZ_2_Reading_timeStamp[50]; 203 204/*************************************************************************************************** 205 * SUBROUTINES 206 ***************************************************************************************************/ 207 208/** 209 * printMessage - reads a message text from the PROGMEM, performs variable substitution and 210 * writes the resulting text to the serial console. Use MSG_* constants for 211 * messageNumber. 212 */ 213void printMessage(int messageNumber, ...) { 214 va_list args; 215 char format[MAX_MESSAGE_LENGTH]; 216 char buffer[MAX_MESSAGE_LENGTH]; 217 va_start(args, messageNumber); 218 strncpy_P(format, (char*)pgm_read_word(&(msgTextTable[messageNumber])), MAX_MESSAGE_LENGTH); 219 vsnprintf(buffer, MAX_MESSAGE_LENGTH, format, args); 220 Serial.println(buffer); 221 va_end(args); 222} 223 224/** 225 * reportFreeMemory - determines the amount of free memory and logs it to the serial console. 226 */ 227void reportFreeMemory() { 228 int freeMemory; 229 uint8_t * heapptr, * stackptr; 230 stackptr = (uint8_t *) malloc(4); // use stackptr temporarily 231 heapptr = stackptr; // save value of heap pointer 232 free(stackptr); // free up the memory again (sets stackptr to 0) 233 stackptr = (uint8_t *) (SP); // save value of stack pointer 234 freeMemory = stackptr - heapptr; 235 printMessage(MSG_FREE_MEMORY, freeMemory); 236} 237 238/** 239 * stop - stops program execution in a controlled fashion (endless loop). 240 */ 241void stop() { 242 printMessage(MSG_PROGRAM_STOPPED); 243 while(1); 244} 245 246/** 247 * findNextFileNumber - determines the number and name of the next file available. This will change 248 * the global variables nextFileName and nextFileNumber. This routine will 249 * stop the entire program if no free filenames can be found. 250 */ 251void findNextFileNumber() { 252 do { 253 sprintf(nextFileName, "%08lu.CSV", nextFileNumber); 254 if (!SD.exists(nextFileName)) { 255 printMessage(MSG_NEXT_FILENAME, nextFileName); 256 return; 257 } else { 258 nextFileNumber += 1; 259 if (nextFileNumber > MAX_FILE_NUMBER) { 260 printMessage(MSG_NO_FILE_NAMES_LEFT); 261// errorSound(ERR_NO_FILE_NAMES_LEFT); 262 stop(); 263 } 264 } 265 } while (true); 266} 267 268void createTimeStamp(int eHZ_Number) { 269 // Get the current time and date from the chip. 270 Time t = rtc.time(); 271 // Format the time and date and insert into the temporary buffer. 272 char currDAndT_buf[50]; 273 snprintf(currDAndT_buf, sizeof(currDAndT_buf), "%04d-%02d-%02d %02d:%02d:%02d;", 274 t.yr, t.mon, t.date, 275 t.hr, t.min, t.sec); 276 277 // assign determined timestamp to either eHZ1 or eHz2 278 if ( eHZ_Number == eHZ1_ID ) { 279 for (int i = 0; i<50; i++) { 280 eHZ_1_Reading_timeStamp[i]=currDAndT_buf[i]; 281 } 282//Serial.println("eHZ_1 Timestamp copied"); 283 } 284 if ( eHZ_Number == eHZ2_ID ) { 285 for (int i = 0; i<50; i++) { 286 eHZ_2_Reading_timeStamp[i]=currDAndT_buf[i]; 287 } 288//Serial.println("eHZ_2 Timestamp copied"); 289 } 290} 291 292void writeTimeAndReadingToFile() { 293 File outputFile; 294 295 outputFile = SD.open(nextFileName, FILE_WRITE); 296 if (!outputFile) { 297 printMessage(MSG_FILE_OPEN_FAILED); 298 } 299 else { 300 outputFile.print(eHZ_1_Reading_timeStamp); 301 outputFile.print(eHZ_1_Reading); 302 outputFile.print(";"); 303 outputFile.print(eHZ_2_Reading_timeStamp); 304 outputFile.println(eHZ_2_Reading); 305 306 outputFile.close(); 307 } 308} 309 310void getSMLData(SoftwareSerial mySoftSer, int eHZ_Number) { 311 unsigned int nextBufferPosition = 0; 312 unsigned long lastReadTime = 0; 313 314 // clear the message buffer 315 memset(buffer, 0, sizeof(buffer)); 316 317 // initialize data acquisition LEDs assuming everything is fine 318 if ( eHZ_Number == eHZ1_ID ) { // remember NO error during SML data acquisition for eHZ1 319 SML_DataAcquisition_eHZ1 = true; 320 } 321 if ( eHZ_Number == eHZ2_ID ) { // remember NO error during SML data acquisition for eHZ2 322 SML_DataAcquisition_eHZ2 = true; 323 } 324 325 // remove a pending overflow flag (might have been left over from a previous run) 326 // and ensure that the SoftwareSerial library is listening 327 mySoftSer.overflow(); 328 mySoftSer.listen(); 329 330 // wait until actual data is available 331 while (!mySoftSer.available()); 332 // Next 2 lines ("Serial.print...") not necessary for algorithm, however might be useful for information purpose on serial monitor. 333 // In addition reading of serial data SEEMs to be more stable (less errors such as "invalid escape sequence"). Maybe 334 // something like "delay(200);" might also work... 335 Serial.print("Number of bytes available at SW serial:"); 336 Serial.println(mySoftSer.available()); 337 338 // keep reading data until either the message buffer is filled or no more data was 339 // received for SERIAL_READ_TIMEOUT_MS ms 340 341 lastReadTime = millis(); 342 while (millis() - lastReadTime < SERIAL_READ_TIMEOUT_MS) { 343 if (mySoftSer.available()) { 344 buffer[nextBufferPosition] = mySoftSer.read(); 345 lastReadTime = millis(); 346 if (nextBufferPosition >= SML_MSG_BUFFER_SIZE) { 347 dummySerial.listen(); // disable further IR input by switching software serial input to a dummy instance 348 printMessage(MSG_BUFFER_OVERFLOW); 349 350 if ( eHZ_Number == eHZ1_ID ) { // remember error during SML data acquisition for eHZ1 351 SML_DataAcquisition_eHZ1 = false; 352 } 353 if ( eHZ_Number == eHZ2_ID ) { // remember error during SML data acquisition for eHZ2 354 SML_DataAcquisition_eHZ2 = false; 355 } 356 return; 357 } 358 nextBufferPosition += 1; 359 numberSMLBytesRead = nextBufferPosition+1; // used to restart reading of SML data in case less than specified number of bytes (typically 397 for my electricity meter) read 360 } 361 } 362 363 // report an error if an overflow condition was encountered - the data received is useless 364 // in this case :-( 365 if (mySoftSer.overflow()) { 366 dummySerial.listen(); // disable further IR input 367 printMessage(MSG_SERIAL_OVERFLOW); 368 369 if ( eHZ_Number == eHZ1_ID ) { // remember error during SML data acquisition for eHZ1 370 SML_DataAcquisition_eHZ1 = false; 371 } 372 if ( eHZ_Number == eHZ2_ID ) { // remember error during SML data acquisition for eHZ2 373 SML_DataAcquisition_eHZ2 = false; 374 } 375 } 376 else { 377 dummySerial.listen(); // disable further IR input 378 // check the header 379 printMessage(MSG_NUM_BYTES_READ, nextBufferPosition + 1); 380 if (!isValidHeader()) { 381 // not a valid header - notify the user... 382 printMessage(MSG_INVALID_HEADER); 383 // ...and empty the receiver buffer (wait for the end of the current data stream 384 while (mySoftSer.available() > 0) { 385 mySoftSer.read(); 386 } 387 if ( eHZ_Number == eHZ1_ID ) { // remember error during SML data acquisition for eHZ1 388 SML_DataAcquisition_eHZ1 = false; 389 } 390 if ( eHZ_Number == eHZ2_ID ) { // remember error during SML data acquisition for eHZ2 391 SML_DataAcquisition_eHZ2 = false; 392 } 393 } 394 } 395} 396 397unsigned long getElectricityMeterReading(char SML_buff) { 398 /* Returns electricity meter reading in kWh 399 * Electricity meter reading (for eHZ-IW8E2A5) is represented by the five bytes at buffer positions 155-159. 400 * Make those 5 bytes to 1 HEX value, convert to DEC, divide by 10 (due to sacler in eHZ), divide by 1000 to get kWh 401 * However, use only lowest 4 bytes (i.e., meaningful data start at position 156). Highest number represented by these four bytes is 402 * in HEX FF FF FF FF or in DEC 4294967295 (= 2^32). This would correspond to approx. 429 496 kWh. 403 * Assuming an (realistic) average energy consumption of about 5000 kWh per year for the heat pump, this would allow 404 * to store the meter reading for about 85 years ! 405 * Therefore use data type unsigned long (allows values from 0 ... 2^32-1 = 0 ... 4 294 967 295) 406 */ 407 unsigned char EMReading_buf[4]; 408 unsigned long EMReading; 409 410 // clear the EMReading buffer 411 memset(EMReading_buf, 0, sizeof(EMReading_buf)); 412 413 // get 4 bytes representing the electricity meter reading and write them in opposite order to new array. Then create unsigned long. 414 // Algorhithm see https://forum.arduino.cc/index.php?topic=108865.0 415 for(int i=3; i>=0; i--) { 416 EMReading_buf[3-i] = (buffer[156+i]); 417 } 418 EMReading = *((unsigned long *) (& EMReading_buf[0])); 419 EMReading = EMReading/10; // consider scaler in SML data -> divide by 10 to get Wh 420 return EMReading; 421} 422 423/** 424 * isValidHeader - returns true if the global message buffer begins with a valid SML escape sequence. 425 */ 426inline boolean isValidHeader() { 427 return ((buffer[0] == 0x1b) && 428 (buffer[1] == 0x1b) && 429 (buffer[2] == 0x1b) && 430 (buffer[3] == 0x1b) && 431 (buffer[4] == 0x01) && 432 (buffer[5] == 0x01) && 433 (buffer[6] == 0x01) && 434 (buffer[7] == 0x01)); 435} 436 437/** 438 * for DS1302 based RTC time. Not necessarily (to be) used for 439 */ 440namespace { 441 442String dayAsString(const Time::Day day) { 443 switch (day) { 444 case Time::kSunday: return "Sunday"; 445 case Time::kMonday: return "Monday"; 446 case Time::kTuesday: return "Tuesday"; 447 case Time::kWednesday: return "Wednesday"; 448 case Time::kThursday: return "Thursday"; 449 case Time::kFriday: return "Friday"; 450 case Time::kSaturday: return "Saturday"; 451 } 452 return "(unknown day)"; 453} 454 455} // namespace 456 457 458/*************************************************************************************************** 459 * MAIN ROUTINES 460 ***************************************************************************************************/ 461 462/** 463 * SETUP 464 */ 465void setup() { 466 // Serial communication 467 // ===================================================================================================== 468 Serial.begin(9600); 469 470 // Pin configuration 471 // ===================================================================================================== 472 printMessage(MSG_INIT_HARDWARE); 473 pinMode(IR_RX_PIN_1, INPUT); 474 pinMode(IR_TX_PIN_1, OUTPUT); 475 pinMode(IR_RX_PIN_2, INPUT); 476 pinMode(IR_TX_PIN_2, OUTPUT); 477 pinMode(SD_CS_PIN, OUTPUT); 478 pinMode(SD_SS_PIN, OUTPUT); 479 pinMode(DUMMY_RX_PIN, INPUT); 480 pinMode(DUMMY_TX_PIN, OUTPUT); 481 Serial.println("...done !"); 482 483 // Setup of diagnosis / warning LEDs 484 // ===================================================================================================== 485 // Data acquisition diagnosis LED ( let blink 5 times to prove it's working) 486 //------------------------------------------------------------------------------------------------------- 487 pinMode(LED_SML_Acquisition_eHZ1, OUTPUT); 488 digitalWrite(LED_SML_Acquisition_eHZ1, LOW); 489 pinMode(LED_SML_Acquisition_eHZ2, OUTPUT); 490 digitalWrite(LED_SML_Acquisition_eHZ2, LOW); 491 492 493 // SD card setup 494 // ===================================================================================================== 495 Serial.println("Initializing SD card..."); 496 if (!SD.begin(SD_CS_PIN)) { 497 printMessage(MSG_INIT_SD_CARD_ERROR); 498// errorSound(ERR_INIT_SD_CARD_ERROR); 499 stop(); 500 } 501 else { 502 Serial.println("...done !"); 503 } 504 505 // RTC setup 506 // ===================================================================================================== 507 // Initialize a new chip by turning off write protection and clearing the clock halt flag. These methods needn't always be called. See the DS1302 datasheet for details. 508 rtc.writeProtect(false); 509 rtc.halt(false); 510/* // Uncomment in case current time should be written to RTC shield 511 // Make a new time object to set the date and time and set the time 512 // Format: year, moth, day, hour, minute, second, day of the week (1=Sunday)) 513 Time t(2020, 3, 14, 9, 12, 0, 7); 514 rtc.time(t); 515*/ 516 517 // Sofware Serial setup 518 // ===================================================================================================== 519 dummySerial.begin(9600); 520 mySerial_1.begin(9600); // eHZ sends at fix rate of 9600 Baud 521 mySerial_2.begin(9600); // eHZ sends at fix rate of 9600 Baud 522 523 // Warning LED ( let blink 5 times to proove it's working) 524 //------------------------------------------------------------------------------------------------------- 525 pinMode(LED_WriteWarning, OUTPUT); 526 for(byte i=0; i<5; i++) { 527 digitalWrite(LED_WriteWarning, HIGH); 528 delay(250); 529 digitalWrite(LED_WriteWarning, LOW); 530 delay(250); 531 } 532 533 // determine the first file name to use 534 findNextFileNumber(); 535 536} 537 538/** 539 * END of SETUP 540 */ 541 542void loop() { 543 // turn off all LEDs 544 digitalWrite(LED_WriteWarning, LOW); 545 digitalWrite(LED_SML_Acquisition_eHZ1, LOW); 546 digitalWrite(LED_SML_Acquisition_eHZ2, LOW); 547// reportFreeMemory(); // for information purposes only 548 549 numberSMLBytesRead = 0; 550 while(numberSMLBytesRead!=397 || SML_DataAcquisition_eHZ1 == false){ // in case SML data is plausible (i.e., SML header is OK and also no buffer overflow) it still happens that eHZ reading is derived from this data. Examining this showed that in those cases typically less than 397 bytes SML data were read. This while loop/condition should prevent this. 551 getSMLData(mySerial_1, eHZ1_ID); // get SML data from 1st electricity meter (Input: A0), store it into (global) buffer 552 } 553 createTimeStamp(eHZ1_ID); 554 Serial.println("Time stamp eHZ_1 created..."); 555 eHZ_1_Reading = getElectricityMeterReading(buffer); // extract OBIS 1.8.0. in Wh x 10 and store it in (global) variable 556 Serial.print("eHZ_1 Reading: "); 557 Serial.println(eHZ_1_Reading); 558 559 numberSMLBytesRead = 0; 560 while(numberSMLBytesRead!=397 || SML_DataAcquisition_eHZ2 == false){ // in case SML data is plausible (i.e., SML header is OK and also no buffer overflow) it still happens that eHZ reading is derived from this data. Examining this showed that in those cases typically less than 397 bytes SML data were read. This while loop/condition should prevent this. 561 getSMLData(mySerial_2, eHZ2_ID); // get SML data from 2nd electricity meter (Input: A1), store it into (global) buffer 562 } 563 createTimeStamp(eHZ2_ID); 564 Serial.println("Time stamp eHZ_2 created..."); 565 eHZ_2_Reading = getElectricityMeterReading(buffer); // extract OBIS 1.8.0. in Wh x 10 and store it in (global) variable 566 Serial.print("eHZ_2 Reading: "); 567 Serial.println(eHZ_2_Reading); 568 569 Serial.println(); 570 571 // turn ON red LED to show that writing is beeing performed and SD card should NOT be removed. Prewarn by blinking for about 3s. 572 for(byte i=0; i<3; i++) { 573 digitalWrite(LED_WriteWarning, HIGH); 574 delay(500); 575 digitalWrite(LED_WriteWarning, LOW); 576 delay(500); 577 } 578 digitalWrite(LED_WriteWarning, HIGH); 579 // write data to file (time + 2 readings) 580 writeTimeAndReadingToFile(); 581 // turn OFF red LED to show that writing is FINISHED and SD card CAN be removed 582 digitalWrite(LED_WriteWarning, LOW); 583 delay(1000); 584 585 // set blinking behaviour of LED depending on result of SML data acquisition of eHZ1 586 if(SML_DataAcquisition_eHZ1 == true) { 587 eHZ1Blink_ON = 1000; 588 } 589 if(SML_DataAcquisition_eHZ1 == false) { 590 eHZ1Blink_ON = 250; 591 } 592 // set blinking behaviour of LED depending on result of SML data acquisition of eHZ1 593 if(SML_DataAcquisition_eHZ2 == true) { 594 eHZ2Blink_ON = 1000; 595 } 596 if(SML_DataAcquisition_eHZ2 == false) { 597 eHZ2Blink_ON = 250; 598 } 599 600 startTime = millis(); 601 // show result of data acquisition by blinking slow (success) or fast (fail). slow/fast defined above. 602 while(millis()-startTime < loggingInterval){ 603 for(int i=0; i<3; i++) { 604 digitalWrite(LED_SML_Acquisition_eHZ1, HIGH); 605 delay(eHZ1Blink_ON); 606 digitalWrite(LED_SML_Acquisition_eHZ1, LOW); 607 delay(eHZ1Blink_OFF); 608 } 609 for(int i=0; i<3; i++) { 610 digitalWrite(LED_SML_Acquisition_eHZ2, HIGH); 611 delay(eHZ2Blink_ON); 612 digitalWrite(LED_SML_Acquisition_eHZ2, LOW); 613 delay(eHZ2Blink_OFF); 614 } 615 } 616 617} 618
IR-Diode-Schaltung_Test_NanoEvery.ino
arduino
Short Arduino sketch used to optimize position of receiving IR phototransistor in front of eHZ's IR sender diode.
1// A0: amplified signal of 1st IR diode (output=collector of 1st transistor) 2// A1: amplified signal of 2nd IR diode (output=collector of 2nd transistor) (in case 2 IR diodes fo 2 eHZs used) 3// A6: raw signal of 1st IR photo diode (base of 1st transistor) 4// A7: raw signal of 2nd IR photo diode (base of 2nd transistor) (in case 2 IR diodes fo 2 eHZs used) 5 6 7void setup() { 8 9 //for 1st IR diode 10 pinMode(A0, INPUT); 11 pinMode(A6, INPUT); 12 13 //for 2nd IR diode 14 pinMode(A1, INPUT); 15 pinMode(A7, INPUT); 16 Serial.begin(9600); 17} 18 19void loop() { 20 //for 1st IR diode 21 22 Serial.print("A0: "); 23 Serial.print(analogRead(A0)); 24 Serial.print(" ; A6: "); 25 Serial.print(analogRead(A6)); 26 27 28 //for 2nd IR diode 29 Serial.print(" ; A1: "); 30 Serial.print(analogRead(A1)); 31 Serial.print(" ; A7: "); 32 Serial.println(analogRead(A7)); 33 34 35} 36
eHZ-IW8E2A5_EMLogger_ArdNanoEvery_TMG_06.ino
arduino
Recording of 2 eHZ readings to an SD card.
1/* 2 * Read output of 2 electricity meters of type eHZ-IW8E2A5 and write data to SD card. 3 * 4 * Written by Torsten Geppert (2020) based on ideas/parts of code from 5 * - https://www.volkszaehler.org/ 6 * - and hardware / software by Volker Wegert https://sourceforge.net/p/ehzy/wiki/Hardware/ 7 * - and "set_clock.ino" by Matt Sparks, 2009, http://quadpoint.org/projects/arduino-ds1302 (seems to be one of the very few FUNCTIONAL DS1302 libraries out there in the www) 8 * 9 * Major changes/adjustments 10 * 1) Hardware modified. Replaced resistors 11 * - between IR diode and GND: 15 kOhm --> 1 MOhm (470 kOhm also sufficient). Necessary to bring BC337(-25) transistor to achieve the necessary gain (probably because of lower gain of BC337-25 as compared to BC337-40 or maybe signal of IR sender of eHZ too weak or alingment IR sender to IR receiver not precise enough 12 * - between collector of transistor and VCC: 15 kOhm --> 22 kOhm 13 * 2) w/ resepect to Volker Wegert's code: 14 * - Changes in syntax of PROGMEM related texts and corresponding array necessary (probably due to other version of IDE as compared to Volker Wegert back in in 2012) 15 * - in the end removal of parts of code (e.g piezo control),I did not use in my final code (Nevertheless, those things were really helpfull for debugging during development phase) 16 * 3) Change in Arduino related Hardware 17 * - Use Arduino Nano Every + DS1302 based RTC module and SD card shield (instead of Arduino Uno + SD card shield incl. RTC). 18 * Reason: Limitation of Arduino Uno's RAM, making code run unstable w/ already 1 electricity meter (of type eHZ-IW8E2A5). It was not possible to realize readout of 2 eHZs using Arduino UNO (at least my experience/impression) 19 * - HW used: 20 * - RTC: VMA301 by Velleman (e.g., from https://www.conrad.de/de/p/makerfactory-echtzeit-uhr-vma301-passend-fuer-arduino-boards-arduino-arduino-uno-fayaduino-freeduino-seeeduino-1612764.html) 21 * - SD shield: VMA304 by Velleman (e.g., https://www.conrad.de/de/p/makerfactory-sd-karten-logging-shield-fuer-arduino-2-tlg-1612765.html) 22 * - Level shifter (needed to adjust output from Arduino (5V) to SD shield (3.3V). In case NOT used the SD (shield ?) card might be irreversibly damaged !!! 23 * 4) Time of RTC shield can be set in section "RTC setup". Should be sufficient to do this initially (i.e., when you record your data for the first time and have to set the actual time) or in case RTC time deviates too much from real time (e.g., due to drift of oscillator in RC module due to missing temperature compensatioen or similar) 24 * see comment below "Uncomment in case current time should be written to RTC shield" 25 * 5) Ouptut into 1 file, at defined interval, for two meters type eHZ-IW8E2A5. (Unfortunately - and currently not understood - sometimes data is written into several files. However, due to naming (increasing number) it is stil possible to bring those files in (temporal) order. In addition time stamp is written into file for each datapoint aacquired. 26 * Output data: OBIS Kennzahl 1.8.0 (at Position 9x16+12 = 156 of SML protocol (In buffer w/ 1st element being number 0: position 155). 4 bytes (right after HEX value "56" at position 155 ## ## ## ## ##) 27 * To understand the SML protocol (and where to find the OBIS data I was interested in) Stefan Weigerts explanation is highly appreciated: http://www.stefan-weigert.de/php_loader/sml.php 28 */ 29 30/* ======================================================================================================= 31 * Include Libraries and alike 32 * ======================================================================================================= */ 33 34#include <stdarg.h> 35#include <stdio.h> 36#include <avr/pgmspace.h> 37#include <SoftwareSerial.h> // Serial interfaces realized by software (to allow, e.g., reading of more than just 1 sensor/eHz) 38#include <SD.h> // e.g., to write files to SD card etc. 39#include <DS1302.h> 40 41/*************************************************************************************************** 42 * CONSTANTS and alike 43 ***************************************************************************************************/ 44 45// Software Serial stuff ============================================================================================= 46// Serial Data from source 1 (IR diode of 1st eHZ) 47#define IR_RX_PIN_1 A0 // Pin 14 on Arduino Nano Every 48#define IR_TX_PIN_1 A2 // not used, but required for SoftwareSerial; Pin 16 on Arduino Nano Every 49// Serial Data from source 2 (IR diode of 2nd eHZ) 50#define IR_RX_PIN_2 A1 // Pin 14 on Arduino Nano Every 51#define IR_TX_PIN_2 A2 // not used, but required for SoftwareSerial; Pin 16 on Arduino Nano Every 52// Dummy software serial 53#define DUMMY_RX_PIN 9 // not connected 54#define DUMMY_TX_PIN A3 // not connected 55 56// SD card shield VMA304 stuff ============================================================================================= 57#define SD_CS_PIN 8 // Pin for SD card selection.Typically use hardware SS pin. (Pin 10 on most Arduino boards; however pin 8=A12 on Arduino Nano Every) 58#define SD_SS_PIN 10 // ###really needed ? Check if can be deleted ### 59 60// RTC stuff shield VMA301 stuff =========================================================================================== 61// uses Pins A3, A4, A5 on Arduino Nano Every (Physical Pins 7, 8, 9; aka ???, SDA, SCL) 62const int kCePin = 17; // Chip Enable 63const int kIoPin = 18; // Input/Output 64const int kSclkPin = 19; // Serial Clock 65 66// Other stuff ============================================================================================= 67#define loggingInterval 300000 // time between readings in milli-seconds (e.g., 5 min = 5 * 60 * 1000 = 300 000) 68 69int numberSMLBytesRead = 0; // used to restart reading of SML data in case less than specified number of bytes (typically 397 for my electricity meter) read 70 71const int LED_WriteWarning = 4; // Pin D4 (physical pin no. 22): LED to signalize approaching or ongoing write to SD card 72const int LED_SML_Acquisition_eHZ1 = 16; // Pin PD1 (phyiscal pin no. 10): LED to signalize status of SML data acquisition for eHZ1 (OK: flashiung @ 1Hz; NOK: flashing @4 Hz*) 73const int LED_SML_Acquisition_eHZ2 = 6; // Pin A13 (phyical pin no. 24): LED to signalize status of SML data acquisition for eHZ1 (OK: flashiung @ 1Hz; NOK: flashing @4 Hz*); for whatever reason does NOT work w/ originally intended Pin D7 (phys pin 3; AVR Pin 39) 74// *frequency can be changed below to, e.g. 2 Hz by setting on time to 500 75int eHZ1Blink_ON=1000; // time LED for eHZ1 is turned on 76int eHZ2Blink_ON=1000; // time LED for eHZ2 is turned on 77int eHZ1Blink_OFF=250; // time LED for eHZ1 is turned off 78int eHZ2Blink_OFF=250; // time LED for eHZ2 is turned off 79bool SML_DataAcquisition_eHZ1 = false; // TRUE: SML data for eHZ1 acquired successfully; FALSE: error (e.g., buffer overflow, wrong header) during SML data acquisition for eHZ1 80bool SML_DataAcquisition_eHZ2 = false; // TRUE: SML data for eHZ2 acquired successfully; FALSE: error (e.g., buffer overflow, wrong header) during SML data acquisition for eHZ2 81const int eHZ1_ID = 1; 82const int eHZ2_ID = 2; 83unsigned long startTime; 84 85/** 86 * size of the SML message buffer = meximum number of bytes that can be received 87 */ 88#define SML_MSG_BUFFER_SIZE 400 // eHZ-IW8E2A5 delivers SML telegram of 396 bytes 89 90/** 91 * size of a filename (8.3 + terminating 0x00 = 13 chars) 92 */ 93#define FILENAME_SIZE 13 94 95/** 96 * maximum number of files that can be stored 97 */ 98#define MAX_FILE_NUMBER 99999999 99 100/** 101 * maximum time to wait for the end of a data packet received via IR 102 */ 103#define SERIAL_READ_TIMEOUT_MS 500 104 105/*************************************************************************************************** 106 * MESSAGES 107 ***************************************************************************************************/ 108 109/** 110 * maximum length of a message (entire text after variable substitution!) 111 */ 112#define MAX_MESSAGE_LENGTH 50 113 114/** 115 * message numbers 116 */ 117#define MSG_PROGRAM_STOPPED 0 118#define MSG_NEXT_FILENAME 1 119#define MSG_NO_FILE_NAMES_LEFT 2 120#define MSG_INIT_HARDWARE 3 121#define MSG_INIT_SD_CARD 4 122#define MSG_INIT_SD_CARD_ERROR 5 123#define MSG_BYTE_READ 6 124#define MSG_BUFFER_OVERFLOW 7 125#define MSG_SERIAL_OVERFLOW 8 126#define MSG_INVALID_HEADER 9 127#define MSG_FILE_OPEN_FAILED 10 128#define MSG_FILE_WRITTEN 11 129#define MSG_FREE_MEMORY 12 130#define MSG_NUM_BYTES_READ 13 131 132/** 133 * actual message texts - caution, adapt MAX_MESSAGE_LENGTH if required! 134 * ....+....1....+....2....+....3....+....4....+....5 135 */ 136const char msgText00[] PROGMEM = "Program stopped."; 137const char msgText01[] PROGMEM = "Next output file name is '%s'"; 138const char msgText02[] PROGMEM = "No more file names left"; 139const char msgText03[] PROGMEM = "Initializing Hardware..."; 140const char msgText04[] PROGMEM = "Initializing SD Card..."; 141const char msgText05[] PROGMEM = "SD Card initialization failed"; 142const char msgText06[] PROGMEM = "Read byte %02hhu from IR receiver"; 143const char msgText07[] PROGMEM = "Message buffer overflow"; 144const char msgText08[] PROGMEM = "Serial buffer overflow"; 145const char msgText09[] PROGMEM = "Invalid escape sequence"; 146const char msgText10[] PROGMEM = "Unable to open output file"; 147const char msgText11[] PROGMEM = "%u bytes of data written to file '%s'"; 148const char msgText12[] PROGMEM = "%u bytes of memory available"; 149const char msgText13[] PROGMEM = "%u bytes read"; 150 151/** 152 * table for easier access to the message texts 153 */ 154const char *const msgTextTable[] PROGMEM = { 155 msgText00, 156 msgText01, 157 msgText02, 158 msgText03, 159 msgText04, 160 msgText05, 161 msgText06, 162 msgText07, 163 msgText08, 164 msgText09, 165 msgText10, 166 msgText11, 167 msgText12, 168 msgText13 169}; 170 171/*************************************************************************************************** 172 * GLOBAL VARIABLES (YUCK!) 173 ***************************************************************************************************/ 174 175/** 176 * instance of the SoftwareSerial library to handle the IR communication 177 */ 178SoftwareSerial mySerial_1(IR_RX_PIN_1, IR_TX_PIN_1); // used for readout of SML data of eHZ1 179SoftwareSerial mySerial_2(IR_RX_PIN_2, IR_TX_PIN_2); // used for readout of SML data of eHZ2 180SoftwareSerial dummySerial(DUMMY_RX_PIN, DUMMY_TX_PIN); 181 182// Create a RTC DS1302 object. 183DS1302 rtc(kCePin, kIoPin, kSclkPin); 184 185/** 186 * variables to keep track of the name of the next file to be written 187 */ 188unsigned long nextFileNumber = 0; 189char nextFileName[FILENAME_SIZE]; 190 191/** 192 * the global buffer to store the SML message currently being read 193 */ 194unsigned char buffer[SML_MSG_BUFFER_SIZE]; 195 196/** 197 * Readings of eHZ 1 and 2 (after extraction from SML data) and corresponding timestamps 198 */ 199unsigned long eHZ_1_Reading; 200unsigned long eHZ_2_Reading; 201char eHZ_1_Reading_timeStamp[50]; 202char eHZ_2_Reading_timeStamp[50]; 203 204/*************************************************************************************************** 205 * SUBROUTINES 206 ***************************************************************************************************/ 207 208/** 209 * printMessage - reads a message text from the PROGMEM, performs variable substitution and 210 * writes the resulting text to the serial console. Use MSG_* constants for 211 * messageNumber. 212 */ 213void printMessage(int messageNumber, ...) { 214 va_list args; 215 char format[MAX_MESSAGE_LENGTH]; 216 char buffer[MAX_MESSAGE_LENGTH]; 217 va_start(args, messageNumber); 218 strncpy_P(format, (char*)pgm_read_word(&(msgTextTable[messageNumber])), MAX_MESSAGE_LENGTH); 219 vsnprintf(buffer, MAX_MESSAGE_LENGTH, format, args); 220 Serial.println(buffer); 221 va_end(args); 222} 223 224/** 225 * reportFreeMemory - determines the amount of free memory and logs it to the serial console. 226 */ 227void reportFreeMemory() { 228 int freeMemory; 229 uint8_t * heapptr, * stackptr; 230 stackptr = (uint8_t *) malloc(4); // use stackptr temporarily 231 heapptr = stackptr; // save value of heap pointer 232 free(stackptr); // free up the memory again (sets stackptr to 0) 233 stackptr = (uint8_t *) (SP); // save value of stack pointer 234 freeMemory = stackptr - heapptr; 235 printMessage(MSG_FREE_MEMORY, freeMemory); 236} 237 238/** 239 * stop - stops program execution in a controlled fashion (endless loop). 240 */ 241void stop() { 242 printMessage(MSG_PROGRAM_STOPPED); 243 while(1); 244} 245 246/** 247 * findNextFileNumber - determines the number and name of the next file available. This will change 248 * the global variables nextFileName and nextFileNumber. This routine will 249 * stop the entire program if no free filenames can be found. 250 */ 251void findNextFileNumber() { 252 do { 253 sprintf(nextFileName, "%08lu.CSV", nextFileNumber); 254 if (!SD.exists(nextFileName)) { 255 printMessage(MSG_NEXT_FILENAME, nextFileName); 256 return; 257 } else { 258 nextFileNumber += 1; 259 if (nextFileNumber > MAX_FILE_NUMBER) { 260 printMessage(MSG_NO_FILE_NAMES_LEFT); 261// errorSound(ERR_NO_FILE_NAMES_LEFT); 262 stop(); 263 } 264 } 265 } while (true); 266} 267 268void createTimeStamp(int eHZ_Number) { 269 // Get the current time and date from the chip. 270 Time t = rtc.time(); 271 // Format the time and date and insert into the temporary buffer. 272 char currDAndT_buf[50]; 273 snprintf(currDAndT_buf, sizeof(currDAndT_buf), "%04d-%02d-%02d %02d:%02d:%02d;", 274 t.yr, t.mon, t.date, 275 t.hr, t.min, t.sec); 276 277 // assign determined timestamp to either eHZ1 or eHz2 278 if ( eHZ_Number == eHZ1_ID ) { 279 for (int i = 0; i<50; i++) { 280 eHZ_1_Reading_timeStamp[i]=currDAndT_buf[i]; 281 } 282//Serial.println("eHZ_1 Timestamp copied"); 283 } 284 if ( eHZ_Number == eHZ2_ID ) { 285 for (int i = 0; i<50; i++) { 286 eHZ_2_Reading_timeStamp[i]=currDAndT_buf[i]; 287 } 288//Serial.println("eHZ_2 Timestamp copied"); 289 } 290} 291 292void writeTimeAndReadingToFile() { 293 File outputFile; 294 295 outputFile = SD.open(nextFileName, FILE_WRITE); 296 if (!outputFile) { 297 printMessage(MSG_FILE_OPEN_FAILED); 298 } 299 else { 300 outputFile.print(eHZ_1_Reading_timeStamp); 301 outputFile.print(eHZ_1_Reading); 302 outputFile.print(";"); 303 outputFile.print(eHZ_2_Reading_timeStamp); 304 outputFile.println(eHZ_2_Reading); 305 306 outputFile.close(); 307 } 308} 309 310void getSMLData(SoftwareSerial mySoftSer, int eHZ_Number) { 311 unsigned int nextBufferPosition = 0; 312 unsigned long lastReadTime = 0; 313 314 // clear the message buffer 315 memset(buffer, 0, sizeof(buffer)); 316 317 // initialize data acquisition LEDs assuming everything is fine 318 if ( eHZ_Number == eHZ1_ID ) { // remember NO error during SML data acquisition for eHZ1 319 SML_DataAcquisition_eHZ1 = true; 320 } 321 if ( eHZ_Number == eHZ2_ID ) { // remember NO error during SML data acquisition for eHZ2 322 SML_DataAcquisition_eHZ2 = true; 323 } 324 325 // remove a pending overflow flag (might have been left over from a previous run) 326 // and ensure that the SoftwareSerial library is listening 327 mySoftSer.overflow(); 328 mySoftSer.listen(); 329 330 // wait until actual data is available 331 while (!mySoftSer.available()); 332 // Next 2 lines ("Serial.print...") not necessary for algorithm, however might be useful for information purpose on serial monitor. 333 // In addition reading of serial data SEEMs to be more stable (less errors such as "invalid escape sequence"). Maybe 334 // something like "delay(200);" might also work... 335 Serial.print("Number of bytes available at SW serial:"); 336 Serial.println(mySoftSer.available()); 337 338 // keep reading data until either the message buffer is filled or no more data was 339 // received for SERIAL_READ_TIMEOUT_MS ms 340 341 lastReadTime = millis(); 342 while (millis() - lastReadTime < SERIAL_READ_TIMEOUT_MS) { 343 if (mySoftSer.available()) { 344 buffer[nextBufferPosition] = mySoftSer.read(); 345 lastReadTime = millis(); 346 if (nextBufferPosition >= SML_MSG_BUFFER_SIZE) { 347 dummySerial.listen(); // disable further IR input by switching software serial input to a dummy instance 348 printMessage(MSG_BUFFER_OVERFLOW); 349 350 if ( eHZ_Number == eHZ1_ID ) { // remember error during SML data acquisition for eHZ1 351 SML_DataAcquisition_eHZ1 = false; 352 } 353 if ( eHZ_Number == eHZ2_ID ) { // remember error during SML data acquisition for eHZ2 354 SML_DataAcquisition_eHZ2 = false; 355 } 356 return; 357 } 358 nextBufferPosition += 1; 359 numberSMLBytesRead = nextBufferPosition+1; // used to restart reading of SML data in case less than specified number of bytes (typically 397 for my electricity meter) read 360 } 361 } 362 363 // report an error if an overflow condition was encountered - the data received is useless 364 // in this case :-( 365 if (mySoftSer.overflow()) { 366 dummySerial.listen(); // disable further IR input 367 printMessage(MSG_SERIAL_OVERFLOW); 368 369 if ( eHZ_Number == eHZ1_ID ) { // remember error during SML data acquisition for eHZ1 370 SML_DataAcquisition_eHZ1 = false; 371 } 372 if ( eHZ_Number == eHZ2_ID ) { // remember error during SML data acquisition for eHZ2 373 SML_DataAcquisition_eHZ2 = false; 374 } 375 } 376 else { 377 dummySerial.listen(); // disable further IR input 378 // check the header 379 printMessage(MSG_NUM_BYTES_READ, nextBufferPosition + 1); 380 if (!isValidHeader()) { 381 // not a valid header - notify the user... 382 printMessage(MSG_INVALID_HEADER); 383 // ...and empty the receiver buffer (wait for the end of the current data stream 384 while (mySoftSer.available() > 0) { 385 mySoftSer.read(); 386 } 387 if ( eHZ_Number == eHZ1_ID ) { // remember error during SML data acquisition for eHZ1 388 SML_DataAcquisition_eHZ1 = false; 389 } 390 if ( eHZ_Number == eHZ2_ID ) { // remember error during SML data acquisition for eHZ2 391 SML_DataAcquisition_eHZ2 = false; 392 } 393 } 394 } 395} 396 397unsigned long getElectricityMeterReading(char SML_buff) { 398 /* Returns electricity meter reading in kWh 399 * Electricity meter reading (for eHZ-IW8E2A5) is represented by the five bytes at buffer positions 155-159. 400 * Make those 5 bytes to 1 HEX value, convert to DEC, divide by 10 (due to sacler in eHZ), divide by 1000 to get kWh 401 * However, use only lowest 4 bytes (i.e., meaningful data start at position 156). Highest number represented by these four bytes is 402 * in HEX FF FF FF FF or in DEC 4294967295 (= 2^32). This would correspond to approx. 429 496 kWh. 403 * Assuming an (realistic) average energy consumption of about 5000 kWh per year for the heat pump, this would allow 404 * to store the meter reading for about 85 years ! 405 * Therefore use data type unsigned long (allows values from 0 ... 2^32-1 = 0 ... 4 294 967 295) 406 */ 407 unsigned char EMReading_buf[4]; 408 unsigned long EMReading; 409 410 // clear the EMReading buffer 411 memset(EMReading_buf, 0, sizeof(EMReading_buf)); 412 413 // get 4 bytes representing the electricity meter reading and write them in opposite order to new array. Then create unsigned long. 414 // Algorhithm see https://forum.arduino.cc/index.php?topic=108865.0 415 for(int i=3; i>=0; i--) { 416 EMReading_buf[3-i] = (buffer[156+i]); 417 } 418 EMReading = *((unsigned long *) (& EMReading_buf[0])); 419 EMReading = EMReading/10; // consider scaler in SML data -> divide by 10 to get Wh 420 return EMReading; 421} 422 423/** 424 * isValidHeader - returns true if the global message buffer begins with a valid SML escape sequence. 425 */ 426inline boolean isValidHeader() { 427 return ((buffer[0] == 0x1b) && 428 (buffer[1] == 0x1b) && 429 (buffer[2] == 0x1b) && 430 (buffer[3] == 0x1b) && 431 (buffer[4] == 0x01) && 432 (buffer[5] == 0x01) && 433 (buffer[6] == 0x01) && 434 (buffer[7] == 0x01)); 435} 436 437/** 438 * for DS1302 based RTC time. Not necessarily (to be) used for 439 */ 440namespace { 441 442String dayAsString(const Time::Day day) { 443 switch (day) { 444 case Time::kSunday: return "Sunday"; 445 case Time::kMonday: return "Monday"; 446 case Time::kTuesday: return "Tuesday"; 447 case Time::kWednesday: return "Wednesday"; 448 case Time::kThursday: return "Thursday"; 449 case Time::kFriday: return "Friday"; 450 case Time::kSaturday: return "Saturday"; 451 } 452 return "(unknown day)"; 453} 454 455} // namespace 456 457 458/*************************************************************************************************** 459 * MAIN ROUTINES 460 ***************************************************************************************************/ 461 462/** 463 * SETUP 464 */ 465void setup() { 466 // Serial communication 467 // ===================================================================================================== 468 Serial.begin(9600); 469 470 // Pin configuration 471 // ===================================================================================================== 472 printMessage(MSG_INIT_HARDWARE); 473 pinMode(IR_RX_PIN_1, INPUT); 474 pinMode(IR_TX_PIN_1, OUTPUT); 475 pinMode(IR_RX_PIN_2, INPUT); 476 pinMode(IR_TX_PIN_2, OUTPUT); 477 pinMode(SD_CS_PIN, OUTPUT); 478 pinMode(SD_SS_PIN, OUTPUT); 479 pinMode(DUMMY_RX_PIN, INPUT); 480 pinMode(DUMMY_TX_PIN, OUTPUT); 481 Serial.println("...done !"); 482 483 // Setup of diagnosis / warning LEDs 484 // ===================================================================================================== 485 // Data acquisition diagnosis LED ( let blink 5 times to prove it's working) 486 //------------------------------------------------------------------------------------------------------- 487 pinMode(LED_SML_Acquisition_eHZ1, OUTPUT); 488 digitalWrite(LED_SML_Acquisition_eHZ1, LOW); 489 pinMode(LED_SML_Acquisition_eHZ2, OUTPUT); 490 digitalWrite(LED_SML_Acquisition_eHZ2, LOW); 491 492 493 // SD card setup 494 // ===================================================================================================== 495 Serial.println("Initializing SD card..."); 496 if (!SD.begin(SD_CS_PIN)) { 497 printMessage(MSG_INIT_SD_CARD_ERROR); 498// errorSound(ERR_INIT_SD_CARD_ERROR); 499 stop(); 500 } 501 else { 502 Serial.println("...done !"); 503 } 504 505 // RTC setup 506 // ===================================================================================================== 507 // Initialize a new chip by turning off write protection and clearing the clock halt flag. These methods needn't always be called. See the DS1302 datasheet for details. 508 rtc.writeProtect(false); 509 rtc.halt(false); 510/* // Uncomment in case current time should be written to RTC shield 511 // Make a new time object to set the date and time and set the time 512 // Format: year, moth, day, hour, minute, second, day of the week (1=Sunday)) 513 Time t(2020, 3, 14, 9, 12, 0, 7); 514 rtc.time(t); 515*/ 516 517 // Sofware Serial setup 518 // ===================================================================================================== 519 dummySerial.begin(9600); 520 mySerial_1.begin(9600); // eHZ sends at fix rate of 9600 Baud 521 mySerial_2.begin(9600); // eHZ sends at fix rate of 9600 Baud 522 523 // Warning LED ( let blink 5 times to proove it's working) 524 //------------------------------------------------------------------------------------------------------- 525 pinMode(LED_WriteWarning, OUTPUT); 526 for(byte i=0; i<5; i++) { 527 digitalWrite(LED_WriteWarning, HIGH); 528 delay(250); 529 digitalWrite(LED_WriteWarning, LOW); 530 delay(250); 531 } 532 533 // determine the first file name to use 534 findNextFileNumber(); 535 536} 537 538/** 539 * END of SETUP 540 */ 541 542void loop() { 543 // turn off all LEDs 544 digitalWrite(LED_WriteWarning, LOW); 545 digitalWrite(LED_SML_Acquisition_eHZ1, LOW); 546 digitalWrite(LED_SML_Acquisition_eHZ2, LOW); 547// reportFreeMemory(); // for information purposes only 548 549 numberSMLBytesRead = 0; 550 while(numberSMLBytesRead!=397 || SML_DataAcquisition_eHZ1 == false){ // in case SML data is plausible (i.e., SML header is OK and also no buffer overflow) it still happens that eHZ reading is derived from this data. Examining this showed that in those cases typically less than 397 bytes SML data were read. This while loop/condition should prevent this. 551 getSMLData(mySerial_1, eHZ1_ID); // get SML data from 1st electricity meter (Input: A0), store it into (global) buffer 552 } 553 createTimeStamp(eHZ1_ID); 554 Serial.println("Time stamp eHZ_1 created..."); 555 eHZ_1_Reading = getElectricityMeterReading(buffer); // extract OBIS 1.8.0. in Wh x 10 and store it in (global) variable 556 Serial.print("eHZ_1 Reading: "); 557 Serial.println(eHZ_1_Reading); 558 559 numberSMLBytesRead = 0; 560 while(numberSMLBytesRead!=397 || SML_DataAcquisition_eHZ2 == false){ // in case SML data is plausible (i.e., SML header is OK and also no buffer overflow) it still happens that eHZ reading is derived from this data. Examining this showed that in those cases typically less than 397 bytes SML data were read. This while loop/condition should prevent this. 561 getSMLData(mySerial_2, eHZ2_ID); // get SML data from 2nd electricity meter (Input: A1), store it into (global) buffer 562 } 563 createTimeStamp(eHZ2_ID); 564 Serial.println("Time stamp eHZ_2 created..."); 565 eHZ_2_Reading = getElectricityMeterReading(buffer); // extract OBIS 1.8.0. in Wh x 10 and store it in (global) variable 566 Serial.print("eHZ_2 Reading: "); 567 Serial.println(eHZ_2_Reading); 568 569 Serial.println(); 570 571 // turn ON red LED to show that writing is beeing performed and SD card should NOT be removed. Prewarn by blinking for about 3s. 572 for(byte i=0; i<3; i++) { 573 digitalWrite(LED_WriteWarning, HIGH); 574 delay(500); 575 digitalWrite(LED_WriteWarning, LOW); 576 delay(500); 577 } 578 digitalWrite(LED_WriteWarning, HIGH); 579 // write data to file (time + 2 readings) 580 writeTimeAndReadingToFile(); 581 // turn OFF red LED to show that writing is FINISHED and SD card CAN be removed 582 digitalWrite(LED_WriteWarning, LOW); 583 delay(1000); 584 585 // set blinking behaviour of LED depending on result of SML data acquisition of eHZ1 586 if(SML_DataAcquisition_eHZ1 == true) { 587 eHZ1Blink_ON = 1000; 588 } 589 if(SML_DataAcquisition_eHZ1 == false) { 590 eHZ1Blink_ON = 250; 591 } 592 // set blinking behaviour of LED depending on result of SML data acquisition of eHZ1 593 if(SML_DataAcquisition_eHZ2 == true) { 594 eHZ2Blink_ON = 1000; 595 } 596 if(SML_DataAcquisition_eHZ2 == false) { 597 eHZ2Blink_ON = 250; 598 } 599 600 startTime = millis(); 601 // show result of data acquisition by blinking slow (success) or fast (fail). slow/fast defined above. 602 while(millis()-startTime < loggingInterval){ 603 for(int i=0; i<3; i++) { 604 digitalWrite(LED_SML_Acquisition_eHZ1, HIGH); 605 delay(eHZ1Blink_ON); 606 digitalWrite(LED_SML_Acquisition_eHZ1, LOW); 607 delay(eHZ1Blink_OFF); 608 } 609 for(int i=0; i<3; i++) { 610 digitalWrite(LED_SML_Acquisition_eHZ2, HIGH); 611 delay(eHZ2Blink_ON); 612 digitalWrite(LED_SML_Acquisition_eHZ2, LOW); 613 delay(eHZ2Blink_OFF); 614 } 615 } 616 617} 618
eHZ-IW8E2A5_EMLogger_ArdNanoEvery_TMG_09.ino
arduino
Recording of 2 eHZ readings to an SD card incl. supply voltage monitoring and recording
1/* 2 * Read output of 2 electricity meters of type eHZ-IW8E2A5 and write data to SD card. 3 * 4 * Written by Torsten Geppert (2020/2021) based on ideas/parts of code from 5 * - https://www.volkszaehler.org/ 6 * - and hardware / software by Volker Wegert https://sourceforge.net/p/ehzy/wiki/Hardware/ 7 * - and "set_clock.ino" by Matt Sparks, 2009, http://quadpoint.org/projects/arduino-ds1302 (seems to be one of the very few FUNCTIONAL DS1302 libraries out there in the www) 8 * 9 * Major changes/adjustments 10 * 1) Hardware modified. Replaced resistors 11 * - between IR diode and GND: 15 kOhm --> 1 MOhm (470 kOhm also sufficient). Necessary to bring BC337(-25) transistor to achieve the necessary gain (probably because of lower gain of BC337-25 as compared to BC337-40 or maybe signal of IR sender of eHZ too weak or alingment IR sender to IR receiver not precise enough 12 * - between collector of transistor and VCC: 15 kOhm --> 22 kOhm 13 * 2) w/ resepect to Volker Wegert's code: 14 * - Changes in syntax of PROGMEM related texts and corresponding array necessary (probably due to other version of IDE as compared to Volker Wegert back in in 2012) 15 * - in the end removal of parts of code (e.g piezo control),I did not use in my final code (Nevertheless, those things were really helpfull for debugging during development phase) 16 * 3) Change in Arduino related Hardware 17 * - Use Arduino Nano Every + DS1302 based RTC module and SD card shield (instead of Arduino Uno + SD card shield incl. RTC). 18 * Reason: Limitation of Arduino Uno's RAM, making code run unstable w/ already 1 electricity meter (of type eHZ-IW8E2A5). It was not possible to realize readout of 2 eHZs using Arduino UNO (at least my experience/impression) 19 * - HW used: 20 * - RTC: VMA301 by Velleman (e.g., from https://www.conrad.de/de/p/makerfactory-echtzeit-uhr-vma301-passend-fuer-arduino-boards-arduino-arduino-uno-fayaduino-freeduino-seeeduino-1612764.html) 21 * - SD shield: VMA304 by Velleman (e.g., https://www.conrad.de/de/p/makerfactory-sd-karten-logging-shield-fuer-arduino-2-tlg-1612765.html) 22 * - Level shifter (needed to adjust output from Arduino (5V) to SD shield (3.3V). In case NOT used the SD (shield ?) card might be irreversibly damaged !!! 23 * - added 3 LEDs to show SUCCESS or FAILURE of SD card initialization and data aqcuisition 24 * - added piezo speaker (accoustic warning in case supply voiltage - e.g., from battery) drops below defineable limit 25 * - additional volatge display (manually triggered by push-button (NOT connected to/controlled by Arduino) 26 * 4) Time of RTC shield can be set in section "RTC setup". Should be sufficient to do this initially (i.e., when you record your data for the first time and have to set the actual time) or in case RTC time deviates too much from real time (e.g., due to drift of oscillator in RC module due to missing temperature compensatioen or similar) 27 * see comment below "Uncomment in case current time should be written to RTC shield" 28 * 5) Ouptut into 1 file, at defined interval, for two meters type eHZ-IW8E2A5. (Unfortunately - and currently not understood - sometimes data is written into several files. However, due to naming (increasing number) it is stil possible to bring those files in (temporal) order. In addition time stamp is written into file for each datapoint aacquired. 29 * Output data: OBIS Kennzahl 1.8.0 (at Position 9x16+12 = 156 of SML protocol (In buffer w/ 1st element being number 0: position 155). 4 bytes (right after HEX value "56" at position 155 ## ## ## ## ##) 30 * To understand the SML protocol (and where to find the OBIS data I was interested in) Stefan Weigerts explanation is highly appreciated: http://www.stefan-weigert.de/php_loader/sml.php 31 */ 32 33/* ======================================================================================================= 34 * Include Libraries and alike 35 * ======================================================================================================= */ 36 37#include <stdarg.h> 38#include <stdio.h> 39#include <avr/pgmspace.h> 40#include <SoftwareSerial.h> // Serial interfaces realized by software (to allow, e.g., reading of more than just 1 sensor/eHz) 41#include <SD.h> // e.g., to write files to SD card etc. 42#include <SPI.h> // needed for SD.h 43#include <DS1302.h> // needed for RTC shield 44 45/*************************************************************************************************** 46 * CONSTANTS and alike 47 ***************************************************************************************************/ 48 49// Software Serial stuff ============================================================================================= 50// Serial Data from source 1 (IR diode of 1st eHZ) 51#define IR_RX_PIN_1 A0 // Pin 14 on Arduino Nano Every 52#define IR_TX_PIN_1 A2 // not used, but required for SoftwareSerial; Pin 16 on Arduino Nano Every 53// Serial Data from source 2 (IR diode of 2nd eHZ) 54#define IR_RX_PIN_2 A1 // Pin 14 on Arduino Nano Every 55#define IR_TX_PIN_2 A2 // not used, but required for SoftwareSerial; Pin 16 on Arduino Nano Every 56// Dummy software serial 57#define DUMMY_RX_PIN 9 // not connected 58#define DUMMY_TX_PIN A3 // not connected 59 60// SD card shield VMA304 stuff ============================================================================================= 61#define SD_CS_PIN 8 // Pin for SD card selection.Typically use hardware SS pin. (Pin 10 on most Arduino boards; however pin 8=A12 on Arduino Nano Every) 62#define SD_SS_PIN 10 // ###really needed ? Check if can be deleted ### 63 64// RTC stuff shield VMA301 stuff =========================================================================================== 65// uses Pins A3, A4, A5 on Arduino Nano Every (Physical Pins 7, 8, 9; aka ???, SDA, SCL) 66const int kCePin = 17; // Chip Enable 67const int kIoPin = 18; // Input/Output 68const int kSclkPin = 19; // Serial Clock 69 70// Other stuff ============================================================================================= 71#define loggingInterval 3600000 // time between readings in milli-seconds, e.g., 72 // 5 min = 5 * 60 * 1000 = 300 000 73 // 60 min = 60 * 60 * 1000 = 3 600 000 74// supply voltage monitoring stuff =========================================================================================== 75#define buzzPin 3 // Buzzer connected to PWM Pin D3 76#define supplVoltPin A2 // center contact of voltage divider (GND <-> supply Voltage (NOT 5V ! but V_in)) 77#define operatVolt 5 // operating voltage of Arduino (used as reference for ADC) 78#define buzzTimeEarly 8 // earliest time (hour of day; e.g., 8 AM = 8; 10PM -> 22) buzzer is triggered in case supply voltage below limit 79#define buzzTimeLate 20 // latest time (hour of day) buzzer is triggered in case supply voltage below limit 80float supplVoltLim = 5.6; // absolute supply voltage limit; serves as trigger for buzzer 81 82int numberSMLBytesRead = 0; // used to restart reading of SML data in case less than specified number of bytes (typically 397 for my electricity meter) read 83 84const int LED_WriteWarning = 4; // Pin D4 (physical pin no. 22): LED to signalize approaching or ongoing write to SD card 85const int LED_SML_Acquisition_eHZ1 = 5; // Pin PD1 (phyiscal pin no. 10): LED to signalize status of SML data acquisition for eHZ1 (OK: flashiung @ 1Hz; NOK: flashing @4 Hz*) 86const int LED_SML_Acquisition_eHZ2 = 6; // Pin A13 (phyical pin no. 24): LED to signalize status of SML data acquisition for eHZ1 (OK: flashiung @ 1Hz; NOK: flashing @4 Hz*); for whatever reason does NOT work w/ originally intended Pin D7 (phys pin 3; AVR Pin 39) 87// *frequency can be changed below to, e.g. 2 Hz by setting on time to 500 88int eHZ1Blink_ON=1000; // time LED for eHZ1 is turned on 89int eHZ2Blink_ON=1000; // time LED for eHZ2 is turned on 90int eHZ1Blink_OFF=250; // time LED for eHZ1 is turned off 91int eHZ2Blink_OFF=250; // time LED for eHZ2 is turned off 92bool SML_DataAcquisition_eHZ1 = false; // TRUE: SML data for eHZ1 acquired successfully; FALSE: error (e.g., buffer overflow, wrong header) during SML data acquisition for eHZ1 93bool SML_DataAcquisition_eHZ2 = false; // TRUE: SML data for eHZ2 acquired successfully; FALSE: error (e.g., buffer overflow, wrong header) during SML data acquisition for eHZ2 94const int eHZ1_ID = 1; 95const int eHZ2_ID = 2; 96unsigned long startTime; 97 98/** 99 * size of the SML message buffer = meximum number of bytes that can be received 100 */ 101#define SML_MSG_BUFFER_SIZE 400 // eHZ-IW8E2A5 delivers SML telegram of 396 bytes 102 103/** 104 * size of a filename (8.3 + terminating 0x00 = 13 chars) 105 */ 106#define FILENAME_SIZE 13 107 108/** 109 * maximum number of files that can be stored 110 */ 111#define MAX_FILE_NUMBER 99999999 112 113/** 114 * maximum time to wait for the end of a data packet received via IR 115 */ 116#define SERIAL_READ_TIMEOUT_MS 500 117 118/*************************************************************************************************** 119 * MESSAGES 120 ***************************************************************************************************/ 121 122/** 123 * maximum length of a message (entire text after variable substitution!) 124 */ 125#define MAX_MESSAGE_LENGTH 50 126 127/** 128 * message numbers 129 */ 130#define MSG_PROGRAM_STOPPED 0 131#define MSG_NEXT_FILENAME 1 132#define MSG_NO_FILE_NAMES_LEFT 2 133#define MSG_INIT_HARDWARE 3 134#define MSG_INIT_SD_CARD 4 135#define MSG_INIT_SD_CARD_ERROR 5 136#define MSG_BYTE_READ 6 137#define MSG_BUFFER_OVERFLOW 7 138#define MSG_SERIAL_OVERFLOW 8 139#define MSG_INVALID_HEADER 9 140#define MSG_FILE_OPEN_FAILED 10 141#define MSG_FILE_WRITTEN 11 142#define MSG_FREE_MEMORY 12 143#define MSG_NUM_BYTES_READ 13 144 145/** 146 * actual message texts - caution, adapt MAX_MESSAGE_LENGTH if required! 147 * ....+....1....+....2....+....3....+....4....+....5 148 */ 149const char msgText00[] PROGMEM = "Program stopped."; 150const char msgText01[] PROGMEM = "Next output file name is '%s'"; 151const char msgText02[] PROGMEM = "No more file names left"; 152const char msgText03[] PROGMEM = "Initializing Hardware..."; 153const char msgText04[] PROGMEM = "Initializing SD Card..."; 154const char msgText05[] PROGMEM = "SD Card initialization failed"; 155const char msgText06[] PROGMEM = "Read byte %02hhu from IR receiver"; 156const char msgText07[] PROGMEM = "Message buffer overflow"; 157const char msgText08[] PROGMEM = "Serial buffer overflow"; 158const char msgText09[] PROGMEM = "Invalid escape sequence"; 159const char msgText10[] PROGMEM = "Unable to open output file"; 160const char msgText11[] PROGMEM = "%u bytes of data written to file '%s'"; 161const char msgText12[] PROGMEM = "%u bytes of memory available"; 162const char msgText13[] PROGMEM = "%u bytes read"; 163 164/** 165 * table for easier access to the message texts 166 */ 167const char *const msgTextTable[] PROGMEM = { 168 msgText00, 169 msgText01, 170 msgText02, 171 msgText03, 172 msgText04, 173 msgText05, 174 msgText06, 175 msgText07, 176 msgText08, 177 msgText09, 178 msgText10, 179 msgText11, 180 msgText12, 181 msgText13 182}; 183 184/*************************************************************************************************** 185 * GLOBAL VARIABLES (YUCK!) 186 ***************************************************************************************************/ 187 188/** 189 * instance of the SoftwareSerial library to handle the IR communication 190 */ 191SoftwareSerial mySerial_1(IR_RX_PIN_1, IR_TX_PIN_1); // used for readout of SML data of eHZ1 192SoftwareSerial mySerial_2(IR_RX_PIN_2, IR_TX_PIN_2); // used for readout of SML data of eHZ2 193SoftwareSerial dummySerial(DUMMY_RX_PIN, DUMMY_TX_PIN); 194 195// Create a RTC DS1302 object. 196DS1302 rtc(kCePin, kIoPin, kSclkPin); 197 198/** 199 * variables to keep track of the name of the next file to be written 200 */ 201unsigned long nextFileNumber = 0; 202char nextFileName[FILENAME_SIZE]; 203 204/** 205 * the global buffer to store the SML message currently being read 206 */ 207unsigned char buffer[SML_MSG_BUFFER_SIZE]; 208 209/** 210 * Readings of eHZ 1 and 2 (after extraction from SML data) and corresponding timestamps 211 */ 212unsigned long eHZ_1_Reading; 213unsigned long eHZ_2_Reading; 214char eHZ_1_Reading_timeStamp[50]; 215char eHZ_2_Reading_timeStamp[50]; 216 217/** 218 * Supply voltage 219 */ 220float supplVolt; 221 222/*************************************************************************************************** 223 * SUBROUTINES 224 ***************************************************************************************************/ 225 226/** 227 * printMessage - reads a message text from the PROGMEM, performs variable substitution and 228 * writes the resulting text to the serial console. Use MSG_* constants for 229 * messageNumber. 230 */ 231void printMessage(int messageNumber, ...) { 232 va_list args; 233 char format[MAX_MESSAGE_LENGTH]; 234 char buffer[MAX_MESSAGE_LENGTH]; 235 va_start(args, messageNumber); 236 strncpy_P(format, (char*)pgm_read_word(&(msgTextTable[messageNumber])), MAX_MESSAGE_LENGTH); 237 vsnprintf(buffer, MAX_MESSAGE_LENGTH, format, args); 238 Serial.println(buffer); 239 va_end(args); 240} 241 242/** 243 * reportFreeMemory - determines the amount of free memory and logs it to the serial console. 244 */ 245void reportFreeMemory() { 246 int freeMemory; 247 uint8_t * heapptr, * stackptr; 248 stackptr = (uint8_t *) malloc(4); // use stackptr temporarily 249 heapptr = stackptr; // save value of heap pointer 250 free(stackptr); // free up the memory again (sets stackptr to 0) 251 stackptr = (uint8_t *) (SP); // save value of stack pointer 252 freeMemory = stackptr - heapptr; 253 printMessage(MSG_FREE_MEMORY, freeMemory); 254} 255 256/** 257 * stop - stops program execution in a controlled fashion (endless loop). 258 */ 259void stop() { 260 printMessage(MSG_PROGRAM_STOPPED); 261 while(1); 262} 263 264/** 265 * findNextFileNumber - determines the number and name of the next file available. This will change 266 * the global variables nextFileName and nextFileNumber. This routine will 267 * stop the entire program if no free filenames can be found. 268 */ 269void findNextFileNumber() { 270 do { 271 sprintf(nextFileName, "%08lu.CSV", nextFileNumber); 272 if (!SD.exists(nextFileName)) { 273 printMessage(MSG_NEXT_FILENAME, nextFileName); 274 return; 275 } else { 276 nextFileNumber += 1; 277 if (nextFileNumber > MAX_FILE_NUMBER) { 278 printMessage(MSG_NO_FILE_NAMES_LEFT); 279// errorSound(ERR_NO_FILE_NAMES_LEFT); 280 stop(); 281 } 282 } 283 } while (true); 284} 285 286void createTimeStamp(int eHZ_Number) { 287 // Get the current time and date from the chip. 288 Time t = rtc.time(); 289 // Format the time and date and insert into the temporary buffer. 290 char currDAndT_buf[50]; 291 snprintf(currDAndT_buf, sizeof(currDAndT_buf), "%04d-%02d-%02d %02d:%02d:%02d;", 292 t.yr, t.mon, t.date, 293 t.hr, t.min, t.sec); 294 295 // assign determined timestamp to either eHZ1 or eHz2 296 if ( eHZ_Number == eHZ1_ID ) { 297 for (int i = 0; i<50; i++) { 298 eHZ_1_Reading_timeStamp[i]=currDAndT_buf[i]; 299 } 300//Serial.println("eHZ_1 Timestamp copied"); 301 } 302 if ( eHZ_Number == eHZ2_ID ) { 303 for (int i = 0; i<50; i++) { 304 eHZ_2_Reading_timeStamp[i]=currDAndT_buf[i]; 305 } 306//Serial.println("eHZ_2 Timestamp copied"); 307 } 308} 309 310void writeTimeAndReadingToFile() { 311 Serial.println("Writing data to SD card..."); 312 313 File outputFile; 314 315 outputFile = SD.open(nextFileName, FILE_WRITE); 316 if (!outputFile) { 317 printMessage(MSG_FILE_OPEN_FAILED); 318 } 319 else { 320 outputFile.print(eHZ_1_Reading_timeStamp); 321 outputFile.print(eHZ_1_Reading); 322 outputFile.print(";"); 323 outputFile.print(eHZ_2_Reading_timeStamp); 324 outputFile.print(eHZ_2_Reading); 325 outputFile.print(";"); 326 outputFile.print(eHZ_2_Reading_timeStamp); 327 outputFile.println(supplVolt); 328 outputFile.close(); 329 Serial.println("...done..."); 330 } 331} 332 333void getSMLData(SoftwareSerial mySoftSer, int eHZ_Number) { 334 unsigned int nextBufferPosition = 0; 335 unsigned long lastReadTime = 0; 336 337 // clear the message buffer 338 memset(buffer, 0, sizeof(buffer)); 339 340 // initialize data acquisition LEDs assuming everything is fine 341 if ( eHZ_Number == eHZ1_ID ) { // remember NO error during SML data acquisition for eHZ1 342 SML_DataAcquisition_eHZ1 = true; 343 } 344 if ( eHZ_Number == eHZ2_ID ) { // remember NO error during SML data acquisition for eHZ2 345 SML_DataAcquisition_eHZ2 = true; 346 } 347 348 // remove a pending overflow flag (might have been left over from a previous run) 349 // and ensure that the SoftwareSerial library is listening 350 mySoftSer.overflow(); 351 mySoftSer.listen(); 352 353 // wait until actual data is available 354 while (!mySoftSer.available()); 355 // Next 2 lines ("Serial.print...") not necessary for algorithm, however might be useful for information purpose on serial monitor. 356 // In addition reading of serial data SEEMs to be more stable (less errors such as "invalid escape sequence"). Maybe 357 // something like "delay(200);" might also work... 358 Serial.print("Number of bytes available at SW serial:"); 359 Serial.println(mySoftSer.available()); 360 361 // keep reading data until either the message buffer is filled or no more data was 362 // received for SERIAL_READ_TIMEOUT_MS ms 363 364 lastReadTime = millis(); 365 while (millis() - lastReadTime < SERIAL_READ_TIMEOUT_MS) { 366 if (mySoftSer.available()) { 367 buffer[nextBufferPosition] = mySoftSer.read(); 368 lastReadTime = millis(); 369 if (nextBufferPosition >= SML_MSG_BUFFER_SIZE) { 370 dummySerial.listen(); // disable further IR input by switching software serial input to a dummy instance 371 printMessage(MSG_BUFFER_OVERFLOW); 372 373 if ( eHZ_Number == eHZ1_ID ) { // remember error during SML data acquisition for eHZ1 374 SML_DataAcquisition_eHZ1 = false; 375 } 376 if ( eHZ_Number == eHZ2_ID ) { // remember error during SML data acquisition for eHZ2 377 SML_DataAcquisition_eHZ2 = false; 378 } 379 return; 380 } 381 nextBufferPosition += 1; 382 numberSMLBytesRead = nextBufferPosition+1; // used to restart reading of SML data in case less than specified number of bytes (typically 397 for my electricity meter) read 383 } 384 } 385 386 // report an error if an overflow condition was encountered - the data received is useless 387 // in this case :-( 388 if (mySoftSer.overflow()) { 389 dummySerial.listen(); // disable further IR input 390 printMessage(MSG_SERIAL_OVERFLOW); 391 392 if ( eHZ_Number == eHZ1_ID ) { // remember error during SML data acquisition for eHZ1 393 SML_DataAcquisition_eHZ1 = false; 394 } 395 if ( eHZ_Number == eHZ2_ID ) { // remember error during SML data acquisition for eHZ2 396 SML_DataAcquisition_eHZ2 = false; 397 } 398 } 399 else { 400 dummySerial.listen(); // disable further IR input 401 // check the header 402 printMessage(MSG_NUM_BYTES_READ, nextBufferPosition + 1); 403 if (!isValidHeader()) { 404 // not a valid header - notify the user... 405 printMessage(MSG_INVALID_HEADER); 406 // ...and empty the receiver buffer (wait for the end of the current data stream 407 while (mySoftSer.available() > 0) { 408 mySoftSer.read(); 409 } 410 if ( eHZ_Number == eHZ1_ID ) { // remember error during SML data acquisition for eHZ1 411 SML_DataAcquisition_eHZ1 = false; 412 } 413 if ( eHZ_Number == eHZ2_ID ) { // remember error during SML data acquisition for eHZ2 414 SML_DataAcquisition_eHZ2 = false; 415 } 416 } 417 } 418} 419 420unsigned long getElectricityMeterReading(char SML_buff) { 421 /* Returns electricity meter reading in kWh 422 * Electricity meter reading (for eHZ-IW8E2A5) is represented by the five bytes at buffer positions 155-159. 423 * Make those 5 bytes to 1 HEX value, convert to DEC, divide by 10 (due to sacler in eHZ), divide by 1000 to get kWh 424 * However, use only lowest 4 bytes (i.e., meaningful data start at position 156). Highest number represented by these four bytes is 425 * in HEX FF FF FF FF or in DEC 4294967295 (= 2^32). This would correspond to approx. 429 496 kWh. 426 * Assuming an (realistic) average energy consumption of about 5000 kWh per year for the heat pump, this would allow 427 * to store the meter reading for about 85 years ! 428 * Therefore use data type unsigned long (allows values from 0 ... 2^32-1 = 0 ... 4 294 967 295) 429 */ 430 unsigned char EMReading_buf[4]; 431 unsigned long EMReading; 432 433 // clear the EMReading buffer 434 memset(EMReading_buf, 0, sizeof(EMReading_buf)); 435 436 // get 4 bytes representing the electricity meter reading and write them in opposite order to new array. Then create unsigned long. 437 // Algorhithm see https://forum.arduino.cc/index.php?topic=108865.0 438 for(int i=3; i>=0; i--) { 439 EMReading_buf[3-i] = (buffer[156+i]); 440 } 441 EMReading = *((unsigned long *) (& EMReading_buf[0])); 442 EMReading = EMReading/10; // consider scaler in SML data -> divide by 10 to get Wh 443 return EMReading; 444} 445 446/** 447 * isValidHeader - returns true if the global message buffer begins with a valid SML escape sequence. 448 */ 449inline boolean isValidHeader() { 450 return ((buffer[0] == 0x1b) && 451 (buffer[1] == 0x1b) && 452 (buffer[2] == 0x1b) && 453 (buffer[3] == 0x1b) && 454 (buffer[4] == 0x01) && 455 (buffer[5] == 0x01) && 456 (buffer[6] == 0x01) && 457 (buffer[7] == 0x01)); 458} 459 460/** 461 * for DS1302 based RTC time. Not necessarily (to be) used for 462 */ 463namespace { 464 465String dayAsString(const Time::Day day) { 466 switch (day) { 467 case Time::kSunday: return "Sunday"; 468 case Time::kMonday: return "Monday"; 469 case Time::kTuesday: return "Tuesday"; 470 case Time::kWednesday: return "Wednesday"; 471 case Time::kThursday: return "Thursday"; 472 case Time::kFriday: return "Friday"; 473 case Time::kSaturday: return "Saturday"; 474 } 475 return "(unknown day)"; 476} 477 478} // namespace 479 480 481/*************************************************************************************************** 482 * MAIN ROUTINES 483 ***************************************************************************************************/ 484 485/** ######################################################################################################## 486 * SETUP 487 ######################################################################################################## 488 */ 489void setup() { 490 // Serial communication 491 // ===================================================================================================== 492 Serial.begin(9600); 493 494 Serial.println("===== Starting setup ====="); 495 // Pin configuration 496 // ===================================================================================================== 497 printMessage(MSG_INIT_HARDWARE); 498 pinMode(IR_RX_PIN_1, INPUT); 499 pinMode(IR_TX_PIN_1, OUTPUT); 500 pinMode(IR_RX_PIN_2, INPUT); 501 pinMode(IR_TX_PIN_2, OUTPUT); 502 pinMode(SD_CS_PIN, OUTPUT); 503 pinMode(SD_SS_PIN, OUTPUT); 504 pinMode(DUMMY_RX_PIN, INPUT); 505 pinMode(DUMMY_TX_PIN, OUTPUT); 506 Serial.println("...done !... pinMode set !"); 507 508 // RTC setup 509 // ===================================================================================================== 510 // Initialize a new chip by turning off write protection and clearing the clock halt flag. These methods needn't always be called. See the DS1302 datasheet for details. 511 rtc.writeProtect(false); 512 rtc.halt(false); 513/* // Uncomment in case current time should be written to RTC shield 514 // Make a new time object to set the date and time and set the time 515 // Format: year, moth, day, hour, minute, second, day of the week (1=Sunday)) 516 Time t(2021, 3, 03, 21, 48, 0, 4); 517 rtc.time(t); 518*/ 519 520 // Setup of supply voltage monitoring 521 // ===================================================================================================== 522 Serial.println("Testing LEDs & piezo speaker..."); 523 pinMode(buzzPin, OUTPUT); 524 pinMode(supplVoltPin, INPUT); 525 // quick sound check to prove piezo speaker works (only during day time...) 526 527 Time t2 = rtc.time(); 528 if( t2.hr > buzzTimeEarly && t2.hr < buzzTimeLate ) { 529 for (int i = 0; i <5; i++) { 530 tone(buzzPin, 1000, 250); 531 delay(500); 532 tone(buzzPin, 5000, 250); 533 delay(500); 534 } 535 } 536 537 // Setup of data acquisition and diagnosis / warning LEDs 538 // ===================================================================================================== 539 pinMode(LED_SML_Acquisition_eHZ1, OUTPUT); 540 digitalWrite(LED_SML_Acquisition_eHZ1, LOW); 541 pinMode(LED_SML_Acquisition_eHZ2, OUTPUT); 542 digitalWrite(LED_SML_Acquisition_eHZ2, LOW); 543 pinMode(LED_WriteWarning, OUTPUT); 544 digitalWrite(LED_WriteWarning, LOW); 545 546 // ...let blink 5 times to proove the're working 547 //------------------------------------------------------------------------------------------------------- 548 Serial.println("Data acquisition LED 1 (BLUE): ...blink 5 times"); 549 for(byte i=0; i<5; i++) { 550 digitalWrite(LED_SML_Acquisition_eHZ1, HIGH); 551 delay(250); 552 digitalWrite(LED_SML_Acquisition_eHZ1, LOW); 553 delay(250); 554 } 555 Serial.println("...done !"); 556 Serial.println("Data acquisition LED 2 (YELLOW): ...blink 5 times"); 557 for(byte i=0; i<5; i++) { 558 digitalWrite(LED_SML_Acquisition_eHZ2, HIGH); 559 delay(250); 560 digitalWrite(LED_SML_Acquisition_eHZ2, LOW); 561 delay(250); 562 } 563 Serial.println("...done !"); 564 Serial.println("SD status LED (RED): ...blink 5 times"); 565 for(byte i=0; i<5; i++) { 566 digitalWrite(LED_WriteWarning, HIGH); 567 delay(250); 568 digitalWrite(LED_WriteWarning, LOW); 569 delay(250); 570 } 571 Serial.println("...done !"); 572 573 // SD card setup 574 // ===================================================================================================== 575 Serial.println("Initializing SD card..."); 576 if (!SD.begin(SD_CS_PIN)) { 577 printMessage(MSG_INIT_SD_CARD_ERROR); 578// errorSound(ERR_INIT_SD_CARD_ERROR); 579 stop(); 580 } 581 else { 582 Serial.println("...done !"); 583 Time t2 = rtc.time(); 584 if( t2.hr > buzzTimeEarly && t2.hr < buzzTimeLate ) { 585 for (int i = 0; i <10; i++) { 586 tone(buzzPin, 250, 250); 587 delay(500); 588 } 589 } 590 } 591 592 // Sofware Serial setup 593 // ===================================================================================================== 594 dummySerial.begin(9600); 595 mySerial_1.begin(9600); // eHZ sends at fix rate of 9600 Baud 596 mySerial_2.begin(9600); // eHZ sends at fix rate of 9600 Baud 597 598 // determine the first file name to use 599 findNextFileNumber(); 600 601} 602 603/** ######################################################################################################## 604 * END of SETUP 605 ######################################################################################################## 606 */ 607 608void loop() { 609 // turn off all LEDs 610 digitalWrite(LED_WriteWarning, LOW); 611 digitalWrite(LED_SML_Acquisition_eHZ1, LOW); 612 digitalWrite(LED_SML_Acquisition_eHZ2, LOW); 613// reportFreeMemory(); // for information purposes only 614 615 numberSMLBytesRead = 0; 616 while(numberSMLBytesRead!=397 || SML_DataAcquisition_eHZ1 == false){ // in case SML data is plausible (i.e., SML header is OK and also no buffer overflow) it still happens that eHZ reading is derived from this data. Examining this showed that in those cases typically less than 397 bytes SML data were read. This while loop/condition should prevent this. 617 getSMLData(mySerial_1, eHZ1_ID); // get SML data from 1st electricity meter (Input: A0), store it into (global) buffer 618 } 619 createTimeStamp(eHZ1_ID); 620 Serial.println("Time stamp eHZ_1 created..."); 621 eHZ_1_Reading = getElectricityMeterReading(buffer); // extract OBIS 1.8.0. in Wh x 10 and store it in (global) variable 622 Serial.print("eHZ_1 Reading: "); 623 Serial.println(eHZ_1_Reading); 624 625 numberSMLBytesRead = 0; 626 while(numberSMLBytesRead!=397 || SML_DataAcquisition_eHZ2 == false){ // in case SML data is plausible (i.e., SML header is OK and also no buffer overflow) it still happens that eHZ reading is derived from this data. Examining this showed that in those cases typically less than 397 bytes SML data were read. This while loop/condition should prevent this. 627 getSMLData(mySerial_2, eHZ2_ID); // get SML data from 2nd electricity meter (Input: A1), store it into (global) buffer 628 } 629 createTimeStamp(eHZ2_ID); 630 Serial.println("Time stamp eHZ_2 created..."); 631 eHZ_2_Reading = getElectricityMeterReading(buffer); // extract OBIS 1.8.0. in Wh x 10 and store it in (global) variable 632 Serial.print("eHZ_2 Reading: "); 633 Serial.println(eHZ_2_Reading); 634 635 supplVolt = analogRead(supplVoltPin) * 2 / 1024.0 * operatVolt; // get actual supply voltage in V (multiply analog reading by 2 due to 1:1 voltage divider 636 637 Serial.println(); 638 639 // turn ON red LED to show that writing is beeing performed and SD card should NOT be removed. Prewarn by blinking for about 3s. 640 // ----------------------------------------------------------------------------------------------------------------------------- 641 for(byte i=0; i<3; i++) { 642 digitalWrite(LED_WriteWarning, HIGH); 643 delay(500); 644 digitalWrite(LED_WriteWarning, LOW); 645 delay(500); 646 } 647 // Write data to SD card 648 // --------------------- 649 digitalWrite(LED_WriteWarning, HIGH); 650 // write data to file (time 1; reading 1; time 2; reading 2; time 2; supply voltage) 651 writeTimeAndReadingToFile(); 652 // turn OFF red LED to show that writing is FINISHED and SD card CAN be removed 653 digitalWrite(LED_WriteWarning, LOW); 654 delay(1000); 655 656 // Show result (SUCCESS/FAIL) of data acquisition for each channel 657 // --------------------------------------------------------------- 658 // set blinking behaviour of LED depending on result of SML data acquisition of eHZ1 659 if(SML_DataAcquisition_eHZ1 == true) { 660 eHZ1Blink_ON = 1000; 661 } 662 if(SML_DataAcquisition_eHZ1 == false) { 663 eHZ1Blink_ON = 250; 664 } 665 // set blinking behaviour of LED depending on result of SML data acquisition of eHZ1 666 if(SML_DataAcquisition_eHZ2 == true) { 667 eHZ2Blink_ON = 1000; 668 } 669 if(SML_DataAcquisition_eHZ2 == false) { 670 eHZ2Blink_ON = 250; 671 } 672 673 // wait for specified time interval until next data acquisition to be performed 674 startTime = millis(); 675 // show result of data acquisition by blinking slow (success) or fast (fail). slow/fast defined above. 676 while(millis()-startTime < loggingInterval){ 677 delay(60000); // only blink once a minute (to save battery energy) 678 for(int i=0; i<3; i++) { 679 digitalWrite(LED_SML_Acquisition_eHZ1, HIGH); 680 delay(eHZ1Blink_ON); 681 digitalWrite(LED_SML_Acquisition_eHZ1, LOW); 682 delay(eHZ1Blink_OFF); 683 } 684 for(int i=0; i<3; i++) { 685 digitalWrite(LED_SML_Acquisition_eHZ2, HIGH); 686 delay(eHZ2Blink_ON); 687 digitalWrite(LED_SML_Acquisition_eHZ2, LOW); 688 delay(eHZ2Blink_OFF); 689 } 690 // Sound buzzer in case supply voltage drops below defined limit; trigger alarm only during specified time slots. 691 Time t2 = rtc.time(); 692Serial.print("supplyVolt: "); 693Serial.println(supplVolt); 694 if( supplVolt < supplVoltLim && t2.hr > buzzTimeEarly && t2.hr < buzzTimeLate ) { 695 for (int i = 0; i <5; i++) { 696 tone(buzzPin, 1000, 250); 697 delay(750); 698 } 699 } 700 } 701 702} 703
IR-Diode-Schaltung_Test_NanoEvery.ino
arduino
Short Arduino sketch used to optimize position of receiving IR phototransistor in front of eHZ's IR sender diode.
1// A0: amplified signal of 1st IR diode (output=collector of 1st transistor) 2// A1: amplified signal of 2nd IR diode (output=collector of 2nd transistor) (in case 2 IR diodes fo 2 eHZs used) 3// A6: raw signal of 1st IR photo diode (base of 1st transistor) 4// A7: raw signal of 2nd IR photo diode (base of 2nd transistor) (in case 2 IR diodes fo 2 eHZs used) 5 6 7void setup() { 8 9 //for 1st IR diode 10 pinMode(A0, INPUT); 11 pinMode(A6, INPUT); 12 13 //for 2nd IR diode 14 pinMode(A1, INPUT); 15 pinMode(A7, INPUT); 16 Serial.begin(9600); 17} 18 19void loop() { 20 //for 1st IR diode 21 22 Serial.print("A0: "); 23 Serial.print(analogRead(A0)); 24 Serial.print(" ; A6: "); 25 Serial.print(analogRead(A6)); 26 27 28 //for 2nd IR diode 29 Serial.print(" ; A1: "); 30 Serial.print(analogRead(A1)); 31 Serial.print(" ; A7: "); 32 Serial.println(analogRead(A7)); 33 34 35} 36
Downloadable files
Schematics for reading 2 smart meters using Arduino Nano Every w/ supply voltage monitoring.
Schematics for reading 2 smart meters using Arduino Nano Every w/ supply voltage monitoring.
Schematics for reading 2 smart meters using Arduino Nano Every
Schematics for reading 2 smart meters using Arduino Nano Every
Schematics for reading 2 smart meters using Arduino Nano Every w/ supply voltage monitoring.
Schematics for reading 2 smart meters using Arduino Nano Every w/ supply voltage monitoring.
Schematics for reading 2 smart meters using Arduino Nano Every
Schematics for reading 2 smart meters using Arduino Nano Every
Schematics for reading 2 smart meters using Arduino Nano Every w/ manual supply voltage display.
Schematics for reading 2 smart meters using Arduino Nano Every w/ manual supply voltage display.
Comments
Only logged in users can leave comments
Torschtele
0 Followers
•0 Projects
Table of contents
Intro
2
0