├── .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 | Buy Me A Coffee 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); --------------------------------------------------------------------------------