inital commit

This commit is contained in:
Chester 2020-02-29 22:11:52 +01:00
parent bc135ecd66
commit a7976820b2
7 changed files with 1098 additions and 0 deletions

6
.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch
include/credentials.h

41
include/WMbusFrame.h Normal file
View file

@ -0,0 +1,41 @@
#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__

230
include/WaterMeter.h Normal file
View file

@ -0,0 +1,230 @@
/*
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/>.
*/
#ifndef _WATERMETER_H_
#define _WATERMETER_H_
#include <Arduino.h>
#include <SPI.h>
#include "WMBusFrame.h"
#define MARCSTATE_SLEEP 0x00
#define MARCSTATE_IDLE 0x01
#define MARCSTATE_XOFF 0x02
#define MARCSTATE_VCOON_MC 0x03
#define MARCSTATE_REGON_MC 0x04
#define MARCSTATE_MANCAL 0x05
#define MARCSTATE_VCOON 0x06
#define MARCSTATE_REGON 0x07
#define MARCSTATE_STARTCAL 0x08
#define MARCSTATE_BWBOOST 0x09
#define MARCSTATE_FS_LOCK 0x0A
#define MARCSTATE_IFADCON 0x0B
#define MARCSTATE_ENDCAL 0x0C
#define MARCSTATE_RX 0x0D
#define MARCSTATE_RX_END 0x0E
#define MARCSTATE_RX_RST 0x0F
#define MARCSTATE_TXRX_SWITCH 0x10
#define MARCSTATE_RXFIFO_OVERFLOW 0x11
#define MARCSTATE_FSTXON 0x12
#define MARCSTATE_TX 0x13
#define MARCSTATE_TX_END 0x14
#define MARCSTATE_RXTX_SWITCH 0x15
#define MARCSTATE_TXFIFO_UNDERFLOW 0x16
#define CC1101_GDO0 D2 // GDO0 input interrupt pin
#define WRITE_BURST 0x40
#define READ_SINGLE 0x80
#define READ_BURST 0xC0
#define CC1101_CONFIG_REGISTER READ_SINGLE
#define CC1101_STATUS_REGISTER READ_BURST
#define CC1101_PATABLE 0x3E // PATABLE address
#define CC1101_TXFIFO 0x3F // TX FIFO address
#define CC1101_RXFIFO 0x3F // RX FIFO address
#define CC1101_SRES 0x30 // Reset CC1101 chip
#define CC1101_SFSTXON 0x31 // Enable and calibrate frequency synthesizer (if MCSM0.FS_AUTOCAL=1). If in RX (with CCA):
// Go to a wait state where only the synthesizer is running (for quick RX / TX turnaround).
#define CC1101_SXOFF 0x32 // Turn off crystal oscillator
#define CC1101_SCAL 0x33 // Calibrate frequency synthesizer and turn it off. SCAL can be strobed from IDLE mode without
// setting manual calibration mode (MCSM0.FS_AUTOCAL=0)
#define CC1101_SRX 0x34 // Enable RX. Perform calibration first if coming from IDLE and MCSM0.FS_AUTOCAL=1
#define CC1101_STX 0x35 // In IDLE state: Enable TX. Perform calibration first if MCSM0.FS_AUTOCAL=1.
// If in RX state and CCA is enabled: Only go to TX if channel is clear
#define CC1101_SIDLE 0x36 // Exit RX / TX, turn off frequency synthesizer and exit Wake-On-Radio mode if applicable
#define CC1101_SWOR 0x38 // Start automatic RX polling sequence (Wake-on-Radio) as described in Section 19.5 if
// WORCTRL.RC_PD=0
#define CC1101_SPWD 0x39 // Enter power down mode when CSn goes high
#define CC1101_SFRX 0x3A // Flush the RX FIFO buffer. Only issue SFRX in IDLE or RXFIFO_OVERFLOW states
#define CC1101_SFTX 0x3B // Flush the TX FIFO buffer. Only issue SFTX in IDLE or TXFIFO_UNDERFLOW states
#define CC1101_SWORRST 0x3C // Reset real time clock to Event1 value
#define CC1101_SNOP 0x3D // No operation. May be used to get access to the chip status byte
#define CC1101_IOCFG2 0x00 // GDO2 Output Pin Configuration
#define CC1101_IOCFG1 0x01 // GDO1 Output Pin Configuration
#define CC1101_IOCFG0 0x02 // GDO0 Output Pin Configuration
#define CC1101_FIFOTHR 0x03 // RX FIFO and TX FIFO Thresholds
#define CC1101_SYNC1 0x04 // Sync Word, High Byte
#define CC1101_SYNC0 0x05 // Sync Word, Low Byte
#define CC1101_PKTLEN 0x06 // Packet Length
#define CC1101_PKTCTRL1 0x07 // Packet Automation Control
#define CC1101_PKTCTRL0 0x08 // Packet Automation Control
#define CC1101_ADDR 0x09 // Device Address
#define CC1101_CHANNR 0x0A // Channel Number
#define CC1101_FSCTRL1 0x0B // Frequency Synthesizer Control
#define CC1101_FSCTRL0 0x0C // Frequency Synthesizer Control
#define CC1101_FREQ2 0x0D // Frequency Control Word, High Byte
#define CC1101_FREQ1 0x0E // Frequency Control Word, Middle Byte
#define CC1101_FREQ0 0x0F // Frequency Control Word, Low Byte
#define CC1101_MDMCFG4 0x10 // Modem Configuration
#define CC1101_MDMCFG3 0x11 // Modem Configuration
#define CC1101_MDMCFG2 0x12 // Modem Configuration
#define CC1101_MDMCFG1 0x13 // Modem Configuration
#define CC1101_MDMCFG0 0x14 // Modem Configuration
#define CC1101_DEVIATN 0x15 // Modem Deviation Setting
#define CC1101_MCSM2 0x16 // Main Radio Control State Machine Configuration
#define CC1101_MCSM1 0x17 // Main Radio Control State Machine Configuration
#define CC1101_MCSM0 0x18 // Main Radio Control State Machine Configuration
#define CC1101_FOCCFG 0x19 // Frequency Offset Compensation Configuration
#define CC1101_BSCFG 0x1A // Bit Synchronization Configuration
#define CC1101_AGCCTRL2 0x1B // AGC Control
#define CC1101_AGCCTRL1 0x1C // AGC Control
#define CC1101_AGCCTRL0 0x1D // AGC Control
#define CC1101_WOREVT1 0x1E // High Byte Event0 Timeout
#define CC1101_WOREVT0 0x1F // Low Byte Event0 Timeout
#define CC1101_WORCTRL 0x20 // Wake On Radio Control
#define CC1101_FREND1 0x21 // Front End RX Configuration
#define CC1101_FREND0 0x22 // Front End TX Configuration
#define CC1101_FSCAL3 0x23 // Frequency Synthesizer Calibration
#define CC1101_FSCAL2 0x24 // Frequency Synthesizer Calibration
#define CC1101_FSCAL1 0x25 // Frequency Synthesizer Calibration
#define CC1101_FSCAL0 0x26 // Frequency Synthesizer Calibration
#define CC1101_RCCTRL1 0x27 // RC Oscillator Configuration
#define CC1101_RCCTRL0 0x28 // RC Oscillator Configuration
#define CC1101_FSTEST 0x29 // Frequency Synthesizer Calibration Control
#define CC1101_PTEST 0x2A // Production Test
#define CC1101_AGCTEST 0x2B // AGC Test
#define CC1101_TEST2 0x2C // Various Test Settings
#define CC1101_TEST1 0x2D // Various Test Settings
#define CC1101_TEST0 0x2E // Various Test Settings
#define CC1101_PARTNUM 0x30 // Chip ID
#define CC1101_VERSION 0x31 // Chip ID
#define CC1101_FREQEST 0x32 // Frequency Offset Estimate from Demodulator
#define CC1101_LQI 0x33 // Demodulator Estimate for Link Quality
#define CC1101_RSSI 0x34 // Received Signal Strength Indication
#define CC1101_MARCSTATE 0x35 // Main Radio Control State Machine State
#define CC1101_WORTIME1 0x36 // High Byte of WOR Time
#define CC1101_WORTIME0 0x37 // Low Byte of WOR Time
#define CC1101_PKTSTATUS 0x38 // Current GDOx Status and Packet Status
#define CC1101_VCO_VC_DAC 0x39 // Current Setting from PLL Calibration Module
#define CC1101_TXBYTES 0x3A // Underflow and Number of Bytes
#define CC1101_RXBYTES 0x3B // Overflow and Number of Bytes
#define CC1101_RCCTRL1_STATUS 0x3C // Last RC Oscillator Calibration Result
#define CC1101_RCCTRL0_STATUS 0x3D // Last RC Oscillator Calibration Result
#define CC1101_DEFVAL_SYNC1 0x54 // Synchronization word, high byte
#define CC1101_DEFVAL_SYNC0 0x3D // Synchronization word, low byte
#define CC1101_DEFVAL_MCSM1 0x00 // Main Radio Control State Machine Configuration
#define CC1101_DEFVAL_IOCFG2 0x2E // GDO2 Output Pin Configuration
#define CC1101_DEFVAL_IOCFG0 0x06 // GDO0 Output Pin Configuration
#define CC1101_DEFVAL_FSCTRL1 0x08 // Frequency Synthesizer Control
#define CC1101_DEFVAL_FSCTRL0 0x00 // Frequency Synthesizer Control
#define CC1101_DEFVAL_FREQ2 0x21 // Frequency Control Word, High Byte
#define CC1101_DEFVAL_FREQ1 0x6B // Frequency Control Word, Middle Byte
#define CC1101_DEFVAL_FREQ0 0xD0 // Frequency Control Word, Low Byte
#define CC1101_DEFVAL_MDMCFG4 0x5C // Modem configuration. Speed = 103 Kbps
#define CC1101_DEFVAL_MDMCFG3 0x04 // Modem Configuration
#define CC1101_DEFVAL_MDMCFG2 0x06 // Modem Configuration
#define CC1101_DEFVAL_MDMCFG1 0x22 // Modem Configuration
#define CC1101_DEFVAL_MDMCFG0 0xF8 // Modem Configuration
#define CC1101_DEFVAL_CHANNR 0x00 // Channel Number
#define CC1101_DEFVAL_DEVIATN 0x44 // Modem Deviation Setting
#define CC1101_DEFVAL_FREND1 0xB6 // Front End RX Configuration
#define CC1101_DEFVAL_FREND0 0x10 // Front End TX Configuration
#define CC1101_DEFVAL_MCSM0 0x18 // Main Radio Control State Machine Configuration
#define CC1101_DEFVAL_FOCCFG 0x2E // Frequency Offset Compensation Configuration
#define CC1101_DEFVAL_BSCFG 0xBF // Bit Synchronization Configuration
#define CC1101_DEFVAL_AGCCTRL2 0x43 // AGC Control
#define CC1101_DEFVAL_AGCCTRL1 0x09 // AGC Control
#define CC1101_DEFVAL_AGCCTRL0 0xB5 // AGC Control
#define CC1101_DEFVAL_FSCAL3 0xEA // Frequency Synthesizer Calibration
#define CC1101_DEFVAL_FSCAL2 0x2A // Frequency Synthesizer Calibration
#define CC1101_DEFVAL_FSCAL1 0x00 // Frequency Synthesizer Calibration
#define CC1101_DEFVAL_FSCAL0 0x1F // Frequency Synthesizer Calibration
#define CC1101_DEFVAL_FSTEST 0x59 // Frequency Synthesizer Calibration Control
#define CC1101_DEFVAL_TEST2 0x81 // Various Test Settings
#define CC1101_DEFVAL_TEST1 0x35 // Various Test Settings
#define CC1101_DEFVAL_TEST0 0x09 // Various Test Settings
#define CC1101_DEFVAL_PKTCTRL1 0x00 // Packet Automation Control
#define CC1101_DEFVAL_PKTCTRL0 0x02 // 2 - infinite length
#define CC1101_DEFVAL_ADDR 0x00 // Device Address
#define CC1101_DEFVAL_PKTLEN 0x30 // Packet Length
#define CC1101_DEFVAL_FIFOTHR 0x00 // RX 4 bytes and TX 61 bytes Thresholds
class WaterMeter
{
private:
inline void selectCC1101(void);
inline void deselectCC1101(void);
inline void waitMiso(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 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);
// receive a wmbus frame
void receive(WMBusFrame *payload);
public:
// constructor
WaterMeter(void);
// startup CC1101 for receiving wmbus mode c
void begin();
// must be called frequently, returns true if a valid frame was received
bool isFrameAvailable(void);
};
#endif // _WATERMETER_H_

22
platformio.ini Normal file
View file

@ -0,0 +1,22 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[env:nodemcuv2]
platform = espressif8266
board = nodemcuv2
framework = arduino
lib_deps =
Crypto
; OTA
upload_port = 10.0.0.86
#upload_port = 10.14.0.139
upload_protocol = espota

115
src/WMBusFrame.cpp Normal file
View file

@ -0,0 +1,115 @@
/*
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);
}

265
src/WaterMeter.cpp Normal file
View file

@ -0,0 +1,265 @@
/*
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 "WaterMeter.h"
WaterMeter::WaterMeter()
{
}
// ChipSelect assert
inline void WaterMeter::selectCC1101(void)
{
digitalWrite(SS, LOW);
}
// ChipSelect deassert
inline void WaterMeter::deselectCC1101(void)
{
digitalWrite(SS, HIGH);
}
// wait for MISO pulling down
inline void WaterMeter::waitMiso(void)
{
while(digitalRead(MISO) == HIGH);
}
// write a single register of CC1101
void WaterMeter::writeReg(uint8 regAddr, uint8 value)
{
selectCC1101(); // Select CC1101
waitMiso(); // Wait until MISO goes low
SPI.transfer(regAddr); // Send register address
SPI.transfer(value); // Send value
deselectCC1101(); // Deselect CC1101
}
// send a strobe command to CC1101
void WaterMeter::cmdStrobe(uint8 cmd)
{
selectCC1101(); // Select CC1101
delayMicroseconds(5);
waitMiso(); // Wait until MISO goes low
SPI.transfer(cmd); // Send strobe command
delayMicroseconds(5);
deselectCC1101(); // Deselect CC1101
}
// read CC1101 register (status or configuration)
uint8 WaterMeter::readReg(uint8 regAddr, uint8 regType)
{
uint8 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
return val;
}
//
void WaterMeter::readBurstReg(uint8 * buffer, uint8 regAddr, uint8 len)
{
uint8 addr, i;
addr = regAddr | READ_BURST;
selectCC1101(); // Select CC1101
delayMicroseconds(5);
waitMiso(); // Wait until MISO goes low
SPI.transfer(addr); // Send register address
for(i=0 ; i<len ; i++)
buffer[i] = SPI.transfer(0x00); // Read result byte by byte
delayMicroseconds(2);
deselectCC1101(); // Deselect CC1101
}
// power on reset
void WaterMeter::reset(void)
{
deselectCC1101(); // Deselect CC1101
delayMicroseconds(3);
digitalWrite(MOSI, LOW);
digitalWrite(SCK, HIGH); // see CC1101 datasheet 11.3
selectCC1101(); // Select CC1101
delayMicroseconds(3);
deselectCC1101(); // Deselect CC1101
delayMicroseconds(45); // at least 40 us
selectCC1101(); // Select CC1101
waitMiso(); // Wait until MISO goes low
SPI.transfer(CC1101_SRES); // Send reset command strobe
waitMiso(); // Wait until MISO goes low
deselectCC1101(); // Deselect CC1101
}
// set IDLE state, flush FIFO and (re)start receiver
void WaterMeter::startReceiver(void)
{
cmdStrobe(CC1101_SIDLE); // Enter IDLE state
while (readReg(CC1101_MARCSTATE, CC1101_STATUS_REGISTER) != MARCSTATE_IDLE);
{
delay(1);
}
cmdStrobe(CC1101_SFRX); // flush receive queue
cmdStrobe(CC1101_SRX); // Enter RX state
while (readReg(CC1101_MARCSTATE, CC1101_STATUS_REGISTER) != MARCSTATE_RX);
{
delay(1);
}
}
// initialize all the CC1101 registers
void WaterMeter::initializeRegisters(void)
{
writeReg(CC1101_IOCFG2, CC1101_DEFVAL_IOCFG2);
writeReg(CC1101_IOCFG0, CC1101_DEFVAL_IOCFG0);
writeReg(CC1101_FIFOTHR, CC1101_DEFVAL_FIFOTHR);
writeReg(CC1101_PKTLEN, CC1101_DEFVAL_PKTLEN);
writeReg(CC1101_PKTCTRL1, CC1101_DEFVAL_PKTCTRL1);
writeReg(CC1101_PKTCTRL0, CC1101_DEFVAL_PKTCTRL0);
writeReg(CC1101_SYNC1, CC1101_DEFVAL_SYNC1);
writeReg(CC1101_SYNC0, CC1101_DEFVAL_SYNC0);
writeReg(CC1101_ADDR, CC1101_DEFVAL_ADDR);
writeReg(CC1101_CHANNR, CC1101_DEFVAL_CHANNR);
writeReg(CC1101_FSCTRL1, CC1101_DEFVAL_FSCTRL1);
writeReg(CC1101_FSCTRL0, CC1101_DEFVAL_FSCTRL0);
writeReg(CC1101_FREQ2, CC1101_DEFVAL_FREQ2);
writeReg(CC1101_FREQ1, CC1101_DEFVAL_FREQ1);
writeReg(CC1101_FREQ0, CC1101_DEFVAL_FREQ0);
writeReg(CC1101_MDMCFG4, CC1101_DEFVAL_MDMCFG4);
writeReg(CC1101_MDMCFG3, CC1101_DEFVAL_MDMCFG3);
writeReg(CC1101_MDMCFG2, CC1101_DEFVAL_MDMCFG2);
writeReg(CC1101_MDMCFG1, CC1101_DEFVAL_MDMCFG1);
writeReg(CC1101_MDMCFG0, CC1101_DEFVAL_MDMCFG0);
writeReg(CC1101_DEVIATN, CC1101_DEFVAL_DEVIATN);
writeReg(CC1101_MCSM1, CC1101_DEFVAL_MCSM1);
writeReg(CC1101_MCSM0, CC1101_DEFVAL_MCSM0);
writeReg(CC1101_FOCCFG, CC1101_DEFVAL_FOCCFG);
writeReg(CC1101_BSCFG, CC1101_DEFVAL_BSCFG);
writeReg(CC1101_AGCCTRL2, CC1101_DEFVAL_AGCCTRL2);
writeReg(CC1101_AGCCTRL1, CC1101_DEFVAL_AGCCTRL1);
writeReg(CC1101_AGCCTRL0, CC1101_DEFVAL_AGCCTRL0);
writeReg(CC1101_FREND1, CC1101_DEFVAL_FREND1);
writeReg(CC1101_FREND0, CC1101_DEFVAL_FREND0);
writeReg(CC1101_FSCAL3, CC1101_DEFVAL_FSCAL3);
writeReg(CC1101_FSCAL2, CC1101_DEFVAL_FSCAL2);
writeReg(CC1101_FSCAL1, CC1101_DEFVAL_FSCAL1);
writeReg(CC1101_FSCAL0, CC1101_DEFVAL_FSCAL0);
writeReg(CC1101_FSTEST, CC1101_DEFVAL_FSTEST);
writeReg(CC1101_TEST2, CC1101_DEFVAL_TEST2);
writeReg(CC1101_TEST1, CC1101_DEFVAL_TEST1);
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) {
// set the flag that a package is available
packetAvailable = true;
}
// should be called frequently, handles the ISR flag
// does the frame checkin and decryption
bool WaterMeter::isFrameAvailable(void)
{
if (packetAvailable)
{
//Serial.println("packet received");
// Disable wireless reception interrupt
detachInterrupt(digitalPinToInterrupt(CC1101_GDO0));
// clear the flag
packetAvailable = false;
WMBusFrame frame;
receive(&frame);
// Enable wireless reception interrupt
attachInterrupt(digitalPinToInterrupt(CC1101_GDO0), GD0_ISR, FALLING);
return frame.isValid;
}
return false;
}
// Initialize CC1101 to receive WMBus MODE C1
void WaterMeter::begin()
{
pinMode(SS, OUTPUT); // SS Pin -> Output
SPI.begin(); // Initialize SPI interface
pinMode(CC1101_GDO0, INPUT); // Config GDO0 as input
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();
}
// reads a single byte from the RX fifo
uint8_t WaterMeter::readByteFromFifo(void)
{
return readReg(CC1101_RXFIFO, CC1101_CONFIG_REGISTER);
}
// handles a received frame and restart the CC1101 receiver
void WaterMeter::receive(WMBusFrame * frame)
{
// read preamble, should be 0x543D
uint8_t p1 = readByteFromFifo();
uint8_t p2 = readByteFromFifo();
//Serial.printf("%02x%02x", p1, p2);
uint8_t payloadLength = readByteFromFifo();
// is it Mode C1, frame B and does it fit in the buffer
if ( (payloadLength < WMBusFrame::MAX_LENGTH )
&& (p1 == 0x54) && (p2 == 0x3D) )
{
// 3rd byte is payload length
frame->length = payloadLength;
//Serial.printf("%02X", lfield);
// starting with 1! index 0 is lfield
for (int i = 0; i < payloadLength; i++)
{
frame->payload[i] = readByteFromFifo();
}
// do some checks: my meterId, crc ok
frame->decode();
}
// flush RX fifo and restart receiver
startReceiver();
//Serial.printf("rxStatus: 0x%02x\n\r", readStatusReg(CC1101_RXBYTES));
}

419
src/main.cpp Normal file
View file

@ -0,0 +1,419 @@
/*
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 <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <SoftwareSerial.h>
#include <WiFiUdp.h>
#include <PubSubClient.h>
#include <ArduinoOTA.h>
#include "WaterMeter.h"
#include "credentials.h"
#define ESP_NAME "ESP-Meter"
// 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 DEBUG 0
//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;
PubSubClient mqttClient(espMqttClient);
char MyIp[16];
int cred = -1;
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;
Serial.println("starting scan");
// scan for nearby networks:
int numSsid = WiFi.scanNetworks();
Serial.print("scanning WIFI, found ");
Serial.print(numSsid);
Serial.println(" available access points:");
if (numSsid == -1)
{
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));
}
// search for given credentials
cred = getWifiToConnect(numSsid);
if (cred == -1)
{
Serial.println("No Wifi!");
return false;
}
// try to connect
WiFi.begin(credentials[cred][0], credentials[cred][1]);
Serial.println("");
Serial.print("Connecting to WiFi ");
Serial.println(credentials[cred][0]);
i = 0;
while (WiFi.status() != WL_CONNECTED)
{
digitalWrite(LED_BUILTIN, LOW);
delay(300);
Serial.print(".");
digitalWrite(LED_BUILTIN, HIGH);
delay(300);
if (i++ > 30)
{
// giving up
return false;
}
}
return true;
}
void mqttDebug(const char* debug_str)
{
String s="/watermeter/debug";
mqttClient.publish(s.c_str(), debug_str);
}
void mqttCallback(char* topic, byte* payload, unsigned int len)
{
// create a local copies of topic and payload
// PubSubClient overwrites it internally
String t(topic);
byte *p = new byte[len];
memcpy(p, payload, len);
/* Serial.print("MQTT-RECV: ");
Serial.print(topic);
Serial.print(" ");
Serial.println((char)payload[0]); // FIXME LEN
*/
if (strstr(topic, "/smarthomeNG/start"))
{
if (len == 4) // True
{
// maybe to something
}
}
else if (strstr(topic, "/espmeter/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();
}
}
// and of course, free it
delete[] p;
}
bool mqttConnect()
{
mqttClient.setServer(credentials[cred][2], 1883);
mqttClient.setCallback(mqttCallback);
// connect client to retainable last will message
return mqttClient.connect(ESP_NAME, "/watermeter/online", 0, true, "False");
}
void mqttSubscribe()
{
String s;
// publish online status
s = "/watermeter/online";
mqttClient.publish(s.c_str(), "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);
// 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());
}
void setupOTA()
{
// Port defaults to 8266
// ArduinoOTA.setPort(8266);
// Hostname defaults to esp8266-[ChipID]
ArduinoOTA.setHostname(ESP_NAME);
// No authentication by default
// ArduinoOTA.setPassword((const char *)"123");
ArduinoOTA.onStart([]() {
String type;
if (ArduinoOTA.getCommand() == U_FLASH) {
type = "sketch";
} else { // U_FS
type = "filesystem";
}
// NOTE: if updating FS this would be the place to unmount FS using FS.end()
Serial.println("Start updating " + type);
});
ArduinoOTA.onEnd([]() {
Serial.println("\nEnd");
});
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
});
ArduinoOTA.onError([](ota_error_t error) {
Serial.printf("Error[%u]: ", error);
if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
else if (error == OTA_END_ERROR) Serial.println("End Failed");
});
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();
Serial.println("Setup done...");
}
enum ControlStateType
{ StateInit
, StateNotConnected
, StateWifiConnect
, StateMqttConnect
, StateConnected
, StateOperating
};
ControlStateType ControlState = StateInit;
void loop()
{
switch (ControlState)
{
case StateInit:
//Serial.println("StateInit:");
WiFi.mode(WIFI_STA);
ControlState = StateNotConnected;
break;
case StateNotConnected:
//Serial.println("StateNotConnected:");
ControlState = StateWifiConnect;
break;
case StateWifiConnect:
//Serial.println("StateWifiConnect:");
// station mode
ConnectWifi();
delay(500);
if (WiFi.status() == WL_CONNECTED)
{
Serial.println("");
Serial.print("Connected to ");
Serial.println(credentials[cred][0]); // FIXME
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
setupOTA();
ControlState = StateMqttConnect;
}
else
{
Serial.println("");
Serial.println("Connection failed.");
// try again
ControlState = StateNotConnected;
// reboot
ESP.restart();
}
break;
case StateMqttConnect:
Serial.println("StateMqttConnect:");
digitalWrite(LED_BUILTIN, HIGH); // off
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())
{
ControlState = StateConnected;
}
else
{
Serial.println("MQTT connect failed");
delay(1000);
// try again
}
ArduinoOTA.handle();
break;
case StateConnected:
Serial.println("StateConnected:");
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");
}
ArduinoOTA.handle();
break;
case StateOperating:
//Serial.println("StateOperating:");
if (WiFi.status() != WL_CONNECTED)
{
ControlState = StateWifiConnect;
break; // exit (hopefully switch statement)
}
if (!mqttClient.connected())
{
Serial.println("not connected to MQTT server");
ControlState = StateMqttConnect;
}
// here we go
waterMeterLoop();
mqttClient.loop();
ArduinoOTA.handle();
break;
default:
Serial.println("Error: invalid ControlState");
}
}