├── .gitignore
├── README.md
├── ep.sh
├── micropython.cmake
├── micropython.mk
└── rcswitch.c
/.gitignore:
--------------------------------------------------------------------------------
1 | main.py
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # micropython-rcswitch
2 |
3 | Port of the [rc-switch](https://github.com/sui77/rc-switch) library to micropython.
4 |
5 | ### Receiving and decoding RC codes
6 | Find out what codes your remote control sends out. Use your remote control to control esp32.
7 | All you need is an esp32, a 315/433 MHz AM receiver (although there is no manual yet, but you can hack an existing device), and a remote control.
8 | At the moment the library only supports signal reception and decoding.
9 |
10 | The `cc1101` module uses SPI to communicate with the `esp32`. Configure the module so that it receives the signal in raw mode. Use pin `gd0` / `gd2` to initialize the `rc-switch` library.
11 |
12 | ### Instalation
13 | This code is an esp-idf project. You will need esp-idf to compile it. Newer versions of esp-idf may introduce incompatibilities with this code; for your reference, the code was tested against verstion `v4.4`
14 |
15 | ```sh
16 | make BOARD=GENERIC_C3 clean
17 | make BOARD=GENERIC_C3 USER_C_MODULES=micropython.cmake deploy
18 | ```
19 |
20 | For docker (use `ep.sh` for entrypoint)
21 | ```sh
22 | docker run -it --device=/dev/ttyUSB0 -v "$(pwd)"/cc1101:/esp32/cc1101 esp-idf:v4.4 /bin/bash -c "/esp32/cc1101/ep.sh"
23 | ```
24 |
25 | ### How to use
26 |
27 | ```python
28 | from rcswitch import RCSwitch
29 |
30 | #callback function
31 | def getCode(val):
32 | print(val)
33 |
34 | rf = RCSwitch()
35 | rf.enableReceive(10, getCode) # set the pin number and callback func
36 | ```
37 |
38 | ### How do I support this?
39 |
40 | Contribute code or buy me a coffee :)
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/ep.sh:
--------------------------------------------------------------------------------
1 | #/bin/bash
2 |
3 | # docker run -it --device=/dev/ttyUSB0 -v "$(pwd)"/cc1101:/esp32/cc1101 esp-idf:v4.4 /bin/bash -c "/esp32/cc1101/ep.sh"
4 |
5 | export LC_ALL=C.UTF-8
6 | export LANG=C.UTF-8
7 |
8 | cd ..
9 | git clone https://github.com/micropython/micropython.git /esp32/micropython
10 | cd /esp32/micropython
11 | git submodule update --init --recursive
12 | make -C mpy-cross
13 |
14 | cd /esp32/micropython/ports/esp32
15 | . $IDF_PATH/export.sh
16 | make submodules
17 |
18 | esptool.py -p /dev/ttyUSB0 -b 460800 erase_flash
19 | make BOARD=GENERIC_C3 clean
20 | make BOARD=GENERIC_C3 USER_C_MODULES=/esp32/cc1101/micropython.cmake deploy
21 |
22 | ampy -p /dev/ttyUSB0 put /esp32/cc1101/main.py
--------------------------------------------------------------------------------
/micropython.cmake:
--------------------------------------------------------------------------------
1 | # Create an INTERFACE library for our C module.
2 | add_library(usermod_rcswitch INTERFACE)
3 |
4 | # Add our source files to the lib
5 | target_sources(usermod_rcswitch INTERFACE
6 | ${CMAKE_CURRENT_LIST_DIR}/rcswitch.c
7 | )
8 |
9 | # Add the current directory as an include directory.
10 | target_include_directories(usermod_rcswitch INTERFACE
11 | ${CMAKE_CURRENT_LIST_DIR}
12 | )
13 |
14 | # Link our INTERFACE library to the usermod target.
15 | target_link_libraries(usermod INTERFACE usermod_rcswitch)
16 |
--------------------------------------------------------------------------------
/micropython.mk:
--------------------------------------------------------------------------------
1 | RCSWITCH_MOD_DIR := $(USERMOD_DIR)
2 |
3 | # Add all C files to SRC_USERMOD.
4 | SRC_USERMOD += $(RCSWITCH_MOD_DIR)/rcswitch.c
5 |
6 | # We can add our module folder to include paths if needed
7 | # This is not actually needed in this example.
8 | CFLAGS_USERMOD += -I$(RCSWITCH_MOD_DIR)
9 | CRCSWITCH_MOD_DIR := $(USERMOD_DIR)
10 |
--------------------------------------------------------------------------------
/rcswitch.c:
--------------------------------------------------------------------------------
1 | #include "py/runtime.h"
2 | #include "py/mphal.h"
3 |
4 | #include
5 | #include
6 | #include "freertos/FreeRTOS.h"
7 | #include "freertos/task.h"
8 | #include "esp_system.h"
9 | #include "esp_log.h"
10 | #include "driver/gpio.h"
11 | #include "esp_timer.h" // for esp-idf v5
12 |
13 | extern bool mp_sched_schedule(mp_obj_t function, mp_obj_t arg);
14 |
15 | // ----------------------------------------------------------------
16 |
17 | typedef struct _rcswitch_HighLow_obj_t {
18 | mp_uint_t high;
19 | mp_uint_t low;
20 | } rcswitch_HighLow_obj_t;
21 |
22 | typedef struct _rcswitch_Protocol_obj_t {
23 | /** base pulse length in microseconds, e.g. 350 */
24 | mp_uint_t pulseLength;
25 |
26 | rcswitch_HighLow_obj_t syncFactor;
27 | rcswitch_HighLow_obj_t zero;
28 | rcswitch_HighLow_obj_t one;
29 |
30 | bool invertedSignal;
31 | } rcswitch_Protocol_obj_t;
32 |
33 | #define RCSWITCH_MAX_CHANGES 67
34 | #define ESP_INTR_FLAG_DEFAULT 0
35 | #define TAG "RF433"
36 |
37 | typedef struct _rcswitch_RCSwitch_obj_t {
38 | // All objects start with the base.
39 | mp_obj_base_t base;
40 | gpio_num_t id;
41 |
42 | mp_uint_t nReceivedValue;
43 | mp_uint_t nReceivedBitlength;
44 | mp_uint_t nReceivedDelay;
45 | mp_uint_t nReceivedProtocol;
46 | mp_int_t nReceiveTolerance;
47 | mp_uint_t nSeparationLimit;
48 | /*
49 | * timings[0] contains sync timing, followed by a number of bits
50 | */
51 | mp_uint_t timings[RCSWITCH_MAX_CHANGES];
52 | mp_int_t nReceiverInterrupt;
53 |
54 | mp_int_t nTransmitterPin;
55 | mp_int_t nRepeatTransmit;
56 |
57 | mp_obj_t handler;
58 |
59 | rcswitch_Protocol_obj_t protocol;
60 | } rcswitch_RCSwitch_obj_t;
61 |
62 | // -----------------------------------------------------------------
63 |
64 | static const rcswitch_Protocol_obj_t proto[] = {
65 | { 350, { 1, 31 }, { 1, 3 }, { 3, 1 }, false }, // protocol 1
66 | { 650, { 1, 10 }, { 1, 2 }, { 2, 1 }, false }, // protocol 2
67 | { 100, { 30, 71 }, { 4, 11 }, { 9, 6 }, false }, // protocol 3
68 | { 380, { 1, 6 }, { 1, 3 }, { 3, 1 }, false }, // protocol 4
69 | { 500, { 6, 14 }, { 1, 2 }, { 2, 1 }, false }, // protocol 5
70 | { 450, { 23, 1 }, { 1, 2 }, { 2, 1 }, true }, // protocol 6 (HT6P20B)
71 | { 150, { 2, 62 }, { 1, 6 }, { 6, 1 }, false }, // protocol 7 (HS2303-PT, i. e. used in AUKEY Remote)
72 | { 200, { 3, 130}, { 7, 16 }, { 3, 16}, false}, // protocol 8 Conrad RS-200 RX
73 | { 200, { 130, 7 }, { 16, 7 }, { 16, 3 }, true}, // protocol 9 Conrad RS-200 TX
74 | { 365, { 18, 1 }, { 3, 1 }, { 1, 3 }, true }, // protocol 10 (1ByOne Doorbell)
75 | { 270, { 36, 1 }, { 1, 2 }, { 2, 1 }, true }, // protocol 11 (HT12E)
76 | { 320, { 36, 1 }, { 1, 2 }, { 2, 1 }, true } // protocol 12 (SM5212)
77 | };
78 |
79 | enum {
80 | numProto = sizeof(proto) / sizeof(proto[0])
81 | };
82 |
83 | STATIC inline unsigned int diff(int A, int B) {
84 | return abs(A - B);
85 | }
86 |
87 | STATIC bool receiveProtocol(mp_obj_t self_in, const int p, unsigned int changeCount) {
88 |
89 | rcswitch_RCSwitch_obj_t *self = MP_OBJ_TO_PTR(self_in);
90 |
91 | const rcswitch_Protocol_obj_t pro = proto[p-1];
92 |
93 | unsigned long code = 0;
94 | //Assuming the longer pulse length is the pulse captured in timings[0]
95 | const unsigned int syncLengthInPulses = ((pro.syncFactor.low) > (pro.syncFactor.high)) ? (pro.syncFactor.low) : (pro.syncFactor.high);
96 | const unsigned int delay = self->timings[0] / syncLengthInPulses;
97 | const unsigned int delayTolerance = delay * self->nReceiveTolerance / 100;
98 |
99 | const unsigned int firstDataTiming = (pro.invertedSignal) ? (2) : (1);
100 |
101 | for (unsigned int i = firstDataTiming; i < changeCount - 1; i += 2) {
102 | code <<= 1;
103 | if (diff(self->timings[i], delay * pro.zero.high) < delayTolerance &&
104 | diff(self->timings[i + 1], delay * pro.zero.low) < delayTolerance) {
105 | // zero
106 | } else if (diff(self->timings[i], delay * pro.one.high) < delayTolerance && diff(self->timings[i + 1], delay * pro.one.low) < delayTolerance) {
107 | // one
108 | code |= 1;
109 | } else {
110 | // Failed
111 | return false;
112 | }
113 | }
114 |
115 | if (changeCount > 7) { // ignore very short transmissions: no device sends them, so this must be noise
116 | self->nReceivedValue = code;
117 | self->nReceivedBitlength = (changeCount - 1) / 2;
118 | self->nReceivedDelay = delay;
119 | self->nReceivedProtocol = p;
120 | return true;
121 | }
122 |
123 | return false;
124 | }
125 |
126 | STATIC void handleInterrupt(void *self_in)
127 | {
128 | rcswitch_RCSwitch_obj_t *self = MP_OBJ_TO_PTR(self_in);
129 |
130 | static unsigned int changeCount = 0;
131 | static unsigned long lastTime = 0;
132 | static unsigned int repeatCount = 0;
133 |
134 | const long time = esp_timer_get_time();
135 | const unsigned int duration = time - lastTime;
136 |
137 | if (duration > self->nSeparationLimit) {
138 | if (diff(duration, self->timings[0]) < 200) {
139 | repeatCount++;
140 | if (repeatCount == 2) {
141 | for(uint8_t i = 1; i <= numProto; i++) {
142 | if (receiveProtocol(self, i, changeCount)) {
143 | // mp_printf(&mp_plat_print, "received succeeded for protocol %d\r\nCode: %d\r\n", i, self->nReceivedValue);
144 | mp_sched_schedule(self->handler, MP_OBJ_NEW_SMALL_INT(self->nReceivedValue));
145 | mp_hal_wake_main_task_from_isr();
146 | break;
147 | }
148 | }
149 | repeatCount = 0;
150 | }
151 | }
152 | changeCount = 0;
153 | }
154 | // detect overflow
155 | if (changeCount >= RCSWITCH_MAX_CHANGES) {
156 | changeCount = 0;
157 | repeatCount = 0;
158 | }
159 |
160 | self->timings[changeCount++] = duration;
161 | lastTime = time;
162 | }
163 |
164 | STATIC mp_obj_t rcswitch_RCSwitch_enableReceive(mp_obj_t self_in, mp_obj_t interrupt, mp_obj_t handler) {
165 | rcswitch_RCSwitch_obj_t *self = MP_OBJ_TO_PTR(self_in);
166 | self->nReceiverInterrupt = mp_obj_get_int(interrupt);
167 | self->handler = handler;
168 |
169 | uint64_t gpio_pin_sel = ( 1ULL << self->nReceiverInterrupt );
170 | ESP_LOGI(TAG, "ESP_LOGI: RCSwitch->nReceiverInterrupt=%d gpio_pin_sel=%llu", self->nReceiverInterrupt, gpio_pin_sel);
171 | // mp_printf(&mp_plat_print, "RCSwitch->nReceiverInterrupt=%d gpio_pin_sel=%llu", self->nReceiverInterrupt, gpio_pin_sel);
172 | gpio_isr_handler_remove(self->nReceiverInterrupt);
173 | gpio_config_t io_conf = {
174 | .intr_type = GPIO_INTR_ANYEDGE,
175 | .mode = GPIO_MODE_INPUT,
176 | .pin_bit_mask = gpio_pin_sel,
177 | .pull_up_en = GPIO_PULLUP_ENABLE,
178 | .pull_down_en = GPIO_PULLDOWN_DISABLE
179 | };
180 | gpio_config(&io_conf);
181 | //install gpio isr service
182 | gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT);
183 | //hook isr handler for specific gpio pin
184 | gpio_isr_handler_add(self->nReceiverInterrupt, handleInterrupt, (void*)self);
185 |
186 | mp_printf(&mp_plat_print, "call enableReceiveInternal\r\n");
187 | return mp_const_none;
188 | }
189 | STATIC MP_DEFINE_CONST_FUN_OBJ_3(rcswitch_RCSwitch_enableReceive_obj, rcswitch_RCSwitch_enableReceive);
190 |
191 | STATIC mp_obj_t rcswitch_RCSwitch_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) {
192 | // Allocates the new object and sets the type.
193 | rcswitch_RCSwitch_obj_t *self = m_new_obj(rcswitch_RCSwitch_obj_t);
194 | self->base.type = (mp_obj_type_t *)type;
195 |
196 | self->nReceivedValue = 0;
197 | self->nReceivedBitlength = 0;
198 | self->nReceivedDelay = 0;
199 | self->nReceivedProtocol = 0;
200 | self->nReceiveTolerance = 60;
201 | self->nSeparationLimit = 4300;
202 |
203 | self->nReceiverInterrupt = -1;
204 | self->nReceivedValue = 0;
205 |
206 | mp_printf(&mp_plat_print, "RCSwitch init v0.2\r\n");
207 |
208 | // The make_new function always returns self.
209 | return MP_OBJ_FROM_PTR(self);
210 | }
211 |
212 | // This collects all methods and other static class attributes of the RCSwitch.
213 | // The table structure is similar to the module table, as detailed below.
214 | STATIC const mp_rom_map_elem_t rcswitch_RCSwitch_locals_dict_table[] = {
215 | { MP_ROM_QSTR(MP_QSTR_enableReceive), MP_ROM_PTR(&rcswitch_RCSwitch_enableReceive_obj) },
216 | };
217 | STATIC MP_DEFINE_CONST_DICT(rcswitch_RCSwitch_locals_dict, rcswitch_RCSwitch_locals_dict_table);
218 |
219 | // This defines the type(RCSwitch) object.
220 | MP_DEFINE_CONST_OBJ_TYPE(
221 | rcswitch_type_RCSwitch,
222 | MP_QSTR_RCSwitch,
223 | MP_TYPE_FLAG_NONE,
224 | make_new, rcswitch_RCSwitch_make_new,
225 | locals_dict, &rcswitch_RCSwitch_locals_dict
226 | );
227 |
228 |
229 | STATIC const mp_rom_map_elem_t rcswitch_globals_table[] = {
230 | { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_rcswitch) },
231 | // { MP_ROM_QSTR(MP_QSTR_setProtocol), MP_ROM_PTR(&rcswitch_setProtocol_obj) },
232 | { MP_ROM_QSTR(MP_QSTR_RCSwitch), MP_ROM_PTR(&rcswitch_type_RCSwitch) },
233 | };
234 | STATIC MP_DEFINE_CONST_DICT(rcswitch_globals, rcswitch_globals_table);
235 |
236 | // Define module object.
237 | const mp_obj_module_t rcswitch_user_cmodule = {
238 | .base = { &mp_type_module },
239 | .globals = (mp_obj_dict_t *)&rcswitch_globals,
240 | };
241 |
242 | // Register the module to make it available in Python.
243 | MP_REGISTER_MODULE(MP_QSTR_rcswitch, rcswitch_user_cmodule);
--------------------------------------------------------------------------------