added CRC check, README update, bug fixes,

This commit is contained in:
Chester 2023-11-26 19:05:51 +01:00
parent b076743f54
commit ff61047706
13 changed files with 769 additions and 434 deletions

View file

@ -1,115 +0,0 @@
/*
Copyright (C) 2020 chester4444@wolke7.net
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "WMbusFrame.h"
WMBusFrame::WMBusFrame()
{
aes128.setKey(key, sizeof(key));
}
void WMBusFrame::check()
{
// check meterId
for (uint8_t i = 0; i< 4; i++)
{
if (meterId[i] != payload[6-i])
{
isValid = false;
return;
}
}
// TBD: check crc
isValid = true;
}
void WMBusFrame::printMeterInfo(uint8_t *data, size_t len)
{
// init positions for compact frame
int pos_tt = 9; // total consumption
int pos_tg = 13; // target consumption
int pos_ic = 7; // info codes
int pos_ft = 17; // flow temp
int pos_at = 18; // ambient temp
if (data[2] == 0x78) // long frame
{
// overwrite it with long frame positions
pos_tt = 10;
pos_tg = 16;
pos_ic = 6;
pos_ft = 22;
pos_at = 25;
}
char total[10];
uint32_t tt = data[pos_tt]
+ (data[pos_tt+1] << 8)
+ (data[pos_tt+2] << 16)
+ (data[pos_tt+3] << 24);
snprintf(total, sizeof(total), "%d.%03d", tt/1000, tt%1000 );
Serial.printf("total: %s m%c - ", total, 179);
char target[10];
uint32_t tg = data[pos_tg]
+ (data[pos_tg+1] << 8)
+ (data[pos_tg+2] << 16)
+ (data[pos_tg+3] << 24);
snprintf(target, sizeof(target), "%d.%03d", tg/1000, tg%1000 );
Serial.printf("target: %s m%c - ", target, 179);
char flow_temp[3];
snprintf(flow_temp, sizeof(flow_temp), "%2d", data[pos_ft]);
Serial.printf("%s %cC - ", flow_temp, 176);
char ambient_temp[3];
snprintf(ambient_temp, sizeof(ambient_temp), "%2d", data[pos_at]);
Serial.printf("%s %cC\n\r", ambient_temp, 176);
}
void WMBusFrame::decode()
{
// check meterId, CRC
check();
if (!isValid) return;
uint8_t cipherLength = length - 2 - 16; // cipher starts at index 16, remove 2 crc bytes
memcpy(cipher, &payload[16], cipherLength);
memset(iv, 0, sizeof(iv)); // padding with 0
memcpy(iv, &payload[1], 8);
iv[8] = payload[10];
memcpy(&iv[9], &payload[12], 4);
aes128.setIV(iv, sizeof(iv));
aes128.decrypt(plaintext, (const uint8_t *) cipher, cipherLength);
/*
Serial.printf("C: ");
for (size_t i = 0; i < cipherLength; i++)
{
Serial.printf("%02X", cipher[i]);
}
Serial.println();
Serial.printf("P(%d): ", cipherLength);
for (size_t i = 0; i < cipherLength; i++)
{
Serial.printf("%02X", plaintext[i]);
}
Serial.println();
*/
printMeterInfo(plaintext, cipherLength);
}

View file

@ -13,12 +13,18 @@
*/
#include "WaterMeter.h"
#include "hwconfig.h"
WaterMeter::WaterMeter()
WaterMeter::WaterMeter(PubSubClient &mqtt)
: mqttClient (mqtt)
, mqttEnabled (false)
{
}
void WaterMeter::enableMqtt(bool enabled)
{
mqttEnabled = enabled;
}
// ChipSelect assert
inline void WaterMeter::selectCC1101(void)
{
@ -34,28 +40,29 @@ inline void WaterMeter::deselectCC1101(void)
// wait for MISO pulling down
inline void WaterMeter::waitMiso(void)
{
while(digitalRead(MISO) == HIGH);
while (digitalRead(MISO) == HIGH)
;
}
// write a single register of CC1101
void WaterMeter::writeReg(uint8_t regAddr, uint8_t value)
void WaterMeter::writeReg(uint8_t regAddr, uint8_t value)
{
selectCC1101(); // Select CC1101
waitMiso(); // Wait until MISO goes low
SPI.transfer(regAddr); // Send register address
SPI.transfer(value); // Send value
deselectCC1101(); // Deselect CC1101
selectCC1101();
waitMiso(); // Wait until MISO goes low
SPI.transfer(regAddr); // Send register address
SPI.transfer(value); // Send value
deselectCC1101();
}
// send a strobe command to CC1101
void WaterMeter::cmdStrobe(uint8_t cmd)
void WaterMeter::cmdStrobe(uint8_t cmd)
{
selectCC1101(); // Select CC1101
selectCC1101();
delayMicroseconds(5);
waitMiso(); // Wait until MISO goes low
SPI.transfer(cmd); // Send strobe command
waitMiso(); // Wait until MISO goes low
SPI.transfer(cmd); // Send strobe command
delayMicroseconds(5);
deselectCC1101(); // Deselect CC1101
deselectCC1101();
}
// read CC1101 register (status or configuration)
@ -64,74 +71,86 @@ uint8_t WaterMeter::readReg(uint8_t regAddr, uint8_t regType)
uint8_t addr, val;
addr = regAddr | regType;
selectCC1101(); // Select CC1101
waitMiso(); // Wait until MISO goes low
SPI.transfer(addr); // Send register address
val = SPI.transfer(0x00); // Read result
deselectCC1101(); // Deselect CC1101
selectCC1101();
waitMiso(); // Wait until MISO goes low
SPI.transfer(addr); // Send register address
val = SPI.transfer(0x00); // Read result
deselectCC1101();
return val;
}
//
void WaterMeter::readBurstReg(uint8_t * buffer, uint8_t regAddr, uint8_t len)
//
void WaterMeter::readBurstReg(uint8_t *buffer, uint8_t regAddr, uint8_t len)
{
uint8_t addr, i;
addr = regAddr | READ_BURST;
selectCC1101(); // Select CC1101
selectCC1101();
delayMicroseconds(5);
waitMiso(); // Wait until MISO goes low
SPI.transfer(addr); // Send register address
for(i=0 ; i<len ; i++)
buffer[i] = SPI.transfer(0x00); // Read result byte by byte
waitMiso(); // Wait until MISO goes low
SPI.transfer(addr); // Send register address
for (i = 0; i < len; i++)
buffer[i] = SPI.transfer(0x00); // Read result byte by byte
delayMicroseconds(2);
deselectCC1101(); // Deselect CC1101
deselectCC1101();
}
// power on reset
void WaterMeter::reset(void)
void WaterMeter::reset(void)
{
deselectCC1101(); // Deselect CC1101
deselectCC1101();
delayMicroseconds(3);
digitalWrite(MOSI, LOW);
digitalWrite(SCK, HIGH); // see CC1101 datasheet 11.3
digitalWrite(SCK, HIGH); // see CC1101 datasheet 11.3
selectCC1101(); // Select CC1101
selectCC1101();
delayMicroseconds(3);
deselectCC1101(); // Deselect CC1101
delayMicroseconds(45); // at least 40 us
deselectCC1101();
delayMicroseconds(45); // at least 40 us
selectCC1101(); // Select CC1101
selectCC1101();
waitMiso(); // Wait until MISO goes low
SPI.transfer(CC1101_SRES); // Send reset command strobe
waitMiso(); // Wait until MISO goes low
waitMiso(); // Wait until MISO goes low
SPI.transfer(CC1101_SRES); // Send reset command strobe
waitMiso(); // Wait until MISO goes low
deselectCC1101(); // Deselect CC1101
deselectCC1101();
}
// set IDLE state, flush FIFO and (re)start receiver
void WaterMeter::startReceiver(void)
{
cmdStrobe(CC1101_SIDLE); // Enter IDLE state
while (readReg(CC1101_MARCSTATE, CC1101_STATUS_REGISTER) != MARCSTATE_IDLE);
uint8_t regCount = 0;
cmdStrobe(CC1101_SIDLE); // Enter IDLE state
while (readReg(CC1101_MARCSTATE, CC1101_STATUS_REGISTER) != MARCSTATE_IDLE)
{
delay(1);
if (regCount++ > 100)
{
Serial.println("Enter idle state failed!\n");
restartRadio();
}
}
cmdStrobe(CC1101_SFRX); // flush receive queue
cmdStrobe(CC1101_SRX); // Enter RX state
while (readReg(CC1101_MARCSTATE, CC1101_STATUS_REGISTER) != MARCSTATE_RX);
cmdStrobe(CC1101_SFRX); // flush receive queue
delay(5);
regCount = 0;
cmdStrobe(CC1101_SRX); // Enter RX state
delay(10);
while (readReg(CC1101_MARCSTATE, CC1101_STATUS_REGISTER) != MARCSTATE_RX)
{
delay(1);
if (regCount++ > 100)
{
Serial.println("Enter RX state failed!\n");
restartRadio();
}
}
}
// initialize all the CC1101 registers
void WaterMeter::initializeRegisters(void)
void WaterMeter::initializeRegisters(void)
{
writeReg(CC1101_IOCFG2, CC1101_DEFVAL_IOCFG2);
writeReg(CC1101_IOCFG0, CC1101_DEFVAL_IOCFG0);
@ -173,56 +192,181 @@ void WaterMeter::initializeRegisters(void)
writeReg(CC1101_TEST0, CC1101_DEFVAL_TEST0);
}
volatile boolean packetAvailable = false;
void ICACHE_RAM_ATTR GD0_ISR(void);
// handle interrupt from CC1101 via GDO0
void GD0_ISR(void) {
IRAM_ATTR void WaterMeter::instanceCC1101Isr()
{
// set the flag that a package is available
packetAvailable = true;
}
// static ISR method, that calls the right instance
IRAM_ATTR void WaterMeter::cc1101Isr(void *p)
{
WaterMeter *ptr = (WaterMeter *)p;
ptr->instanceCC1101Isr();
}
// should be called frequently, handles the ISR flag
// does the frame checkin and decryption
bool WaterMeter::isFrameAvailable(void)
void WaterMeter::loop(void)
{
if (packetAvailable)
{
//Serial.println("packet received");
// Disable wireless reception interrupt
// Serial.println("packet received");
// Disable wireless reception interrupt
detachInterrupt(digitalPinToInterrupt(CC1101_GDO0));
// clear the flag
packetAvailable = false;
WMBusFrame frame;
receive(&frame);
receive();
// Enable wireless reception interrupt
attachInterrupt(digitalPinToInterrupt(CC1101_GDO0), GD0_ISR, FALLING);
return frame.isValid;
attachInterruptArg(digitalPinToInterrupt(CC1101_GDO0), cc1101Isr, this, FALLING);
}
if (millis() - lastFrameReceived > RECEIVE_TIMEOUT)
{
// workaround: reset CC1101, since it stops receiving from time to time
restartRadio();
}
return false;
}
// Initialize CC1101 to receive WMBus MODE C1
void WaterMeter::begin()
// Initialize CC1101 to receive WMBus MODE C1
void WaterMeter::begin(uint8_t *key, uint8_t *id)
{
pinMode(SS, OUTPUT); // SS Pin -> Output
SPI.begin(); // Initialize SPI interface
pinMode(CC1101_GDO0, INPUT); // Config GDO0 as input
pinMode(SS, OUTPUT); // SS Pin -> Output
SPI.begin(); // Initialize SPI interface
pinMode(CC1101_GDO0, INPUT); // Config GDO0 as input
reset(); // power on CC1101
memcpy(aesKey, key, sizeof(aesKey));
aes128.setKey(aesKey, sizeof(aesKey));
//Serial.println("Setting CC1101 registers");
initializeRegisters(); // init CC1101 registers
memcpy(meterId, id, sizeof(meterId));
restartRadio();
attachInterruptArg(digitalPinToInterrupt(CC1101_GDO0), cc1101Isr, this, FALLING);
lastFrameReceived = millis();
}
void WaterMeter::restartRadio()
{
Serial.println("resetting CC1101");
reset(); // power on CC1101
// Serial.println("Setting CC1101 registers");
initializeRegisters(); // init CC1101 registers
cmdStrobe(CC1101_SCAL);
delay(1);
attachInterrupt(digitalPinToInterrupt(CC1101_GDO0), GD0_ISR, FALLING);
startReceiver();
lastFrameReceived = millis();
}
bool WaterMeter::checkFrame(void)
{
#if DEBUG
Serial.printf("frame serial ID: ");
for (uint8_t i = 0; i < 4; i++)
{
Serial.printf("%02x", payload[7-i]);
}
Serial.printf(" - %d", length);
Serial.println();
#endif
// check meterId
for (uint8_t i = 0; i < 4; i++)
{
if (meterId[i] != payload[7 - i])
{
#if DEBUG
Serial.println("Meter serial doesnt match!");
#endif
return false;
}
}
#if DEBUG
Serial.println("Frame payload:");
for (uint8_t i = 0; i <= length; i++)
{
Serial.printf("%02x", payload[i]);
}
Serial.println();
#endif
uint16_t crc = crcEN13575(payload, length - 1); // -2 (CRC) + 1 (L-field)
if (crc != (payload[length - 1] << 8 | payload[length]))
{
Serial.println("CRC Error");
Serial.printf("%04x - %02x%02x\n", crc, payload[length - 1], payload[length]);
return false;
}
return true;
}
void WaterMeter::getMeterInfo(uint8_t *data, size_t len)
{
// init positions for compact frame
int pos_tt = 9; // total consumption
int pos_tg = 13; // target consumption
int pos_ic = 7; // info codes
int pos_ft = 17; // flow temp
int pos_at = 18; // ambient temp
if (data[2] == 0x78) // long frame
{
// overwrite it with long frame positions
pos_tt = 10;
pos_tg = 16;
pos_ic = 6;
pos_ft = 22;
pos_at = 25;
}
totalWater = data[pos_tt] + (data[pos_tt + 1] << 8) + (data[pos_tt + 2] << 16) + (data[pos_tt + 3] << 24);
targetWater = data[pos_tg] + (data[pos_tg + 1] << 8) + (data[pos_tg + 2] << 16) + (data[pos_tg + 3] << 24);
flowTemp = data[pos_ft];
ambientTemp = data[pos_at];
infoCodes = data[pos_ic];
}
void WaterMeter::publishMeterInfo()
{
char total[12];
snprintf(total, sizeof(total), "%d.%03d", totalWater/ 1000, totalWater % 1000);
Serial.printf("total: %s m%c - ", total, 179);
char target[12];
snprintf(target, sizeof(target), "%d.%03d", targetWater / 1000, targetWater % 1000);
Serial.printf("target: %s m%c - ", target, 179);
char flow_temp[4];
snprintf(flow_temp, sizeof(flow_temp), "%2d", flowTemp);
Serial.printf("%s %cC - ", flow_temp, 176);
char ambient_temp[4];
snprintf(ambient_temp, sizeof(ambient_temp), "%2d", ambientTemp);
Serial.printf("%s %cC - ", ambient_temp, 176);
char info_codes[3];
snprintf(info_codes, sizeof(info_codes), "%02x", infoCodes);
Serial.printf("0x%s \n\r", info_codes);
if (!mqttEnabled) return; // no MQTT broker connected, leave
// change the topics as you like
mqttClient.publish(MQTT_PREFIX MQTT_total, total);
mqttClient.publish(MQTT_PREFIX MQTT_target, target);
mqttClient.loop();
mqttClient.publish(MQTT_PREFIX MQTT_ftemp, flow_temp);
mqttClient.publish(MQTT_PREFIX MQTT_atemp, ambient_temp);
mqttClient.publish(MQTT_PREFIX MQTT_info, info_codes);
mqttClient.loop();
}
// reads a single byte from the RX fifo
@ -232,35 +376,79 @@ uint8_t WaterMeter::readByteFromFifo(void)
}
// handles a received frame and restart the CC1101 receiver
void WaterMeter::receive(WMBusFrame * frame)
void WaterMeter::receive()
{
// read preamble, should be 0x543D
uint8_t p1 = readByteFromFifo();
uint8_t p2 = readByteFromFifo();
//Serial.printf("%02x%02x", p1, p2);
#if DEBUG
Serial.printf("%02x%02x", p1, p2);
#endif
uint8_t payloadLength = readByteFromFifo();
// get length
payload[0] = readByteFromFifo();
// is it Mode C1, frame B and does it fit in the buffer
if ( (payloadLength < WMBusFrame::MAX_LENGTH )
&& (p1 == 0x54) && (p2 == 0x3D) )
{
if ((payload[0] < MAX_LENGTH) && (p1 == 0x54) && (p2 == 0x3D))
{
// 3rd byte is payload length
frame->length = payloadLength;
length = payload[0];
//Serial.printf("%02X", lfield);
#if DEBUG
Serial.printf("%02X", length);
#endif
// starting with 1! index 0 is lfield
for (int i = 0; i < payloadLength; i++)
for (int i = 0; i < length; i++)
{
frame->payload[i] = readByteFromFifo();
payload[i + 1] = readByteFromFifo();
}
// do some checks: my meterId, crc ok
frame->decode();
// check meterId, CRC
if (checkFrame())
{
uint8_t cipherLength = length - 2 - 16; // cipher starts at index 16, remove 2 crc bytes
memcpy(cipher, &payload[17], cipherLength);
memset(iv, 0, sizeof(iv)); // padding with 0
memcpy(iv, &payload[2], 8);
iv[8] = payload[11];
memcpy(&iv[9], &payload[13], 4);
#if DEBUG
printHex(iv, sizeof(iv));
printHex(cipher, cipherLength);
#endif
aes128.setIV(iv, sizeof(iv));
aes128.decrypt(plaintext, (const uint8_t *)cipher, cipherLength);
/*
Serial.printf("C: ");
for (size_t i = 0; i < cipherLength; i++)
{
Serial.printf("%02X", cipher[i]);
}
Serial.println();
Serial.printf("P(%d): ", cipherLength);
for (size_t i = 0; i < cipherLength; i++)
{
Serial.printf("%02X", plaintext[i]);
}
Serial.println();
*/
// received packet is ok
lastPacketDecoded = millis();
lastFrameReceived = millis();
getMeterInfo(plaintext, cipherLength);
publishMeterInfo();
}
}
// flush RX fifo and restart receiver
startReceiver();
//Serial.printf("rxStatus: 0x%02x\n\r", readStatusReg(CC1101_RXBYTES));
// Serial.printf("rxStatus: 0x%02x\n\r", readStatusReg(CC1101_RXBYTES));
}

View file

@ -15,7 +15,6 @@
#if defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <SoftwareSerial.h>
#elif defined(ESP32)
#include <WiFi.h>
#include <ESPmDNS.h>
@ -23,62 +22,24 @@
#include <PubSubClient.h>
#include <ArduinoOTA.h>
#include "WaterMeter.h"
#include "credentials.h"
#include "hwconfig.h"
//#include "config.h"
#define ESP_NAME "WaterMeter"
CREDENTIAL currentWifi; // global to store found wifi
#define DEBUG 0
#if defined(ESP32)
#define LED_BUILTIN 4
#endif
//Wifi settings: SSID, PW, MQTT broker
#define NUM_SSID_CREDENTIALS 3
const char *credentials[NUM_SSID_CREDENTIALS][4] =
// SSID, PW, MQTT
{ {SSID1, PW1, MQTT1 }
, {SSID2, PW2, MQTT2 }
, {SSID3, PW3, MQTT3 }
};
WaterMeter waterMeter;
uint8_t wifiConnectCounter = 0; // count retries
WiFiClient espMqttClient;
PubSubClient mqttClient(espMqttClient);
WaterMeter waterMeter(mqttClient);
char MyIp[16];
int cred = -1;
bool mqttEnabled = false; // true, if a broker is given in credentials.h
int getWifiToConnect(int numSsid)
{
for (int i = 0; i < NUM_SSID_CREDENTIALS; i++)
{
//Serial.println(WiFi.SSID(i));
for (int j = 0; j < numSsid; ++j)
{
/*Serial.print(j);
Serial.print(": ");
Serial.print(WiFi.SSID(i).c_str());
Serial.print(" = ");
Serial.println(credentials[j][0]);*/
if (strcmp(WiFi.SSID(j).c_str(), credentials[i][0]) == 0)
{
Serial.println("Credentials found for: ");
Serial.println(credentials[i][0]);
return i;
}
}
}
return -1;
}
// connect to wifi returns true if successful or false if not
bool ConnectWifi(void)
{
int i = 0;
bool isWifiValid = false;
Serial.println("starting scan");
// scan for nearby networks:
@ -93,48 +54,69 @@ bool ConnectWifi(void)
Serial.println("Couldn't get a wifi connection");
return false;
}
for (int i = 0; i < numSsid; i++)
{
Serial.print(i+1);
Serial.print(") ");
Serial.println(WiFi.SSID(i));
Serial.print(i + 1);
Serial.print(". ");
Serial.print(WiFi.SSID(i));
Serial.print(" ");
Serial.println(WiFi.RSSI(i));
}
// search for given credentials
cred = getWifiToConnect(numSsid);
if (cred == -1)
for (CREDENTIAL credential : credentials)
{
Serial.println("No Wifi!");
for (int j = 0; j < numSsid; ++j)
{
if (strcmp(WiFi.SSID(j).c_str(), credential.ssid) == 0)
{
Serial.print("credentials found for: ");
Serial.println(credential.ssid);
currentWifi = credential;
isWifiValid = true;
}
}
}
if (!isWifiValid)
{
Serial.println("no matching credentials");
return false;
}
// try to connect
WiFi.begin(credentials[cred][0], credentials[cred][1]);
Serial.println(WiFi.macAddress());
// try to connect WPA
WiFi.begin(currentWifi.ssid, currentWifi.password);
WiFi.setHostname(ESP_NAME);
Serial.println("");
Serial.print("Connecting to WiFi ");
Serial.println(credentials[cred][0]);
Serial.println(currentWifi.ssid);
i = 0;
while (WiFi.status() != WL_CONNECTED)
{
digitalWrite(LED_BUILTIN, LOW);
digitalWrite(PIN_LED_BUILTIN, LOW);
delay(300);
Serial.print(".");
digitalWrite(LED_BUILTIN, HIGH);
Serial.print(F("."));
digitalWrite(PIN_LED_BUILTIN, HIGH);
delay(300);
if (i++ > 30)
if (i++ > 50)
{
// giving up
return false;
ESP.restart();
return false; // gcc shut up
}
}
return true;
}
void mqttDebug(const char* debug_str)
{
String s="/watermeter/debug";
String s=MQTT_PREFIX"/debug";
mqttClient.publish(s.c_str(), debug_str);
}
@ -151,24 +133,18 @@ void mqttCallback(char* topic, byte* payload, unsigned int len)
Serial.print(" ");
Serial.println((char)payload[0]); // FIXME LEN
*/
if (strstr(topic, "/smarthomeNG/start"))
if (strstr(topic, "smarthomeNG/start"))
{
if (len == 4) // True
{
// maybe to something
}
}
else if (strstr(topic, "/espmeter/reset"))
else if (strstr(topic, MQTT_PREFIX "/reset"))
{
if (len == 4) // True
{
// maybe to something
const char *topic = "/espmeter/reset/status";
const char *msg = "False";
mqttClient.publish(topic, msg);
mqttClient.loop();
delay(200);
// reboot
ESP.restart();
}
@ -179,45 +155,59 @@ void mqttCallback(char* topic, byte* payload, unsigned int len)
bool mqttConnect()
{
mqttClient.setServer(credentials[cred][2], 1883);
bool connected=false;
Serial.print("try to connect to MQTT server ");
Serial.println(currentWifi.mqtt_broker);
// use given MQTT broker
mqttClient.setServer(currentWifi.mqtt_broker, 1883);
// connect client with retainable last will message
if (strlen(currentWifi.mqtt_username) && strlen(currentWifi.mqtt_password))
{
Serial.print("with user: ");
Serial.println(currentWifi.mqtt_username);
// connect with user/pass
connected = mqttClient.connect( ESP_NAME
, currentWifi.mqtt_username
, currentWifi.mqtt_password
, MQTT_PREFIX"/online"
, 0
, true
, "False"
);
}
else
{
// connect without user/pass
connected = mqttClient.connect(ESP_NAME, MQTT_PREFIX"/online", 0, true, "False");
}
mqttClient.setCallback(mqttCallback);
// connect client to retainable last will message
return mqttClient.connect(ESP_NAME, "/watermeter/online", 0, true, "False");
return connected;
}
void mqttSubscribe()
{
String s;
// publish online status
s = "/watermeter/online";
mqttClient.publish(s.c_str(), "True", true);
mqttClient.publish(MQTT_PREFIX "/online", "True", true);
// Serial.print("MQTT-SEND: ");
// Serial.print(s);
// Serial.println(" True");
// publish ip address
s="/watermeter/ipaddr";
IPAddress MyIP = WiFi.localIP();
snprintf(MyIp, 16, "%d.%d.%d.%d", MyIP[0], MyIP[1], MyIP[2], MyIP[3]);
mqttClient.publish(s.c_str(), MyIp, true);
mqttClient.publish(MQTT_PREFIX"/ipaddr", MyIp, true);
// Serial.print("MQTT-SEND: ");
// Serial.print(s);
// Serial.print(" ");
// Serial.println(MyIp);
// if smarthome.py restarts -> publish init values
s = "/smarthomeNG/start";
mqttClient.subscribe(s.c_str());
// if True; meter data are published every 5 seconds
// if False: meter data are published once a minute
s = "/watermeter/liveData";
mqttClient.subscribe(s.c_str());
// if True -> perform an reset
s = "/espmeter/reset";
mqttClient.subscribe(s.c_str());
mqttClient.subscribe(MQTT_PREFIX"/reset");
}
void setupOTA()
@ -259,23 +249,16 @@ void setupOTA()
ArduinoOTA.begin();
}
// receive encrypted packets -> send it via MQTT to decrypter
void waterMeterLoop()
{
if (waterMeter.isFrameAvailable())
{
// publish meter info via MQTT
}
}
void setup()
{
pinMode(LED_BUILTIN, OUTPUT);
Serial.begin(115200);
waterMeter.begin();
uint8_t key[16] = { ENCRYPTION_KEY }; // AES-128 key
uint8_t id[4] = { SERIAL_NUMBER }; // Multical21 serial number
waterMeter.begin(key, id);
Serial.println("Setup done...");
}
@ -286,6 +269,7 @@ enum ControlStateType
, StateMqttConnect
, StateConnected
, StateOperating
, StateOperatingNoWifi
};
ControlStateType ControlState = StateInit;
@ -309,7 +293,11 @@ void loop()
case StateWifiConnect:
//Serial.println("StateWifiConnect:");
// station mode
ConnectWifi();
if (ConnectWifi() == false)
{
ControlState = StateOperatingNoWifi;
break;
}
delay(500);
@ -317,13 +305,25 @@ void loop()
{
Serial.println("");
Serial.print("Connected to ");
Serial.println(credentials[cred][0]); // FIXME
Serial.println(currentWifi.ssid);
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
setupOTA();
ControlState = StateMqttConnect;
if (strlen(currentWifi.mqtt_broker)) // MQTT is used
{
mqttEnabled = true;
ControlState = StateMqttConnect;
}
else
{
// no MQTT server -> go operating
mqttEnabled = false;
ControlState = StateOperating;
Serial.println(F("MQTT not enabled"));
Serial.println(F("StateOperating:"));
}
}
else
{
@ -342,25 +342,33 @@ void loop()
Serial.println("StateMqttConnect:");
digitalWrite(LED_BUILTIN, HIGH); // off
waterMeter.enableMqtt(false);
if (WiFi.status() != WL_CONNECTED)
{
ControlState = StateNotConnected;
break; // exit (hopefully) switch statement
}
Serial.print("try to connect to MQTT server ");
Serial.println(credentials[cred][2]); // FIXME
if (mqttConnect())
if (mqttEnabled)
{
ControlState = StateConnected;
if (mqttConnect())
{
ControlState = StateConnected;
waterMeter.enableMqtt(true);
}
else
{
Serial.println("MQTT connect failed");
delay(1000);
// try again
}
}
else
{
Serial.println("MQTT connect failed");
delay(1000);
// try again
// no MQTT is used at all
ControlState = StateConnected;
}
ArduinoOTA.handle();
@ -369,20 +377,27 @@ void loop()
case StateConnected:
Serial.println("StateConnected:");
if (!mqttClient.connected())
if (mqttEnabled)
{
ControlState = StateMqttConnect;
delay(1000);
if (!mqttClient.connected())
{
ControlState = StateMqttConnect;
delay(1000);
}
else
{
// subscribe to given topics
mqttSubscribe();
ControlState = StateOperating;
digitalWrite(LED_BUILTIN, LOW); // on
Serial.println("StateOperating:");
//mqttDebug("up and running");
}
}
else
{
// subscribe to given topics
mqttSubscribe();
ControlState = StateOperating;
digitalWrite(LED_BUILTIN, LOW); // on
Serial.println("StateOperating:");
//mqttDebug("up and running");
}
ArduinoOTA.handle();
@ -397,21 +412,29 @@ void loop()
break; // exit (hopefully switch statement)
}
if (!mqttClient.connected())
if (mqttEnabled)
{
Serial.println("not connected to MQTT server");
ControlState = StateMqttConnect;
if (!mqttClient.connected())
{
Serial.println("not connected to MQTT server");
ControlState = StateMqttConnect;
}
mqttClient.loop();
}
// here we go
waterMeterLoop();
mqttClient.loop();
waterMeter.loop();
ArduinoOTA.handle();
break;
case StateOperatingNoWifi:
waterMeter.loop();
break;
default:
Serial.println("Error: invalid ControlState");
}

103
src/utils.cpp Normal file
View file

@ -0,0 +1,103 @@
#include "utils.h"
void printHex(uint8_t *buf, size_t len)
{
for (size_t i = 0; i < len; i++)
{
Serial.printf("%02X ", buf[i]);
if ((i+1) % 16 == 0) Serial.println();
}
Serial.println();
}
uint16_t crcX25(uint8_t *payload, uint16_t length)
{
return crcInternal(payload, length, 0x1021, 0xffff, true, true);
}
uint16_t crcEN13575(uint8_t *payload, uint16_t length)
{
return crcInternal(payload, length, 0x3D65, 0x0000, false, false);
}
uint16_t mirror(uint16_t crc, uint8_t bitnum)
{
// mirrors the lower 'bitnum' bits of 'crc'
uint16_t i, j = 1, crcout = 0;
for (i = (uint16_t)1 << (bitnum - 1); i; i >>= 1)
{
if (crc & i)
{
crcout |= j;
}
j <<= 1;
}
return crcout;
}
uint16_t crcInternal(uint8_t *p, uint16_t len, uint16_t poly, uint16_t init, bool revIn, bool revOut)
{
uint16_t i, j, c, bit, crc;
crc = init;
for (i = 0; i < 16; i++)
{
bit = crc & 1;
if (bit) crc ^= poly;
crc >>= 1;
if (bit) crc |= 0x8000;
}
// bit by bit algorithm with augmented zero bytes.
// does not use lookup table, suited for polynom orders between 1...32.
for (i = 0; i < len; i++)
{
c = (uint16_t)*p++;
if (revIn) c = mirror(c, 8);
for (j = 0x80; j; j >>= 1)
{
bit = crc & 0x8000;
crc <<= 1;
if (c & j) crc |= 1;
if (bit) crc ^= poly;
}
}
for (i = 0; i < 16; i++)
{
bit = crc & 0x8000;
crc <<= 1;
if (bit) crc ^= poly;
}
if (revOut) crc = mirror(crc, 16);
crc ^= 0xffff; // crcxor
return crc;
}
// convert _in_ to _len_ hex numbers stored in _out_
// _in_ "EF01" to 2 hex numbers: 0xEf, 0x01
void hex2bin(const char *in, size_t len, uint8_t *out)
{
const char *pos = in;
for(size_t count = 0; count < len; count++)
{
char buf[5] = {'0', 'x', pos[0], pos[1], 0};
out[count] = strtol(buf, NULL, 0);
pos += 2 * sizeof(char);
}
}
void bin2hex(char *xp, uint8_t *bb, int n)
{
const char xx[]= "0123456789ABCDEF";
while (--n >= 0) xp[n] = xx[(bb[n>>1] >> ((1 - (n&1)) << 2)) & 0xF];
}