diff options
| author | manuel <manuel@mausz.at> | 2020-03-17 12:18:56 +0100 |
|---|---|---|
| committer | manuel <manuel@mausz.at> | 2020-03-17 12:18:56 +0100 |
| commit | 0553b9907f912e56a2cd2a08b03a83ce6f2a75c6 (patch) | |
| tree | 45ed1908a50168f8420f13866cfd68386d0f2c78 | |
| parent | f0ecb4d38fff522c72905a8551355ca925381fa3 (diff) | |
| download | arduino-0553b9907f912e56a2cd2a08b03a83ce6f2a75c6.tar.gz arduino-0553b9907f912e56a2cd2a08b03a83ce6f2a75c6.tar.bz2 arduino-0553b9907f912e56a2cd2a08b03a83ce6f2a75c6.zip | |
Add martin/door
| -rw-r--r-- | bs_button/.travis.yml | 67 | ||||
| -rw-r--r-- | martin/door/.gitignore | 5 | ||||
| -rw-r--r-- | martin/door/platformio.ini | 29 | ||||
| -rw-r--r-- | martin/door/src/cc1101.cpp | 395 | ||||
| -rw-r--r-- | martin/door/src/cc1101.h | 158 | ||||
| -rw-r--r-- | martin/door/src/hcs200.cpp | 242 | ||||
| -rw-r--r-- | martin/door/src/hcs200.h | 97 | ||||
| -rw-r--r-- | martin/door/src/main.cpp | 171 |
8 files changed, 1097 insertions, 67 deletions
diff --git a/bs_button/.travis.yml b/bs_button/.travis.yml deleted file mode 100644 index 7c486f1..0000000 --- a/bs_button/.travis.yml +++ /dev/null | |||
| @@ -1,67 +0,0 @@ | |||
| 1 | # Continuous Integration (CI) is the practice, in software | ||
| 2 | # engineering, of merging all developer working copies with a shared mainline | ||
| 3 | # several times a day < https://docs.platformio.org/page/ci/index.html > | ||
| 4 | # | ||
| 5 | # Documentation: | ||
| 6 | # | ||
| 7 | # * Travis CI Embedded Builds with PlatformIO | ||
| 8 | # < https://docs.travis-ci.com/user/integration/platformio/ > | ||
| 9 | # | ||
| 10 | # * PlatformIO integration with Travis CI | ||
| 11 | # < https://docs.platformio.org/page/ci/travis.html > | ||
| 12 | # | ||
| 13 | # * User Guide for `platformio ci` command | ||
| 14 | # < https://docs.platformio.org/page/userguide/cmd_ci.html > | ||
| 15 | # | ||
| 16 | # | ||
| 17 | # Please choose one of the following templates (proposed below) and uncomment | ||
| 18 | # it (remove "# " before each line) or use own configuration according to the | ||
| 19 | # Travis CI documentation (see above). | ||
| 20 | # | ||
| 21 | |||
| 22 | |||
| 23 | # | ||
| 24 | # Template #1: General project. Test it using existing `platformio.ini`. | ||
| 25 | # | ||
| 26 | |||
| 27 | # language: python | ||
| 28 | # python: | ||
| 29 | # - "2.7" | ||
| 30 | # | ||
| 31 | # sudo: false | ||
| 32 | # cache: | ||
| 33 | # directories: | ||
| 34 | # - "~/.platformio" | ||
| 35 | # | ||
| 36 | # install: | ||
| 37 | # - pip install -U platformio | ||
| 38 | # - platformio update | ||
| 39 | # | ||
| 40 | # script: | ||
| 41 | # - platformio run | ||
| 42 | |||
| 43 | |||
| 44 | # | ||
| 45 | # Template #2: The project is intended to be used as a library with examples. | ||
| 46 | # | ||
| 47 | |||
| 48 | # language: python | ||
| 49 | # python: | ||
| 50 | # - "2.7" | ||
| 51 | # | ||
| 52 | # sudo: false | ||
| 53 | # cache: | ||
| 54 | # directories: | ||
| 55 | # - "~/.platformio" | ||
| 56 | # | ||
| 57 | # env: | ||
| 58 | # - PLATFORMIO_CI_SRC=path/to/test/file.c | ||
| 59 | # - PLATFORMIO_CI_SRC=examples/file.ino | ||
| 60 | # - PLATFORMIO_CI_SRC=path/to/test/directory | ||
| 61 | # | ||
| 62 | # install: | ||
| 63 | # - pip install -U platformio | ||
| 64 | # - platformio update | ||
| 65 | # | ||
| 66 | # script: | ||
| 67 | # - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N | ||
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 @@ | |||
| 1 | .pio | ||
| 2 | .vscode/.browse.c_cpp.db* | ||
| 3 | .vscode/c_cpp_properties.json | ||
| 4 | .vscode/launch.json | ||
| 5 | .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 @@ | |||
| 1 | ;PlatformIO Project Configuration File | ||
| 2 | ; | ||
| 3 | ; Build options: build flags, source filter | ||
| 4 | ; Upload options: custom upload port, speed and extra flags | ||
| 5 | ; Library options: dependencies, extra library storages | ||
| 6 | ; Advanced options: extra scripting | ||
| 7 | ; | ||
| 8 | ; Please visit documentation for the other options and examples | ||
| 9 | ; https://docs.platformio.org/page/projectconf.html | ||
| 10 | |||
| 11 | [platformio] | ||
| 12 | default_envs = d1_mini | ||
| 13 | |||
| 14 | [common_env_data] | ||
| 15 | lib_deps = | ||
| 16 | |||
| 17 | [env:nodemcuv2] | ||
| 18 | platform = espressif8266 | ||
| 19 | framework = arduino | ||
| 20 | board = nodemcuv2 | ||
| 21 | lib_deps = ${common_env_data.lib_deps} | ||
| 22 | monitor_speed = 9600 | ||
| 23 | |||
| 24 | [env:d1_mini] | ||
| 25 | platform = espressif8266 | ||
| 26 | framework = arduino | ||
| 27 | board = d1_mini | ||
| 28 | lib_deps = ${common_env_data.lib_deps} | ||
| 29 | 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 @@ | |||
| 1 | #include <Arduino.h> | ||
| 2 | #if defined(LINUX_ARCH_RASPBERRYPI) | ||
| 3 | #include <wiringPi.h> | ||
| 4 | #include <wiringPiSPI.h> | ||
| 5 | #elif defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) | ||
| 6 | #include <SPI.h> | ||
| 7 | #endif | ||
| 8 | |||
| 9 | #include "cc1101.h" | ||
| 10 | |||
| 11 | static const uint8_t cc1101_init[] = { | ||
| 12 | // IDX NAME RESET COMMENT | ||
| 13 | 0x0D, // 00 IOCFG2 29 GDO2 as serial output | ||
| 14 | 0x2E, // 01 IOCFG1 Tri-State | ||
| 15 | 0x2E, // 02 IOCFG0 3F GDO0 for input | ||
| 16 | 0x47, // 03 FIFOTHR RX filter bandwidth = 325 kHz, FIFOTHR = 0x47 | ||
| 17 | 0xD3, // 04 SYNC1 | ||
| 18 | 0x91, // 05 SYNC0 | ||
| 19 | 0x3D, // 06 PKTLEN 0F | ||
| 20 | 0x04, // 07 PKTCTRL1 | ||
| 21 | 0x32, // 08 PKTCTRL0 45 | ||
| 22 | 0x00, // 09 ADDR | ||
| 23 | 0x00, // 0A CHANNR | ||
| 24 | 0x06, // 0B FSCTRL1 0F 152kHz IF Frquency | ||
| 25 | 0x00, // 0C FSCTRL0 | ||
| 26 | 0x10, // 0D FREQ2 1E Freq #12 Reg Pos 0C | ||
| 27 | 0xB0, // 0E FREQ1 C4 Reg Pos 0D | ||
| 28 | 0x71, // 0F FREQ0 EC Reg Pos 0E | ||
| 29 | 0x57, // 10 MDMCFG4 8C bWidth 325kHz | ||
| 30 | 0xC4, // 11 MDMCFG3 22 DataRate | ||
| 31 | 0x30, // 12 MDMCFG2 02 Modulation: ASK | ||
| 32 | 0x23, // 13 MDMCFG1 22 | ||
| 33 | 0xb9, // 14 MDMCFG0 F8 ChannelSpace: 350kHz | ||
| 34 | 0x00, // 15 DEVIATN 47 | ||
| 35 | 0x07, // 16 MCSM2 07 | ||
| 36 | 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 #### | ||
| 37 | 0x18, // 18 MCSM0 04 Calibration: RX/TX->IDLE | ||
| 38 | 0x14, // 19 FOCCFG 36 | ||
| 39 | 0x6C, // 1A BSCFG | ||
| 40 | 0x07, // 1B AGCCTRL2 03 42 dB instead of 33dB | ||
| 41 | 0x00, // 1C AGCCTRL1 40 | ||
| 42 | 0x91, // 1D AGCCTRL0 91 8dB decision boundery | ||
| 43 | 0x87, // 1E WOREVT1 | ||
| 44 | 0x6B, // 1F WOREVT0 | ||
| 45 | 0xF8, // 20 WORCTRL | ||
| 46 | 0xB6, // 21 FREND1 B6 RX filter bandwidth > 101 kHz, FREND1 = 0xB6 | ||
| 47 | 0x11, // 22 FREND0 16 0x11 for no PA ramping | ||
| 48 | 0xE9, // 23 FSCAL3 A9 E9 ?? | ||
| 49 | 0x2A, // 24 FSCAL2 0A | ||
| 50 | 0x00, // 25 FSCAL1 20 19 ?? | ||
| 51 | 0x1F, // 26 FSCAL0 0D | ||
| 52 | 0x41, // 27 RCCTRL1 | ||
| 53 | 0x00, // 28 RCCTRL0 | ||
| 54 | }; | ||
| 55 | |||
| 56 | static const uint8_t patable_power_315[8] = { 0x17, 0x1D, 0x26, 0x69, 0x51, 0x86, 0xCC, 0xC3 }; | ||
| 57 | static const uint8_t patable_power_434[8] = { 0x6C, 0x1C, 0x06, 0x3A, 0x51, 0x85, 0xC8, 0xC0 }; | ||
| 58 | static const uint8_t patable_power_868[8] = { 0x00, 0x17, 0x1D, 0x26, 0x50, 0x86, 0xCD, 0xC0 }; | ||
| 59 | static const uint8_t patable_power_915[8] = { 0x0B, 0x1B, 0x6D, 0x67, 0x50, 0x85, 0xC9, 0xC1 }; | ||
| 60 | |||
| 61 | // 5 dB default value for factory reset | ||
| 62 | static const uint8_t patable_power_ook[8] = { 0x00, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; | ||
| 63 | |||
| 64 | enum MarcState | ||
| 65 | : uint8_t | ||
| 66 | { | ||
| 67 | STATE_SLEEP = 0x00, | ||
| 68 | STATE_IDLE = 0x01, | ||
| 69 | STATE_XOFF = 0x02, | ||
| 70 | STATE_VCOON_MC = 0x03, | ||
| 71 | STATE_REGON_MC = 0x04, | ||
| 72 | STATE_MANCAL = 0x05, | ||
| 73 | STATE_VCOON = 0x06, | ||
| 74 | STATE_REGCON = 0x07, | ||
| 75 | STATE_STARTCAL = 0x08, | ||
| 76 | STATE_BWBOOST = 0x09, | ||
| 77 | STATE_FS_LOCK = 0x0A, | ||
| 78 | STATE_IFADCON = 0x0B, | ||
| 79 | STATE_ENDCAL = 0x0C, | ||
| 80 | STATE_RX = 0x0D, | ||
| 81 | STATE_RX_END = 0x0E, | ||
| 82 | STATE_TX_RST = 0x0F, | ||
| 83 | STATE_TXRX_SWITCH = 0x10, | ||
| 84 | STATE_RXFIFO_OVERFLOW = 0x11, | ||
| 85 | STATE_FSTXON = 0x12, | ||
| 86 | STATE_TX = 0x13, | ||
| 87 | STATE_TX_END = 0x14, | ||
| 88 | STATE_RXTX_SWITCH = 0x15, | ||
| 89 | STATE_TXFIFO_UNDERFLOW = 0x16, | ||
| 90 | STATE_MASK = 0x1F, | ||
| 91 | }; | ||
| 92 | |||
| 93 | CC1101::~CC1101() | ||
| 94 | { | ||
| 95 | if (_init_done) | ||
| 96 | (void)idle(); | ||
| 97 | } | ||
| 98 | |||
| 99 | #if !defined(LINUX_ARCH_RASPBERRYPI) | ||
| 100 | void CC1101::set_spi_pins(uint8_t mosi, uint8_t miso, uint8_t sck) | ||
| 101 | { | ||
| 102 | _mosi = mosi; | ||
| 103 | _miso = miso; | ||
| 104 | _sck = sck; | ||
| 105 | } | ||
| 106 | |||
| 107 | void CC1101::select() | ||
| 108 | { | ||
| 109 | digitalWrite(_ss, LOW); | ||
| 110 | } | ||
| 111 | |||
| 112 | void CC1101::deselect() | ||
| 113 | { | ||
| 114 | digitalWrite(_ss, HIGH); | ||
| 115 | } | ||
| 116 | |||
| 117 | bool CC1101::wait_MISO() | ||
| 118 | { | ||
| 119 | uint8_t miso_count = 255; | ||
| 120 | while(digitalRead(_miso) == HIGH && miso_count > 0) | ||
| 121 | --miso_count; | ||
| 122 | return (miso_count > 0); | ||
| 123 | } | ||
| 124 | |||
| 125 | uint8_t CC1101::spi_putc(const uint8_t value) | ||
| 126 | { | ||
| 127 | #if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) | ||
| 128 | return SPI.transfer(value); | ||
| 129 | #else | ||
| 130 | SPDR = value; | ||
| 131 | asm volatile("nop"); | ||
| 132 | while(!(SPSR & _BV(SPIF))); | ||
| 133 | return SPDR; | ||
| 134 | #endif | ||
| 135 | } | ||
| 136 | #endif | ||
| 137 | |||
| 138 | bool CC1101::spi_begin() | ||
| 139 | { | ||
| 140 | #if defined(LINUX_ARCH_RASPBERRYPI) | ||
| 141 | //4MHz SPI speed | ||
| 142 | return (wiringPiSPISetup(0, 4000000) >= 0); | ||
| 143 | #elif defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) | ||
| 144 | pinMode(_ss, OUTPUT); | ||
| 145 | deselect(); | ||
| 146 | |||
| 147 | SPI.setDataMode(SPI_MODE0); | ||
| 148 | SPI.setBitOrder(MSBFIRST); | ||
| 149 | SPI.begin(); | ||
| 150 | SPI.setClockDivider(SPI_CLOCK_DIV4); | ||
| 151 | #else | ||
| 152 | pinMode(_ss, OUTPUT); | ||
| 153 | deselect(); | ||
| 154 | |||
| 155 | pinMode(_sck, OUTPUT); | ||
| 156 | pinMode(_mosi, OUTPUT); | ||
| 157 | pinMode(_miso, INPUT); | ||
| 158 | |||
| 159 | // SPI init | ||
| 160 | SPCR = _BV(SPE) | _BV(MSTR); // SPI speed = CLK/4 | ||
| 161 | digitalWrite(_sck, HIGH); | ||
| 162 | digitalWrite(_mosi, LOW); | ||
| 163 | #endif | ||
| 164 | return true; | ||
| 165 | } | ||
| 166 | |||
| 167 | void CC1101::spi_write_register(Register spi_instr, uint8_t value) | ||
| 168 | { | ||
| 169 | #if defined(LINUX_ARCH_RASPBERRYPI) | ||
| 170 | uint8_t tbuf[2] = {0}; | ||
| 171 | tbuf[0] = spi_instr | WRITE_SINGLE_BYTE; | ||
| 172 | tbuf[1] = value; | ||
| 173 | wiringPiSPIDataRW(0, tbuf, 2); | ||
| 174 | #else | ||
| 175 | select(); | ||
| 176 | wait_MISO(); | ||
| 177 | spi_putc(spi_instr | WRITE_SINGLE_BYTE); | ||
| 178 | spi_putc(value); | ||
| 179 | deselect(); | ||
| 180 | #endif | ||
| 181 | } | ||
| 182 | |||
| 183 | uint8_t CC1101::spi_read_register(Register spi_instr) | ||
| 184 | { | ||
| 185 | #if defined(LINUX_ARCH_RASPBERRYPI) | ||
| 186 | uint8_t rbuf[2] = {0}; | ||
| 187 | rbuf[0] = spi_instr | READ_SINGLE_BYTE; | ||
| 188 | wiringPiSPIDataRW(0, rbuf, 2); | ||
| 189 | return rbuf[1]; | ||
| 190 | #else | ||
| 191 | select(); | ||
| 192 | wait_MISO(); | ||
| 193 | spi_putc(spi_instr | READ_SINGLE_BYTE); | ||
| 194 | uint8_t ret = spi_putc(0x00); | ||
| 195 | deselect(); | ||
| 196 | return ret; | ||
| 197 | #endif | ||
| 198 | } | ||
| 199 | |||
| 200 | uint8_t CC1101::spi_write_strobe(Strobe spi_instr) | ||
| 201 | { | ||
| 202 | #if defined(LINUX_ARCH_RASPBERRYPI) | ||
| 203 | uint8_t tbuf[1] = {0}; | ||
| 204 | tbuf[0] = spi_instr; | ||
| 205 | wiringPiSPIDataRW(0, tbuf, 1); | ||
| 206 | return tbuf[0]; | ||
| 207 | #else | ||
| 208 | select(); | ||
| 209 | wait_MISO(); | ||
| 210 | uint8_t ret = spi_putc(spi_instr); | ||
| 211 | wait_MISO(); | ||
| 212 | deselect(); | ||
| 213 | return ret; | ||
| 214 | #endif | ||
| 215 | } | ||
| 216 | |||
| 217 | void CC1101::spi_read_burst(SPI_RW spi_instr, uint8_t *pArr, uint8_t len) | ||
| 218 | { | ||
| 219 | #if defined(LINUX_ARCH_RASPBERRYPI) | ||
| 220 | uint8_t rbuf[len + 1]; | ||
| 221 | rbuf[0] = spi_instr | READ_BURST; | ||
| 222 | wiringPiSPIDataRW(0, rbuf, len + 1); | ||
| 223 | for (uint8_t i = 0; i < len ;i++ ) | ||
| 224 | pArr[i] = rbuf[i + 1]; | ||
| 225 | #else | ||
| 226 | select(); | ||
| 227 | wait_MISO(); | ||
| 228 | spi_putc(spi_instr | READ_BURST); | ||
| 229 | for (uint8_t i = 0; i < len ;i++ ) | ||
| 230 | pArr[i] = spi_putc(0x00); | ||
| 231 | deselect(); | ||
| 232 | #endif | ||
| 233 | } | ||
| 234 | |||
| 235 | void CC1101::spi_write_burst(SPI_RW spi_instr, const uint8_t *pArr, uint8_t len) | ||
| 236 | { | ||
| 237 | #if defined(LINUX_ARCH_RASPBERRYPI) | ||
| 238 | uint8_t tbuf[len + 1]; | ||
| 239 | tbuf[0] = spi_instr | WRITE_BURST; | ||
| 240 | for (uint8_t i = 0; i < len; i++) | ||
| 241 | tbuf[i + 1] = pArr[i]; | ||
| 242 | wiringPiSPIDataRW(0, tbuf, len + 1); | ||
| 243 | #else | ||
| 244 | select(); | ||
| 245 | wait_MISO(); | ||
| 246 | spi_putc(spi_instr | WRITE_BURST); | ||
| 247 | for (uint8_t i = 0; i < len; i++) | ||
| 248 | spi_putc(pArr[i]); | ||
| 249 | deselect(); | ||
| 250 | #endif | ||
| 251 | } | ||
| 252 | |||
| 253 | bool CC1101::setup() | ||
| 254 | { | ||
| 255 | #if defined(LINUX_ARCH_RASPBERRYPI) | ||
| 256 | wiringPiSetup(); | ||
| 257 | #endif | ||
| 258 | pinMode(_gdo0, OUTPUT); | ||
| 259 | digitalWrite(_gdo0, LOW); | ||
| 260 | pinMode(_gdo2, INPUT); | ||
| 261 | |||
| 262 | spi_begin(); | ||
| 263 | reset(); | ||
| 264 | |||
| 265 | //uint8_t partnum = spi_read_register(PARTNUM); | ||
| 266 | uint8_t version = spi_read_register(VERSION); | ||
| 267 | //checks if valid Chip ID is found. Usualy 0x03 or 0x14. if not -> abort | ||
| 268 | if (version == 0x00 || version == 0xFF) | ||
| 269 | return false; | ||
| 270 | |||
| 271 | spi_write_burst(WRITE_BURST, cc1101_init, sizeof(cc1101_init)); | ||
| 272 | if (!register_check()) | ||
| 273 | return false; | ||
| 274 | _init_done = true; | ||
| 275 | return true; | ||
| 276 | } | ||
| 277 | |||
| 278 | bool CC1101::setISM(CC1101::ISM_FREQ ism_freq) | ||
| 279 | { | ||
| 280 | uint8_t freq2, freq1, freq0; | ||
| 281 | const uint8_t *patable = patable_power_ook; | ||
| 282 | switch(ism_freq) | ||
| 283 | { | ||
| 284 | case CC1101::FREQ_315MHZ: | ||
| 285 | freq2 = 0x0C; | ||
| 286 | freq1 = 0x1D; | ||
| 287 | freq0 = 0x89; | ||
| 288 | //patable = patable_power_315; | ||
| 289 | break; | ||
| 290 | case CC1101::FREQ_434MHZ: | ||
| 291 | freq2 = 0x10; | ||
| 292 | freq1 = 0xB0; | ||
| 293 | freq0 = 0x71; | ||
| 294 | //patable = patable_power_434; | ||
| 295 | break; | ||
| 296 | case CC1101::FREQ_868MHZ: | ||
| 297 | freq2 = 0x21; | ||
| 298 | freq1 = 0x65; | ||
| 299 | freq0 = 0x6A; | ||
| 300 | //patable = patable_power_868; | ||
| 301 | break; | ||
| 302 | case CC1101::FREQ_915MHZ: | ||
| 303 | freq2 = 0x23; | ||
| 304 | freq1 = 0x31; | ||
| 305 | freq0 = 0x3B; | ||
| 306 | //patable = patable_power_915; | ||
| 307 | break; | ||
| 308 | /* | ||
| 309 | case CC1101::FREQ_2430MHZ: | ||
| 310 | freq2 = 0x5D; | ||
| 311 | freq1 = 0x76; | ||
| 312 | freq0 = 0x27; | ||
| 313 | //patable = patable_power_2430; | ||
| 314 | break; | ||
| 315 | */ | ||
| 316 | default: | ||
| 317 | return false; | ||
| 318 | } | ||
| 319 | |||
| 320 | spi_write_burst(PATABLE_BURST, patable, 8); | ||
| 321 | |||
| 322 | // stores the new freq setting for defined ISM band | ||
| 323 | spi_write_register(FREQ2, freq2); | ||
| 324 | spi_write_register(FREQ1, freq1); | ||
| 325 | spi_write_register(FREQ0, freq0); | ||
| 326 | |||
| 327 | return true; | ||
| 328 | } | ||
| 329 | |||
| 330 | bool CC1101::register_check() | ||
| 331 | { | ||
| 332 | return (spi_read_register(PKTCTRL0) == cc1101_init[PKTCTRL0] | ||
| 333 | && spi_read_register(IOCFG2) == cc1101_init[IOCFG2]); | ||
| 334 | } | ||
| 335 | |||
| 336 | bool CC1101::transmit() | ||
| 337 | { | ||
| 338 | idle(); | ||
| 339 | setGDO0(LOW); | ||
| 340 | spi_write_strobe(STX); | ||
| 341 | uint8_t maxloop = 255; | ||
| 342 | while (--maxloop && (spi_read_register(MARCSTATE) & STATE_MASK) != STATE_TX) | ||
| 343 | delay(1); | ||
| 344 | return (maxloop != 0); | ||
| 345 | } | ||
| 346 | |||
| 347 | void CC1101::setGDO0(int value) | ||
| 348 | { | ||
| 349 | digitalWrite(_gdo0, value); | ||
| 350 | } | ||
| 351 | |||
| 352 | bool CC1101::receive() | ||
| 353 | { | ||
| 354 | idle(); | ||
| 355 | spi_write_strobe(SRX); | ||
| 356 | uint8_t maxloop = 255; | ||
| 357 | while (--maxloop && (spi_read_register(MARCSTATE) & STATE_MASK) != STATE_RX) | ||
| 358 | delay(1); | ||
| 359 | return (maxloop != 0); | ||
| 360 | } | ||
| 361 | |||
| 362 | void CC1101::wakeup() | ||
| 363 | { | ||
| 364 | digitalWrite(_ss, LOW); | ||
| 365 | delayMicroseconds(10); | ||
| 366 | digitalWrite(_ss, HIGH); | ||
| 367 | delayMicroseconds(10); | ||
| 368 | idle(); | ||
| 369 | } | ||
| 370 | |||
| 371 | void CC1101::powerdown() | ||
| 372 | { | ||
| 373 | idle(); | ||
| 374 | spi_write_strobe(SPWD); | ||
| 375 | } | ||
| 376 | |||
| 377 | void CC1101::idle() | ||
| 378 | { | ||
| 379 | spi_write_strobe(SIDLE); | ||
| 380 | uint8_t maxloop = 0xff; | ||
| 381 | while (--maxloop && (spi_read_register(MARCSTATE) & STATE_MASK) != STATE_IDLE) | ||
| 382 | delay(1); | ||
| 383 | delayMicroseconds(100); | ||
| 384 | } | ||
| 385 | |||
| 386 | void CC1101::reset(void) | ||
| 387 | { | ||
| 388 | digitalWrite(_ss, LOW); | ||
| 389 | delayMicroseconds(10); | ||
| 390 | digitalWrite(_ss, HIGH); | ||
| 391 | delayMicroseconds(40); | ||
| 392 | |||
| 393 | spi_write_strobe(SRES); | ||
| 394 | delay(1); | ||
| 395 | } | ||
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 @@ | |||
| 1 | #pragma once | ||
| 2 | |||
| 3 | class CC1101 | ||
| 4 | { | ||
| 5 | public: | ||
| 6 | enum ISM_FREQ | ||
| 7 | { | ||
| 8 | FREQ_315MHZ, | ||
| 9 | FREQ_434MHZ, | ||
| 10 | FREQ_868MHZ, | ||
| 11 | FREQ_915MHZ, | ||
| 12 | //FREQ_2430MHZ, | ||
| 13 | }; | ||
| 14 | |||
| 15 | CC1101(uint8_t gdo0, uint8_t gdo2, uint8_t ss) | ||
| 16 | : _gdo0(gdo0), _gdo2(gdo2), _ss(ss) | ||
| 17 | {} | ||
| 18 | ~CC1101(); | ||
| 19 | #if !defined(LINUX_ARCH_RASPBERRYPI) | ||
| 20 | void set_spi_pins(uint8_t mosi = MOSI, uint8_t miso = MISO, uint8_t sck = SCK); | ||
| 21 | #endif | ||
| 22 | bool setup(); | ||
| 23 | bool setISM(ISM_FREQ ism_freq); | ||
| 24 | bool register_check(); | ||
| 25 | bool transmit(); | ||
| 26 | void setGDO0(int value); | ||
| 27 | bool receive(); | ||
| 28 | void wakeup(void); | ||
| 29 | void powerdown(void); | ||
| 30 | void idle(); | ||
| 31 | void reset(void); | ||
| 32 | |||
| 33 | private: | ||
| 34 | enum Strobe | ||
| 35 | : uint8_t | ||
| 36 | { | ||
| 37 | SRES = 0x30, // Reset chip | ||
| 38 | SFSTXON = 0x31, // Enable/calibrate freq synthesizer | ||
| 39 | SXOFF = 0x32, // Turn off crystal oscillator | ||
| 40 | SCAL = 0x33, // Calibrate freq synthesizer & disable | ||
| 41 | SRX = 0x34, // Enable RX | ||
| 42 | STX = 0x35, // Enable TX | ||
| 43 | SIDLE = 0x36, // Exit RX / TX | ||
| 44 | SAFC = 0x37, // AFC adjustment of freq synthesizer | ||
| 45 | SWOR = 0x38, // Start automatic RX polling sequence | ||
| 46 | SPWD = 0x39, // Enter pwr down mode when CSn goes hi | ||
| 47 | SFRX = 0x3A, // Flush the RX FIFO buffer | ||
| 48 | SFTX = 0x3A, // Flush the TX FIFO buffer | ||
| 49 | SWORRST = 0x3B, // Reset real time clock | ||
| 50 | SNOP = 0x3C, // No operation | ||
| 51 | }; | ||
| 52 | |||
| 53 | enum SPI_RW | ||
| 54 | : uint8_t | ||
| 55 | { | ||
| 56 | WRITE_SINGLE_BYTE = 0x00, | ||
| 57 | WRITE_BURST = 0x40, | ||
| 58 | READ_SINGLE_BYTE = 0x80, | ||
| 59 | READ_BURST = 0xC0, | ||
| 60 | TXFIFO_BURST = 0x7F, // write burst only | ||
| 61 | TXFIFO_SINGLE_BYTE = 0x3F, // write single only | ||
| 62 | RXFIFO_BURST = 0xFF, // read burst only | ||
| 63 | RXFIFO_SINGLE_BYTE = 0xBF, // read single only | ||
| 64 | PATABLE_BURST = 0x7E, // power control read/write | ||
| 65 | PATABLE_SINGLE_BYTE = 0xFE, // power control read/write | ||
| 66 | }; | ||
| 67 | |||
| 68 | enum Register | ||
| 69 | : uint8_t | ||
| 70 | { | ||
| 71 | IOCFG2 = 0x00, // GDO2 output pin configuration | ||
| 72 | IOCFG1 = 0x01, // GDO1 output pin configuration | ||
| 73 | IOCFG0 = 0x02, // GDO0 output pin configuration | ||
| 74 | FIFOTHR = 0x03, // RX FIFO and TX FIFO thresholds | ||
| 75 | SYNC1 = 0x04, // Sync word, high byte | ||
| 76 | SYNC0 = 0x05, // Sync word, low byte | ||
| 77 | PKTLEN = 0x06, // Packet length | ||
| 78 | PKTCTRL1 = 0x07, // Packet automation control | ||
| 79 | PKTCTRL0 = 0x08, // Packet automation control | ||
| 80 | ADDR = 0x09, // Device address | ||
| 81 | CHANNR = 0x0A, // Channel number | ||
| 82 | FSCTRL1 = 0x0B, // Frequency synthesizer control | ||
| 83 | FSCTRL0 = 0x0C, // Frequency synthesizer control | ||
| 84 | FREQ2 = 0x0D, // Frequency control word, high byte | ||
| 85 | FREQ1 = 0x0E, // Frequency control word, middle byte | ||
| 86 | FREQ0 = 0x0F, // Frequency control word, low byte | ||
| 87 | MDMCFG4 = 0x10, // Modem configuration | ||
| 88 | MDMCFG3 = 0x11, // Modem configuration | ||
| 89 | MDMCFG2 = 0x12, // Modem configuration | ||
| 90 | MDMCFG1 = 0x13, // Modem configuration | ||
| 91 | MDMCFG0 = 0x14, // Modem configuration | ||
| 92 | DEVIATN = 0x15, // Modem deviation setting | ||
| 93 | MCSM2 = 0x16, // Main Radio Cntrl State Machine config | ||
| 94 | MCSM1 = 0x17, // Main Radio Cntrl State Machine config | ||
| 95 | MCSM0 = 0x18, // Main Radio Cntrl State Machine config | ||
| 96 | FOCCFG = 0x19, // Frequency Offset Compensation config | ||
| 97 | BSCFG = 0x1A, // Bit Synchronization configuration | ||
| 98 | AGCCTRL2 = 0x1B, // AGC control | ||
| 99 | AGCCTRL1 = 0x1C, // AGC control | ||
| 100 | AGCCTRL0 = 0x1D, // AGC control | ||
| 101 | WOREVT1 = 0x1E, // High byte Event 0 timeout | ||
| 102 | WOREVT0 = 0x1F, // Low byte Event 0 timeout | ||
| 103 | WORCTRL = 0x20, // Wake On Radio control | ||
| 104 | FREND1 = 0x21, // Front end RX configuration | ||
| 105 | FREND0 = 0x22, // Front end TX configuration | ||
| 106 | FSCAL3 = 0x23, // Frequency synthesizer calibration | ||
| 107 | FSCAL2 = 0x24, // Frequency synthesizer calibration | ||
| 108 | FSCAL1 = 0x25, // Frequency synthesizer calibration | ||
| 109 | FSCAL0 = 0x26, // Frequency synthesizer calibration | ||
| 110 | RCCTRL1 = 0x27, // RC oscillator configuration | ||
| 111 | RCCTRL0 = 0x28, // RC oscillator configuration | ||
| 112 | FSTEST = 0x29, // Frequency synthesizer cal control | ||
| 113 | PTEST = 0x2A, // Production test | ||
| 114 | AGCTEST = 0x2B, // AGC test | ||
| 115 | TEST2 = 0x2C, // Various test settings | ||
| 116 | TEST1 = 0x2D, // Various test settings | ||
| 117 | TEST0 = 0x2E, // Various test settings | ||
| 118 | |||
| 119 | PARTNUM = 0xF0, // Part number | ||
| 120 | VERSION = 0xF1, // Current version number | ||
| 121 | FREQEST = 0xF2, // Frequency offset estimate | ||
| 122 | LQI = 0xF3, // Demodulator estimate for link quality | ||
| 123 | RSSI = 0xF4, // Received signal strength indication | ||
| 124 | MARCSTATE = 0xF5, // Control state machine state | ||
| 125 | WORTIME1 = 0xF6, // High byte of WOR timer | ||
| 126 | WORTIME0 = 0xF7, // Low byte of WOR timer | ||
| 127 | PKTSTATUS = 0xF8, // Current GDOx status and packet status | ||
| 128 | VCO_VC_DAC = 0xF9, // Current setting from PLL cal module | ||
| 129 | TXBYTES = 0xFA, // Underflow and # of bytes in TXFIFO | ||
| 130 | RXBYTES = 0xFB, // Overflow and # of bytes in RXFIFO | ||
| 131 | RCCTRL1_STATUS = 0xFC, // Last RC Oscillator Calibration Result | ||
| 132 | RCCTRL0_STATUS = 0xFD, // Last RC Oscillator Calibration Result | ||
| 133 | }; | ||
| 134 | |||
| 135 | bool spi_begin(); | ||
| 136 | #if !defined(LINUX_ARCH_RASPBERRYPI) | ||
| 137 | void select(); | ||
| 138 | void deselect(); | ||
| 139 | bool wait_MISO(); | ||
| 140 | uint8_t spi_putc(const uint8_t value); | ||
| 141 | #endif | ||
| 142 | void spi_write_register(Register spi_instr, uint8_t value); | ||
| 143 | uint8_t spi_read_register(Register spi_instr); | ||
| 144 | uint8_t spi_write_strobe(Strobe spi_instr); | ||
| 145 | void spi_read_burst(SPI_RW spi_instr, uint8_t *pArr, uint8_t len); | ||
| 146 | void spi_write_burst(SPI_RW spi_instr, const uint8_t *pArr, uint8_t len); | ||
| 147 | |||
| 148 | private: | ||
| 149 | uint8_t _gdo0; | ||
| 150 | uint8_t _gdo2; | ||
| 151 | uint8_t _ss; | ||
| 152 | bool _init_done = false; | ||
| 153 | #if !defined(LINUX_ARCH_RASPBERRYPI) | ||
| 154 | uint8_t _mosi = MOSI; | ||
| 155 | uint8_t _miso = MISO; | ||
| 156 | uint8_t _sck = SCK; | ||
| 157 | #endif | ||
| 158 | }; | ||
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 @@ | |||
| 1 | #include <Arduino.h> | ||
| 2 | |||
| 3 | #include "hcs200.h" | ||
| 4 | |||
| 5 | /** Pulse width lengths used by the transmitter. A pulse width is the time | ||
| 6 | * between a transition from 0 to 1 or from 1 to 0 of the input pin. | ||
| 7 | */ | ||
| 8 | enum RbType | ||
| 9 | { | ||
| 10 | RB_SHORT = 0, // pulse_width | ||
| 11 | RB_LONG, // 2 * pulse_width | ||
| 12 | RB_ERROR | ||
| 13 | }; | ||
| 14 | |||
| 15 | /** Determine if 'pulse' is a short pulse, RB_SHORT or a long one, RB_LONG. | ||
| 16 | * Normally, a short pulse is the same as the 'tx_clock' and a long pulse is | ||
| 17 | * twice as long. However, timing problems with servicing interrupts means | ||
| 18 | * that we need to add some fudge factors. | ||
| 19 | */ | ||
| 20 | int HCS200::classify(unsigned pulse) | ||
| 21 | { | ||
| 22 | int d = pulse - tx_clock; | ||
| 23 | if (d < -100) | ||
| 24 | return RB_ERROR; | ||
| 25 | else if (d < 100) | ||
| 26 | return RB_SHORT; | ||
| 27 | else | ||
| 28 | { | ||
| 29 | d -= tx_clock; | ||
| 30 | if (d < -100) | ||
| 31 | return RB_ERROR; | ||
| 32 | else if (d < 100) | ||
| 33 | return RB_LONG; | ||
| 34 | else | ||
| 35 | return RB_ERROR; | ||
| 36 | } | ||
| 37 | } | ||
| 38 | |||
| 39 | #define IN_RANGE(x, min, max) (x >= min && x <= max) | ||
| 40 | void HCS200::on_isr(uint8_t value) | ||
| 41 | { | ||
| 42 | unsigned long timestamp = micros(); | ||
| 43 | unsigned long pulse_width = timestamp - last_timestamp; | ||
| 44 | int d; | ||
| 45 | |||
| 46 | switch (rx_state) { | ||
| 47 | case RS_PREAMBLE_START: | ||
| 48 | // value == 0 | ||
| 49 | preamble_count = 1; | ||
| 50 | rx_state = RS_PREAMBLE_LOW; | ||
| 51 | tx_clock = pulse_width; | ||
| 52 | break; | ||
| 53 | case RS_PREAMBLE_LOW: | ||
| 54 | // value == 1 | ||
| 55 | preamble_count++; | ||
| 56 | d = pulse_width - tx_clock; | ||
| 57 | rx_state = (IN_RANGE(d, -100, 100)) ? RS_PREAMBLE_HIGH : RS_NOSYNC; | ||
| 58 | break; | ||
| 59 | case RS_PREAMBLE_HIGH: | ||
| 60 | // value == 0 | ||
| 61 | preamble_count++; | ||
| 62 | d = pulse_width - tx_clock; | ||
| 63 | if (IN_RANGE(d, -100, 100)) | ||
| 64 | rx_state = (preamble_count == 23) ? RS_PREAMBLE_HEADER : RS_PREAMBLE_LOW; | ||
| 65 | else | ||
| 66 | rx_state = RS_NOSYNC; | ||
| 67 | break; | ||
| 68 | case RS_PREAMBLE_HEADER: | ||
| 69 | // header should be low for 10*tx_clock | ||
| 70 | d = pulse_width - 10 * tx_clock; | ||
| 71 | if (value == 1 && IN_RANGE(d, -100, 100)) | ||
| 72 | { | ||
| 73 | rx_state = RS_DATA; | ||
| 74 | rx_bit_count = 0; | ||
| 75 | memset(rx_buf, 0, sizeof(rx_buf)); | ||
| 76 | } | ||
| 77 | else | ||
| 78 | rx_state = RS_NOSYNC; | ||
| 79 | break; | ||
| 80 | case RS_DATA: | ||
| 81 | if (value == 1) | ||
| 82 | { | ||
| 83 | int first = classify(last_pulse_width); | ||
| 84 | int second = classify(pulse_width); | ||
| 85 | // received a 1 bit | ||
| 86 | if (first == RB_SHORT && second == RB_LONG) | ||
| 87 | { | ||
| 88 | int idx = rx_bit_count / 32; | ||
| 89 | rx_buf[idx] >>= 1; | ||
| 90 | rx_buf[idx] |= 0x80000000; | ||
| 91 | rx_bit_count++; | ||
| 92 | } | ||
| 93 | // received a 0 bit | ||
| 94 | else if (first == RB_LONG && second == RB_SHORT) | ||
| 95 | { | ||
| 96 | int idx = rx_bit_count / 32; | ||
| 97 | rx_buf[idx] >>= 1; | ||
| 98 | rx_bit_count++; | ||
| 99 | } | ||
| 100 | else | ||
| 101 | rx_state = RS_NOSYNC; | ||
| 102 | |||
| 103 | // we ignore the last bit as it's always "1" | ||
| 104 | // instead we use the raising edge as trigger to stop | ||
| 105 | if (rx_bit_count == MAX_BITS - 1) | ||
| 106 | rx_state = RS_COMPLETED; | ||
| 107 | } | ||
| 108 | break; | ||
| 109 | } | ||
| 110 | |||
| 111 | // check outside of the state machine | ||
| 112 | // this is important as otherwise otherwise we always miss the start | ||
| 113 | if (rx_state == RS_NOSYNC && value == 1) | ||
| 114 | rx_state = RS_PREAMBLE_START; | ||
| 115 | |||
| 116 | last_timestamp = timestamp; | ||
| 117 | last_pulse_width = pulse_width; | ||
| 118 | } | ||
| 119 | |||
| 120 | void HCS200::reset() | ||
| 121 | { | ||
| 122 | rx_state = RS_NOSYNC; | ||
| 123 | } | ||
| 124 | |||
| 125 | bool HCS200::decode(HCS200_Keycode &out) | ||
| 126 | { | ||
| 127 | if (rx_state != RS_COMPLETED) | ||
| 128 | return false; | ||
| 129 | out.encrypted = rx_buf[0]; | ||
| 130 | out.serial = rx_buf[1] & 0x0FFFFFFF; | ||
| 131 | out.buttons = (rx_buf[1] >> 28) & 0xF; | ||
| 132 | out.lowbat = rx_buf[2] & 0x80000000; | ||
| 133 | return true; | ||
| 134 | } | ||
| 135 | |||
| 136 | void HCS200::print_state(Print &stream) | ||
| 137 | { | ||
| 138 | stream.print("rx_state="); | ||
| 139 | stream.print(rx_state, DEC); | ||
| 140 | stream.print(", rx_bit_count="); | ||
| 141 | Serial.print(rx_bit_count, DEC); | ||
| 142 | stream.print(", tx_clock="); | ||
| 143 | stream.print(tx_clock, DEC); | ||
| 144 | stream.print(", preamble_count="); | ||
| 145 | stream.println(preamble_count, DEC); | ||
| 146 | } | ||
| 147 | |||
| 148 | void HCS200_Keycode::print(Print &stream) | ||
| 149 | { | ||
| 150 | stream.print("Keyfob# "); | ||
| 151 | stream.print(serial, HEX); | ||
| 152 | stream.print(", buttons:"); | ||
| 153 | if (buttons & BM_S0) | ||
| 154 | stream.print(" 1"); | ||
| 155 | if (buttons & BM_S1) | ||
| 156 | stream.print(" 2"); | ||
| 157 | if (buttons & BM_S2) | ||
| 158 | stream.print(" 3"); | ||
| 159 | if (buttons & BM_S3) | ||
| 160 | stream.print(" 4"); | ||
| 161 | stream.print(", lowbat="); | ||
| 162 | stream.print(lowbat, DEC); | ||
| 163 | stream.print(", code="); | ||
| 164 | stream.print(encrypted, HEX); | ||
| 165 | stream.print("\n"); | ||
| 166 | } | ||
| 167 | |||
| 168 | #define PULSE_WIDTH 400 | ||
| 169 | |||
| 170 | inline void HCS200_Keycode::send(bool value, std::function<void(int)> setOutput) | ||
| 171 | { | ||
| 172 | if (!value) | ||
| 173 | { | ||
| 174 | setOutput(HIGH); | ||
| 175 | delayMicroseconds(PULSE_WIDTH); | ||
| 176 | delayMicroseconds(PULSE_WIDTH); | ||
| 177 | setOutput(LOW); | ||
| 178 | delayMicroseconds(PULSE_WIDTH); | ||
| 179 | } | ||
| 180 | else | ||
| 181 | { | ||
| 182 | setOutput(HIGH); | ||
| 183 | delayMicroseconds(PULSE_WIDTH); | ||
| 184 | setOutput(LOW); | ||
| 185 | delayMicroseconds(PULSE_WIDTH); | ||
| 186 | delayMicroseconds(PULSE_WIDTH); | ||
| 187 | } | ||
| 188 | } | ||
| 189 | |||
| 190 | void HCS200_Keycode::send(std::function<void(int)> setOutput) | ||
| 191 | { | ||
| 192 | uint32_t val; | ||
| 193 | |||
| 194 | // preamble | ||
| 195 | for(unsigned short i = 0; i < 11; i++) | ||
| 196 | { | ||
| 197 | setOutput(HIGH); | ||
| 198 | delayMicroseconds(PULSE_WIDTH); | ||
| 199 | setOutput(LOW); | ||
| 200 | delayMicroseconds(PULSE_WIDTH); | ||
| 201 | } | ||
| 202 | setOutput(HIGH); | ||
| 203 | delayMicroseconds(PULSE_WIDTH); | ||
| 204 | |||
| 205 | // header | ||
| 206 | setOutput(LOW); | ||
| 207 | delayMicroseconds(PULSE_WIDTH * 10); | ||
| 208 | |||
| 209 | // encrypted | ||
| 210 | val = this->encrypted; | ||
| 211 | for(unsigned short i = 0; i < 32; i++) | ||
| 212 | { | ||
| 213 | send(val & 0x01, setOutput); | ||
| 214 | val >>= 1; | ||
| 215 | } | ||
| 216 | |||
| 217 | // serial | ||
| 218 | val = this->serial; | ||
| 219 | for(unsigned short i = 0; i < 28; i++) | ||
| 220 | { | ||
| 221 | send(val & 0x01, setOutput); | ||
| 222 | val >>= 1; | ||
| 223 | } | ||
| 224 | |||
| 225 | // buttons | ||
| 226 | val = this->buttons; | ||
| 227 | for(unsigned short i = 0; i < 4; i++) | ||
| 228 | { | ||
| 229 | send(val & 0x01, setOutput); | ||
| 230 | val >>= 1; | ||
| 231 | } | ||
| 232 | |||
| 233 | // lowbat | ||
| 234 | send(this->lowbat, setOutput); | ||
| 235 | |||
| 236 | // RPT | ||
| 237 | send(1, setOutput); | ||
| 238 | |||
| 239 | // guard time | ||
| 240 | setOutput(LOW); | ||
| 241 | delayMicroseconds(PULSE_WIDTH * 39); | ||
| 242 | } | ||
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 @@ | |||
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include <functional> | ||
| 4 | #include <Print.h> | ||
| 5 | |||
| 6 | class HCS200_Keycode | ||
| 7 | { | ||
| 8 | public: | ||
| 9 | /** Bit masks to determine which button was pressed on the keyfob. | ||
| 10 | * NOTE: the bit position does not match the button number | ||
| 11 | */ | ||
| 12 | enum ButtonMask | ||
| 13 | : unsigned char | ||
| 14 | { | ||
| 15 | BM_S3 = 0x1, | ||
| 16 | BM_S0 = 0x2, | ||
| 17 | BM_S1 = 0x4, | ||
| 18 | BM_S2 = 0x8 | ||
| 19 | }; | ||
| 20 | |||
| 21 | void print(Print &stream); | ||
| 22 | void send(std::function<void(int)> setOutput); | ||
| 23 | |||
| 24 | private: | ||
| 25 | void send(bool value, std::function<void(int)> setOutput); | ||
| 26 | |||
| 27 | public: | ||
| 28 | uint32_t encrypted; | ||
| 29 | uint32_t serial; | ||
| 30 | unsigned char buttons; | ||
| 31 | bool lowbat; | ||
| 32 | }; | ||
| 33 | |||
| 34 | class HCS200 | ||
| 35 | { | ||
| 36 | public: | ||
| 37 | enum RxState | ||
| 38 | { | ||
| 39 | RS_NOSYNC = 0, // Receiver is inactive | ||
| 40 | RS_PREAMBLE_START, | ||
| 41 | RS_PREAMBLE_LOW, | ||
| 42 | RS_PREAMBLE_HIGH, | ||
| 43 | RS_PREAMBLE_HEADER, | ||
| 44 | RS_DATA, // DATA is being received | ||
| 45 | RS_COMPLETED // Receive complete | ||
| 46 | }; | ||
| 47 | |||
| 48 | void on_isr(uint8_t value); | ||
| 49 | void reset(); | ||
| 50 | bool decode(struct HCS200_Keycode &out); | ||
| 51 | void print_state(Print &stream); | ||
| 52 | |||
| 53 | public: | ||
| 54 | int classify(unsigned pulse); | ||
| 55 | |||
| 56 | /** The state of the code word received, this is 'volatile' since it is | ||
| 57 | * accessed from both the main loop and the interrupt service routine. | ||
| 58 | */ | ||
| 59 | volatile char rx_state = RS_NOSYNC; | ||
| 60 | |||
| 61 | /** Time stamp when the previous interrupt was triggered (when the input pin | ||
| 62 | * transitioned either from 0 to 1 or from 1 to 0. This is the value of | ||
| 63 | * micros() function. | ||
| 64 | */ | ||
| 65 | unsigned long last_timestamp = 0; | ||
| 66 | |||
| 67 | /** The duration of the last pulse width (the time between a transition of the | ||
| 68 | * input pin) | ||
| 69 | */ | ||
| 70 | unsigned last_pulse_width = 0; | ||
| 71 | |||
| 72 | /** Number of changes during preamble | ||
| 73 | */ | ||
| 74 | char preamble_count = 0; | ||
| 75 | |||
| 76 | /** Number of bits received already. | ||
| 77 | */ | ||
| 78 | char rx_bit_count = 0; | ||
| 79 | |||
| 80 | /** Duration of the transmitter clock -- this is determined when the preamble | ||
| 81 | * is received, as the preamble has all the pulse widths the same length. | ||
| 82 | * | ||
| 83 | * The remotes I tested transmit at 2400 bauds, so the tx_clock is always | ||
| 84 | * about 420 micro seconds, however, this code attempts to determine it | ||
| 85 | * dynamically and should adapt to transmitters of different speeds. | ||
| 86 | */ | ||
| 87 | unsigned tx_clock = 0; | ||
| 88 | |||
| 89 | /** Number of bits we expect to receive. The HCS200 data sheet specifies that | ||
| 90 | * 66 bits are transmited. | ||
| 91 | */ | ||
| 92 | const unsigned short MAX_BITS = 66; | ||
| 93 | |||
| 94 | /** Buffer where we store the received bits. HCS sends only 66 | ||
| 95 | */ | ||
| 96 | uint32_t rx_buf[3]; | ||
| 97 | }; | ||
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 @@ | |||
| 1 | #include <Arduino.h> | ||
| 2 | #include <WiFiManager.h> | ||
| 3 | #include <ESP8266mDNS.h> | ||
| 4 | #include <ESP8266WebServer.h> | ||
| 5 | |||
| 6 | #include "cc1101.h" | ||
| 7 | #include "hcs200.h" | ||
| 8 | |||
| 9 | void http_door_open(); | ||
| 10 | void serial_door_open(); | ||
| 11 | |||
| 12 | ESP8266WebServer http_service(80); | ||
| 13 | CC1101 radio(D0 /* GDO0 */, D2 /* GDO2 */, D8 /* SS */); | ||
| 14 | |||
| 15 | #define PIN_HCS200_ENABLE D3 | ||
| 16 | #define PIN_HCS200_DATA D1 | ||
| 17 | //#define HCS200_TEST | ||
| 18 | HCS200 hcs200; | ||
| 19 | |||
| 20 | void setup() | ||
| 21 | { | ||
| 22 | Serial.begin(9600); | ||
| 23 | |||
| 24 | pinMode(PIN_HCS200_ENABLE, OUTPUT); | ||
| 25 | digitalWrite(PIN_HCS200_ENABLE, LOW); | ||
| 26 | pinMode(PIN_HCS200_DATA, INPUT); | ||
| 27 | |||
| 28 | if (!radio.setup()) | ||
| 29 | Serial.println("Radio not found!"); | ||
| 30 | else if (!radio.setISM(CC1101::FREQ_434MHZ)) | ||
| 31 | Serial.println("Unable to set radio frequency"); | ||
| 32 | else | ||
| 33 | { | ||
| 34 | Serial.println("Radio found"); | ||
| 35 | (void)radio.idle(); | ||
| 36 | } | ||
| 37 | |||
| 38 | WiFiManager wifiManager; | ||
| 39 | wifiManager.setConfigPortalTimeout(600); | ||
| 40 | Serial.printf_P(PSTR("Setting up WiFi\n")); | ||
| 41 | WiFi.hostname("door"); | ||
| 42 | if (!wifiManager.autoConnect("a-door-able")) | ||
| 43 | { | ||
| 44 | Serial.printf_P(PSTR("Failed to connect and hit timeout\n")); | ||
| 45 | delay(5000); | ||
| 46 | ESP.restart(); | ||
| 47 | } | ||
| 48 | |||
| 49 | if (!MDNS.begin("door")) | ||
| 50 | Serial.println("Error setting up MDNS responder!"); | ||
| 51 | MDNS.addService("http", "tcp", 80); | ||
| 52 | |||
| 53 | http_service.on("/", HTTP_GET, []() { | ||
| 54 | http_service.send_P(200, PSTR("text/plain"), PSTR("I'm a-door-able")); | ||
| 55 | }); | ||
| 56 | http_service.on("/door/open", HTTP_PUT, http_door_open); | ||
| 57 | http_service.begin(); | ||
| 58 | |||
| 59 | Serial.println("Started listening"); | ||
| 60 | } | ||
| 61 | |||
| 62 | void ICACHE_RAM_ATTR on_hcs200_isr() | ||
| 63 | { | ||
| 64 | hcs200.on_isr(digitalRead(PIN_HCS200_DATA)); | ||
| 65 | } | ||
| 66 | |||
| 67 | bool hcs200_get_keycode(HCS200_Keycode &keycode) | ||
| 68 | { | ||
| 69 | #if !defined(HCS200_TEST) | ||
| 70 | hcs200.reset(); | ||
| 71 | attachInterrupt(digitalPinToInterrupt(PIN_HCS200_DATA), on_hcs200_isr, CHANGE); | ||
| 72 | digitalWrite(PIN_HCS200_ENABLE, HIGH); | ||
| 73 | uint8_t maxloop = 255; | ||
| 74 | while(--maxloop) | ||
| 75 | { | ||
| 76 | if (hcs200.decode(keycode)) | ||
| 77 | break; | ||
| 78 | delay(1); | ||
| 79 | } | ||
| 80 | digitalWrite(PIN_HCS200_ENABLE, LOW); | ||
| 81 | detachInterrupt(digitalPinToInterrupt(PIN_HCS200_DATA)); | ||
| 82 | return (maxloop != 0); | ||
| 83 | #else | ||
| 84 | keycode.encrypted = 0xDEAD; | ||
| 85 | keycode.serial = 0xC0DE; | ||
| 86 | keycode.buttons = HCS200_Keycode::BM_S0; | ||
| 87 | keycode.lowbat = false; | ||
| 88 | return true; | ||
| 89 | #endif | ||
| 90 | } | ||
| 91 | |||
| 92 | void http_door_open() | ||
| 93 | { | ||
| 94 | if (!radio.register_check()) | ||
| 95 | { | ||
| 96 | http_service.send_P(500, PSTR("text/plain"), PSTR("Radio error")); | ||
| 97 | return; | ||
| 98 | } | ||
| 99 | |||
| 100 | HCS200_Keycode keycode; | ||
| 101 | if (!hcs200_get_keycode(keycode)) | ||
| 102 | { | ||
| 103 | http_service.send_P(500, PSTR("text/plain"), PSTR("No data from HCS200")); | ||
| 104 | return; | ||
| 105 | } | ||
| 106 | |||
| 107 | Serial.print("Got keycode: "); | ||
| 108 | keycode.print(Serial); | ||
| 109 | |||
| 110 | if (!radio.transmit()) | ||
| 111 | { | ||
| 112 | http_service.send_P(500, PSTR("text/plain"), PSTR("Unable to enable radio transmit mode")); | ||
| 113 | return; | ||
| 114 | } | ||
| 115 | |||
| 116 | long count = constrain(http_service.arg("count").toInt(), 2, 10); | ||
| 117 | for (unsigned short i = 0; i < count; i++) | ||
| 118 | keycode.send([&](int value) { radio.setGDO0(value); }); | ||
| 119 | |||
| 120 | (void)radio.idle(); | ||
| 121 | Serial.println("Sending done"); | ||
| 122 | http_service.send_P(200, PSTR("text/plain"), PSTR("OK")); | ||
| 123 | } | ||
| 124 | |||
| 125 | void serial_door_open(Print &stream) | ||
| 126 | { | ||
| 127 | if (!radio.register_check()) | ||
| 128 | { | ||
| 129 | stream.println("Radio error"); | ||
| 130 | return; | ||
| 131 | } | ||
| 132 | |||
| 133 | HCS200_Keycode keycode; | ||
| 134 | bool key_valid = hcs200_get_keycode(keycode); | ||
| 135 | if (!key_valid) | ||
| 136 | stream.println("No data from HCS200"); | ||
| 137 | stream.print("HCS200 decoder state: "); | ||
| 138 | hcs200.print_state(stream); | ||
| 139 | if (!key_valid) | ||
| 140 | return; | ||
| 141 | |||
| 142 | stream.print("Got keycode: "); | ||
| 143 | keycode.print(stream); | ||
| 144 | |||
| 145 | if (!radio.transmit()) | ||
| 146 | { | ||
| 147 | stream.println("Unable to enable radio transmit mode"); | ||
| 148 | return; | ||
| 149 | } | ||
| 150 | |||
| 151 | /* send twice */ | ||
| 152 | keycode.send([&](int value) { radio.setGDO0(value); }); | ||
| 153 | keycode.send([&](int value) { radio.setGDO0(value); }); | ||
| 154 | |||
| 155 | (void)radio.idle(); | ||
| 156 | stream.println("Sending done"); | ||
| 157 | } | ||
| 158 | |||
| 159 | void loop() | ||
| 160 | { | ||
| 161 | MDNS.update(); | ||
| 162 | http_service.handleClient(); | ||
| 163 | |||
| 164 | if (Serial.available() > 0) | ||
| 165 | { | ||
| 166 | (void)Serial.read(); | ||
| 167 | serial_door_open(Serial); | ||
| 168 | } | ||
| 169 | |||
| 170 | delay(1); | ||
| 171 | } | ||
