added CRC check, README update, bug fixes,
This commit is contained in:
parent
b076743f54
commit
ff61047706
13 changed files with 769 additions and 434 deletions
81
README.md
81
README.md
|
@ -1,10 +1,79 @@
|
||||||
# esp-multical21
|
# esp-multical21
|
||||||
ESP8266 decrypts wireless MBus frames from a Multical21 water meter
|
ESP8266/ESP32 decrypts wireless MBus frames from a Multical21 water meter.
|
||||||
|
|
||||||
A CC1101 868 MHz modul is connected via SPI to the ESP8266 an configured to receive Wireless MBus frames.
|
<img align="right" src="multical21.png" alt="Multical21" width="300"/>
|
||||||
The Multical21 is sending every 16 seconds wireless MBus frames (Mode C1, frame type B). The encrypted
|
A CC1101 868 MHz module is connected via SPI to the ESP8266/ESP32.
|
||||||
frames are received from the ESP8266 an it decrypts them with AES-128-CTR. The meter information
|
The Multical21 is transmitting encrypted MBus frames (Mode C1, frame type B) every 16 seconds.
|
||||||
(total counter, target counter, medium temperature, ambient temperature, alalm flags (BURST, LEAK, DRY,
|
The ESP8266/ESP32 does some validation (right serial number, crc checking) and then
|
||||||
REVERSE) are sent via MQTT to a smarthomeNG/smartVISU service (running on a raspberry).
|
decrypts them with AES-128-CTR.
|
||||||
|
|
||||||
|
The serial number (8 digits) is printed on the water meter (above the LCD).
|
||||||
|
Ask your water supplier for the decryption key (16 bytes). I got mine packed in a so called
|
||||||
|
KEM-file. To extract the key i used a python script [kem-decryptor.py](https://gist.github.com/mbursa/caa654a01b9e804ad44d1f00208a2490)
|
||||||
|
|
||||||
|
|
||||||
|
The Multical21 provides the following meter values:
|
||||||
|
<ul>
|
||||||
|
<li> total counter - total water consumption in m³
|
||||||
|
<li> target counter - water consumption till 1. day of the current month
|
||||||
|
<li> medium temperature - in °C
|
||||||
|
<li> ambient temperature - in °C
|
||||||
|
<li> info codes - BURST, LEAK, DRY, REVERSE, TAMPER, RADIO OFF
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
The ESP8266/ESP32 prints out the current meter values via UART (baudrate: 115200).
|
||||||
|
The UART output looks something like this:
|
||||||
|
```
|
||||||
|
total: 1636.265 m³ - target: 1624.252 m³ - 13 °C - 22 °C - 0x00
|
||||||
|
```
|
||||||
|
Additionally the values are sent via MQTT to a given broker.
|
||||||
|
|
||||||
|
Rename [config_template.h](include/config_template.h) to _**config.h**_ and fill in some information.
|
||||||
|
|
||||||
|
Provide your water meter serial number and decryption key.
|
||||||
|
|
||||||
|
```
|
||||||
|
// ask your water supplier for your personal encryption key
|
||||||
|
#define ENCRYPTION_KEY 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF
|
||||||
|
// serial number is printed on your multical21
|
||||||
|
#define SERIAL_NUMBER 0x63, 0x00, 0x05, 0x43
|
||||||
|
```
|
||||||
|
|
||||||
|
Add your Wifi credentials (ssid, password). Add your MQTT broker ip address. If your
|
||||||
|
broker uses authentication add MQTT username/password.
|
||||||
|
|
||||||
|
```
|
||||||
|
// more than one wifi credentials are supported, upper one wins
|
||||||
|
// "ssid", "wifi_passphrase", "mqtt_broker", "mqtt_username", "mqtt_password"
|
||||||
|
std::vector<CREDENTIAL> const credentials = {
|
||||||
|
{ "ssid1", "********", "", "", ""} // no MQTT
|
||||||
|
, { "ssid2", "********", "10.14.0.1", "", ""} // MQTT without auth
|
||||||
|
, { "ssid3", "********", "10.0.0.111", "mqttuser", "mqtt1234"} // MQTT with auth
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Change the MQTT prefix and the topic names as you like. Currently the water counter value
|
||||||
|
is published in **watermeter/0/total**
|
||||||
|
```
|
||||||
|
#define MQTT_PREFIX "watermeter/0"
|
||||||
|
#define MQTT_total "/total"
|
||||||
|
#define MQTT_target "/target"
|
||||||
|
#define MQTT_ftemp "/flowtemp"
|
||||||
|
#define MQTT_atemp "/ambienttemp"
|
||||||
|
#define MQTT_info "/infocode"
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Connect your ESP8266/ESP32 to the CC1101 868Mhz module:
|
||||||
|
| CC1101 | ESP8266 | ESP32 |
|
||||||
|
|--------|:-------:|:-----:|
|
||||||
|
| VCC | 3V3 | 3V3 |
|
||||||
|
| GND | GND | GND |
|
||||||
|
| CSN | D8 | 4 |
|
||||||
|
| MOSI | D7 | 23 |
|
||||||
|
| MISO | D6 | 19 |
|
||||||
|
| SCK | D5 | 18 |
|
||||||
|
| GD0 | D2 | 32 |
|
||||||
|
| GD2 | not connected| not connected|
|
||||||
|
|
||||||
Thanks to [weetmuts](https://github.com/weetmuts) for his great job on the wmbusmeters.
|
Thanks to [weetmuts](https://github.com/weetmuts) for his great job on the wmbusmeters.
|
||||||
|
|
|
@ -1,41 +0,0 @@
|
||||||
#ifndef __WMBUS_FRAME__
|
|
||||||
#define __WMBUS_FRAME__
|
|
||||||
|
|
||||||
#include <Arduino.h>
|
|
||||||
#include <Crypto.h>
|
|
||||||
#include <AES.h>
|
|
||||||
#include <CTR.h>
|
|
||||||
#include "credentials.h"
|
|
||||||
|
|
||||||
class WMBusFrame
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
static const uint8_t MAX_LENGTH = 64;
|
|
||||||
private:
|
|
||||||
CTR<AESSmall128> aes128;
|
|
||||||
const uint8_t meterId[4] = { W_SERIAL_NUMBER }; // Multical21 serial number
|
|
||||||
const uint8_t key[16] = { W_ENCRYPTION_KEY }; // AES-128 key
|
|
||||||
uint8_t cipher[MAX_LENGTH];
|
|
||||||
uint8_t plaintext[MAX_LENGTH];
|
|
||||||
uint8_t iv[16];
|
|
||||||
void check(void);
|
|
||||||
void printMeterInfo(uint8_t *data, size_t len);
|
|
||||||
|
|
||||||
public:
|
|
||||||
// check frame and decrypt it
|
|
||||||
void decode(void);
|
|
||||||
|
|
||||||
// true, if meter information is valid for the last received frame
|
|
||||||
bool isValid = false;
|
|
||||||
|
|
||||||
// payload length
|
|
||||||
uint8_t length = 0;
|
|
||||||
|
|
||||||
// payload data
|
|
||||||
uint8_t payload[MAX_LENGTH];
|
|
||||||
|
|
||||||
// constructor
|
|
||||||
WMBusFrame();
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // __WMBUS_FRAME__
|
|
|
@ -17,7 +17,12 @@
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <SPI.h>
|
#include <SPI.h>
|
||||||
#include "WMBusFrame.h"
|
#include <Crypto.h>
|
||||||
|
#include <AES.h>
|
||||||
|
#include <CTR.h>
|
||||||
|
#include <PubSubClient.h>
|
||||||
|
#include "config.h"
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
#define MARCSTATE_SLEEP 0x00
|
#define MARCSTATE_SLEEP 0x00
|
||||||
#define MARCSTATE_IDLE 0x01
|
#define MARCSTATE_IDLE 0x01
|
||||||
|
@ -179,50 +184,72 @@
|
||||||
class WaterMeter
|
class WaterMeter
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
|
const uint32_t RECEIVE_TIMEOUT = 300000UL; // in millis
|
||||||
|
const uint32_t PACKET_TIMEOUT = 180000UL; // in seconds
|
||||||
|
uint32_t lastPacketDecoded = -PACKET_TIMEOUT;
|
||||||
|
uint32_t lastFrameReceived = 0;
|
||||||
|
volatile boolean packetAvailable = false;
|
||||||
|
uint8_t meterId[4];
|
||||||
|
uint8_t aesKey[16];
|
||||||
inline void selectCC1101(void);
|
inline void selectCC1101(void);
|
||||||
inline void deselectCC1101(void);
|
inline void deselectCC1101(void);
|
||||||
inline void waitMiso(void);
|
inline void waitMiso(void);
|
||||||
|
static const uint8_t MAX_LENGTH = 64;
|
||||||
|
CTR<AESSmall128> aes128;
|
||||||
|
uint8_t cipher[MAX_LENGTH];
|
||||||
|
uint8_t plaintext[MAX_LENGTH];
|
||||||
|
uint8_t iv[16];
|
||||||
|
bool isValid = false; // true, if meter information is valid for the last received frame
|
||||||
|
uint8_t length = 0; // payload length
|
||||||
|
uint8_t payload[MAX_LENGTH]; // payload data
|
||||||
|
uint32_t totalWater;
|
||||||
|
uint32_t targetWater;
|
||||||
|
uint32_t lastTarget=0;
|
||||||
|
uint8_t flowTemp;
|
||||||
|
uint8_t ambientTemp;
|
||||||
|
uint8_t infoCodes;
|
||||||
|
|
||||||
|
PubSubClient &mqttClient;
|
||||||
|
bool mqttEnabled;
|
||||||
|
|
||||||
|
// reset HW and restart receiver
|
||||||
|
void restartRadio(void);
|
||||||
|
|
||||||
// flush fifo and (re)start receiver
|
// flush fifo and (re)start receiver
|
||||||
void startReceiver(void);
|
void startReceiver(void);
|
||||||
|
|
||||||
// burst write registers of cc1101
|
//void writeBurstReg(uint8_t regaddr, uint8_t* buffer, uint8_t len);
|
||||||
void writeBurstReg(uint8_t regaddr, uint8_t* buffer, uint8_t len);
|
|
||||||
|
|
||||||
// burst read registers of cc1101
|
|
||||||
void readBurstReg(uint8_t * buffer, uint8_t regaddr, uint8_t len);
|
void readBurstReg(uint8_t * buffer, uint8_t regaddr, uint8_t len);
|
||||||
|
|
||||||
// strobe command
|
|
||||||
void cmdStrobe(uint8_t cmd);
|
void cmdStrobe(uint8_t cmd);
|
||||||
|
|
||||||
// read a register of cc1101
|
|
||||||
uint8_t readReg(uint8_t regaddr, uint8_t regtype);
|
uint8_t readReg(uint8_t regaddr, uint8_t regtype);
|
||||||
|
|
||||||
// read a byte from fifo
|
|
||||||
uint8_t readByteFromFifo(void);
|
uint8_t readByteFromFifo(void);
|
||||||
|
|
||||||
// write a register of cc1101
|
|
||||||
void writeReg(uint8_t regaddr, uint8_t value);
|
void writeReg(uint8_t regaddr, uint8_t value);
|
||||||
|
|
||||||
// initialize cc1101 registers
|
|
||||||
void initializeRegisters(void);
|
void initializeRegisters(void);
|
||||||
|
|
||||||
// reset cc1101
|
|
||||||
void reset(void);
|
void reset(void);
|
||||||
|
|
||||||
|
// static ISR calls instanceISR via this pointer
|
||||||
|
IRAM_ATTR static void cc1101Isr(void *p);
|
||||||
|
|
||||||
// receive a wmbus frame
|
// receive a wmbus frame
|
||||||
void receive(WMBusFrame *payload);
|
void receive(void); // read frame from CC1101
|
||||||
|
bool checkFrame(void); // check id, CRC
|
||||||
|
void getMeterInfo(uint8_t *data, size_t len);
|
||||||
|
void publishMeterInfo();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
// constructor
|
// constructor
|
||||||
WaterMeter(void);
|
WaterMeter(PubSubClient &mqtt);
|
||||||
|
|
||||||
|
void enableMqtt(bool enable);
|
||||||
|
|
||||||
// startup CC1101 for receiving wmbus mode c
|
// startup CC1101 for receiving wmbus mode c
|
||||||
void begin();
|
void begin(uint8_t *key, uint8_t *id);
|
||||||
|
|
||||||
// must be called frequently, returns true if a valid frame was received
|
// must be called frequently
|
||||||
bool isFrameAvailable(void);
|
void loop(void);
|
||||||
|
|
||||||
|
IRAM_ATTR void instanceCC1101Isr();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // _WATERMETER_H_
|
#endif // _WATERMETER_H_
|
67
include/config_template.h
Normal file
67
include/config_template.h
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
#ifndef __CONFIG_H__
|
||||||
|
#define __CONFIG_H__
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#define DEBUG 1 // set to 1 for detailed logging at UART
|
||||||
|
#define ESP_NAME "esp-multical21" // for dns resolving
|
||||||
|
#define MQTT_PREFIX "watermeter/0" // mqtt prefix topic
|
||||||
|
#define MQTT_total "/total"
|
||||||
|
#define MQTT_target "/target"
|
||||||
|
#define MQTT_ftemp "/flowtemp"
|
||||||
|
#define MQTT_atemp "/ambienttemp"
|
||||||
|
#define MQTT_info "/infocode"
|
||||||
|
|
||||||
|
// ask your water supplier for your personal encryption key
|
||||||
|
#define ENCRYPTION_KEY 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF
|
||||||
|
// serial number is printed on your multical21
|
||||||
|
#define SERIAL_NUMBER 0x12, 0x34, 0x56, 0x78
|
||||||
|
|
||||||
|
// WIFI configuration, supports more than one WIFI, first found first served
|
||||||
|
// if you dont use MQTT, leave broker/user/pass empty ("")
|
||||||
|
// if you dont need user/pass for MQTT, leave it empty ("")
|
||||||
|
struct CREDENTIAL {
|
||||||
|
char const* ssid; // Wifi ssid
|
||||||
|
char const* password; // Wifi password
|
||||||
|
char const* mqtt_broker; // MQTT broker ip address
|
||||||
|
char const* mqtt_username; // MQTT username
|
||||||
|
char const* mqtt_password; // MQTT password
|
||||||
|
};
|
||||||
|
|
||||||
|
// more than one wifi credentials are supported, upper one wins
|
||||||
|
// "ssid", "wifi_passphrase", "mqtt_broker", "mqtt_username", "mqtt_password"
|
||||||
|
std::vector<CREDENTIAL> const credentials = {
|
||||||
|
{ "ssid1", "********", "", "", ""} // no MQTT
|
||||||
|
, { "ssid2", "********", "10.14.0.1", "", ""} // MQTT without auth
|
||||||
|
, { "ssid3", "********", "10.0.0.111", "mqttuser", "mqtt1234"} // MQTT with auth
|
||||||
|
};
|
||||||
|
|
||||||
|
#if defined(ESP8266)
|
||||||
|
// Attach CC1101 pins to ESP8266 SPI pins
|
||||||
|
// VCC => 3V3
|
||||||
|
// GND => GND
|
||||||
|
// CSN => D8
|
||||||
|
// MOSI => D7
|
||||||
|
// MISO => D6
|
||||||
|
// SCK => D5
|
||||||
|
// GD0 => D2 A valid interrupt pin for your platform (defined below this)
|
||||||
|
// GD2 => not connected
|
||||||
|
#define CC1101_GDO0 D2 // GDO0 input interrupt pin
|
||||||
|
#define PIN_LED_BUILTIN D4
|
||||||
|
#elif defined(ESP32)
|
||||||
|
// Attach CC1101 pins to ESP32 SPI pins
|
||||||
|
// VCC => 3V3
|
||||||
|
// GND => GND
|
||||||
|
// CSN => 4
|
||||||
|
// MOSI => 23
|
||||||
|
// MISO => 19
|
||||||
|
// SCK => 18
|
||||||
|
// GD0 => 32 any valid interrupt pin for your platform will do
|
||||||
|
// GD2 => not connected
|
||||||
|
|
||||||
|
// attach CC1101 pins to ESP32 SPI pins
|
||||||
|
|
||||||
|
#define CC1101_GDO0 32
|
||||||
|
#define PIN_LED_BUILTIN 2
|
||||||
|
#endif
|
||||||
|
#endif // __CONFIG_H__
|
31
include/credentials_template.h
Normal file
31
include/credentials_template.h
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
#ifndef __CREDENTIALS_H__
|
||||||
|
#define __CREDENTIALS_H__
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
// ask your supplier for your personal encryption key (16 bytes)
|
||||||
|
#define ENCRYPTION_KEY 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF
|
||||||
|
// serial number is printed on your multical21
|
||||||
|
#define SERIAL_NUMBER 0x12, 0x34, 0x56, 0x78
|
||||||
|
|
||||||
|
// WIFI configuration, supports more than one WIFI, first found first served
|
||||||
|
// if you dont use MQTT, leave it empty ("")
|
||||||
|
// if you dont need user/pass for MQTT, leave it empty ("")
|
||||||
|
struct CREDENTIAL {
|
||||||
|
char const* ssid;
|
||||||
|
char const* password;
|
||||||
|
char const* mqtt_broker; // MQTT broker
|
||||||
|
char const* mqtt_username; // MQTT username
|
||||||
|
char const* mqtt_password; // MQTT password
|
||||||
|
};
|
||||||
|
|
||||||
|
CREDENTIAL currentWifi; // global to store found wifi
|
||||||
|
|
||||||
|
// "ssid", "wifi_passphrase", "mqtt_broker", "mqtt_username", "mqtt_password"
|
||||||
|
std::vector<CREDENTIAL> const credentials = {
|
||||||
|
{ "ssid1", "********", "", "", ""} // no MQTT - just serial output
|
||||||
|
, { "ssid3", "********", "10.0.0.11", "", ""} // MQTT without authentication
|
||||||
|
, { "ssid2", "********", "10.0.0.10", "loxone", "loxy1234"} // MQTT with user/pass
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // __CREDENTIALS_H__
|
|
@ -1,33 +0,0 @@
|
||||||
#ifndef __HWCONFIG_H__
|
|
||||||
#define __HWCONFIG_H__
|
|
||||||
|
|
||||||
#if defined(ESP8266)
|
|
||||||
// Attach CC1101 pins to ESP8266 SPI pins
|
|
||||||
// VCC => 3V3
|
|
||||||
// GND => GND
|
|
||||||
// CSN => D8
|
|
||||||
// MOSI => D7
|
|
||||||
// MISO => D6
|
|
||||||
// SCK => D5
|
|
||||||
// GD0 => D2 A valid interrupt pin for your platform (defined below this)
|
|
||||||
// GD2 => not connected
|
|
||||||
#define CC1101_GDO0 D2 // GDO0 input interrupt pin
|
|
||||||
#define PIN_LED_BUILTIN D4
|
|
||||||
#elif defined(ESP32)
|
|
||||||
// Attach CC1101 pins to ESP32 SPI pins
|
|
||||||
// VCC => 3V3
|
|
||||||
// GND => GND
|
|
||||||
// CSN => 4
|
|
||||||
// MOSI => 23
|
|
||||||
// MISO => 19
|
|
||||||
// SCK => 18
|
|
||||||
// GD0 => 32 any valid interrupt pin for your platform will do
|
|
||||||
// GD2 => not connected
|
|
||||||
|
|
||||||
// attach CC1101 pins to ESP32 SPI pins
|
|
||||||
|
|
||||||
#define CC1101_GDO0 32
|
|
||||||
#define PIN_LED_BUILTIN 2
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif //__HWCONFIG_H__
|
|
15
include/utils.h
Normal file
15
include/utils.h
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
#ifndef __UTILS_H__
|
||||||
|
#define __UTILS_H__
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
void printHex(uint8_t * buf, size_t len);
|
||||||
|
|
||||||
|
uint16_t crcEN13575(uint8_t *payload, uint16_t length);
|
||||||
|
uint16_t mirror(uint16_t crc, uint8_t bitnum);
|
||||||
|
uint16_t crcInternal(uint8_t *p, uint16_t len, uint16_t poly, uint16_t init, bool revIn, bool revOut);
|
||||||
|
void bin2hex(char *xp, uint8_t *bb, int n);
|
||||||
|
void hex2bin(const char *in, size_t len, uint8_t *out);
|
||||||
|
|
||||||
|
#endif //__UTILS_H__
|
BIN
multical21.png
Normal file
BIN
multical21.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 216 KiB |
|
@ -9,8 +9,8 @@
|
||||||
; https://docs.platformio.org/page/projectconf.html
|
; https://docs.platformio.org/page/projectconf.html
|
||||||
|
|
||||||
[platformio]
|
[platformio]
|
||||||
;default_envs = esp8266-test
|
default_envs = esp8266
|
||||||
default_envs = esp32, esp8266
|
;default_envs = esp32, esp8266
|
||||||
|
|
||||||
[env:esp32]
|
[env:esp32]
|
||||||
framework = arduino
|
framework = arduino
|
||||||
|
@ -25,6 +25,7 @@ platform = espressif8266
|
||||||
board = d1_mini_lite
|
board = d1_mini_lite
|
||||||
board_build.mcu = esp8266
|
board_build.mcu = esp8266
|
||||||
lib_deps = rweather/Crypto @ ^0.2.0
|
lib_deps = rweather/Crypto @ ^0.2.0
|
||||||
|
monitor_speed=115200
|
||||||
; OTA
|
; OTA
|
||||||
;upload_port = 10.0.0.86
|
upload_port = 10.0.0.131
|
||||||
;upload_protocol = espota
|
upload_protocol = espota
|
|
@ -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);
|
|
||||||
}
|
|
|
@ -13,12 +13,18 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "WaterMeter.h"
|
#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
|
// ChipSelect assert
|
||||||
inline void WaterMeter::selectCC1101(void)
|
inline void WaterMeter::selectCC1101(void)
|
||||||
{
|
{
|
||||||
|
@ -34,28 +40,29 @@ inline void WaterMeter::deselectCC1101(void)
|
||||||
// wait for MISO pulling down
|
// wait for MISO pulling down
|
||||||
inline void WaterMeter::waitMiso(void)
|
inline void WaterMeter::waitMiso(void)
|
||||||
{
|
{
|
||||||
while(digitalRead(MISO) == HIGH);
|
while (digitalRead(MISO) == HIGH)
|
||||||
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
// write a single register of CC1101
|
// 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
|
selectCC1101();
|
||||||
waitMiso(); // Wait until MISO goes low
|
waitMiso(); // Wait until MISO goes low
|
||||||
SPI.transfer(regAddr); // Send register address
|
SPI.transfer(regAddr); // Send register address
|
||||||
SPI.transfer(value); // Send value
|
SPI.transfer(value); // Send value
|
||||||
deselectCC1101(); // Deselect CC1101
|
deselectCC1101();
|
||||||
}
|
}
|
||||||
|
|
||||||
// send a strobe command to CC1101
|
// send a strobe command to CC1101
|
||||||
void WaterMeter::cmdStrobe(uint8_t cmd)
|
void WaterMeter::cmdStrobe(uint8_t cmd)
|
||||||
{
|
{
|
||||||
selectCC1101(); // Select CC1101
|
selectCC1101();
|
||||||
delayMicroseconds(5);
|
delayMicroseconds(5);
|
||||||
waitMiso(); // Wait until MISO goes low
|
waitMiso(); // Wait until MISO goes low
|
||||||
SPI.transfer(cmd); // Send strobe command
|
SPI.transfer(cmd); // Send strobe command
|
||||||
delayMicroseconds(5);
|
delayMicroseconds(5);
|
||||||
deselectCC1101(); // Deselect CC1101
|
deselectCC1101();
|
||||||
}
|
}
|
||||||
|
|
||||||
// read CC1101 register (status or configuration)
|
// read CC1101 register (status or configuration)
|
||||||
|
@ -64,11 +71,11 @@ uint8_t WaterMeter::readReg(uint8_t regAddr, uint8_t regType)
|
||||||
uint8_t addr, val;
|
uint8_t addr, val;
|
||||||
|
|
||||||
addr = regAddr | regType;
|
addr = regAddr | regType;
|
||||||
selectCC1101(); // Select CC1101
|
selectCC1101();
|
||||||
waitMiso(); // Wait until MISO goes low
|
waitMiso(); // Wait until MISO goes low
|
||||||
SPI.transfer(addr); // Send register address
|
SPI.transfer(addr); // Send register address
|
||||||
val = SPI.transfer(0x00); // Read result
|
val = SPI.transfer(0x00); // Read result
|
||||||
deselectCC1101(); // Deselect CC1101
|
deselectCC1101();
|
||||||
|
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
@ -79,54 +86,66 @@ void WaterMeter::readBurstReg(uint8_t * buffer, uint8_t regAddr, uint8_t len)
|
||||||
uint8_t addr, i;
|
uint8_t addr, i;
|
||||||
|
|
||||||
addr = regAddr | READ_BURST;
|
addr = regAddr | READ_BURST;
|
||||||
selectCC1101(); // Select CC1101
|
selectCC1101();
|
||||||
delayMicroseconds(5);
|
delayMicroseconds(5);
|
||||||
waitMiso(); // Wait until MISO goes low
|
waitMiso(); // Wait until MISO goes low
|
||||||
SPI.transfer(addr); // Send register address
|
SPI.transfer(addr); // Send register address
|
||||||
for (i = 0; i < len; i++)
|
for (i = 0; i < len; i++)
|
||||||
buffer[i] = SPI.transfer(0x00); // Read result byte by byte
|
buffer[i] = SPI.transfer(0x00); // Read result byte by byte
|
||||||
delayMicroseconds(2);
|
delayMicroseconds(2);
|
||||||
deselectCC1101(); // Deselect CC1101
|
deselectCC1101();
|
||||||
}
|
}
|
||||||
|
|
||||||
// power on reset
|
// power on reset
|
||||||
void WaterMeter::reset(void)
|
void WaterMeter::reset(void)
|
||||||
{
|
{
|
||||||
deselectCC1101(); // Deselect CC1101
|
deselectCC1101();
|
||||||
delayMicroseconds(3);
|
delayMicroseconds(3);
|
||||||
|
|
||||||
digitalWrite(MOSI, LOW);
|
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);
|
delayMicroseconds(3);
|
||||||
deselectCC1101(); // Deselect CC1101
|
deselectCC1101();
|
||||||
delayMicroseconds(45); // at least 40 us
|
delayMicroseconds(45); // at least 40 us
|
||||||
|
|
||||||
selectCC1101(); // Select CC1101
|
selectCC1101();
|
||||||
|
|
||||||
waitMiso(); // Wait until MISO goes low
|
waitMiso(); // Wait until MISO goes low
|
||||||
SPI.transfer(CC1101_SRES); // Send reset command strobe
|
SPI.transfer(CC1101_SRES); // Send reset command strobe
|
||||||
waitMiso(); // Wait until MISO goes low
|
waitMiso(); // Wait until MISO goes low
|
||||||
|
|
||||||
deselectCC1101(); // Deselect CC1101
|
deselectCC1101();
|
||||||
}
|
}
|
||||||
|
|
||||||
// set IDLE state, flush FIFO and (re)start receiver
|
// set IDLE state, flush FIFO and (re)start receiver
|
||||||
void WaterMeter::startReceiver(void)
|
void WaterMeter::startReceiver(void)
|
||||||
{
|
{
|
||||||
|
uint8_t regCount = 0;
|
||||||
cmdStrobe(CC1101_SIDLE); // Enter IDLE state
|
cmdStrobe(CC1101_SIDLE); // Enter IDLE state
|
||||||
while (readReg(CC1101_MARCSTATE, CC1101_STATUS_REGISTER) != MARCSTATE_IDLE);
|
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_SFRX); // flush receive queue
|
||||||
|
delay(5);
|
||||||
|
|
||||||
|
regCount = 0;
|
||||||
cmdStrobe(CC1101_SRX); // Enter RX state
|
cmdStrobe(CC1101_SRX); // Enter RX state
|
||||||
while (readReg(CC1101_MARCSTATE, CC1101_STATUS_REGISTER) != MARCSTATE_RX);
|
delay(10);
|
||||||
|
while (readReg(CC1101_MARCSTATE, CC1101_STATUS_REGISTER) != MARCSTATE_RX)
|
||||||
{
|
{
|
||||||
delay(1);
|
if (regCount++ > 100)
|
||||||
|
{
|
||||||
|
Serial.println("Enter RX state failed!\n");
|
||||||
|
restartRadio();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,18 +192,22 @@ void WaterMeter::initializeRegisters(void)
|
||||||
writeReg(CC1101_TEST0, CC1101_DEFVAL_TEST0);
|
writeReg(CC1101_TEST0, CC1101_DEFVAL_TEST0);
|
||||||
}
|
}
|
||||||
|
|
||||||
volatile boolean packetAvailable = false;
|
IRAM_ATTR void WaterMeter::instanceCC1101Isr()
|
||||||
void ICACHE_RAM_ATTR GD0_ISR(void);
|
{
|
||||||
|
|
||||||
// handle interrupt from CC1101 via GDO0
|
|
||||||
void GD0_ISR(void) {
|
|
||||||
// set the flag that a package is available
|
// set the flag that a package is available
|
||||||
packetAvailable = true;
|
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
|
// should be called frequently, handles the ISR flag
|
||||||
// does the frame checkin and decryption
|
// does the frame checkin and decryption
|
||||||
bool WaterMeter::isFrameAvailable(void)
|
void WaterMeter::loop(void)
|
||||||
{
|
{
|
||||||
if (packetAvailable)
|
if (packetAvailable)
|
||||||
{
|
{
|
||||||
|
@ -194,25 +217,40 @@ bool WaterMeter::isFrameAvailable(void)
|
||||||
|
|
||||||
// clear the flag
|
// clear the flag
|
||||||
packetAvailable = false;
|
packetAvailable = false;
|
||||||
|
receive();
|
||||||
WMBusFrame frame;
|
|
||||||
|
|
||||||
receive(&frame);
|
|
||||||
|
|
||||||
// Enable wireless reception interrupt
|
// Enable wireless reception interrupt
|
||||||
attachInterrupt(digitalPinToInterrupt(CC1101_GDO0), GD0_ISR, FALLING);
|
attachInterruptArg(digitalPinToInterrupt(CC1101_GDO0), cc1101Isr, this, FALLING);
|
||||||
return frame.isValid;
|
}
|
||||||
|
|
||||||
|
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
|
// Initialize CC1101 to receive WMBus MODE C1
|
||||||
void WaterMeter::begin()
|
void WaterMeter::begin(uint8_t *key, uint8_t *id)
|
||||||
{
|
{
|
||||||
pinMode(SS, OUTPUT); // SS Pin -> Output
|
pinMode(SS, OUTPUT); // SS Pin -> Output
|
||||||
SPI.begin(); // Initialize SPI interface
|
SPI.begin(); // Initialize SPI interface
|
||||||
pinMode(CC1101_GDO0, INPUT); // Config GDO0 as input
|
pinMode(CC1101_GDO0, INPUT); // Config GDO0 as input
|
||||||
|
|
||||||
|
memcpy(aesKey, key, sizeof(aesKey));
|
||||||
|
aes128.setKey(aesKey, sizeof(aesKey));
|
||||||
|
|
||||||
|
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
|
reset(); // power on CC1101
|
||||||
|
|
||||||
// Serial.println("Setting CC1101 registers");
|
// Serial.println("Setting CC1101 registers");
|
||||||
|
@ -221,8 +259,114 @@ void WaterMeter::begin()
|
||||||
cmdStrobe(CC1101_SCAL);
|
cmdStrobe(CC1101_SCAL);
|
||||||
delay(1);
|
delay(1);
|
||||||
|
|
||||||
attachInterrupt(digitalPinToInterrupt(CC1101_GDO0), GD0_ISR, FALLING);
|
|
||||||
startReceiver();
|
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
|
// reads a single byte from the RX fifo
|
||||||
|
@ -232,32 +376,76 @@ uint8_t WaterMeter::readByteFromFifo(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handles a received frame and restart the CC1101 receiver
|
// handles a received frame and restart the CC1101 receiver
|
||||||
void WaterMeter::receive(WMBusFrame * frame)
|
void WaterMeter::receive()
|
||||||
{
|
{
|
||||||
// read preamble, should be 0x543D
|
// read preamble, should be 0x543D
|
||||||
uint8_t p1 = readByteFromFifo();
|
uint8_t p1 = readByteFromFifo();
|
||||||
uint8_t p2 = readByteFromFifo();
|
uint8_t p2 = readByteFromFifo();
|
||||||
//Serial.printf("%02x%02x", p1, p2);
|
|
||||||
|
|
||||||
uint8_t payloadLength = readByteFromFifo();
|
#if DEBUG
|
||||||
|
Serial.printf("%02x%02x", p1, p2);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// get length
|
||||||
|
payload[0] = readByteFromFifo();
|
||||||
|
|
||||||
// is it Mode C1, frame B and does it fit in the buffer
|
// is it Mode C1, frame B and does it fit in the buffer
|
||||||
if ( (payloadLength < WMBusFrame::MAX_LENGTH )
|
if ((payload[0] < MAX_LENGTH) && (p1 == 0x54) && (p2 == 0x3D))
|
||||||
&& (p1 == 0x54) && (p2 == 0x3D) )
|
|
||||||
{
|
{
|
||||||
// 3rd byte is payload length
|
// 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
|
// 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
|
// check meterId, CRC
|
||||||
frame->decode();
|
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
|
// flush RX fifo and restart receiver
|
||||||
|
|
231
src/main.cpp
231
src/main.cpp
|
@ -15,7 +15,6 @@
|
||||||
#if defined(ESP8266)
|
#if defined(ESP8266)
|
||||||
#include <ESP8266WiFi.h>
|
#include <ESP8266WiFi.h>
|
||||||
#include <ESP8266mDNS.h>
|
#include <ESP8266mDNS.h>
|
||||||
#include <SoftwareSerial.h>
|
|
||||||
#elif defined(ESP32)
|
#elif defined(ESP32)
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
#include <ESPmDNS.h>
|
#include <ESPmDNS.h>
|
||||||
|
@ -23,62 +22,24 @@
|
||||||
#include <PubSubClient.h>
|
#include <PubSubClient.h>
|
||||||
#include <ArduinoOTA.h>
|
#include <ArduinoOTA.h>
|
||||||
#include "WaterMeter.h"
|
#include "WaterMeter.h"
|
||||||
#include "credentials.h"
|
//#include "config.h"
|
||||||
#include "hwconfig.h"
|
|
||||||
|
|
||||||
#define ESP_NAME "WaterMeter"
|
CREDENTIAL currentWifi; // global to store found wifi
|
||||||
|
|
||||||
#define DEBUG 0
|
uint8_t wifiConnectCounter = 0; // count retries
|
||||||
|
|
||||||
#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;
|
|
||||||
|
|
||||||
WiFiClient espMqttClient;
|
WiFiClient espMqttClient;
|
||||||
PubSubClient mqttClient(espMqttClient);
|
PubSubClient mqttClient(espMqttClient);
|
||||||
|
WaterMeter waterMeter(mqttClient);
|
||||||
|
|
||||||
char MyIp[16];
|
char MyIp[16];
|
||||||
int cred = -1;
|
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)
|
bool ConnectWifi(void)
|
||||||
{
|
{
|
||||||
int i = 0;
|
int i = 0;
|
||||||
|
bool isWifiValid = false;
|
||||||
|
|
||||||
Serial.println("starting scan");
|
Serial.println("starting scan");
|
||||||
// scan for nearby networks:
|
// scan for nearby networks:
|
||||||
|
@ -97,44 +58,65 @@ bool ConnectWifi(void)
|
||||||
for (int i = 0; i < numSsid; i++)
|
for (int i = 0; i < numSsid; i++)
|
||||||
{
|
{
|
||||||
Serial.print(i + 1);
|
Serial.print(i + 1);
|
||||||
Serial.print(") ");
|
Serial.print(". ");
|
||||||
Serial.println(WiFi.SSID(i));
|
Serial.print(WiFi.SSID(i));
|
||||||
|
Serial.print(" ");
|
||||||
|
Serial.println(WiFi.RSSI(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
// search for given credentials
|
// search for given credentials
|
||||||
cred = getWifiToConnect(numSsid);
|
for (CREDENTIAL credential : credentials)
|
||||||
if (cred == -1)
|
|
||||||
{
|
{
|
||||||
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// try to connect
|
// 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.println("");
|
||||||
Serial.print("Connecting to WiFi ");
|
Serial.print("Connecting to WiFi ");
|
||||||
Serial.println(credentials[cred][0]);
|
Serial.println(currentWifi.ssid);
|
||||||
|
|
||||||
i = 0;
|
i = 0;
|
||||||
while (WiFi.status() != WL_CONNECTED)
|
while (WiFi.status() != WL_CONNECTED)
|
||||||
{
|
{
|
||||||
digitalWrite(LED_BUILTIN, LOW);
|
digitalWrite(PIN_LED_BUILTIN, LOW);
|
||||||
delay(300);
|
delay(300);
|
||||||
Serial.print(".");
|
Serial.print(F("."));
|
||||||
digitalWrite(LED_BUILTIN, HIGH);
|
digitalWrite(PIN_LED_BUILTIN, HIGH);
|
||||||
delay(300);
|
delay(300);
|
||||||
if (i++ > 30)
|
if (i++ > 50)
|
||||||
{
|
{
|
||||||
// giving up
|
// giving up
|
||||||
return false;
|
ESP.restart();
|
||||||
|
return false; // gcc shut up
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void mqttDebug(const char* debug_str)
|
void mqttDebug(const char* debug_str)
|
||||||
{
|
{
|
||||||
String s="/watermeter/debug";
|
String s=MQTT_PREFIX"/debug";
|
||||||
mqttClient.publish(s.c_str(), debug_str);
|
mqttClient.publish(s.c_str(), debug_str);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,24 +133,18 @@ void mqttCallback(char* topic, byte* payload, unsigned int len)
|
||||||
Serial.print(" ");
|
Serial.print(" ");
|
||||||
Serial.println((char)payload[0]); // FIXME LEN
|
Serial.println((char)payload[0]); // FIXME LEN
|
||||||
*/
|
*/
|
||||||
if (strstr(topic, "/smarthomeNG/start"))
|
if (strstr(topic, "smarthomeNG/start"))
|
||||||
{
|
{
|
||||||
if (len == 4) // True
|
if (len == 4) // True
|
||||||
{
|
{
|
||||||
// maybe to something
|
// maybe to something
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (strstr(topic, "/espmeter/reset"))
|
else if (strstr(topic, MQTT_PREFIX "/reset"))
|
||||||
{
|
{
|
||||||
if (len == 4) // True
|
if (len == 4) // True
|
||||||
{
|
{
|
||||||
// maybe to something
|
// maybe to something
|
||||||
const char *topic = "/espmeter/reset/status";
|
|
||||||
const char *msg = "False";
|
|
||||||
mqttClient.publish(topic, msg);
|
|
||||||
mqttClient.loop();
|
|
||||||
delay(200);
|
|
||||||
|
|
||||||
// reboot
|
// reboot
|
||||||
ESP.restart();
|
ESP.restart();
|
||||||
}
|
}
|
||||||
|
@ -179,45 +155,59 @@ void mqttCallback(char* topic, byte* payload, unsigned int len)
|
||||||
|
|
||||||
bool mqttConnect()
|
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);
|
mqttClient.setCallback(mqttCallback);
|
||||||
|
|
||||||
// connect client to retainable last will message
|
return connected;
|
||||||
return mqttClient.connect(ESP_NAME, "/watermeter/online", 0, true, "False");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void mqttSubscribe()
|
void mqttSubscribe()
|
||||||
{
|
{
|
||||||
String s;
|
|
||||||
// publish online status
|
// publish online status
|
||||||
s = "/watermeter/online";
|
mqttClient.publish(MQTT_PREFIX "/online", "True", true);
|
||||||
mqttClient.publish(s.c_str(), "True", true);
|
|
||||||
// Serial.print("MQTT-SEND: ");
|
// Serial.print("MQTT-SEND: ");
|
||||||
// Serial.print(s);
|
// Serial.print(s);
|
||||||
// Serial.println(" True");
|
// Serial.println(" True");
|
||||||
|
|
||||||
// publish ip address
|
// publish ip address
|
||||||
s="/watermeter/ipaddr";
|
|
||||||
IPAddress MyIP = WiFi.localIP();
|
IPAddress MyIP = WiFi.localIP();
|
||||||
snprintf(MyIp, 16, "%d.%d.%d.%d", MyIP[0], MyIP[1], MyIP[2], MyIP[3]);
|
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("MQTT-SEND: ");
|
||||||
// Serial.print(s);
|
// Serial.print(s);
|
||||||
// Serial.print(" ");
|
// Serial.print(" ");
|
||||||
// Serial.println(MyIp);
|
// 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
|
// if True -> perform an reset
|
||||||
s = "/espmeter/reset";
|
mqttClient.subscribe(MQTT_PREFIX"/reset");
|
||||||
mqttClient.subscribe(s.c_str());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void setupOTA()
|
void setupOTA()
|
||||||
|
@ -259,23 +249,16 @@ void setupOTA()
|
||||||
ArduinoOTA.begin();
|
ArduinoOTA.begin();
|
||||||
}
|
}
|
||||||
|
|
||||||
// receive encrypted packets -> send it via MQTT to decrypter
|
|
||||||
void waterMeterLoop()
|
|
||||||
{
|
|
||||||
if (waterMeter.isFrameAvailable())
|
|
||||||
{
|
|
||||||
// publish meter info via MQTT
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setup()
|
void setup()
|
||||||
{
|
{
|
||||||
pinMode(LED_BUILTIN, OUTPUT);
|
pinMode(LED_BUILTIN, OUTPUT);
|
||||||
|
|
||||||
Serial.begin(115200);
|
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...");
|
Serial.println("Setup done...");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -286,6 +269,7 @@ enum ControlStateType
|
||||||
, StateMqttConnect
|
, StateMqttConnect
|
||||||
, StateConnected
|
, StateConnected
|
||||||
, StateOperating
|
, StateOperating
|
||||||
|
, StateOperatingNoWifi
|
||||||
};
|
};
|
||||||
ControlStateType ControlState = StateInit;
|
ControlStateType ControlState = StateInit;
|
||||||
|
|
||||||
|
@ -309,7 +293,11 @@ void loop()
|
||||||
case StateWifiConnect:
|
case StateWifiConnect:
|
||||||
//Serial.println("StateWifiConnect:");
|
//Serial.println("StateWifiConnect:");
|
||||||
// station mode
|
// station mode
|
||||||
ConnectWifi();
|
if (ConnectWifi() == false)
|
||||||
|
{
|
||||||
|
ControlState = StateOperatingNoWifi;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
delay(500);
|
delay(500);
|
||||||
|
|
||||||
|
@ -317,15 +305,27 @@ void loop()
|
||||||
{
|
{
|
||||||
Serial.println("");
|
Serial.println("");
|
||||||
Serial.print("Connected to ");
|
Serial.print("Connected to ");
|
||||||
Serial.println(credentials[cred][0]); // FIXME
|
Serial.println(currentWifi.ssid);
|
||||||
Serial.print("IP address: ");
|
Serial.print("IP address: ");
|
||||||
Serial.println(WiFi.localIP());
|
Serial.println(WiFi.localIP());
|
||||||
|
|
||||||
setupOTA();
|
setupOTA();
|
||||||
|
|
||||||
|
if (strlen(currentWifi.mqtt_broker)) // MQTT is used
|
||||||
|
{
|
||||||
|
mqttEnabled = true;
|
||||||
ControlState = StateMqttConnect;
|
ControlState = StateMqttConnect;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
// no MQTT server -> go operating
|
||||||
|
mqttEnabled = false;
|
||||||
|
ControlState = StateOperating;
|
||||||
|
Serial.println(F("MQTT not enabled"));
|
||||||
|
Serial.println(F("StateOperating:"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
Serial.println("");
|
Serial.println("");
|
||||||
Serial.println("Connection failed.");
|
Serial.println("Connection failed.");
|
||||||
|
@ -342,18 +342,20 @@ void loop()
|
||||||
Serial.println("StateMqttConnect:");
|
Serial.println("StateMqttConnect:");
|
||||||
digitalWrite(LED_BUILTIN, HIGH); // off
|
digitalWrite(LED_BUILTIN, HIGH); // off
|
||||||
|
|
||||||
|
waterMeter.enableMqtt(false);
|
||||||
|
|
||||||
if (WiFi.status() != WL_CONNECTED)
|
if (WiFi.status() != WL_CONNECTED)
|
||||||
{
|
{
|
||||||
ControlState = StateNotConnected;
|
ControlState = StateNotConnected;
|
||||||
break; // exit (hopefully) switch statement
|
break; // exit (hopefully) switch statement
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.print("try to connect to MQTT server ");
|
if (mqttEnabled)
|
||||||
Serial.println(credentials[cred][2]); // FIXME
|
{
|
||||||
|
|
||||||
if (mqttConnect())
|
if (mqttConnect())
|
||||||
{
|
{
|
||||||
ControlState = StateConnected;
|
ControlState = StateConnected;
|
||||||
|
waterMeter.enableMqtt(true);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -362,6 +364,12 @@ void loop()
|
||||||
delay(1000);
|
delay(1000);
|
||||||
// try again
|
// try again
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// no MQTT is used at all
|
||||||
|
ControlState = StateConnected;
|
||||||
|
}
|
||||||
ArduinoOTA.handle();
|
ArduinoOTA.handle();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -369,6 +377,8 @@ void loop()
|
||||||
case StateConnected:
|
case StateConnected:
|
||||||
Serial.println("StateConnected:");
|
Serial.println("StateConnected:");
|
||||||
|
|
||||||
|
if (mqttEnabled)
|
||||||
|
{
|
||||||
if (!mqttClient.connected())
|
if (!mqttClient.connected())
|
||||||
{
|
{
|
||||||
ControlState = StateMqttConnect;
|
ControlState = StateMqttConnect;
|
||||||
|
@ -384,6 +394,11 @@ void loop()
|
||||||
Serial.println("StateOperating:");
|
Serial.println("StateOperating:");
|
||||||
//mqttDebug("up and running");
|
//mqttDebug("up and running");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ControlState = StateOperating;
|
||||||
|
}
|
||||||
ArduinoOTA.handle();
|
ArduinoOTA.handle();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -397,21 +412,29 @@ void loop()
|
||||||
break; // exit (hopefully switch statement)
|
break; // exit (hopefully switch statement)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mqttEnabled)
|
||||||
|
{
|
||||||
if (!mqttClient.connected())
|
if (!mqttClient.connected())
|
||||||
{
|
{
|
||||||
Serial.println("not connected to MQTT server");
|
Serial.println("not connected to MQTT server");
|
||||||
ControlState = StateMqttConnect;
|
ControlState = StateMqttConnect;
|
||||||
}
|
}
|
||||||
|
|
||||||
// here we go
|
|
||||||
waterMeterLoop();
|
|
||||||
|
|
||||||
mqttClient.loop();
|
mqttClient.loop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// here we go
|
||||||
|
waterMeter.loop();
|
||||||
|
|
||||||
ArduinoOTA.handle();
|
ArduinoOTA.handle();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case StateOperatingNoWifi:
|
||||||
|
|
||||||
|
waterMeter.loop();
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
Serial.println("Error: invalid ControlState");
|
Serial.println("Error: invalid ControlState");
|
||||||
}
|
}
|
||||||
|
|
103
src/utils.cpp
Normal file
103
src/utils.cpp
Normal 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];
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue