Using low-cost wireless sensors in the unlicensed bands
Rtl_433 is a great program. I have it running on an RPi with an RTL-SDR logging the temperature readings from the sensors for a couple of AcuRite thermometers I have, plus whatever random sensors I pick up from my neighbors, plus a tipping rain gauge I have.<p>It makes it really easy for DIY projects like the aforementioned raid gauge. To illustrate this I&#x27;ll give some details about my rain gauge project.<p>This is the rain gauge I&#x27;m using [1]. It&#x27;s got a reed switch that gets momentarily closed by a magnet on the tipping part whenever it tips. I hooked that up to an GPIO on a cheap microcontroller (an ATTiny 85 [2]) which counts the tips. Also connected to the microcontroller is a 434 MHz transmitter [3]. This is all powered by 3 NiMH rechargeable batteries in series.<p>That transmitter is remarkably simple. It just contains an oscillator at ~434 MHz that can be turned on and off by the microcontroller.<p>My send message function takes a byte string and sends it by turning the transmitter one for 1.5 ms, then off for 1.5 ms, and then for each bit off the message sending it as follows: for a 1 it turns on the transmitter for 0.4 ms then off for 0.6 ms, and for a 0 it turns on the transmitter for 0.6 ms then off for 0.4 ms. There is no gap between bits.<p>The data sent is the string &quot;TZS\4Count:&quot; and then a two byte count.<p>Here&#x27;s a config file for rtl_433 that tells it how to recognize those messages and display the count:<p><pre><code>  decoder {
    name        = TZS Rain Gauge,
    modulation  = OOK_PWM,
    short       = 416,
    long        = 612,
    reset       = 1452,
    gap         = 0,
    tolerance   = 0,
    sync        = 1484,
    bits        = 96,
    match       = {24}0x545a53,
    get         = Tips:@80:{16},
  }
</code></pre>
I really should have included some kind of checksum, but for now I&#x27;m relying on the fact that the count should never go down (except when I change batteries) and should not go up too fast. With those constraints I should be able to look at a series of messages from the rain gauge and figure out which ones were corrupted by interference.<p>I got information on how fast the count might go up here (I&#x27;m in Washington) from the Washington State Department of Transportation&#x27;s Hydraulics Manual [4]. It gives a formula for calculating at various places what would be an N year amount of rain over an M minute interval. E.g., how much rain over 5 minutes would be a once in a 100 year kind of event? (This is of interest to the DoT for things like calculating drainage requirements for roads).<p>Here&#x27;s the Arduino code for the transmitting to show how straightforward it is:<p><pre><code>    #define CYCLE       1000
    #define W_0         600
    #define W_1         400
    
    #define INTRO_1     1500
    #define INTRO_0     1500
    
    #define OUTRO_1     1500
    
    
    void report(int pin, unsigned long count)
    {
        char msg[16] = {&#x27;T&#x27;, &#x27;Z&#x27;, &#x27;S&#x27;, 0x04, &#x27;C&#x27;, &#x27;o&#x27;, &#x27;u&#x27;, &#x27;n&#x27;, &#x27;t&#x27;, &#x27;:&#x27;};
        msg[10] = (count &gt;&gt; 8) &amp; 0xff;
        msg[11] = count &amp; 0xff;
        send_msg(pin, msg, 12);
    }
    
    void send_msg(int pin, char * bp, int n)
    {
        intro(pin);
        send_bytes(pin, bp, n);
        &#x2F;&#x2F;outro(pin);
    }
    
    void send_bytes(int pin, char * bp, int n)
    {
        while (n-- &gt; 0) {
            int b = *bp++ &amp; 0xff;
            int m = 0x80;
            while (m != 0) {
                int w = (b &amp; m) ? W_1 : W_0;
                digitalWrite(pin, HIGH);
                delayMicroseconds(w);
                digitalWrite(pin, LOW);
                delayMicroseconds(CYCLE-w);
                m &gt;&gt;= 1;
            }
        }
    }
    
    void intro(int pin)
    {
        digitalWrite(pin, HIGH);
        delayMicroseconds(INTRO_1);
        digitalWrite(pin, LOW);
        delayMicroseconds(INTRO_0);
    }
    
    void outro(int pin)
    {
        digitalWrite(pin, HIGH);
        delayMicroseconds(OUTRO_1);
        digitalWrite(pin, LOW);
    }
</code></pre>
For completeness, here is the rest of the code:<p><pre><code>    #include &lt;avr&#x2F;interrupt.h&gt;
    #include &lt;Adafruit_SleepyDog.h&gt;
    
    &#x2F;*          RST |1  8|  Vcc
        SERIAL  3   |2  7|  2 LED   (SCK)
        RADIO   4   |3  6|  1 TIP   (MISO)
                GND |4  5|  0       (MOSI)
    *&#x2F;
    
    #define TIP         1   &#x2F;&#x2F; physical 6, PCINT1
    #define RADIO_TX    4   &#x2F;&#x2F; physical 3
    #define SERIAL_TX   3   &#x2F;&#x2F; physical 2
    #define LED         2   &#x2F;&#x2F; physical 7
    
    volatile unsigned long int_count = 0;
    unsigned long   last_interrupt_time = 0;
    
    void setup() {
      pinMode(TIP, INPUT_PULLUP);
      pinMode(LED, OUTPUT);
      pinMode(RADIO_TX, OUTPUT);
      pinMode(SERIAL_TX, INPUT_PULLUP);
    
      cli();
      GIMSK |= 0b00100000;  &#x2F;* enabled pin change interrupt *&#x2F;
      PCMSK |= 0b00000010;  &#x2F;* PCINT0 *&#x2F;
      sei();
    }
    
    ISR(PCINT0_vect) {
        ++int_count;
    }
    
    void loop() {
        int led = 1;
        unsigned long tip_count = 0;
        unsigned long tip_int_count = 0;
        unsigned long old_int_count = 0;
        int silence_time = 0;
    
    
        while (1) {
            int slept = Watchdog.sleep(1000);
            int changed = 0;
            if (int_count != old_int_count) {
                old_int_count = int_count;
            } else {
                if (old_int_count != tip_int_count) {
                    ++tip_count;
                    ++changed;
                    tip_int_count = old_int_count;
                }
            }
            digitalWrite(LED, 0);
            led = 1 - led;
            if (changed || ++silence_time &gt;= 30) {
                report(RADIO_TX, tip_count);
                silence_time = 0;
            }
        }
    }
</code></pre>
(The code mentions an LED, but that was just used during prototyping. I did not include the LED on the deployed rain gauge).<p>Battery life was 285 days on the first set of batteries, and 219 on the second set. I haven&#x27;t yet went over my records to see if it was more rainy during the second set&#x27;s deployment.<p>[1] <a href="https:&#x2F;&#x2F;www.argentdata.com&#x2F;catalog&#x2F;product_info.php?products_id=168" rel="nofollow">https:&#x2F;&#x2F;www.argentdata.com&#x2F;catalog&#x2F;product_info.php?products...</a><p>[2] <a href="https:&#x2F;&#x2F;www.digikey.com&#x2F;en&#x2F;products&#x2F;detail&#x2F;microchip-technology&#x2F;ATTINY85-20PU&#x2F;735469" rel="nofollow">https:&#x2F;&#x2F;www.digikey.com&#x2F;en&#x2F;products&#x2F;detail&#x2F;microchip-technol...</a><p>[3] <a href="https:&#x2F;&#x2F;www.sparkfun.com&#x2F;products&#x2F;10534" rel="nofollow">https:&#x2F;&#x2F;www.sparkfun.com&#x2F;products&#x2F;10534</a><p>[4] <a href="https:&#x2F;&#x2F;wsdot.wa.gov&#x2F;engineering-standards&#x2F;all-manuals-and-standards&#x2F;manuals&#x2F;hydraulics-manual" rel="nofollow">https:&#x2F;&#x2F;wsdot.wa.gov&#x2F;engineering-standards&#x2F;all-manuals-and-s...</a>