Project in progress
P1 Energy Hub

P1 Energy Hub © MIT

P1 energy monitoring hub with Arduino MKR - WiFi

  • 770 views
  • 0 comments
  • 0 respects

Components and supplies

Apps and online services

About this project

P1

P1 interface for the Smart-Energy meters has been done many times before.This Project is targeting a small footprint with battery operated MKR board using WiFi to Node-Red as privit-'cloud' solution.Background read on P1 port and meters can be found here:

  • Domotix (basic info - Dutch)
  • Domotix (Telegram info and details per meter type - Dutch)

In this project we use a Landis & Gyr E350 with DSMR 4.0. The meter has the following P1 interface:

Powering the first optocoupler (pin2 DataReq/RTS) signals the meter to provide a serial bitstream via optocoupler (pin5-pin3). In this L&G E350 it is a serial 115200b/s with 8, N, 1 bit setting. The RTS-opto works officially with 5V, but 3.3V works fine too. The Data interface is open collector, so it requires some help (pull up) to signal correctly to our Micro.

Inverter

Data from the meter is inverted serial. The RX interface on the ATSAM of the MKR1010 has no feature to invert polarity, so we have to build a simple inverter in between using a signal transistor:

In this diagram the Pin2-RTS is driven by a 3.3V IO (for low power reasons, used in case we run on battery). However, this is proven not to be stable, and it could be preferred to keep pin2-RTS to 3.3V (this does however consumes an est. 10-20 mA)

Coding

Arduino is coded in C, using the following libraries:

  • WIFININA.h - used for uBlox/ESP32 wifi module
  • ArduinoLowpoer.h - low power modes for arduino (sleep/deepsleep)
  • EasyWiFi.h - easy wifi setup with secure credential storage on uBlox module
  • RTCzero.h - real time clock for ATSamD20/21

Global variables all start with G_. Debug via Serial USB port, (#define DEBUG_X)Program runs the following sequence: setup hw open wifi, start main loop.

Main loop has 2 options: Mode=9 : continiously read and send data, Mode=1: read and send data per time interval with sleepmode.P1 port is connected to pin13 - RX, which is standard Serial1 on the Arduino.Data on P1 is checked for start ('/'), then complete Telegram is dumped in a buffer (G_P1buffer), then parsed for the details required, till the end ('!').CRC is calculated and checked. G_P1Valid holds the flag for a valid P1 buffer or not.

CloudConnect

Data offload is setup via HTTP-server: Telegram data is converted to a JSON string:

{"p1":true,"header":"","etime":"201221125734W","eserial":"4530303034303031353436383536383134","eusageunit":"kWh","eusage1":16144.12,"esupply1":0,"eusage2":14411.69,"esupply2":0,"eactualunit":"kW","eactualuse":0.52,"eactualsupply":0,"efailures":8,"eiunit":"A","eicurrent":3,"gserial":"4730303233353631323235393831383134","gtime":"201221120000W","gunit":"m3","gusage":7514.4}

Then POST-ed to the server by HTTP-POST command. Back-channel mechanism is a HTTP-GET to receive the server response on a possible new Mode.

Node-Red

The HTTP server is build in Node-red, which can be hosted on a Raspi or on Windows,

Dependancy is on the standard Node-red plus the Dashboard nodes to make use of buttons and graphics Dashboard. Node-red Json code is attached in the download section. The nodes look like this:

The P1 Telegram is send every 10 seconds, Node-Red updated the dashboard accordingly. The same data also contains information on Gas Usage and net-feed power (in case you have solar panels) Adapt the nodes accordingly. In node-red the Json is as follows:

ToDo

  • Add MQTT server via PubSub
  • Add Battery monitoring
  • Measure low power usage

Code

P1_demo.jsonJSON
Node-Red Json setup
[{"id":"906ddf10.b3ce8","type":"http in","z":"9acd9aec.bc22d8","name":"Post /JSON","url":"/JSON","method":"post","upload":false,"swaggerDoc":"","x":210,"y":200,"wires":[["55b1a28f.66945c"]]},{"id":"55b1a28f.66945c","type":"json","z":"9acd9aec.bc22d8","name":"","property":"payload","action":"","pretty":false,"x":430,"y":200,"wires":[["2bd473e2.4d8f8c"]]},{"id":"acde231e.0561b","type":"ui_chart","z":"9acd9aec.bc22d8","name":"ActualPowerUse","group":"ff7229bb.c9d068","order":1,"width":"18","height":10,"label":"<small>Power Use Kw","chartType":"line","legend":"false","xformat":"dd HH:mm","interpolate":"cubic","nodata":"","dot":false,"ymin":"","ymax":"","removeOlder":"12","removeOlderPoints":"5000","removeOlderUnit":"3600","cutout":0,"useOneColor":false,"useUTC":false,"colors":["#e60000","#f40101","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"outputs":1,"x":960,"y":200,"wires":[[]]},{"id":"2bd473e2.4d8f8c","type":"function","z":"9acd9aec.bc22d8","name":"ActualPower","func":"msg.payload = msg.payload.eactualuse; \nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":750,"y":200,"wires":[["acde231e.0561b","999b3f10.d7076"]]},{"id":"f8f6c942.6af088","type":"comment","z":"9acd9aec.bc22d8","name":"P1 HTTP SERVER","info":"","x":230,"y":120,"wires":[]},{"id":"999b3f10.d7076","type":"debug","z":"9acd9aec.bc22d8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":950,"y":140,"wires":[]},{"id":"de98f380.0bd28","type":"http response","z":"9acd9aec.bc22d8","name":"Reply Mode","statusCode":"","headers":{},"x":750,"y":320,"wires":[]},{"id":"d2f8232c.24a1c","type":"function","z":"9acd9aec.bc22d8","name":"Push Mode","func":"// set new mode if requested\nif(msg.payload ==9 || msg.payload ==1  ) // set mode if we received number only 1 = low power 9= continues (test)\n{\n    global.set(\"P1Mode\", msg.payload );\n}\nelse  // send reponse if we require mode set\n{\n    msg.payload = global.get(\"P1Mode\");\n    global.set(\"P1Mode\", -1 );\n    return msg;    \n}\n","outputs":1,"noerr":0,"initialize":"// Code added here will be run once\n// whenever the node is deployed.\nglobal.set(\"P1Mode\", -1 );","finalize":"","x":390,"y":320,"wires":[["63641ef4.ae1ad"]]},{"id":"a801bf49.1ad07","type":"inject","z":"9acd9aec.bc22d8","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"P1","payload":"9","payloadType":"num","x":190,"y":360,"wires":[["d2f8232c.24a1c"]]},{"id":"63641ef4.ae1ad","type":"template","z":"9acd9aec.bc22d8","name":"","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"<! DOCTYPE html>\n<html>\n<body>\n{\"MODE\":{{payload}}}\n</body>\n</html>\n","output":"str","x":560,"y":320,"wires":[["de98f380.0bd28","fcea8db7.96bcc"]]},{"id":"fcea8db7.96bcc","type":"debug","z":"9acd9aec.bc22d8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":750,"y":380,"wires":[]},{"id":"8d80f2f.a4cf11","type":"http in","z":"9acd9aec.bc22d8","name":"Get /P1","url":"/P1","method":"get","upload":false,"swaggerDoc":"","x":190,"y":280,"wires":[["d2f8232c.24a1c"]]},{"id":"45d7e0b6.591bc","type":"inject","z":"9acd9aec.bc22d8","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"P1","payload":"1","payloadType":"num","x":190,"y":320,"wires":[["d2f8232c.24a1c"]]},{"id":"ff7229bb.c9d068","type":"ui_group","name":"P1 Demo","tab":"74fdc55.0ac173c","order":2,"disp":true,"width":"24","collapse":false},{"id":"74fdc55.0ac173c","type":"ui_tab","name":"Test","icon":"dashboard","order":2,"disabled":false,"hidden":false}]
P1_Server_V1.inoC/C++
C-code Arduino
/*
P1 Port Reader for WifiNiNa Wireless Hub
Dutch Energy Meters

App Flow:
Setup Hardware - Serial, Serial1, RTC
Setup Wifi - if no Wifi - halt !
Main loop Modes :

1 = HTTP post server (sleepmode)
9 = test mode: read & post continiously Actual Power

(c) JayFox 2020
*************************************************************/
#include <WiFiNINA.h>
#include <EasyWiFi.h>
#include <RTCZero.h>
#include <ArduinoLowPower.h>

#define DEBUG_X 1  // Debug info for messages
#define DEBUG_XX 1  // Debug info for stream data

//
//Defines
//
#define VERSION "1.1.0"               // Software verson nr
#define MAX_MISSED_DATA 2000          // MAX data missed from Client/Web HTTP reply before time-out (accept short messages only)
#define MAXBUFFER   1024              // MAX buffer size of P1 Telegram
#define P1_en_port 7                  // Enable pin - optional on Hardware print
#define P1START "/"
#define P1TEST 9
#define P1POSTNODE 1

IPAddress G_Myip;
IPAddress G_Hostip=IPAddress(192,168,200,20); // Node-Red Server
EasyWiFi MyEasyWiFi;
char MyAPName[]= {"P1Demo_AP"},MyTest[]={"JSON"}; // node-red HTTP side for Test mode
long G_Myrssi;
//
//Define RTCSettings
//
RTCZero G_rtc;                         // RTC Clock
unsigned long G_timer,G_Epoch= 1541062800UL;   // epoch time global variable, adapted by NTP routines
//
//Define P1 Settings
//
String G_P1header="/";
String G_backdoor= "P1"; // backdoor HTTP get for mode change
String G_P1error="";
char G_P1buffer[MAXBUFFER],G_Time[16],G_Date[16];
unsigned int G_P1crc=0; 
int G_P1length=0;
boolean G_P1valid = false,G_Noled = false;
struct P1_data { // /status P! port
  String header;      // /
  String etimestamp;  // 0-0:1.0.0
  String eserial;     // 0-0:96.1.1    
  String eusage1;     // 1-0:1.8.1
  String esupply1;    // 1-0:2.8.1
  String eusage2;     // 1-0:1.8.2
  String esupply2;    // 1-0:2.8.2
  String eactualuse;  // 1-0:1.7.0
  String eactualsupply; // 1-0:2.7.0 
  String epowerfailures; //  0-0:96.7.21 
  String einstantcurrent; //  1-0:31.7.0 
  String gserial; //  0-1:96.1.0
  String gtimestamp; //  0-1:24.2.1 first () -> double () used for this entry (timestamp)(gasusage)
  String gusage; //  0-1:24.2.1 second ()
  } G_Smartmeter;    

struct P1_setting {
  byte mode;                // 1byte
  IPAddress hostip;         // 4 bytes
  unsigned int hostport;    // 2 bytes
  unsigned int sleeptime;   // 2 bytes
  char topic[16];           // 16 bytes
  char login[16];           // 16 bytes  // not used
  char pass[16];            // 16 bytes  // not used
  } G_Setting;              // total 57 bytes *2 = 114 = MAXSIZE ASCII HEX-array

//
// Setup
//
void setup() {
pinMode(P1_en_port, OUTPUT);
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(P1_en_port, LOW);
Mkrled(false);

/*********** Serial SETUP  **********/
 
#if DEBUG_X
int t=10;  //Initialize serial and wait for port to open, max 10 second waiting
  Serial.begin(115200);
  while (!Serial) {
    ; delay(1000);
    if ( (t--)== 0 ) break;
  }
Serial.println("* P1 Server, serial setup, Starting Initialisation");
#endif 
/*********** RTC SETUP  **********/
G_rtc.begin();
G_rtc.disableAlarm();                         

/*********** P1 @ Serial1 SETUP  **********/
Serial1.begin(115200,SERIAL_8N1);
Serial1.flush();
G_Epoch=P1_checktime();
if (G_P1valid == false){
#if DEBUG_X
    Serial.println("* !! No P1 Port or Message !!");
#endif  
}
else {
#if DEBUG_X
    Serial.println("* P1 Port found");
#endif
}

/*********** WIFI SETUP  **********/
if (WiFi.status() == WL_NO_SHIELD) {   // check for the presence of the shield:
#if DEBUG_X
    Serial.println("* WiFi shield not present - halted");
#endif
    while (true);     // don't continue if no shield
  }
delay(1000);
#if DEBUG_X
   Serial.println("* WiFi starting...");
#endif  
MyEasyWiFi.apname(MyAPName);
MyEasyWiFi.seed(4);     // set seed for encoded storage
MyEasyWiFi.start();     // Start Wifi login 
if ( WiFi.status() != WL_CONNECTED){
#if DEBUG_X
   Serial.println("* WiFi cant connect- halted.");
#endif
    while (true);     // don't continue if no wifi
    }
G_Myip = WiFi.localIP();

//*********** Init settings for Server settings to Node-Red  **********/
  G_Setting.mode=9;
  G_Setting.hostport=1880;
  G_Setting.hostip=G_Hostip;
  G_Setting.topic[0]='J';G_Setting.topic[1]='S';G_Setting.topic[2]='O';G_Setting.topic[3]='N';G_Setting.topic[4]=0;
  G_Setting.sleeptime=1;
  
#if DEBUG_X
Serial.println("* Checking P1 Data..");
#endif  
P1_Fillstructure();P1_Poststructure(G_Setting.topic);
G_timer=G_rtc.getEpoch();

#if DEBUG_X
Serial.println("* Starting Main Program.");
#endif 
}








void loop() {
int t;

switch (G_Setting.mode) {
  case (P1TEST) :             
       Serial.println("* Test loop...");
       P1_Fillstructure();
       Serial.println(G_P1buffer);
       if (G_P1valid) 
          {
          NINAled(0,0,8) ; // turn blue
          P1_Poststructure(MyTest);
          }
       else NINAled(8,0,0) ; // turn red
       delay(500);
       break;
  case (P1POSTNODE) :            
#if DEBUG_X
       Serial.print("* PostServer: Good Night, sleeping for ");Serial.print(G_Setting.sleeptime);Serial.println(" minute(s)..");
       Serial.end();
#endif
       WiFi.end();WiFi.lowPowerMode();
       Serial1.end();
       NINAled(0,0,0) ; // turn off
       t=G_Setting.sleeptime*60000;
       LowPower.deepSleep(t);
       // *************************************  at HTTP Sleep ******************************
#if DEBUG_X
      Serial.begin(115200);delay(1000);
      Serial.println("* I'll be back ..");
#endif   
      G_rtc.begin();
      Serial1.begin(115200,SERIAL_8N1);
      WiFi.noLowPowerMode();  // turn off low power wifi
      MyEasyWiFi.start();     // Start Wifi login 
      G_Epoch=P1_checktime();
      NINAled(8,0,8) ; // turn led purple
      P1_Fillstructure();
      P1_Poststructure(G_Setting.topic);
      break;
  default :
      break; 
    } // end Switch           
} // end main loop


/***************** P1 Routines ****************************/

boolean P1_Fillstructure()
{
int pos=0;
// Parse meter data into G_Smartmeter Structure
P1_dump(G_P1buffer,&G_P1length,&G_P1crc);       // load buffer into Global Memory variables
if (G_P1valid )                                       // if there is a valif buffer, start parsing
  {
  P1_parse_item(G_P1buffer,pos,&pos,"0-0:1.0.0",&(G_Smartmeter.etimestamp) ); 
  P1_parse_item(G_P1buffer,pos,&pos,"0-0:96.1.1",&(G_Smartmeter.eserial) ); 
  P1_parse_item(G_P1buffer,pos,&pos,"1-0:1.8.1",&(G_Smartmeter.eusage1) ); 
  P1_parse_item(G_P1buffer,pos,&pos,"1-0:2.8.1",&(G_Smartmeter.esupply1) ); 
  P1_parse_item(G_P1buffer,pos,&pos,"1-0:1.8.2",&(G_Smartmeter.eusage2) );
  P1_parse_item(G_P1buffer,pos,&pos,"1-0:2.8.2",&(G_Smartmeter.esupply2) ); 
  P1_parse_item(G_P1buffer,pos,&pos,"1-0:1.7.0",&(G_Smartmeter.eactualuse) );
  P1_parse_item(G_P1buffer,pos,&pos,"1-0:2.7.0",&(G_Smartmeter.eactualsupply) );
  P1_parse_item(G_P1buffer,pos,&pos,"0-0:96.7.21",&(G_Smartmeter.epowerfailures) ); 
  P1_parse_item(G_P1buffer,pos,&pos,"1-0:31.7.0",&(G_Smartmeter.einstantcurrent) ); 
  P1_parse_item(G_P1buffer,pos,&pos,"0-1:96.1.0",&(G_Smartmeter.gserial) ); 
  P1_parse_item(G_P1buffer,pos,&pos,"0-1:24.2.1",&(G_Smartmeter.gtimestamp) );  // EXCEPTION WITH DOUBLE INFO
  G_Smartmeter.gusage="";G_Smartmeter.gusage.concat(G_Smartmeter.gtimestamp.substring(13,G_Smartmeter.gtimestamp.length()) );
  G_Smartmeter.gtimestamp.remove(13,G_Smartmeter.gtimestamp.length()-13);
#if DEBUG_XX
    Serial.print("\nTime: ");Serial.println(G_Smartmeter.etimestamp);
    Serial.print("Header: ");Serial.println(G_Smartmeter.header);
    Serial.print("e-Serial: ");Serial.println(G_Smartmeter.eserial);
    Serial.print("ActualUse: ");Serial.println(G_Smartmeter.eactualuse);
    Serial.print("e-Usage1: ");Serial.println(G_Smartmeter.eusage1);
    Serial.print("e-Usage2: ");Serial.println(G_Smartmeter.eusage2);
    Serial.print("Failures: ");Serial.println(G_Smartmeter.epowerfailures);
    Serial.print("GasSerial: ");Serial.println(G_Smartmeter.gserial);
    Serial.print("GasTimestamp: ");Serial.println(G_Smartmeter.gtimestamp);
    Serial.print("GasUsage: ");Serial.println(G_Smartmeter.gusage);
#endif
    return(1);
    }
else return(0);
} // end P1 functiuon


// Check Serial P1 and set time of the global RTC
unsigned long P1_checktime()
{
String h;  
P1_dump(G_P1buffer,&G_P1length,&G_P1crc);       // load buffer into Global Memory variables

if (G_P1valid )  // if P1 buffer is valid
  { 
  int t;
   P1_parse_item(G_P1buffer,0,&t,"0-0:1.0.0",&(G_Smartmeter.etimestamp));     // Time-content is 201210211313W = YYMMDDHHMMSSW
    t= 2000+ (G_Smartmeter.etimestamp[0]-48)*10 + G_Smartmeter.etimestamp[1]-48;
    G_rtc.setYear(t);
    t= (G_Smartmeter.etimestamp[2]-48)*10 + G_Smartmeter.etimestamp[3]-48;
    G_rtc.setMonth(t);
    t= (G_Smartmeter.etimestamp[4]-48)*10 + G_Smartmeter.etimestamp[5]-48;
    G_rtc.setDay(t);
    t= (G_Smartmeter.etimestamp[6]-48)*10 + G_Smartmeter.etimestamp[7]-48;
    G_rtc.setHours(t);
    t= (G_Smartmeter.etimestamp[8]-48)*10 + G_Smartmeter.etimestamp[9]-48;
    G_rtc.setMinutes(t);
    t= (G_Smartmeter.etimestamp[10]-48)*10 + G_Smartmeter.etimestamp[11]-48;
    G_rtc.setSeconds(t);
#if DEBUG_X
    Serial.println("* Time set");
#endif  
   }

return(G_rtc.getEpoch()); // return Epoch time old or new
     
}

// Parse Start item in buffer and return content "[header]([item])", start parse at position from, post back last position
boolean P1_parse_item(char*buff,int from,int *last,String header,String *item)
{
String telegramLine = ""; 
char c;
int ct,pos=from;
boolean line_complete=false;
boolean  message_error = false;
while( !line_complete && !message_error && G_P1valid)
  {
  if (buff[pos]!=0) 
    {
    c= buff[pos++]; //Serial.print(c);
    telegramLine+=c;
    if (telegramLine.endsWith(header)) 
        { //Serial.print("Item found"); 
          telegramLine = "";
          pos++; // read the "("
          line_complete=false;
          while ( !line_complete )   
                {
                  if(buff[pos]==0){message_error=true; break; } //escape when end of buffer is reached
                  c=buff[pos++]; 
                  if(c!=')') telegramLine+=c; 
                  else line_complete=true;
                  }
#if DEBUG_X
           Serial.print("* Parsed item [");Serial.print(telegramLine);Serial.println("]");
#endif
        } // end found item-header  

        }  
    else  message_error=true; // end of buffer reached
       
    if (header.endsWith("0-1:24.2.1") && line_complete==true) // EXCEPTION FOR GAS INFO: Double info (xxx)(yyyy)
    {
          pos++; // read the 2nd "("
          line_complete=false;
          while ( !line_complete )   
                {
                 if (Serial1.available())
                  { if(buff[pos]==0){message_error=true; break; } //escape when end of buffer is reached
                  c=buff[pos++];
                  if(c!=')') telegramLine+=c; 
                  else line_complete=true;}
                  }
    }    
    if (c == '\r') {telegramLine = "";}  // end of line
    if (c == '!')  {message_error=true; } // end of Telegram, no item found

  }
if (message_error == false && G_P1valid)
{
 *item = "";
(*item).concat(telegramLine);  // write item date
*last=pos-1;                   // write back last position
return (true);
}
else return(false);
}


// Parse Start of Serial1 stream and dump data into buffer, return length -
boolean P1_dump(char *b,int *l, unsigned int *p_crc)
{
char c,*buffer,bcrc[4];
long t=0,timer;
// Parsing Flags
boolean message_start=false;
boolean message_end=false;
boolean  message_error = false;
G_P1error="";
// initialise counter and string buffer
buffer=b;
timer=G_rtc.getEpoch();
delay(1000); // short delay for repititive reads
digitalWrite(P1_en_port, HIGH); Mkrled(true);
delay(1000); // short delay for repititive reads
Serial1.flush();
while (!message_end && !message_error) // find message end
  {
  if (G_rtc.getEpoch()-timer > 25000) {message_error=true; G_P1error="Time out";break;} //takes too lonm > 25s, should be in a message every 10 seconds
  if (Serial1.available()) 
    {
    c= Serial1.read(); 
    if (c == '/')  { message_start=true; break;}  //found the end
    }
  }
if(!message_error)
  {
#if DEBUG_X
  Serial.print("* Dump P1, found start, parsing, ");
#endif
  message_end=false;message_error=false;t=0;buffer[t++]='/'; //Serial.print(c);
  while (!message_end && !message_error) // find message end or error
    {
      if (Serial1.available()) 
        {
        c=Serial1.read(); //Serial.print(c);
        buffer[t++]=c;                             // fill buffer, calcualte crc16
        if (t>MAXBUFFER ){message_error=true; G_P1error="Max buffer"; break;}       // check max buffer
        if (c == '!') { message_end=true; break;}  //check end of telegrtam '!' 
        }
    } // while end
   buffer[t]=0; *l=t-1;        // close buffer, set length  
   P1_bufcrc16(buffer, p_crc);
   //read CRC
           while(! Serial1.available()) ; // wait for next char
           bcrc[0]=Serial1.read(); // read the CRC
           while(! Serial1.available()) ; // wait for next char
           bcrc[1]=Serial1.read(); // read the CRC
           while(! Serial1.available()) ; // wait for next char
           bcrc[2]=Serial1.read(); // read the CRC
           while(! Serial1.available()) ; // wait for next char
           bcrc[3]=Serial1.read(); // read the CRC
           bcrc[4]=0;
           if(*p_crc == hexstringtoint(bcrc) ) {G_P1valid=true;}
           else {message_error=true;G_P1error="CRC error";G_P1valid=false;}
#if DEBUG_X
Serial.print("Size=[");Serial.print(*l);Serial.print("], crc=[");Serial.print(bcrc);Serial.print("]");
Serial.print("vs CRC=[");Serial.print(*p_crc,HEX);Serial.print("], = ");Serial.println(G_P1valid);
#endif   
  }

if(message_error)
    {
    buffer[0]=0; *l=0;G_P1valid=false;
#if DEBUG_X
    Serial.print("* P1 Telegram is invalid: ");Serial.println(G_P1error);
#endif   
    }
else
   {
    G_P1error="";G_P1valid=true;  
    }
digitalWrite(P1_en_port, LOW); Mkrled(false);
return (!message_error);  // 1 = ok, 0 = error
}


// CRC16 calculator : ca lcrc of whole buffer (end character = zero)
void P1_bufcrc16(char *b,unsigned int *crc)
{
  unsigned int t,i,tmp;
  tmp=0;t=0;
  while(b[t]!=0)
  {
  tmp ^=  (unsigned int) b[t];
  for (i = 0; i < 8; i++)
    {
    if ((tmp & 0x0001) == 0x0001) tmp = (tmp >> 1) ^ 0xA001;
    else tmp >>= 1;
    }
   t++;
  }
  *crc = tmp;    
}

// hex String to integer, assumes end of sting is zero (0) and max converstion is 4 bytes (8 characters)
unsigned int hexstringtoint(char *b)
{
 int i,l,m=1;
 unsigned int t=0;
 for(i=0;i<8;++i) if(b[i]==0) {l=i-1;i=8;} // caclualte length of hex string
 for(i=l;i>=0;--i){
    if(b[i]>=65 && b[i]<=70 ) t=t+(b[i]-55)*m;
    if(b[i]>=48 && b[i]<=57 ) t=t+(b[i]-48)*m;
  m=m*16;
  }
#if DEBUG_X
//Serial.print("* Hex$_to_Int Input [");Serial.print(b);Serial.print("], Output=[");Serial.print(t,HEX);Serial.println("]");
#endif  
return t;  
}


boolean P1_convertunit(String s1, float *val, String *un)
{
String tt;
byte t,l=0;  
for(t=1;t<128;++t)
  {
   if (s1[t] == '*') l=t;  // calculate index for *
  }
if(l==0) return(0);
tt="";
tt.concat(  s1.substring(0,l) );
*(val)=tt.toFloat();
*un="";
(*un).concat(  s1.substring(l+1,s1.length()) );
return(1);
}


boolean P1_buildpayload(String *payloadstring)
{
String s2,s;
float f;
if(G_P1valid) // check valid payload;
{
  s="{";
  if(G_P1valid == true) s+="\"p1\":true,";
  else s+="\"p1\":false,";
  s+="\"header\":\"";s+=G_Smartmeter.header;s+="\",";
  s+="\"etime\":\"";s+=G_Smartmeter.etimestamp;s+="\",";
  s+="\"eserial\":\"";s+=G_Smartmeter.eserial;s+="\",";
    P1_convertunit(G_Smartmeter.eusage1, &f, &s2);
  s+="\"eusageunit\":\"";s+=s2;s+="\",";
  s+="\"eusage1\":";s+=f;s+=",";
    P1_convertunit(G_Smartmeter.esupply1, &f, &s2);
  s+="\"esupply1\":";s+=f;s+=",";
     P1_convertunit(G_Smartmeter.eusage2, &f, &s2);
  s+="\"eusage2\":";s+=f;s+=",";
    P1_convertunit(G_Smartmeter.esupply2, &f, &s2);
  s+="\"esupply2\":";s+=f;s+=",";
    P1_convertunit(G_Smartmeter.eactualuse, &f, &s2);
  s+="\"eactualunit\":\"";s+=s2;s+="\",";
  s+="\"eactualuse\":";s+=f;s+=",";
    P1_convertunit(G_Smartmeter.eactualsupply, &f, &s2);
  s+="\"eactualsupply\":";s+=f;s+=",";
  s+="\"efailures\":";s+=G_Smartmeter.epowerfailures.toInt();s+=",";
    P1_convertunit(G_Smartmeter.einstantcurrent, &f, &s2);  
  s+="\"eiunit\":\"";s+=s2;s+="\",";
  s+="\"eicurrent\":";s+=f,3;s+=",";
  s+="\"gserial\":\"";s+=G_Smartmeter.gserial;s+="\",";
  s+="\"gtime\":\"";s+=G_Smartmeter.gtimestamp;s+="\",";
    P1_convertunit(G_Smartmeter.gusage, &f, &s2);  
  s+="\"gunit\":\"";s+=s2;s+="\",";  
  s+="\"gusage\":";s+=f;s+="";  
  s+="}";
  *payloadstring = "";        // set payload to empty
  (*payloadstring).concat(s); // copy build string into payload
  return(1);
}
else { *payloadstring = "";return(0);} // no valid payload
}

// HTTP /POST function with /GET readback for mode changes
boolean P1_Poststructure(char *suburl)
{
String s;
char c;
long unsigned timer;
G_Myip = WiFi.localIP();
P1_buildpayload(&s);
WiFiClient client1,client2;
  if (client1.connect(G_Setting.hostip,G_Setting.hostport)){
#if DEBUG_X
      Serial.print("* Connected to server ");Serial.print(G_Setting.hostip);Serial.print(":");Serial.println(G_Setting.hostport);
#endif
      client1.print("POST /");client1.print(suburl);client1.println(" HTTP/1.1");
      client2.print("Host: http://"); client2.println(G_Myip);
//      client1.println("Content-type: application/json"); // does not work at Node-red, fucks up
      client1.print("content-length: ");client1.println(s.length());
      client1.println("Connection: close");
      client1.println();
      client1.println(s);
      client1.println();
      Serial.println("* HTTP-Post DataSend");  
      client1.stop();
      delay(1000); // wait 1 second
  if (client2.connect(G_Setting.hostip,G_Setting.hostport)){
      client2.print("GET /");client2.print(G_backdoor);client2.println(" HTTP/1.1");
      client2.print("Host: http://"); client2.println(G_Myip);
      client2.println("Connection: close");
      client2.println();
      Serial.print("* HTTP-Get DataSend for ");Serial.println(G_backdoor);
      s="";
      timer =G_rtc.getEpoch();
      while (client2.connected()) {           // loop while the client's connected
      if (client2.available()) {             // if there's bytes to read from the client,
         c = client2.read();  // Serial.print(c); 
         s=s+c; 
         if(s.endsWith("{\"MODE\":") ) // parse JSON MODE
             {
             c = client2.read(); // Serial.print(c);
             if (c=='0' || c=='9' || c=='1') { // only mode 0 of mode 9 accepter (Server or Test)
                G_Setting.mode = (byte) c-48;
#if DEBUG_X    
      Serial.print("* HTTP-Get found new mode ");Serial.println(G_Setting.mode);
#endif          
                }
             break;
             }
        if(s.length()>512) break;
        }
      if (G_rtc.getEpoch() - timer > 25000) break;  // break after 5 sec no resonse
      }
  }        
      client2.stop();
      return(1);   
  }else{
#if DEBUG_X    
      Serial.println("* Connection to server failed");
#endif    
     return(0);  
  }

}


// MKR1010 RGB Led via NINA module
void NINAled(char r, char g, char b)
{
 if (!G_Noled) 
  {
  // Set LED pin modes to output
  WiFiDrv::pinMode(25, OUTPUT);
  WiFiDrv::pinMode(26, OUTPUT);
  WiFiDrv::pinMode(27, OUTPUT);
  
  // Set all LED color 
  WiFiDrv::analogWrite(25, g%128);    // GREEN
  WiFiDrv::analogWrite(26, r%128);    // RED
  WiFiDrv::analogWrite(27, b%128);    // BLUE
  }
}

void Mkrled(boolean s)
{
  if(s==true) digitalWrite(LED_BUILTIN, HIGH);
  else digitalWrite(LED_BUILTIN, LOW);
}

Schematics

Schematics
P1 schematic atdhphdex6

Comments

Similar projects you might like

Interfacing Arduino MKR or ESP via MQTT - Node-RED 101

Project in progress by Officine Innesto

  • 13,668 views
  • 3 comments
  • 14 respects

Sonos Hub

Project showcase by JayV

  • 1,916 views
  • 2 comments
  • 2 respects

Monitor Your Energy Bill via Modbus, MKR WiFi 1010 and RS485

Project tutorial by 3 developers

  • 52,893 views
  • 22 comments
  • 102 respects

Home Automation With Arduino 101 Using Bluetooth Low Energy

Project tutorial by naveen manwani

  • 3,454 views
  • 0 comments
  • 8 respects

How to Build a DIY Arduino-Based Smart Home Hub with 1Sheeld

Project tutorial by amrmostaafaa

  • 39,502 views
  • 1 comment
  • 51 respects

Energy Alerter: a Power Monitoring System

Project tutorial by Giuseppe Torino

  • 6,053 views
  • 1 comment
  • 8 respects
Add projectSign up / Login