diff --git a/README.md b/README.md
index 8043da2..9964cc0 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,79 @@
# 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.
-The Multical21 is sending every 16 seconds wireless MBus frames (Mode C1, frame type B). The encrypted
-frames are received from the ESP8266 an it decrypts them with AES-128-CTR. The meter information
-(total counter, target counter, medium temperature, ambient temperature, alalm flags (BURST, LEAK, DRY,
-REVERSE) are sent via MQTT to a smarthomeNG/smartVISU service (running on a raspberry).
+
+A CC1101 868 MHz module is connected via SPI to the ESP8266/ESP32.
+The Multical21 is transmitting encrypted MBus frames (Mode C1, frame type B) every 16 seconds.
+The ESP8266/ESP32 does some validation (right serial number, crc checking) and then
+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:
+
+- total counter - total water consumption in m³
+
- target counter - water consumption till 1. day of the current month
+
- medium temperature - in °C
+
- ambient temperature - in °C
+
- info codes - BURST, LEAK, DRY, REVERSE, TAMPER, RADIO OFF
+
+
+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 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.
diff --git a/include/WMbusFrame.h b/include/WMbusFrame.h
deleted file mode 100644
index e59d8da..0000000
--- a/include/WMbusFrame.h
+++ /dev/null
@@ -1,41 +0,0 @@
-#ifndef __WMBUS_FRAME__
-#define __WMBUS_FRAME__
-
-#include
-#include
-#include
-#include
-#include "credentials.h"
-
-class WMBusFrame
-{
- public:
- static const uint8_t MAX_LENGTH = 64;
- private:
- CTR 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__
diff --git a/include/WaterMeter.h b/include/WaterMeter.h
index 60821e2..9c32d98 100644
--- a/include/WaterMeter.h
+++ b/include/WaterMeter.h
@@ -17,7 +17,12 @@
#include
#include
-#include "WMBusFrame.h"
+#include
+#include
+#include
+#include
+#include "config.h"
+#include "utils.h"
#define MARCSTATE_SLEEP 0x00
#define MARCSTATE_IDLE 0x01
@@ -179,50 +184,72 @@
class WaterMeter
{
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 deselectCC1101(void);
inline void waitMiso(void);
+ static const uint8_t MAX_LENGTH = 64;
+ CTR 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
void startReceiver(void);
- // burst write registers of cc1101
- void writeBurstReg(uint8_t regaddr, uint8_t* buffer, uint8_t len);
-
- // burst read registers of cc1101
+ //void writeBurstReg(uint8_t regaddr, uint8_t* buffer, uint8_t len);
void readBurstReg(uint8_t * buffer, uint8_t regaddr, uint8_t len);
-
- // strobe command
void cmdStrobe(uint8_t cmd);
-
- // read a register of cc1101
uint8_t readReg(uint8_t regaddr, uint8_t regtype);
-
- // read a byte from fifo
uint8_t readByteFromFifo(void);
-
- // write a register of cc1101
void writeReg(uint8_t regaddr, uint8_t value);
-
- // initialize cc1101 registers
void initializeRegisters(void);
-
- // reset cc1101
void reset(void);
+ // static ISR calls instanceISR via this pointer
+ IRAM_ATTR static void cc1101Isr(void *p);
+
// 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:
// constructor
- WaterMeter(void);
+ WaterMeter(PubSubClient &mqtt);
+
+ void enableMqtt(bool enable);
// 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
- bool isFrameAvailable(void);
+ // must be called frequently
+ void loop(void);
+
+ IRAM_ATTR void instanceCC1101Isr();
};
-#endif // _WATERMETER_H_
\ No newline at end of file
+#endif // _WATERMETER_H_
diff --git a/include/config_template.h b/include/config_template.h
new file mode 100644
index 0000000..87cbacc
--- /dev/null
+++ b/include/config_template.h
@@ -0,0 +1,67 @@
+#ifndef __CONFIG_H__
+#define __CONFIG_H__
+
+#include
+
+#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 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__
\ No newline at end of file
diff --git a/include/credentials_template.h b/include/credentials_template.h
new file mode 100644
index 0000000..08f1eeb
--- /dev/null
+++ b/include/credentials_template.h
@@ -0,0 +1,31 @@
+#ifndef __CREDENTIALS_H__
+#define __CREDENTIALS_H__
+
+#include
+
+// 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 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__
\ No newline at end of file
diff --git a/include/hwconfig.h b/include/hwconfig.h
deleted file mode 100644
index dc97e52..0000000
--- a/include/hwconfig.h
+++ /dev/null
@@ -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__
\ No newline at end of file
diff --git a/include/utils.h b/include/utils.h
new file mode 100644
index 0000000..18acc99
--- /dev/null
+++ b/include/utils.h
@@ -0,0 +1,15 @@
+#ifndef __UTILS_H__
+#define __UTILS_H__
+
+#include
+#include
+
+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__
\ No newline at end of file
diff --git a/multical21.png b/multical21.png
new file mode 100644
index 0000000..e6a6052
Binary files /dev/null and b/multical21.png differ
diff --git a/platformio.ini b/platformio.ini
index aadf99b..cf7ac8f 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -9,8 +9,8 @@
; https://docs.platformio.org/page/projectconf.html
[platformio]
-;default_envs = esp8266-test
-default_envs = esp32, esp8266
+default_envs = esp8266
+;default_envs = esp32, esp8266
[env:esp32]
framework = arduino
@@ -25,6 +25,7 @@ platform = espressif8266
board = d1_mini_lite
board_build.mcu = esp8266
lib_deps = rweather/Crypto @ ^0.2.0
+monitor_speed=115200
; OTA
-;upload_port = 10.0.0.86
-;upload_protocol = espota
\ No newline at end of file
+upload_port = 10.0.0.131
+upload_protocol = espota
\ No newline at end of file
diff --git a/src/WMBusFrame.cpp b/src/WMBusFrame.cpp
deleted file mode 100644
index c3c484d..0000000
--- a/src/WMBusFrame.cpp
+++ /dev/null
@@ -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 .
-*/
-
-#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);
-}
\ No newline at end of file
diff --git a/src/WaterMeter.cpp b/src/WaterMeter.cpp
index 7b1682e..9363124 100644
--- a/src/WaterMeter.cpp
+++ b/src/WaterMeter.cpp
@@ -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 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));
}
\ No newline at end of file
diff --git a/src/main.cpp b/src/main.cpp
index 10283ad..156ccdf 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -15,7 +15,6 @@
#if defined(ESP8266)
#include
#include
- #include
#elif defined(ESP32)
#include
#include
@@ -23,62 +22,24 @@
#include
#include
#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");
}
diff --git a/src/utils.cpp b/src/utils.cpp
new file mode 100644
index 0000000..fc2c451
--- /dev/null
+++ b/src/utils.cpp
@@ -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];
+}
\ No newline at end of file