summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormanuel <manuel@mausz.at>2020-03-17 12:18:56 +0100
committermanuel <manuel@mausz.at>2020-03-17 12:18:56 +0100
commit0553b9907f912e56a2cd2a08b03a83ce6f2a75c6 (patch)
tree45ed1908a50168f8420f13866cfd68386d0f2c78
parentf0ecb4d38fff522c72905a8551355ca925381fa3 (diff)
downloadarduino-0553b9907f912e56a2cd2a08b03a83ce6f2a75c6.tar.gz
arduino-0553b9907f912e56a2cd2a08b03a83ce6f2a75c6.tar.bz2
arduino-0553b9907f912e56a2cd2a08b03a83ce6f2a75c6.zip
Add martin/door
-rw-r--r--bs_button/.travis.yml67
-rw-r--r--martin/door/.gitignore5
-rw-r--r--martin/door/platformio.ini29
-rw-r--r--martin/door/src/cc1101.cpp395
-rw-r--r--martin/door/src/cc1101.h158
-rw-r--r--martin/door/src/hcs200.cpp242
-rw-r--r--martin/door/src/hcs200.h97
-rw-r--r--martin/door/src/main.cpp171
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]
12default_envs = d1_mini
13
14[common_env_data]
15lib_deps =
16
17[env:nodemcuv2]
18platform = espressif8266
19framework = arduino
20board = nodemcuv2
21lib_deps = ${common_env_data.lib_deps}
22monitor_speed = 9600
23
24[env:d1_mini]
25platform = espressif8266
26framework = arduino
27board = d1_mini
28lib_deps = ${common_env_data.lib_deps}
29monitor_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
11static 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
56static const uint8_t patable_power_315[8] = { 0x17, 0x1D, 0x26, 0x69, 0x51, 0x86, 0xCC, 0xC3 };
57static const uint8_t patable_power_434[8] = { 0x6C, 0x1C, 0x06, 0x3A, 0x51, 0x85, 0xC8, 0xC0 };
58static const uint8_t patable_power_868[8] = { 0x00, 0x17, 0x1D, 0x26, 0x50, 0x86, 0xCD, 0xC0 };
59static const uint8_t patable_power_915[8] = { 0x0B, 0x1B, 0x6D, 0x67, 0x50, 0x85, 0xC9, 0xC1 };
60
61// 5 dB default value for factory reset
62static const uint8_t patable_power_ook[8] = { 0x00, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
63
64enum 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
93CC1101::~CC1101()
94{
95 if (_init_done)
96 (void)idle();
97}
98
99#if !defined(LINUX_ARCH_RASPBERRYPI)
100void 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
107void CC1101::select()
108{
109 digitalWrite(_ss, LOW);
110}
111
112void CC1101::deselect()
113{
114 digitalWrite(_ss, HIGH);
115}
116
117bool 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
125uint8_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
138bool 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
167void 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
183uint8_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
200uint8_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
217void 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
235void 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
253bool 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
278bool 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
330bool CC1101::register_check()
331{
332 return (spi_read_register(PKTCTRL0) == cc1101_init[PKTCTRL0]
333 && spi_read_register(IOCFG2) == cc1101_init[IOCFG2]);
334}
335
336bool 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
347void CC1101::setGDO0(int value)
348{
349 digitalWrite(_gdo0, value);
350}
351
352bool 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
362void CC1101::wakeup()
363{
364 digitalWrite(_ss, LOW);
365 delayMicroseconds(10);
366 digitalWrite(_ss, HIGH);
367 delayMicroseconds(10);
368 idle();
369}
370
371void CC1101::powerdown()
372{
373 idle();
374 spi_write_strobe(SPWD);
375}
376
377void 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
386void 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
3class 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 */
8enum 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 */
20int 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)
40void 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
120void HCS200::reset()
121{
122 rx_state = RS_NOSYNC;
123}
124
125bool 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
136void 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
148void 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
170inline 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
190void 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
6class 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
34class 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
9void http_door_open();
10void serial_door_open();
11
12ESP8266WebServer http_service(80);
13CC1101 radio(D0 /* GDO0 */, D2 /* GDO2 */, D8 /* SS */);
14
15#define PIN_HCS200_ENABLE D3
16#define PIN_HCS200_DATA D1
17//#define HCS200_TEST
18HCS200 hcs200;
19
20void 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
62void ICACHE_RAM_ATTR on_hcs200_isr()
63{
64 hcs200.on_isr(digitalRead(PIN_HCS200_DATA));
65}
66
67bool 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
92void 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
125void 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
159void 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}