{"id":13802001,"url":"https://github.com/peterhinch/micropython_remote","last_synced_at":"2025-07-03T21:37:57.381Z","repository":{"id":45781030,"uuid":"250756817","full_name":"peterhinch/micropython_remote","owner":"peterhinch","description":"Capture and replay 433MHz remote control codes. Control remote switched power adaptors.","archived":false,"fork":false,"pushed_at":"2021-12-17T18:13:31.000Z","size":737,"stargazers_count":65,"open_issues_count":1,"forks_count":11,"subscribers_count":8,"default_branch":"master","last_synced_at":"2024-08-05T00:07:01.619Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/peterhinch.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-03-28T09:30:49.000Z","updated_at":"2024-08-03T20:52:44.000Z","dependencies_parsed_at":"2022-08-31T09:21:23.323Z","dependency_job_id":null,"html_url":"https://github.com/peterhinch/micropython_remote","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/peterhinch%2Fmicropython_remote","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/peterhinch%2Fmicropython_remote/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/peterhinch%2Fmicropython_remote/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/peterhinch%2Fmicropython_remote/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/peterhinch","download_url":"https://codeload.github.com/peterhinch/micropython_remote/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":224215504,"owners_count":17274798,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-04T00:01:32.806Z","updated_at":"2024-11-12T04:21:08.551Z","avatar_url":"https://github.com/peterhinch.png","language":"Python","readme":"# A library for 433MHz remote control\n\nRemote controlled wall sockets provide a convenient way to control power to\nelectrical equipment.\n\n![Image](images/socket.png)\n\nThey are cheap, reliable and consume negligible power. However they lack\nflexibility: they can only be controlled by the matching remote. This library\nprovides a means of incorporating them into an IOT (internet of things)\nsolution, or building a remote capable of controlling more devices with a\nbetter antenna and longer range than the stock item.\n\nFor the constructor a key benefit is that no high voltage wiring is required.\n\nThe approach relies on the fact that most units use a common frequency of\n433.92MHz. Transmitter and receiver modules are available for this frequency at\nlow cost, e.g. [from Seeed](https://www.seeedstudio.com/433Mhz-RF-link-kit-p-127.html).\nThe Seeed units:\n\n![Image](images/seeed.png)\n\nI also tried one from eBay. This worked, but the receiver had poor sensitivity\nrequiring the remote to be held very close to it to achieve results.\n\n#### Receiver\n\nThe signal from the supplied remote is captured by a simple utility and stored\nin a file. Multiple signals - optionally from multiple remotes - may be stored\nin a file. The utility is used interactively at the REPL. Supported targets are\nPyboard D, Pyboard 1.x, Pyboard Lite, ESP32 and Raspberry Pi Pico.\n\n#### Transmitter\n\nThis module is intended to be used by applications. The application loads the\nfile created by the receiver and transmits captured codes on demand.\nTransmission is nonblocking. Supported targets are Pyboard D, Pyboard 1.x,\nESP32 and Raspberry Pi Pico. Pyboard Lite works in my testing, but only in\nblocking mode. The module does not use `uasyncio` but is compatible with it.\n\n#### Warning\n\nIt should be noted that this is for experimenters. The capture process cannot\nbe guaranteed to work for all possible remotes and all radio receivers, not\nleast because the timing requirements are quite stringent. The receivers I own\nintroduce significant jitter. The library uses averaging over multiple frames\nto improve accuracy.\n\nSee [section 6](./README.md#6-background) for the reasons for this approach.\n\n#### Raspberry Pi Pico note\n\nEarly firmware has [this issue](https://github.com/micropython/micropython/issues/6866)\naffecting USB communication with some PC's. This has been fixed: please use\nfirmware V1.16 or later.\n\n#### ESP32 note\n\nA breaking change was introduced to the firmware in July 2021 affecting the\ntransmitter. The code has been adapted to accommodate this: in consequence\nfirmware more recent than this must now be used (a daily build, until the\nrelease of V1.17).\n\n# 1. Installation\n\n## 1.1 Code\n\nReceiver: copy the `rx` directory and contents to the target's filesystem.  \nTransmitter: copy the `tx` directory and contents to the target's filesystem.\n\nIn each directory there is a file `get_pin.py`. This provides a convenient way\nto instantiate a `Pin` on Pyboard, ESP32 or Raspberry Pi Pico. This may be\nmodified for your own needs or ignored and replaced with your own code.\n\nThere are no dependencies.\n\n## 1.2 Hardware\n\nIt is difficult to generalise as there are multiple sources for 433MHz\ntransceivers. Check the data for your modules.\n\nMy transmitter and receiver need a 5V supply. The receiver produces a 0-5V\nsignal: this is compatible with Pyboards but the ESP32 and Raspberry Pi Pico\nrequire a circuit to ensure 0-3.3V levels. The receiver code is polarity\nagnostic so an inverting buffer as shown below will suffice.\n\nReceiver defaults are pin X3 on Pyboard, pin 27 on ESP32 and pin 17 on Pico,\nbut any pins may be used.\n\n![Image](images/buffer.png)\n\nThe transmitter can be directly connected as 5V devices are normally compatible\nwith 3.3V logic levels. Transmitter defaults are X3 on Pyboard, 23 on ESP32 and\n16 on the Pico. Any pins may be substituted. The \n[data for the Seeed transmitter](https://www.seeedstudio.com/433Mhz-RF-link-kit-p-127.html)\nstates that a supply of up to 12V may be used to increase power. Whether this\napplies to other versions is moot: try at your own risk. I haven't. All the\nremotes I've seen use a miniature 12V battery. This may (or may not) mean that\na 12V supply is commonplace on transmitter modules.\n\n## 1.3 Hardware usage\n\nPyboards: Timer 5.  \nESP32: RMT channel 0.  \nPico: PIO state machine 0, IRQ 0, PIO 0.  \n\nThe Pico uses `tx/rp2_rmt.py` which uses the PIO for nonblocking modulation in\na similar way to the ESP32 RMT device. To use this library there is no need to\nstudy the code, but documentation is available\n[here](https://github.com/peterhinch/micropython_ir/blob/master/RP2_RMT.md) for\nthose interested.\n\n# 2. Acquiring data\n\nThe `RX` class behaves similarly to a dictionary, with individual captures\nindexed by arbitrary strings. An `RX` instance is created with\n```python\nfrom rx import RX\nfrom rx.get_pin import pin\nrecv = RX(pin())\n```\nTo capture a pin on a remote and associate it with the key \"on\", the remote\nshould be placed close to the receiver and the button held down. Then issue\n```python\nrecv('on')\n```\nIf the capture is successful, [diagnostic](./README.md#22-diagnostics)\ninformation will be output. If capture fails with an error message, repeat the\nprocess with the same key string. It is important that the button is pressed\nbefore issuing the above line, and only released when the REPL reappears.\n\nTo capture further buttons, repeat the procedure with a unique key for each\nbutton.\n\nWhen this is complete, the dictionary can be saved as a JSON file (in this\nexample called \"remotes\") with:\n```python\nrecv.save('remotes')\n```\n\n## 2.1 RX class\n\nExamples assume an `RX` instance `recv`; `key` is a string used as a dictionary\nkey.\n\nConstructor args:  \n 1. `pin` A `Pin` instance initialised as input.\n 2. `nedges=800` The number of transitions acquired in a capture. Larger values\n may provide better accuracy at the cost of RAM use.\n\nMethods:\n 1. `load(fname)` Load an existing JSON file.\n 2. `save(fname)` Save the current set of captures to a JSON file.\n 3. `__call__(key)` Start a capture using the passed (string) key: `recv('TV on')`.\n 4. `__delitem__(key)` Delete a key: `del recv['TV on']`.\n 5. `__getitem__(key)` Return a list of pulse durations (in μs): `lst = recv['TV on']`.\n 6. `show(key)` As above but in more human readable form.\n 7. `keys()` List the keys.\n\n## 2.2 Diagnostics\n\nThese are intended to provide some feedback on the likely success of a capture.\nThe acid test is, of course, to transmit it. A successful capture will produce\noutput similar to this:\n```\nFrame length = 50 No. of frames = 14\nAveraging 14 frames\nCapture quality  34.3 (perfect = 0)\n```\nThis shows that each frame comprises 50 edges (25 \"mark\" pulses). The capture\nprocess acquired 14 complete frames. In this instance all frames were of the\nsame length so none were discarded. Averaging was done on all of them to yield\naccurate timing information.\n\nOn occasion the diagnostics will report discarding frames of the wrong length:\nif only one or two are discarded the capture will probably be successful.\n\nThe \"Capture quality\" is a measure of the standard deviation of the timing of\nthe frames; a perfect capture would produce a value of zero. Its purpose is to\nhelp with placement of the remote near to the receiver. There is no particular\n\"fail\" threshold: I have had successful captures with values \u003e60. A value\naround 15 is good.\n\n# 3. Transmitting\n\nAssuming the JSON file \"remotes\" is on the target's filesystem, and it contains\na button capture with the key \"TV on\":\n```python\nfrom tx import TX\nfrom tx.get_pin import pin\ntransmit = TX(pin(), 'remotes')\ntransmit('TV on')  # Immediate return\n```\nThe transmit method is nonblocking, on Pyboard, ESP32 and Raspberry Pi Pico.\nThere is an alternative blocking method for use on Pyboard only. This offers\nmore precise timing, and I found it necessary on the Pyboard Lite only. This is\naccessed as:\n```python\ntransmit.send('TV on')  # Blocks\n```\nThe capture process stores data with a resolution of +-1μs (absolute accuracy\nis less, as discussed above). The ESP32 uses the RMT class and the Pico uses a\nPIO library: both these solutions produce pulses whose lengths match the data\nvalue +-1μs. To put this in context, the shortest pulse I have measured from a\nremote is 110μs.\n\n## 3.1 The TX class\n\nExamples assume a `TX` instance `tx`; `key` is a string used as a dictionary\nkey.\n\nConstructor args:  \n 1. `pin` A `Pin` instance initialised as output, with `value=0`.\n 2. `fname` Filename containing the captures.\n 3. `reps=5` On transmit, the captured pulse train is repeated `reps` times.\n\nMethods:  \n 1. `__call__(key)` Normal nonblocking transmit: `tx('TV on')`. Blocks for\n ~2.2ms on Pyboard 1.1, 980μs on ESP32. Transmission continues as a background\n process.\n 2. `send(key)` Blocking transmit. For more precise timing on Pyboard only.\n 3. `__getitem__(key)` Return a list of pulse durations: `lst = tx['TV on']`.\n Values are μs.\n 4. `show(key)` As above but in more human readable form.\n 5. `keys()` List the keys.\n 6. `latency()` Returns a time in ms calculated from the capture file. It is\n the recommended minimum length of time which should elapse between successive\n nonblocking transmissions. The value is conservative. Switching wall sockets\n is subject to unknown amounts of latency in the sockets and in the powered\n devices themselves: it is not capable of millisecond-level precision.\n\nClass method:  \n 1. `active_low()` Match a transmitter which transmits on a logic 0 (if such\n things exist). Pyboard only. Call before transmitting data. In this case the\n `Pin` passed to the constructor should be initialised with `value=1`.\n\nOn ESP32 and Raspberry Pi Pico if an active low signal is required an external\ninverter must be used.\n\nThe value of the `reps` constructor arg may need to be increased for some types\nof socket or in cases where radio interference is present. If your captures are\nnot received, try a value of 10. Larger values increase RAM use (on ESP32, not\non Raspberry Pi Pico). They improve the probability of successful reception.\nThe\n[rc-switch](https://github.com/sui77/rc-switch) library uses a value of 10.\n\n## 3.2 Example uasyncio usage\n\nThis assumes that the new `uasyncio` will acquire a `Queue` class. Other tasks\nplace keys onto the queue, `send_queued()` transmits them ensuring that the\nlatency limit is met.\n\n```python\nimport uasyncio as asyncio\nfrom tx import TX\nfrom tx.get_pin import pin\ntxq = asyncio.Queue()  # Other tasks put data onto queue.\nasync def send_queued():\n    transmit = TX(pin(), 'remotes')\n    delay = transmit.latency()  # Only need to calculate this once\n    while True:\n        to_send = await txq.get()\n        transmit(to_send)\n        await asyncio.sleep_ms(delay)\n```\nIn the continued absence of an official `Queue` class, an unofficial version is\navailable, documented\n[here](https://github.com/peterhinch/micropython-async/blob/master/v3/docs/TUTORIAL.md#35-queue).\n\n# 4. File maintenance\n\nThe `RX` class enables maintenance of the JSON file: it is possible to add new\ncaptures and overwrite or delete existing ones.\n\n### Adding new captures\n\nLoad an existing file, add a new capture, and update the file. The same\nprocedure might be used to replace a capture which had failed to work:\n```python\nfrom rx import RX\nfrom rx.get_pin import pin\nrecv = RX(pin())\nrecv.load('remotes')  # Load file, start remote transmitting, then issue:\nrecv('TV on')  # With remote continuously transmitting\nrecv.save('remotes')  # Save file to same name\n```\n\n### Deleting a capture\n\n```python\nfrom rx import RX\nfrom rx.get_pin import pin\nrecv = RX(pin())\nrecv.load('remotes')  # Load file\ndel recv['TV on']\nrecv.save('remotes')  # Save file to same name\n```\n\n### Printing timing information\n\nData is stored as a series of pulse lengths in μs. These may be printed out.\n```python\nfrom rx import RX\nfrom rx.get_pin import pin\nrecv = RX(pin())\nrecv.load('remotes')  # Load file\nrecv.show('TV on')  # Access capture\n```\nThe default state of the transmitter is not transmitting, so the first entry\n(#0) represents carrier on (mark). Consequently even numbered entries are marks\nand odd numbers are spaces. Captured frames have an even number of entries.\nThis ensures that the carrier is off between sequences. Leaving it on is an\ninvalid use of the 433MHz band. If creating sequences in code, this should be\nhonoured.\n\n# 5. It doesn't work. What should I do?\n\nIf the capture process reports success, the problem is likely to be with the\ntransmitter.\n\nTry running the receiver, connected to a scop or logic analyser while operating\nthe transmitter to see if pulses are being received. Alternatively run the\nreceiver utility on another MicroPython device. Try first with the remote and\nthen using the transmitter. If they are detected you can be confident of the\ntransmitter hardware.\n\nOn any target increase the `reps` constructor arg to 10 and possibly beyond.\n\nOn the Pyboard try the blocking `send` method. I found this necessary on the\nPyboard Lite: it offers better timing. ESP32 and Raspberry Pi Pico timing are\nhighly accurate for reasons discussed [above](./README.md#3-transmitting).\n\n# 6. Background\n\nMy house is littered with remote controlled mains sockets. These are usually\nlocated in hard to reach places, behind computers or other kit, and are\ncontrolled by tiny 433MHz remote controls. They have two limitations:\n 1. Range. This can be down to two metres. With decent antennas 433MHz band\n devices can communicate over 100M or more. The problem results from small\n antennas and difficult receiver locations.\n 2. Flexibility. Sockets can only be controlled by the bundled remote.\n\nFor some time I've been considering ways to control mains devices with greater\nrange, ideally from the internet, but all had drawbacks. These 433MHz sockets\nhave the benefits of being utterly reliable with power consumption too low for\nme to measure (\u003c0.5W). A means of transmitting to them from a MicroPython\ntarget would present a wide range of control options.\n\nThis was inspired by \n[a forum post](https://forum.micropython.org/viewtopic.php?f=14\u0026t=7854#p45239)\nby Kevin Köck. Having posted [my IR library](https://github.com/peterhinch/micropython_ir)\nit struck me that there was a lot of commonality. In each case we generate and\nreceive OOK (on-off keying) messages. In the case of 433MHz the conversion\nbetween modulation and carrier is done by external hardware. A cheap 433MHz\ntransmitter, driven by a Pyboard D or ESP32, might provide a solution with\nnetwork connectivity.\n\nDepending on range, these could be deployed in two ways:  \n 1. A single, centrally located, TX with a good antenna and groundplane might\n serve all sockets in a house.\n 2. Failing that, several transmitters could be located near their respective\n sockets.\n\n## 6.1 Implementation\n\nThere seems to be one measure of standardisation between these devices: the RF\ncarrier frequency of 433.92MHz. There are two potential ways of approaching the\nproblem, both of which work with IR transceivers.\n\n## 6.2 Solution 1: Implement specific protocols\n\nThis has been done in [rc-switch](https://github.com/sui77/rc-switch), a C\nlibrary for Arduino and the Raspberry Pi. It supports 12 protocols: it seems\nevident that there is much less standardisation than in the IR arena where a\nfew well-documented protocols find wide use. In many IR applications the\nprogrammer can choose the protocol and buy a remote which supports it. This\nluxury isn't available to someone with a pre-existing set of switched sockets.\n\nAdvantages:\n 1. Efficient applications can be written: each remote key can be represented\n by a single integer or string, being the data to transmit.\n 2. There is no need for data capture and hence no need for a receiver.\n 3. Transmit timing based on protocol knowledge is as accurate as possible.\n\nDrawbacks:\n 1. Some protocols use weird concepts such as tri-bits. The set of N-bit binary\n numbers representing transmit data contains invalid bit patterns.\n 2. It's hard to do because of the multitude of protocols. The Arduino library\n is quite big.\n 3. Porting is problematic: an example of all 12 types of socket would be\n required and I believe many are for US 115V power.\n\n[This reference](http://tinkerman.eldiariblau.net/decoding-433mhz-rf-data-from-wireless-switches/)\ndescribes some of the issues.\n\n## 6.3 Solution 2: capture and play back\n\nThis involves setting up a receiver, pressing a key on the remote, and storing\nthe received pulse train for subsequent playback, typically on a different\ndevice.\n\nWith IR protocols this is problematic. Protocols have radically different ways\nto deal with the case where a key is held down. Radio protocols repeatedly send\nthe same code, greatly simplifying capture.\n\nAdvantages:\n 1. It is protocol-agnostic so should work on any set of sockets.\n 2. The code is simple and easily tested.\n\nDrawbacks:\n 1. It requires a receiver to perform the initial capture.\n 2. It needs a fairly fast and capable target because timing is critical.\n 3. It is relatively inefficient: every key will be represented by a list of N\n integers, being the on or off duration in μs. In practice N is typically 50;\n data volume is hardly excessive.\n 4. There is some loss of timing precision as the capture process introduces\n uncertainty.\n\nNote that once the capture task is complete the receiver and target can be\nre-purposed. Receivers are cheap and are usually bundled with transmitters.\n\n## 6.4 Test results\n\nThe modulation is quite fast with pulse durations down to values on the order\nof 100μs. Successful capture depends on the quality of the receiver. The\n[Seeed](https://www.seeedstudio.com/433Mhz-RF-link-kit-p-127.html) one had much\nbetter sensitivity than this one, bought on eBay:\n\n![Image](images/rxtx.png)\n\nWith this receiver, even with the remote very close to the receiver, there was\njitter in the output pulse train. This was sufficient to prevent successful\ntransmission. Attaching an antenna made no discernible difference.\n\nBecause of these problems the code captures a few frames and performs\naveraging. The eBay one worked, but I recommend the Seeed one which produced\nbetter diagnostic information without having to place the remote centimetres\nfrom the receiver.\n\n# 7. File format\n\nIn response to requests I have provided `example_file` as an instance of the\nfile format in use. It is a JSON encoded `dict`. The key is a string defining\nthe button name. The example has keys \"on\" and \"off\". The value is a list of\ntimes in μs. The first is a transmitter ON time, followed by an OFF time,\ncontinuing to the end of the list.\n\n","funding_links":[],"categories":["Libraries"],"sub_categories":["Communications"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpeterhinch%2Fmicropython_remote","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpeterhinch%2Fmicropython_remote","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpeterhinch%2Fmicropython_remote/lists"}