Hardware components | ||||||
![]() |
| × | 1 | |||
![]() |
| × | 1 | |||
Software apps and online services | ||||||
![]() |
| |||||
![]() |
|
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:
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.
InverterData 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)
CodingArduino 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.
CloudConnectData 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-RedThe 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:
- Add MQTT server via PubSub
- Add Battery monitoring
- Measure low power usage
[{"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 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);
}
Comments
Please log in or sign up to comment.