#include "arduino_secrets.h"
// ArduinoIoTCloud - Version: 1.11.1
#include <ArduinoIoTCloud.h>
// ArduinoHttpClient - Version: Latest
#include <ArduinoHttpClient.h>
/*
Sketch generated by the Arduino IoT Cloud Thing "Untitled 3"
https://create.arduino.cc/cloud/things/5ed29724-742e-4cbc-a713-7482c74632c6
Arduino IoT Cloud Variables description
The following variables are automatically generated and updated when changes are made to the Thing
- No variables have been created, add cloud variables on the Thing Setup page
to see them declared here
Variables which are marked as READ/WRITE in the Cloud Thing will also have functions
which are called when their values are changed from the Dashboard.
These functions are generated with the Thing and added at the end of this sketch.
*/
// HttpClient - Version: Latest
/*
LED_D0: STATUS 1
LED_D1: STATUS 2
LED_D2: STATUS 3
LED_D3: STATUS 4
LED_RESET: LED above the reset button
LED_USER: LED above the user button
*/
#include <b64.h>
// ArduinoRS485 - Version: Latest
#include <ArduinoRS485.h>
// ArduinoModbus - Version: Latest
#include <ArduinoModbus.h>
// Ethernet - Version: Latest
#include <Ethernet.h>
#include <stdlib.h>
#include <stdio.h>
#include <SPI.h>
#include "thingProperties.h"
int i;
int intTemperature, intEnergy;// nur zum Testen
constexpr auto baudrate { 9600 };
// Calculate preDelay and postDelay in microseconds as per Modbus RTU Specification
// MODBUS over serial line specification and implementation guide V1.02
// Paragraph 2.5.1.1 MODBUS Message RTU Framing
// https://modbus.org/docs/Modbus_over_serial_line_V1_02.pdf
constexpr auto bitduration { 1.f / baudrate };
constexpr auto preDelayBR { bitduration * 9.6f * 3.5f * 1e6 };
constexpr auto postDelayBR { bitduration * 9.6f * 3.5f * 1e6 };
unsigned long longLastMillis10=0;
unsigned long longLastMillis1=0;
unsigned long longLastMillis5min=0;
unsigned long longLastMillis60min=0;
int intHttpErr=0;
int intConfigSuccess=0;
int intStatusCode=0;
int intHTTPRetry=0;
const int INT_MAX_METER = 25;
const int INT_MAX_IO = 8;
String token="ttttttt";
String password="xxxxxxx";
String strSerial = "H0523A01000001";
struct meter {
int intMID;
int intHWtype;
String strHashCode;
String strMeterbezeichnung;
int intValue1;
int intValue2;
int intValue3;
int intValue4;
};
meter allMeter[INT_MAX_METER];
int intCounterInProject = 0;
//meter allMeter[];
struct building {
String strObjName;
String strStrasseNr;
String strPLZ;
String strOrt;
int intBID;
};
building thisBuilding;
struct IOChannel{
int intChNumber;
String strIOHash;
int intValue;
};
IOChannel allIOChannel[INT_MAX_IO];
int intIOInProject =0;
//IOChannel allIOChannel[];
struct MeterHW{
int intMeterType;
int intNbModbusValuesToRead;
int intValue1Length; // Anzahl Modbus Register
int intValue2Length;
int intValue3Length;
int intValue4Length;
};
MeterHW MeterHWDef[] = {
{0,0,0,0,0,0}, //No Hardware
{10,1,2,0,0,0}, //Eltako DSZ12DM
{20,4,2,2,1,1}, //Techem C4s (4 Werte, 2 Register kWh 2 Reg. Volumen, 1 Temp VL 1 RL)
{30,4,2,2,1,1}, //Techem Ultra S3
{40,1,2,0,0,0}, //Techem NZR Modularis KWZ Modul
{50,1,2,0,0,0}, //ABB B23 113-100 - muss noch getestet werden, es könnten auch 3 Modbus Register sein - schauen was das Seneca Teil dazu sagt ###############################
{60,4,2,2,1,1}, //Salzburg AG UH50
{70,0,0,0,0,0} //AutoWMZ - wird hier nicht benötigt
};
bool boolDIcurrentState[INT_MAX_IO];
bool boolDIprevState[INT_MAX_IO];
// Enter a MAC address for your controller below.
// Newer Ethernet shields have a MAC address printed on a sticker on the shield
// The IP address will be dependent on your local network:
byte mac[] = {
0xA8, 0x61, 0x0A, 0x50, 0x06, 0xA2 //little Hermes02 A8-61-0A-50-06-A2
};
//IPAddress ip(192, 168, 1, 200); //enter fixed IP of Opta here
EthernetClient ethClient;
EthernetClient ethClient01;
EthernetClient ethClient02;
HttpClient httpClient(ethClient,"www.sourceburg.at");
HttpClient httpClientIO(ethClient01,"www.sourceburg.at");
HttpClient httpClientHeartBeat(ethClient02,"www.sourceburg.at");
void setup() {
// Initialize serial and wait for port to open:
Serial.begin(9600);
// This delay gives the chance to wait for a Serial Monitor without blocking if none is found
delay(1500);
// Defined in thingProperties.h
initProperties();
// Connect to Arduino IoT Cloud
ArduinoCloud.begin(ArduinoIoTPreferredConnection);
/*
The following function allows you to obtain more information
related to the state of network and IoT Cloud connection and errors
the higher number the more granular information you’ll get.
The default is 0 (only errors).
Maximum is 4
*/
setDebugMessageLevel(2);
ArduinoCloud.printDebugInfo();
RS485.setDelays(preDelayBR, postDelayBR);
if (!ModbusRTUClient.begin(9600)) {
Serial.println("Failed to start Modbus RTU Client!");
}
while (Ethernet.begin(mac) != 1)
{
Serial.println("Error getting IP address via DHCP, trying again...");
delay(15000);
}
//get Config - Anzahl DI, Anzahl/Type Mbus
intConfigSuccess = F_GetConfig();
while (intConfigSuccess != 1){
Serial.println("Error getting config from Hermes-Cloud, trying again...");
delay(15000);
intConfigSuccess = F_GetConfig();
}
Serial.print("Success getting Config. Zähler: ");
for(i=0; i<25; i++){
if (allMeter[i].intHWtype > 0) intCounterInProject +=1;
}
Serial.println(intCounterInProject);
//Serial.print(sizeof(allMeter)/sizeof(allMeter[0]));
Serial.print("IO Channel: ");
for(i=0; i<8; i++){
if (allIOChannel[i].intChNumber > 0) intIOInProject +=1;
}
Serial.println(intIOInProject);
//Serial.println(sizeof(allIOChannel)/sizeof(allIOChannel[0]));
// Initialize Opta LEDs
pinMode(LED_D0, OUTPUT); //---arduino Cloud connected
pinMode(LED_D1, OUTPUT);
pinMode(LED_D2, OUTPUT);
pinMode(LED_D3, OUTPUT);
// Initialize Relays outputs
pinMode(D0, OUTPUT);
pinMode(D1, OUTPUT);
pinMode(D2, OUTPUT);
pinMode(D3, OUTPUT);
// Initialize DI
pinMode(A0, INPUT);
pinMode(A1, INPUT);
pinMode(A2, INPUT);
pinMode(A3, INPUT);
pinMode(A4, INPUT);
pinMode(A5, INPUT);
pinMode(A6, INPUT);
pinMode(A7, INPUT);
}
void loop() {
ArduinoCloud.update();
//#############################################################################
//######### Überwachen der Digitaleingänge ####################################
//######### und Cloud connection ####################################
//#############################################################################
if ((millis()-longLastMillis10)>=5000){
longLastMillis10=millis();
if (ArduinoCloud.connected()){
digitalWrite(LED_D0, HIGH);
}
else{
digitalWrite(LED_D0, LOW);
ArduinoCloud.begin(ArduinoIoTPreferredConnection); // Test ob dann die Verbindung wiederhergestellt wird
}
boolDIcurrentState[0] = digitalRead(A0);
boolDIcurrentState[1] = digitalRead(A1);
boolDIcurrentState[2] = digitalRead(A2);
boolDIcurrentState[3] = digitalRead(A3);
boolDIcurrentState[4] = digitalRead(A4);
boolDIcurrentState[5] = digitalRead(A5);
boolDIcurrentState[6] = digitalRead(A6);
boolDIcurrentState[7] = digitalRead(A7);
for (i=0;i<intIOInProject;i++){
if (boolDIcurrentState[i] != boolDIprevState[i]){
allIOChannel[i].intValue = boolDIcurrentState[i];
String postData = "her_bid=" + String(thisBuilding.intBID) + "&her_iohash=" + allIOChannel[i].strIOHash + "&her_value=" + String(allIOChannel[i].intValue);
String strPayload = "";
intStatusCode=400;
intHTTPRetry=0;
while (((intStatusCode>299)||(intStatusCode<200)) && (intHTTPRetry<5))
{
httpClientIO.beginRequest();
httpClientIO.post("/hermes/inject_io.php","application/x-www-form-urlencoded",postData);
httpClientIO.endRequest();
// Ausgabe der Serverantwort
intStatusCode = httpClientIO.responseStatusCode();
Serial.print("Status code: ");
Serial.println(intStatusCode);
if (intHTTPRetry>0) delay(2000);
intHTTPRetry+=1;
}
strPayload = httpClientIO.responseBody();
httpClientIO.stop();
//strPayload = httpClientIO.responseBody();
Serial.println("Server response: " + strPayload);
}
}
for (i=0;i<INT_MAX_IO;i++){
boolDIprevState[i] = boolDIcurrentState[i];
}
}
/*
struct IOChannel{
int intChNumber;
String strIOHash;
int intValue;
};
IOChannel allIOChannel[INT_MAX_IO];
int intIOInProject =0;
*/
//#############################################################################
//######### Auslesen der Zählerwerte - Modbus Register alle 10s ###############
//#############################################################################
if ((millis()-longLastMillis1)>=10000){
longLastMillis1=millis();
F_ReadAllMeter();
for (i=0;i<intCounterInProject;i++){
Serial.print("Meter: " + String(i+1) + "Value1: ");
Serial.println(allMeter[i].intValue1);
Serial.print("Meter: " + String(i+1) + "Value2: ");
Serial.println(allMeter[i].intValue2);
Serial.print("Meter: " + String(i+1) + "Value3: ");
Serial.println(allMeter[i].intValue3);
Serial.print("Meter: " + String(i+1) + "Value4: ");
Serial.println(allMeter[i].intValue4);
}
}
//#############################################################################
//######### Schicke Heartbeat alle 5 min ############# ########################
//#############################################################################
if ((millis()-longLastMillis5min)>=300000){
longLastMillis5min=millis();
F_Heartbeat();
}
//#############################################################################
//######### Schicke Zählerstände an Hermes Cloud alle 60 min ###################
//##############################################################################
if ((millis()-longLastMillis60min)>=3600000){
longLastMillis60min=millis();
F_SendMeterData();
}
// DI Überwachen - bei Änderung push an Hermes Server
}
void F_SendMeterData(){
for (i=0;i<intCounterInProject;i++){
String postData="";
postData = "her_mid=" + String(allMeter[i].intMID) + "&her_hash=" + allMeter[i].strHashCode
+ "&her_value=" + String(allMeter[i].intValue1)
+ "&her_value_add_1=" + String(allMeter[i].intValue2)
+ "&her_value_add_2=" + String(allMeter[i].intValue3)
+ "&her_value_add_3=" + String(allMeter[i].intValue4);
String strPayload = "";
httpClient.beginRequest();
httpClient.post("/hermes/inject.php","application/x-www-form-urlencoded",postData);
httpClient.endRequest();
// Ausgabe der Serverantwort
strPayload = httpClient.responseBody();
Serial.println("Server response: " + strPayload);
httpClient.stop();
}
}
void F_Heartbeat(){
String postData = "bid=" + String(thisBuilding.intBID) + "&sernum=" + strSerial;
String strPayload = "";
httpClientHeartBeat.beginRequest();
httpClientHeartBeat.post("/hermes/heartbeat.php","application/x-www-form-urlencoded",postData);
httpClientHeartBeat.endRequest();
// Ausgabe der Serverantwort
strPayload = httpClientHeartBeat.responseBody();
Serial.println("Server response: " + strPayload);
httpClientHeartBeat.stop();
}
void F_ReadAllMeter(){
for (i=0;i<intCounterInProject;i++){
switch (allMeter[i].intHWtype){
case 0:
break;
case 10:
allMeter[i].intValue1 = F_ReadUINT32 (1,10+i*20);
break;
case 20:
allMeter[i].intValue1 = F_ReadUINT32 (1,10+i*20);
allMeter[i].intValue2 = F_ReadUINT32 (1,12+i*20);
allMeter[i].intValue3 = F_ReadUINT16 (1,14+i*20);
allMeter[i].intValue4 = F_ReadUINT16 (1,16+i*20);
break;
case 30:
allMeter[i].intValue1 = F_ReadUINT32 (1,10+i*20);
allMeter[i].intValue2 = F_ReadUINT32 (1,12+i*20);
allMeter[i].intValue3 = F_ReadUINT16 (1,14+i*20);
allMeter[i].intValue4 = F_ReadUINT16 (1,16+i*20);
break;
case 40:
allMeter[i].intValue1 = F_ReadUINT32 (1,10+i*20);
break;
case 50:
allMeter[i].intValue1 = F_ReadUINT32 (1,10+i*20);
break;
case 60:
allMeter[i].intValue1 = F_ReadUINT32 (1,10+i*20);
allMeter[i].intValue2 = F_ReadUINT32 (1,12+i*20);
allMeter[i].intValue3 = F_ReadUINT16 (1,14+i*20);
allMeter[i].intValue4 = F_ReadUINT16 (1,16+i*20);
break;
case 70:
break;
}
}
}
//--------------- Wandelt hex in dec --------------------------
byte F_Hex2Dec (byte byteIN){
int inthelplow, inthelphigh;
inthelplow=byteIN & 0x0F;
inthelphigh=byteIN>>4;
return inthelplow+inthelphigh*10;
}
//--------------- get Config Function -------------------------
int F_GetConfig(){
String postData = "token=" + token + "&password=" + password;
String strPayload = "";
String strLine = "";
int intPos = 0;
int intLineType = 0;
int intCounterCount = -1;
int intIOCount = -1;
httpClient.beginRequest();
httpClient.post("/hermes/getconfig.php","application/x-www-form-urlencoded",postData);
httpClient.endRequest();
// Ausgabe der Serverantwort
strPayload = httpClient.responseBody();
Serial.println("Server response: " + strPayload);
httpClient.stop();
while (strPayload.indexOf('\r\n', intPos) != -1) {
strLine = strPayload.substring(intPos, strPayload.indexOf('\r\n', intPos));
Serial.println("Zeile erhalten: " + strLine);
// Felder verarbeiten
int intFieldPos = 0;
String strField = "";
int intFieldCount = 0; // für die weiterverarbeitung in die struct
while (intFieldPos < strLine.length()) {
int intnextFieldPos = strLine.indexOf(';', intFieldPos);
if (intnextFieldPos == -1) {
intnextFieldPos = strLine.length();
}
strField = strLine.substring(intFieldPos, intnextFieldPos);
Serial.println("Feld erhalten: " + strField);
intFieldPos = intnextFieldPos + 1;
//Wir checken welches Feld daherkommt
//0020 = Gebäude - 5 Nutzfelder
//0030 = Zähler - 6 Nutzfelder
//0040 = IO Störmeldung - 4 Nutzfelder
if (strField == "0020"){
intLineType = 20;
}
else if (strField == "0030"){
intLineType = 30;
intCounterCount += 1;
}
else if (strField == "0040"){
intLineType = 40;
intIOCount += 1;
}
switch (intLineType) {
case 20:
// statements
// Gebäudedaten befüllen
switch (intFieldCount){
case 1:
thisBuilding.strObjName = strField;
break;
case 2:
thisBuilding.strStrasseNr = strField;
break;
case 3:
thisBuilding.strPLZ = strField;
break;
case 4:
thisBuilding.strOrt = strField;
break;
case 5:
thisBuilding.intBID = strField.toInt();
break;
}
intFieldCount += 1;
break;
case 30:
switch (intFieldCount){
case 1:
allMeter[intCounterCount].intMID = strField.toInt();
break;
case 2:
allMeter[intCounterCount].strHashCode = strField;
break;
case 3:
allMeter[intCounterCount].strMeterbezeichnung = strField;
break;
case 5:
allMeter[intCounterCount].intHWtype = strField.toInt();
break;
}
intFieldCount += 1;
break;
case 40:
// wie legen wir die Störmeldungen an ?? auch struct array
switch (intFieldCount){
case 1:
allIOChannel[intIOCount].intChNumber = strField.toInt();
break;
case 3:
allIOChannel[intIOCount].strIOHash = strField;
break;
}
intFieldCount += 1;
break;
}
}
intPos = strPayload.indexOf('\r\n', intPos) + 1;
}
return 1;
}
//####################################################################################################
//########## Funktion liest einen Uint32 Wert via Modbus ########################################
//####################################################################################################
unsigned int F_ReadUINT32 (int intSlaveID, int intStartReg){
unsigned int i, uintReadValue;
word wordtemp=0;
byte bytetemp=0;
//--------- read --------------
if (!ModbusRTUClient.requestFrom(intSlaveID, HOLDING_REGISTERS, intStartReg, 2)) {
Serial.print("failed to read registers! ");
Serial.println(ModbusRTUClient.lastError());
}
else {
wordtemp=ModbusRTUClient.read();
bytetemp=wordtemp;
uintReadValue=F_Hex2Dec(bytetemp)*10000;
bytetemp=wordtemp>>8;
uintReadValue+=F_Hex2Dec(bytetemp)*1000000;
wordtemp=0;
bytetemp=0;
wordtemp=ModbusRTUClient.read();
bytetemp=wordtemp;
uintReadValue+=F_Hex2Dec(bytetemp);
bytetemp=wordtemp>>8;
uintReadValue+=F_Hex2Dec(bytetemp)*100;
}
return uintReadValue;
}
int F_ReadUINT16 (int intSlaveID, int intStartReg){
word wordtemp=0;
byte bytetemp=0;
int i, intReadValue;
if (!ModbusRTUClient.requestFrom(intSlaveID, HOLDING_REGISTERS, intStartReg, 1)) {
Serial.print("failed to read registers! ");
Serial.println(ModbusRTUClient.lastError());
}
else {
wordtemp=ModbusRTUClient.read();
bytetemp=wordtemp;
intReadValue=F_Hex2Dec(bytetemp);
bytetemp=wordtemp>>8;
intReadValue+=F_Hex2Dec(bytetemp)*100;
}
return intReadValue;
}