From 0553b9907f912e56a2cd2a08b03a83ce6f2a75c6 Mon Sep 17 00:00:00 2001 From: manuel Date: Tue, 17 Mar 2020 12:18:56 +0100 Subject: Add martin/door --- martin/door/.gitignore | 5 + martin/door/platformio.ini | 29 ++++ martin/door/src/cc1101.cpp | 395 +++++++++++++++++++++++++++++++++++++++++++++ martin/door/src/cc1101.h | 158 ++++++++++++++++++ martin/door/src/hcs200.cpp | 242 +++++++++++++++++++++++++++ martin/door/src/hcs200.h | 97 +++++++++++ martin/door/src/main.cpp | 171 ++++++++++++++++++++ 7 files changed, 1097 insertions(+) create mode 100644 martin/door/.gitignore create mode 100644 martin/door/platformio.ini create mode 100644 martin/door/src/cc1101.cpp create mode 100644 martin/door/src/cc1101.h create mode 100644 martin/door/src/hcs200.cpp create mode 100644 martin/door/src/hcs200.h create mode 100644 martin/door/src/main.cpp (limited to 'martin') diff --git a/martin/door/.gitignore b/martin/door/.gitignore new file mode 100644 index 0000000..89cc49c --- /dev/null +++ b/martin/door/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/martin/door/platformio.ini b/martin/door/platformio.ini new file mode 100644 index 0000000..1a3b624 --- /dev/null +++ b/martin/door/platformio.ini @@ -0,0 +1,29 @@ +;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 + +[platformio] +default_envs = d1_mini + +[common_env_data] +lib_deps = + +[env:nodemcuv2] +platform = espressif8266 +framework = arduino +board = nodemcuv2 +lib_deps = ${common_env_data.lib_deps} +monitor_speed = 9600 + +[env:d1_mini] +platform = espressif8266 +framework = arduino +board = d1_mini +lib_deps = ${common_env_data.lib_deps} +monitor_speed = 9600 diff --git a/martin/door/src/cc1101.cpp b/martin/door/src/cc1101.cpp new file mode 100644 index 0000000..86129db --- /dev/null +++ b/martin/door/src/cc1101.cpp @@ -0,0 +1,395 @@ +#include +#if defined(LINUX_ARCH_RASPBERRYPI) +#include +#include +#elif defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) +#include +#endif + +#include "cc1101.h" + +static const uint8_t cc1101_init[] = { + // IDX NAME RESET COMMENT + 0x0D, // 00 IOCFG2 29 GDO2 as serial output + 0x2E, // 01 IOCFG1 Tri-State + 0x2E, // 02 IOCFG0 3F GDO0 for input + 0x47, // 03 FIFOTHR RX filter bandwidth = 325 kHz, FIFOTHR = 0x47 + 0xD3, // 04 SYNC1 + 0x91, // 05 SYNC0 + 0x3D, // 06 PKTLEN 0F + 0x04, // 07 PKTCTRL1 + 0x32, // 08 PKTCTRL0 45 + 0x00, // 09 ADDR + 0x00, // 0A CHANNR + 0x06, // 0B FSCTRL1 0F 152kHz IF Frquency + 0x00, // 0C FSCTRL0 + 0x10, // 0D FREQ2 1E Freq #12 Reg Pos 0C + 0xB0, // 0E FREQ1 C4 Reg Pos 0D + 0x71, // 0F FREQ0 EC Reg Pos 0E + 0x57, // 10 MDMCFG4 8C bWidth 325kHz + 0xC4, // 11 MDMCFG3 22 DataRate + 0x30, // 12 MDMCFG2 02 Modulation: ASK + 0x23, // 13 MDMCFG1 22 + 0xb9, // 14 MDMCFG0 F8 ChannelSpace: 350kHz + 0x00, // 15 DEVIATN 47 + 0x07, // 16 MCSM2 07 + 0x00, // 17 MCSM1 30 Bit 3:2 RXOFF_MODE: Select what should happen when a packet has been received: 0 = IDLE 3 = Stay in RX #### + 0x18, // 18 MCSM0 04 Calibration: RX/TX->IDLE + 0x14, // 19 FOCCFG 36 + 0x6C, // 1A BSCFG + 0x07, // 1B AGCCTRL2 03 42 dB instead of 33dB + 0x00, // 1C AGCCTRL1 40 + 0x91, // 1D AGCCTRL0 91 8dB decision boundery + 0x87, // 1E WOREVT1 + 0x6B, // 1F WOREVT0 + 0xF8, // 20 WORCTRL + 0xB6, // 21 FREND1 B6 RX filter bandwidth > 101 kHz, FREND1 = 0xB6 + 0x11, // 22 FREND0 16 0x11 for no PA ramping + 0xE9, // 23 FSCAL3 A9 E9 ?? + 0x2A, // 24 FSCAL2 0A + 0x00, // 25 FSCAL1 20 19 ?? + 0x1F, // 26 FSCAL0 0D + 0x41, // 27 RCCTRL1 + 0x00, // 28 RCCTRL0 +}; + +static const uint8_t patable_power_315[8] = { 0x17, 0x1D, 0x26, 0x69, 0x51, 0x86, 0xCC, 0xC3 }; +static const uint8_t patable_power_434[8] = { 0x6C, 0x1C, 0x06, 0x3A, 0x51, 0x85, 0xC8, 0xC0 }; +static const uint8_t patable_power_868[8] = { 0x00, 0x17, 0x1D, 0x26, 0x50, 0x86, 0xCD, 0xC0 }; +static const uint8_t patable_power_915[8] = { 0x0B, 0x1B, 0x6D, 0x67, 0x50, 0x85, 0xC9, 0xC1 }; + +// 5 dB default value for factory reset +static const uint8_t patable_power_ook[8] = { 0x00, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +enum MarcState + : uint8_t +{ + STATE_SLEEP = 0x00, + STATE_IDLE = 0x01, + STATE_XOFF = 0x02, + STATE_VCOON_MC = 0x03, + STATE_REGON_MC = 0x04, + STATE_MANCAL = 0x05, + STATE_VCOON = 0x06, + STATE_REGCON = 0x07, + STATE_STARTCAL = 0x08, + STATE_BWBOOST = 0x09, + STATE_FS_LOCK = 0x0A, + STATE_IFADCON = 0x0B, + STATE_ENDCAL = 0x0C, + STATE_RX = 0x0D, + STATE_RX_END = 0x0E, + STATE_TX_RST = 0x0F, + STATE_TXRX_SWITCH = 0x10, + STATE_RXFIFO_OVERFLOW = 0x11, + STATE_FSTXON = 0x12, + STATE_TX = 0x13, + STATE_TX_END = 0x14, + STATE_RXTX_SWITCH = 0x15, + STATE_TXFIFO_UNDERFLOW = 0x16, + STATE_MASK = 0x1F, +}; + +CC1101::~CC1101() +{ + if (_init_done) + (void)idle(); +} + +#if !defined(LINUX_ARCH_RASPBERRYPI) +void CC1101::set_spi_pins(uint8_t mosi, uint8_t miso, uint8_t sck) +{ + _mosi = mosi; + _miso = miso; + _sck = sck; +} + +void CC1101::select() +{ + digitalWrite(_ss, LOW); +} + +void CC1101::deselect() +{ + digitalWrite(_ss, HIGH); +} + +bool CC1101::wait_MISO() +{ + uint8_t miso_count = 255; + while(digitalRead(_miso) == HIGH && miso_count > 0) + --miso_count; + return (miso_count > 0); +} + +uint8_t CC1101::spi_putc(const uint8_t value) +{ +#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) + return SPI.transfer(value); +#else + SPDR = value; + asm volatile("nop"); + while(!(SPSR & _BV(SPIF))); + return SPDR; +#endif +} +#endif + +bool CC1101::spi_begin() +{ +#if defined(LINUX_ARCH_RASPBERRYPI) + //4MHz SPI speed + return (wiringPiSPISetup(0, 4000000) >= 0); +#elif defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) + pinMode(_ss, OUTPUT); + deselect(); + + SPI.setDataMode(SPI_MODE0); + SPI.setBitOrder(MSBFIRST); + SPI.begin(); + SPI.setClockDivider(SPI_CLOCK_DIV4); +#else + pinMode(_ss, OUTPUT); + deselect(); + + pinMode(_sck, OUTPUT); + pinMode(_mosi, OUTPUT); + pinMode(_miso, INPUT); + + // SPI init + SPCR = _BV(SPE) | _BV(MSTR); // SPI speed = CLK/4 + digitalWrite(_sck, HIGH); + digitalWrite(_mosi, LOW); +#endif + return true; +} + +void CC1101::spi_write_register(Register spi_instr, uint8_t value) +{ +#if defined(LINUX_ARCH_RASPBERRYPI) + uint8_t tbuf[2] = {0}; + tbuf[0] = spi_instr | WRITE_SINGLE_BYTE; + tbuf[1] = value; + wiringPiSPIDataRW(0, tbuf, 2); +#else + select(); + wait_MISO(); + spi_putc(spi_instr | WRITE_SINGLE_BYTE); + spi_putc(value); + deselect(); +#endif +} + +uint8_t CC1101::spi_read_register(Register spi_instr) +{ +#if defined(LINUX_ARCH_RASPBERRYPI) + uint8_t rbuf[2] = {0}; + rbuf[0] = spi_instr | READ_SINGLE_BYTE; + wiringPiSPIDataRW(0, rbuf, 2); + return rbuf[1]; +#else + select(); + wait_MISO(); + spi_putc(spi_instr | READ_SINGLE_BYTE); + uint8_t ret = spi_putc(0x00); + deselect(); + return ret; +#endif +} + +uint8_t CC1101::spi_write_strobe(Strobe spi_instr) +{ +#if defined(LINUX_ARCH_RASPBERRYPI) + uint8_t tbuf[1] = {0}; + tbuf[0] = spi_instr; + wiringPiSPIDataRW(0, tbuf, 1); + return tbuf[0]; +#else + select(); + wait_MISO(); + uint8_t ret = spi_putc(spi_instr); + wait_MISO(); + deselect(); + return ret; +#endif +} + +void CC1101::spi_read_burst(SPI_RW spi_instr, uint8_t *pArr, uint8_t len) +{ +#if defined(LINUX_ARCH_RASPBERRYPI) + uint8_t rbuf[len + 1]; + rbuf[0] = spi_instr | READ_BURST; + wiringPiSPIDataRW(0, rbuf, len + 1); + for (uint8_t i = 0; i < len ;i++ ) + pArr[i] = rbuf[i + 1]; +#else + select(); + wait_MISO(); + spi_putc(spi_instr | READ_BURST); + for (uint8_t i = 0; i < len ;i++ ) + pArr[i] = spi_putc(0x00); + deselect(); +#endif +} + +void CC1101::spi_write_burst(SPI_RW spi_instr, const uint8_t *pArr, uint8_t len) +{ +#if defined(LINUX_ARCH_RASPBERRYPI) + uint8_t tbuf[len + 1]; + tbuf[0] = spi_instr | WRITE_BURST; + for (uint8_t i = 0; i < len; i++) + tbuf[i + 1] = pArr[i]; + wiringPiSPIDataRW(0, tbuf, len + 1); +#else + select(); + wait_MISO(); + spi_putc(spi_instr | WRITE_BURST); + for (uint8_t i = 0; i < len; i++) + spi_putc(pArr[i]); + deselect(); +#endif +} + +bool CC1101::setup() +{ +#if defined(LINUX_ARCH_RASPBERRYPI) + wiringPiSetup(); +#endif + pinMode(_gdo0, OUTPUT); + digitalWrite(_gdo0, LOW); + pinMode(_gdo2, INPUT); + + spi_begin(); + reset(); + + //uint8_t partnum = spi_read_register(PARTNUM); + uint8_t version = spi_read_register(VERSION); + //checks if valid Chip ID is found. Usualy 0x03 or 0x14. if not -> abort + if (version == 0x00 || version == 0xFF) + return false; + + spi_write_burst(WRITE_BURST, cc1101_init, sizeof(cc1101_init)); + if (!register_check()) + return false; + _init_done = true; + return true; +} + +bool CC1101::setISM(CC1101::ISM_FREQ ism_freq) +{ + uint8_t freq2, freq1, freq0; + const uint8_t *patable = patable_power_ook; + switch(ism_freq) + { + case CC1101::FREQ_315MHZ: + freq2 = 0x0C; + freq1 = 0x1D; + freq0 = 0x89; + //patable = patable_power_315; + break; + case CC1101::FREQ_434MHZ: + freq2 = 0x10; + freq1 = 0xB0; + freq0 = 0x71; + //patable = patable_power_434; + break; + case CC1101::FREQ_868MHZ: + freq2 = 0x21; + freq1 = 0x65; + freq0 = 0x6A; + //patable = patable_power_868; + break; + case CC1101::FREQ_915MHZ: + freq2 = 0x23; + freq1 = 0x31; + freq0 = 0x3B; + //patable = patable_power_915; + break; + /* + case CC1101::FREQ_2430MHZ: + freq2 = 0x5D; + freq1 = 0x76; + freq0 = 0x27; + //patable = patable_power_2430; + break; + */ + default: + return false; + } + + spi_write_burst(PATABLE_BURST, patable, 8); + + // stores the new freq setting for defined ISM band + spi_write_register(FREQ2, freq2); + spi_write_register(FREQ1, freq1); + spi_write_register(FREQ0, freq0); + + return true; +} + +bool CC1101::register_check() +{ + return (spi_read_register(PKTCTRL0) == cc1101_init[PKTCTRL0] + && spi_read_register(IOCFG2) == cc1101_init[IOCFG2]); +} + +bool CC1101::transmit() +{ + idle(); + setGDO0(LOW); + spi_write_strobe(STX); + uint8_t maxloop = 255; + while (--maxloop && (spi_read_register(MARCSTATE) & STATE_MASK) != STATE_TX) + delay(1); + return (maxloop != 0); +} + +void CC1101::setGDO0(int value) +{ + digitalWrite(_gdo0, value); +} + +bool CC1101::receive() +{ + idle(); + spi_write_strobe(SRX); + uint8_t maxloop = 255; + while (--maxloop && (spi_read_register(MARCSTATE) & STATE_MASK) != STATE_RX) + delay(1); + return (maxloop != 0); +} + +void CC1101::wakeup() +{ + digitalWrite(_ss, LOW); + delayMicroseconds(10); + digitalWrite(_ss, HIGH); + delayMicroseconds(10); + idle(); +} + +void CC1101::powerdown() +{ + idle(); + spi_write_strobe(SPWD); +} + +void CC1101::idle() +{ + spi_write_strobe(SIDLE); + uint8_t maxloop = 0xff; + while (--maxloop && (spi_read_register(MARCSTATE) & STATE_MASK) != STATE_IDLE) + delay(1); + delayMicroseconds(100); +} + +void CC1101::reset(void) +{ + digitalWrite(_ss, LOW); + delayMicroseconds(10); + digitalWrite(_ss, HIGH); + delayMicroseconds(40); + + spi_write_strobe(SRES); + delay(1); +} diff --git a/martin/door/src/cc1101.h b/martin/door/src/cc1101.h new file mode 100644 index 0000000..04d828e --- /dev/null +++ b/martin/door/src/cc1101.h @@ -0,0 +1,158 @@ +#pragma once + +class CC1101 +{ + public: + enum ISM_FREQ + { + FREQ_315MHZ, + FREQ_434MHZ, + FREQ_868MHZ, + FREQ_915MHZ, + //FREQ_2430MHZ, + }; + + CC1101(uint8_t gdo0, uint8_t gdo2, uint8_t ss) + : _gdo0(gdo0), _gdo2(gdo2), _ss(ss) + {} + ~CC1101(); +#if !defined(LINUX_ARCH_RASPBERRYPI) + void set_spi_pins(uint8_t mosi = MOSI, uint8_t miso = MISO, uint8_t sck = SCK); +#endif + bool setup(); + bool setISM(ISM_FREQ ism_freq); + bool register_check(); + bool transmit(); + void setGDO0(int value); + bool receive(); + void wakeup(void); + void powerdown(void); + void idle(); + void reset(void); + + private: + enum Strobe + : uint8_t + { + SRES = 0x30, // Reset chip + SFSTXON = 0x31, // Enable/calibrate freq synthesizer + SXOFF = 0x32, // Turn off crystal oscillator + SCAL = 0x33, // Calibrate freq synthesizer & disable + SRX = 0x34, // Enable RX + STX = 0x35, // Enable TX + SIDLE = 0x36, // Exit RX / TX + SAFC = 0x37, // AFC adjustment of freq synthesizer + SWOR = 0x38, // Start automatic RX polling sequence + SPWD = 0x39, // Enter pwr down mode when CSn goes hi + SFRX = 0x3A, // Flush the RX FIFO buffer + SFTX = 0x3A, // Flush the TX FIFO buffer + SWORRST = 0x3B, // Reset real time clock + SNOP = 0x3C, // No operation + }; + + enum SPI_RW + : uint8_t + { + WRITE_SINGLE_BYTE = 0x00, + WRITE_BURST = 0x40, + READ_SINGLE_BYTE = 0x80, + READ_BURST = 0xC0, + TXFIFO_BURST = 0x7F, // write burst only + TXFIFO_SINGLE_BYTE = 0x3F, // write single only + RXFIFO_BURST = 0xFF, // read burst only + RXFIFO_SINGLE_BYTE = 0xBF, // read single only + PATABLE_BURST = 0x7E, // power control read/write + PATABLE_SINGLE_BYTE = 0xFE, // power control read/write + }; + + enum Register + : uint8_t + { + IOCFG2 = 0x00, // GDO2 output pin configuration + IOCFG1 = 0x01, // GDO1 output pin configuration + IOCFG0 = 0x02, // GDO0 output pin configuration + FIFOTHR = 0x03, // RX FIFO and TX FIFO thresholds + SYNC1 = 0x04, // Sync word, high byte + SYNC0 = 0x05, // Sync word, low byte + PKTLEN = 0x06, // Packet length + PKTCTRL1 = 0x07, // Packet automation control + PKTCTRL0 = 0x08, // Packet automation control + ADDR = 0x09, // Device address + CHANNR = 0x0A, // Channel number + FSCTRL1 = 0x0B, // Frequency synthesizer control + FSCTRL0 = 0x0C, // Frequency synthesizer control + FREQ2 = 0x0D, // Frequency control word, high byte + FREQ1 = 0x0E, // Frequency control word, middle byte + FREQ0 = 0x0F, // Frequency control word, low byte + MDMCFG4 = 0x10, // Modem configuration + MDMCFG3 = 0x11, // Modem configuration + MDMCFG2 = 0x12, // Modem configuration + MDMCFG1 = 0x13, // Modem configuration + MDMCFG0 = 0x14, // Modem configuration + DEVIATN = 0x15, // Modem deviation setting + MCSM2 = 0x16, // Main Radio Cntrl State Machine config + MCSM1 = 0x17, // Main Radio Cntrl State Machine config + MCSM0 = 0x18, // Main Radio Cntrl State Machine config + FOCCFG = 0x19, // Frequency Offset Compensation config + BSCFG = 0x1A, // Bit Synchronization configuration + AGCCTRL2 = 0x1B, // AGC control + AGCCTRL1 = 0x1C, // AGC control + AGCCTRL0 = 0x1D, // AGC control + WOREVT1 = 0x1E, // High byte Event 0 timeout + WOREVT0 = 0x1F, // Low byte Event 0 timeout + WORCTRL = 0x20, // Wake On Radio control + FREND1 = 0x21, // Front end RX configuration + FREND0 = 0x22, // Front end TX configuration + FSCAL3 = 0x23, // Frequency synthesizer calibration + FSCAL2 = 0x24, // Frequency synthesizer calibration + FSCAL1 = 0x25, // Frequency synthesizer calibration + FSCAL0 = 0x26, // Frequency synthesizer calibration + RCCTRL1 = 0x27, // RC oscillator configuration + RCCTRL0 = 0x28, // RC oscillator configuration + FSTEST = 0x29, // Frequency synthesizer cal control + PTEST = 0x2A, // Production test + AGCTEST = 0x2B, // AGC test + TEST2 = 0x2C, // Various test settings + TEST1 = 0x2D, // Various test settings + TEST0 = 0x2E, // Various test settings + + PARTNUM = 0xF0, // Part number + VERSION = 0xF1, // Current version number + FREQEST = 0xF2, // Frequency offset estimate + LQI = 0xF3, // Demodulator estimate for link quality + RSSI = 0xF4, // Received signal strength indication + MARCSTATE = 0xF5, // Control state machine state + WORTIME1 = 0xF6, // High byte of WOR timer + WORTIME0 = 0xF7, // Low byte of WOR timer + PKTSTATUS = 0xF8, // Current GDOx status and packet status + VCO_VC_DAC = 0xF9, // Current setting from PLL cal module + TXBYTES = 0xFA, // Underflow and # of bytes in TXFIFO + RXBYTES = 0xFB, // Overflow and # of bytes in RXFIFO + RCCTRL1_STATUS = 0xFC, // Last RC Oscillator Calibration Result + RCCTRL0_STATUS = 0xFD, // Last RC Oscillator Calibration Result + }; + + bool spi_begin(); +#if !defined(LINUX_ARCH_RASPBERRYPI) + void select(); + void deselect(); + bool wait_MISO(); + uint8_t spi_putc(const uint8_t value); +#endif + void spi_write_register(Register spi_instr, uint8_t value); + uint8_t spi_read_register(Register spi_instr); + uint8_t spi_write_strobe(Strobe spi_instr); + void spi_read_burst(SPI_RW spi_instr, uint8_t *pArr, uint8_t len); + void spi_write_burst(SPI_RW spi_instr, const uint8_t *pArr, uint8_t len); + + private: + uint8_t _gdo0; + uint8_t _gdo2; + uint8_t _ss; + bool _init_done = false; +#if !defined(LINUX_ARCH_RASPBERRYPI) + uint8_t _mosi = MOSI; + uint8_t _miso = MISO; + uint8_t _sck = SCK; +#endif +}; diff --git a/martin/door/src/hcs200.cpp b/martin/door/src/hcs200.cpp new file mode 100644 index 0000000..401e670 --- /dev/null +++ b/martin/door/src/hcs200.cpp @@ -0,0 +1,242 @@ +#include + +#include "hcs200.h" + +/** Pulse width lengths used by the transmitter. A pulse width is the time + * between a transition from 0 to 1 or from 1 to 0 of the input pin. + */ +enum RbType +{ + RB_SHORT = 0, // pulse_width + RB_LONG, // 2 * pulse_width + RB_ERROR +}; + +/** Determine if 'pulse' is a short pulse, RB_SHORT or a long one, RB_LONG. + * Normally, a short pulse is the same as the 'tx_clock' and a long pulse is + * twice as long. However, timing problems with servicing interrupts means + * that we need to add some fudge factors. + */ +int HCS200::classify(unsigned pulse) +{ + int d = pulse - tx_clock; + if (d < -100) + return RB_ERROR; + else if (d < 100) + return RB_SHORT; + else + { + d -= tx_clock; + if (d < -100) + return RB_ERROR; + else if (d < 100) + return RB_LONG; + else + return RB_ERROR; + } +} + +#define IN_RANGE(x, min, max) (x >= min && x <= max) +void HCS200::on_isr(uint8_t value) +{ + unsigned long timestamp = micros(); + unsigned long pulse_width = timestamp - last_timestamp; + int d; + + switch (rx_state) { + case RS_PREAMBLE_START: + // value == 0 + preamble_count = 1; + rx_state = RS_PREAMBLE_LOW; + tx_clock = pulse_width; + break; + case RS_PREAMBLE_LOW: + // value == 1 + preamble_count++; + d = pulse_width - tx_clock; + rx_state = (IN_RANGE(d, -100, 100)) ? RS_PREAMBLE_HIGH : RS_NOSYNC; + break; + case RS_PREAMBLE_HIGH: + // value == 0 + preamble_count++; + d = pulse_width - tx_clock; + if (IN_RANGE(d, -100, 100)) + rx_state = (preamble_count == 23) ? RS_PREAMBLE_HEADER : RS_PREAMBLE_LOW; + else + rx_state = RS_NOSYNC; + break; + case RS_PREAMBLE_HEADER: + // header should be low for 10*tx_clock + d = pulse_width - 10 * tx_clock; + if (value == 1 && IN_RANGE(d, -100, 100)) + { + rx_state = RS_DATA; + rx_bit_count = 0; + memset(rx_buf, 0, sizeof(rx_buf)); + } + else + rx_state = RS_NOSYNC; + break; + case RS_DATA: + if (value == 1) + { + int first = classify(last_pulse_width); + int second = classify(pulse_width); + // received a 1 bit + if (first == RB_SHORT && second == RB_LONG) + { + int idx = rx_bit_count / 32; + rx_buf[idx] >>= 1; + rx_buf[idx] |= 0x80000000; + rx_bit_count++; + } + // received a 0 bit + else if (first == RB_LONG && second == RB_SHORT) + { + int idx = rx_bit_count / 32; + rx_buf[idx] >>= 1; + rx_bit_count++; + } + else + rx_state = RS_NOSYNC; + + // we ignore the last bit as it's always "1" + // instead we use the raising edge as trigger to stop + if (rx_bit_count == MAX_BITS - 1) + rx_state = RS_COMPLETED; + } + break; + } + + // check outside of the state machine + // this is important as otherwise otherwise we always miss the start + if (rx_state == RS_NOSYNC && value == 1) + rx_state = RS_PREAMBLE_START; + + last_timestamp = timestamp; + last_pulse_width = pulse_width; +} + +void HCS200::reset() +{ + rx_state = RS_NOSYNC; +} + +bool HCS200::decode(HCS200_Keycode &out) +{ + if (rx_state != RS_COMPLETED) + return false; + out.encrypted = rx_buf[0]; + out.serial = rx_buf[1] & 0x0FFFFFFF; + out.buttons = (rx_buf[1] >> 28) & 0xF; + out.lowbat = rx_buf[2] & 0x80000000; + return true; +} + +void HCS200::print_state(Print &stream) +{ + stream.print("rx_state="); + stream.print(rx_state, DEC); + stream.print(", rx_bit_count="); + Serial.print(rx_bit_count, DEC); + stream.print(", tx_clock="); + stream.print(tx_clock, DEC); + stream.print(", preamble_count="); + stream.println(preamble_count, DEC); +} + +void HCS200_Keycode::print(Print &stream) +{ + stream.print("Keyfob# "); + stream.print(serial, HEX); + stream.print(", buttons:"); + if (buttons & BM_S0) + stream.print(" 1"); + if (buttons & BM_S1) + stream.print(" 2"); + if (buttons & BM_S2) + stream.print(" 3"); + if (buttons & BM_S3) + stream.print(" 4"); + stream.print(", lowbat="); + stream.print(lowbat, DEC); + stream.print(", code="); + stream.print(encrypted, HEX); + stream.print("\n"); +} + +#define PULSE_WIDTH 400 + +inline void HCS200_Keycode::send(bool value, std::function setOutput) +{ + if (!value) + { + setOutput(HIGH); + delayMicroseconds(PULSE_WIDTH); + delayMicroseconds(PULSE_WIDTH); + setOutput(LOW); + delayMicroseconds(PULSE_WIDTH); + } + else + { + setOutput(HIGH); + delayMicroseconds(PULSE_WIDTH); + setOutput(LOW); + delayMicroseconds(PULSE_WIDTH); + delayMicroseconds(PULSE_WIDTH); + } +} + +void HCS200_Keycode::send(std::function setOutput) +{ + uint32_t val; + + // preamble + for(unsigned short i = 0; i < 11; i++) + { + setOutput(HIGH); + delayMicroseconds(PULSE_WIDTH); + setOutput(LOW); + delayMicroseconds(PULSE_WIDTH); + } + setOutput(HIGH); + delayMicroseconds(PULSE_WIDTH); + + // header + setOutput(LOW); + delayMicroseconds(PULSE_WIDTH * 10); + + // encrypted + val = this->encrypted; + for(unsigned short i = 0; i < 32; i++) + { + send(val & 0x01, setOutput); + val >>= 1; + } + + // serial + val = this->serial; + for(unsigned short i = 0; i < 28; i++) + { + send(val & 0x01, setOutput); + val >>= 1; + } + + // buttons + val = this->buttons; + for(unsigned short i = 0; i < 4; i++) + { + send(val & 0x01, setOutput); + val >>= 1; + } + + // lowbat + send(this->lowbat, setOutput); + + // RPT + send(1, setOutput); + + // guard time + setOutput(LOW); + delayMicroseconds(PULSE_WIDTH * 39); +} diff --git a/martin/door/src/hcs200.h b/martin/door/src/hcs200.h new file mode 100644 index 0000000..95aa18b --- /dev/null +++ b/martin/door/src/hcs200.h @@ -0,0 +1,97 @@ +#pragma once + +#include +#include + +class HCS200_Keycode +{ + public: + /** Bit masks to determine which button was pressed on the keyfob. + * NOTE: the bit position does not match the button number + */ + enum ButtonMask + : unsigned char + { + BM_S3 = 0x1, + BM_S0 = 0x2, + BM_S1 = 0x4, + BM_S2 = 0x8 + }; + + void print(Print &stream); + void send(std::function setOutput); + + private: + void send(bool value, std::function setOutput); + + public: + uint32_t encrypted; + uint32_t serial; + unsigned char buttons; + bool lowbat; +}; + +class HCS200 +{ + public: + enum RxState + { + RS_NOSYNC = 0, // Receiver is inactive + RS_PREAMBLE_START, + RS_PREAMBLE_LOW, + RS_PREAMBLE_HIGH, + RS_PREAMBLE_HEADER, + RS_DATA, // DATA is being received + RS_COMPLETED // Receive complete + }; + + void on_isr(uint8_t value); + void reset(); + bool decode(struct HCS200_Keycode &out); + void print_state(Print &stream); + + public: + int classify(unsigned pulse); + + /** The state of the code word received, this is 'volatile' since it is + * accessed from both the main loop and the interrupt service routine. + */ + volatile char rx_state = RS_NOSYNC; + + /** Time stamp when the previous interrupt was triggered (when the input pin + * transitioned either from 0 to 1 or from 1 to 0. This is the value of + * micros() function. + */ + unsigned long last_timestamp = 0; + + /** The duration of the last pulse width (the time between a transition of the + * input pin) + */ + unsigned last_pulse_width = 0; + + /** Number of changes during preamble + */ + char preamble_count = 0; + + /** Number of bits received already. + */ + char rx_bit_count = 0; + + /** Duration of the transmitter clock -- this is determined when the preamble + * is received, as the preamble has all the pulse widths the same length. + * + * The remotes I tested transmit at 2400 bauds, so the tx_clock is always + * about 420 micro seconds, however, this code attempts to determine it + * dynamically and should adapt to transmitters of different speeds. + */ + unsigned tx_clock = 0; + + /** Number of bits we expect to receive. The HCS200 data sheet specifies that + * 66 bits are transmited. + */ + const unsigned short MAX_BITS = 66; + + /** Buffer where we store the received bits. HCS sends only 66 + */ + uint32_t rx_buf[3]; +}; diff --git a/martin/door/src/main.cpp b/martin/door/src/main.cpp new file mode 100644 index 0000000..4491785 --- /dev/null +++ b/martin/door/src/main.cpp @@ -0,0 +1,171 @@ +#include +#include +#include +#include + +#include "cc1101.h" +#include "hcs200.h" + +void http_door_open(); +void serial_door_open(); + +ESP8266WebServer http_service(80); +CC1101 radio(D0 /* GDO0 */, D2 /* GDO2 */, D8 /* SS */); + +#define PIN_HCS200_ENABLE D3 +#define PIN_HCS200_DATA D1 +//#define HCS200_TEST +HCS200 hcs200; + +void setup() +{ + Serial.begin(9600); + + pinMode(PIN_HCS200_ENABLE, OUTPUT); + digitalWrite(PIN_HCS200_ENABLE, LOW); + pinMode(PIN_HCS200_DATA, INPUT); + + if (!radio.setup()) + Serial.println("Radio not found!"); + else if (!radio.setISM(CC1101::FREQ_434MHZ)) + Serial.println("Unable to set radio frequency"); + else + { + Serial.println("Radio found"); + (void)radio.idle(); + } + + WiFiManager wifiManager; + wifiManager.setConfigPortalTimeout(600); + Serial.printf_P(PSTR("Setting up WiFi\n")); + WiFi.hostname("door"); + if (!wifiManager.autoConnect("a-door-able")) + { + Serial.printf_P(PSTR("Failed to connect and hit timeout\n")); + delay(5000); + ESP.restart(); + } + + if (!MDNS.begin("door")) + Serial.println("Error setting up MDNS responder!"); + MDNS.addService("http", "tcp", 80); + + http_service.on("/", HTTP_GET, []() { + http_service.send_P(200, PSTR("text/plain"), PSTR("I'm a-door-able")); + }); + http_service.on("/door/open", HTTP_PUT, http_door_open); + http_service.begin(); + + Serial.println("Started listening"); +} + +void ICACHE_RAM_ATTR on_hcs200_isr() +{ + hcs200.on_isr(digitalRead(PIN_HCS200_DATA)); +} + +bool hcs200_get_keycode(HCS200_Keycode &keycode) +{ +#if !defined(HCS200_TEST) + hcs200.reset(); + attachInterrupt(digitalPinToInterrupt(PIN_HCS200_DATA), on_hcs200_isr, CHANGE); + digitalWrite(PIN_HCS200_ENABLE, HIGH); + uint8_t maxloop = 255; + while(--maxloop) + { + if (hcs200.decode(keycode)) + break; + delay(1); + } + digitalWrite(PIN_HCS200_ENABLE, LOW); + detachInterrupt(digitalPinToInterrupt(PIN_HCS200_DATA)); + return (maxloop != 0); +#else + keycode.encrypted = 0xDEAD; + keycode.serial = 0xC0DE; + keycode.buttons = HCS200_Keycode::BM_S0; + keycode.lowbat = false; + return true; +#endif +} + +void http_door_open() +{ + if (!radio.register_check()) + { + http_service.send_P(500, PSTR("text/plain"), PSTR("Radio error")); + return; + } + + HCS200_Keycode keycode; + if (!hcs200_get_keycode(keycode)) + { + http_service.send_P(500, PSTR("text/plain"), PSTR("No data from HCS200")); + return; + } + + Serial.print("Got keycode: "); + keycode.print(Serial); + + if (!radio.transmit()) + { + http_service.send_P(500, PSTR("text/plain"), PSTR("Unable to enable radio transmit mode")); + return; + } + + long count = constrain(http_service.arg("count").toInt(), 2, 10); + for (unsigned short i = 0; i < count; i++) + keycode.send([&](int value) { radio.setGDO0(value); }); + + (void)radio.idle(); + Serial.println("Sending done"); + http_service.send_P(200, PSTR("text/plain"), PSTR("OK")); +} + +void serial_door_open(Print &stream) +{ + if (!radio.register_check()) + { + stream.println("Radio error"); + return; + } + + HCS200_Keycode keycode; + bool key_valid = hcs200_get_keycode(keycode); + if (!key_valid) + stream.println("No data from HCS200"); + stream.print("HCS200 decoder state: "); + hcs200.print_state(stream); + if (!key_valid) + return; + + stream.print("Got keycode: "); + keycode.print(stream); + + if (!radio.transmit()) + { + stream.println("Unable to enable radio transmit mode"); + return; + } + + /* send twice */ + keycode.send([&](int value) { radio.setGDO0(value); }); + keycode.send([&](int value) { radio.setGDO0(value); }); + + (void)radio.idle(); + stream.println("Sending done"); +} + +void loop() +{ + MDNS.update(); + http_service.handleClient(); + + if (Serial.available() > 0) + { + (void)Serial.read(); + serial_door_open(Serial); + } + + delay(1); +} -- cgit v1.2.3