These programs implement communication protocols for ISM-band (433MHz in the US) messages that are recognized byrtl_433
from its library of remote sensors. The programs have been tested on an Arduino Uno R3, Sparkfun SAMD21, and Raspberry Pi Pico 2.
ISM_Emulator
provides the general model and class
definitions for ISM-band remote sensor emulation. Its use is demonstrated in the implementation of communication protocols compatible with several specific devices, including the Acurite AR609TXC and Lacrosse TX141TH-BV2 temperature/humidity sensors and the Lacrosse WS7000-20 temperature/humidity/barometric-pressure sensor. The transmissions from this program are recognized by rtl_433
, and monitoring of the MQTT publications from rtl_433
can be displayed by DNT
or monitored with an MQTT client or via rtl_watch
.
In addition to the .ino
code that implements protocols compatible with specific devices, this distribution includes C++
code that can be used to develop programs to describe the waveform prior to implementing on an Arduino-like device. The prototypes do not require Arduino-like devices or transmitters but do expedite development of the Arduino code to instantiate the device.
Implementation of one of the sensor devices requires:
- An Arduino or similar microcontroller that can be programmed through the Arduino IDE;
- An Arduino IDE installed on your host computer system;
- A 433MHZ transmitter (or other frequency as permitted in your locale), available from Amazon for ~$2US each but frequently sold as transmitter and receiver pairs packaged 5 or 6 pairs per package;
- A temperature/humidity sensor. This program uses a Bosch BME688 (available from Adafruit or Sparkfun for ~$19US), but other sensors such as the Silicon Labs Si7021, available from Adafruit or Sparkfun for ~$10US, would work equally well.
If your host microcontroller supports it, you might find the BME688 devices with Qwiic to be easiest to wire up.
Begin by cloning ISM_Emulator
from http://github.com/hdtodd/ISM_Emulator .
To explore the prototyping code, connect to the prototypes
directory and compile one of the .cpp
files with g++ -std=c++11 <prototype>.cpp
, then execute it with ./a.out
.
To implement the transmission of sensor data using the protocol of one of the specific devices:
- Connect your components:
-
By default, the program uses GPIO pin 4 to control the 433MHz data transmitter.
-
You can use either I2C or SPIO to connect to a BME 68x board. The program defaults to I2C. If your BME 68x board and your Arudino-compatible microcontroller both have STEMMA or QWIIC connectors, connect your BME board to your Arudino compatible that way. If not, you'll use 4 wires to connect your BME to your Arduino-compatible:
-
Connect the BME Vin to the power supply, 3-5V. After confirming that your BME board has integrated 3.3V-5V level-shifting, use the same voltage that the microcontroller logic uses.. For most Arduinos, that is 5V. For 3.3V logic devices, use 3.3V
-
Connect the BME GND to common power/data ground
-
Connect the BME SCK breakout pin to the I2C clock SCL pin on your Arduino compatible (see microcontoller pinout diagram)
-
Connect the BME SDI breakout pin to the I2C data SDA pin on your Arduino compatible (see microcontoller pinout diagram)
-
On the Pico 2, connect the BME SCK and SDI pins to the Pico 2 SCL and SDA pins numbered 6 and 7; attempts to use other Pico I2C0 or I2C1 pins were unsucessful. Use GPIO pin 3 (pin numbered 5) for the 433MHz transmitter data line.
-
- If you have a BME688, install the library for it in your IDE; if you're using a different type of sensor, install its library and modify the
ISM_Emulator.ino
code to replace the BME calls with the equivalent for your sensor. - Confirm your connection to the BME68x with the
BME680test.ino
example in the Adafruit library, for example. - Start the Arduino IDE
- Ensure that you have installed the libraries for
<Wire.h>
,<SPI.h>
,<Adafruit_Sensor.h>
, and"Adafruit_BME680.h"
(or other libraries if you are using other sensor communication systems). - Open the
.ino
file from theISM_Emulator
directory you'd like to emulate (e.g.AR609
). - Compile and download the code to your Arduino
- Monitor execution with the Serial Monitor screen of your Arduino IDE.
- Ensure that you have installed the libraries for
- Monitor the transmitted sensor readings using
rtl_433
; enable MQTT publishing on thertl_433
host and monitor with an MQTT client or other tools such asDNT
(http://github.com/hdtodd/DNT) orrtl_watch
(http://github.com/hdtodd/rtl_watch).
The ISM_Emulator
code provides a base class containing structure definitions, variables, and procedures that can be inherited and expanded to generate transmissions with protocol that are compatible with specific devices.
The device implemention program drives a 433MHz transmitter (or local ISM-band transmitter) to send temperature/humidity readings using the protocol specific to the device (Acurite 609TXC or Lacrosse WS7000-20, for example). See the device file in the rtl_433
distribution (https://github.com/merbanan/rtl_433) for details about the packet format for the specific device, or examine the prototype code procedure make_wave
. The data packet format created here matches the format recognized by rtl_433
.
The transmitted waveform is a series of up/down voltages (pulses) that turn the ISM transmitter on/off (OOK) followed by timing gaps of various durations. The duration indicates the type of signal (PWM -- pulse-width modulation). The message is on-off keying/pulse-width modulation..
Transmission format is:
- 2 pulses followed by sync delays;
- 1 pulse followed by a sync-gap delay;
- A 40-bit message of pulses followed by a short or long delay (short = 0, long = 1); and
- A final pulse follow by interval gap.
The function AR609.make_wave()
shows how the waveform for any OOK/PWM device can be quickly specified for emulation.
Most ISM devices REPEAT the message 2-5 times per transmission to increase the possibility of correct reception (since this is a simplex communication system -- no indication that the information was correctly received). This implementation of the protocol repeats the message 3 times per transmission.
The Acurite 609TXC protocol sends only temperature and humidity data.
Transmission format is:
- A preamble of 4 SYNC pulses+gaps;
- A 40-bit message of 0/1 pulses+gaps
- A postamble of 2 SYNC pulses+gaps followed by a an Inter-Message gap
The preamble and message are repeated 12 times by the TX141 but only 6 times by this program (
rtl_433
needs at least 5 repetitions to recognize the transmission as valid).
The function TX141.make_wave()
shows how the waveform for any OOK/PWM device can be quickly specified for emulation.
The Lacrosse TX141THC protocol sends only temperature and humidity data.
Transmission format is:
- Ten "0" high-low pulses
- One "1" high-low pulse
- A 56-bit message transmitted as 12 4-bit nibbles, a CheckXOR nibble, and a CheckSum nibble, all transmitted least-significant-bit first, and with each 4-bit nibble followed by a "1" high-low pulse
- An inter-message gap pulse.
This implementation of the WS7000-20 protocol does not repeat messages within a transmission.
The Lacrosse WS7000-20 message protocol transmits temperature, humidity, and barometric pressure readings and so uses more of the data available from the BME 68x sensor.
Take care if you modify these .ino
programs to use the F()
macro around any text strings you introduce or edit. The programs are RAM memory-bound on the Arduino Uno R3, so it was necessary to use the F()
macro to store text strings in flash memory. If you introduce strings without F()
, the IDE pulls in the Serial library on SAMD architectures and can't find the USB serial port.
The generalized device class includes procedures for creating the list of signals to be sent as one transmission (insert()
) and for playing the signal list through the transmitter (playback()
).
The critical elements for creating the transmission for a specific device are defined in the class <device>
section:
- the signal timings (pulse and gap durations), which are specified in the table
<device>_signals[6]
; - the
<device>
instantiation code, that links this specific signal table into the general device class; - the
<device>.pack_msg()
procedure that creates the formatted message array from the raw data; that procedure may need associated procedures for creating checksum bytes, reflecting messages, etc.; - the
<device.make_wave()
procedure that creates the transmission packet that includes the preamble, message data, and postamble, with as many repetitions as are needed to be recognized byrtl_433
.
When asserting/deasserting voltage to the signal pin, timing is critical. The strategy of this program is to have the "playback" -- the setting of voltages at specific times to convey information -- be as simple as possible to minimize computer processing delays in the signal-setting timings. So the program generates a "waveform" as a series of program commands in an array to tell when to assert/deassert voltages and to delay the specified times to communicate information. Those very simple commands in the array represent the waveform.
The playback, then, just retrieves the commands to assert/deassert voltages or delay specific length of time, and then executes them, with minimal processing overhead. The result is that the various timings, as reported by rtl_433 -A
, have very little variation within a transmission.
The program provides extensive feedback about its operation by printing to the serial or usbserial monitor if #define DEBUG
is enabled (it is, by default).
This program was modeled, somewhat, on Joans pigpiod
(Pi GPIO daemon) code for waveform generation. But because the microcontroller devices such as Arduinos are single-program rather than multitasking OSes, the code here does not need to provide for contingencies as those in multi-tasking environments -- it just generates the waveform description which a subsequent code module uses to drive the transmitter voltages.
Using the specific implementation of the AR609TXC transmission protocol as an example ...
The setup() procedure creates the BME688 object and sets its operational parameters.
The BME688 code for reading temp/press/hum/VOC was adapted from the Adafruit demo program http://www.adafruit.com/products/3660
The BME688 temperature reading may need calibration against an external thermometer. The DEFINEd parameter BME_TEMP_OFFSET
can be used to perform an adjustment, if needed.
The loop() procedure samples the sensor (BME688), reformats the information into the 40-bit data packet of the Acurite 609TXC protocol, creates an array of commands to drive the transmitter and delay the appropriate times, and then invokes AR609.playback() to actually drive the transmitter. It then delays for the length of time defined by #define DELAY
, in microseconds (must be less than 64K µs!) before repeating the loop.
To create another ISM device, you'll need to:
- Enumerate the types of signals and their pulse and gap durations:
- Examine the device description in the
rtl_433
distribution under the/src/devices
directory - Examine the
.cu8
timings that can be analyzed byrtl_433 -A -r <filename>
from thertl_433_tests
directory - Examine the waveforms that can be visualized from the triq
http
link thatrtl_433 -A -r
provides.
- Examine the device description in the
- Note, particularly, whether the message or transmission is reflected (in whole or by nibbles or by bytes), inverted (ditto), and the type of checksum needed to create a valid packet
- Define those signal timings in your device's equivalent of
AR609_signals[]
; - Write a function to reformat the data as it comes from the sensor object into the format expected by the device, including checksums, reflections, and inversions: the AR609 protocol code
AR609.pack_msg()
demonstrates how to do that; be sure to set the expected message data length, too; - Write the procedure
.make_wave()
to create the array of signaling commands: again,AR609.make_wave()
demonstrates how to do that.
- V1.0: First operational version, 2024.12.29; added WS7000 2025.01.14.
Written by David Todd, hdtodd@gmail.com, v1.0.0 2024.12.30.