Low Power Radio Sensors

This is a work in progress and I am mainly using it for my own learning...but here is the spec:

  1. Low power.

  2. Cheap.

  3. Low Maintenance.

  4. Multiple Sensor locations.

  5. Different Sensors (Light, Temp, Voltage and possibly humidity or moisture later).

  6. Simple to add new sensors.

  7. I want a system for the greenhouse and other parts of the garden that monitors the temperature, light and maybe the humidity/moisture of various parts.

  8. The data is to be sent to a raspberry Pi running a mySQL DB with a PHP front end.

  9. Range 30-50m ish with clear LOS.

Here is what I have so far. I will add to this thread if there is any further interest! I am learning, so suggestions will always be taken...I may take time to understand them so may not implement them till I fully understand what they do!

The Transmitters

The transmitters I wanted to be cheap.
I decided on:

  1. ATtiny85 MCUs
  2. DS1820B Temp sensors in waterproof packaging (1m cable).
  3. 4F 5.5V supercap (Thanks China!)
  4. Diode (with 0.7V drop)
  5. 6V solar panel (Thanks China!)
  6. 433Mhz Tx (the cheap Chinese ones).

All in all, this makes them around £5 each to make. Most parts are about £1 when bought in a small batch/pack.


As far as coding, I have used watchdog and the Manchester Library.
It needs tidying and some modifying later for the different sensor types.
50degC is added to the temperature value, which is removed at the reciever end. This deals with "negative" values...so an signed int is not required (which the Manchester library only likes unsigned apparently).

/*
// A LOW POWER Tx SYSTEM - USING A SOLAR PANEL (6V) AND A SUPER CAP (5.5V)
// USE A DIODE TO STOP CURRENT BACK FLOW (ALSO Vdrop) to stop overcharging of the CAP)
//
// D1 - 433MHz Tx data line
// D2 = DS1820 Data line

WDT sleeps :
0=16ms, 1=32ms,2=64ms,3=128ms,4=250ms,5=500ms, 6=1 sec,7=2 sec, 8=4 sec, 9= 8sec

Manchester Transmitter
  Try different speeds using these constants, your maximum
  possible speed will depend on various factors like transmitter
  type, distance, microcontroller speed, ...
  MAN_300 0
  MAN_600 1
  MAN_1200 2
  MAN_2400 3
  MAN_4800 4
  MAN_9600 5
  MAN_19200 6
  MAN_38400 7
*/
#define TX_PIN 1

#define UNITID 9    // The identity of the unit. Used to ID the data when using multiple units.

#include <Manchester.h>  //Protocol for sending data via 433Mhz Tx. Includes preambles and such.
#include <avr/sleep.h>  // library for sleeping
#include <avr/wdt.h>    //library for sleeping
#include <OneWire.h> //For the DS182B0 T Sensors

OneWire TemperatureSensor(2);  // Dallas one wire data buss pin, a 4.7K resistor pullup is needed

//Some stuff for the watchdog timer. Sets some fuse bits.
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif

byte num_sleeps = 0;

void setup() {

  setup_watchdog(9); // approximately 8 seconds sleep
  man.setupTransmit(TX_PIN, MAN_300);

}

void loop() {
  
    while (num_sleeps < 20) {
      system_sleep();
      num_sleeps++;
    }
    
    num_sleeps = 0;

    byte i;
    byte data[12];
    int16_t raw;
    int celsius;

    TemperatureSensor.reset();       // reset one wire buss
    TemperatureSensor.skip();        // select only device
    TemperatureSensor.write(0x44);   // start conversion

    delay(1000);                     // wait for the conversion

    TemperatureSensor.reset();
    TemperatureSensor.skip();
    TemperatureSensor.write(0xBE);   // Read Scratchpad
    for ( i = 0; i < 9; i++) {       // 9 bytes
      data[i] = TemperatureSensor.read();
    }

    // Convert the data to actual temperature
    raw = (data[1] << 8) | data[0];
    celsius = ((int)raw / 16.0);

    // Consruct the data packet for sending.
    int byteT = (celsius+50) << 6;
    byte byteUID = UNITID << 2;
    byte byteDtype = 1; 
   
    uint16_t send_packet = byteT + byteUID + byteDtype;

    // Transmit the packet.
    man.transmit(send_packet);
    
   // Get the capacitor voltage
    int cap_volts = (int)readVcc();
    int cap_volts_scaled= (cap_volts/10) << 6;
    
    // Crate a packet for the super cap voltage level.
    byteDtype=0;
    cap_volts_scaled=cap_volts_scaled+byteUID+byteDtype;
    
    //Send the super cap battery level (good for diagnostic...sensor placement issues?)
    man.transmit(cap_volts_scaled);
    
    delay(200);
  }

void system_sleep() {
  cbi(ADCSRA, ADEN);                   // switch Analog to Digitalconverter OFF

  set_sleep_mode(SLEEP_MODE_PWR_DOWN); // sleep mode is set here
  sleep_enable();

  sleep_mode();                        // System sleeps here

  sleep_disable();                     // System continues execution here when watchdog timed out
  sbi(ADCSRA, ADEN);                   // switch Analog to Digitalconverter ON
}

void setup_watchdog(int ii) {

  byte bb;
  int ww;
  if (ii > 9 ) ii = 9;
  bb = ii & 7;
  if (ii > 7) bb |= (1 << 5);
  bb |= (1 << WDCE);
  ww = bb;

  MCUSR &= ~(1 << WDRF);
  // start timed sequence
  WDTCR |= (1 << WDCE) | (1 << WDE);
  // set new watchdog timeout value
  WDTCR = bb;
  WDTCR |= _BV(WDIE);
}

ISR(WDT_vect) {
  
}

long readVcc() {
  // Read 1.1V reference against AVcc
  // set the reference to Vcc and the measurement to the internal 1.1V reference
  #if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
    ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  #elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
    ADMUX = _BV(MUX5) | _BV(MUX0);
  #elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
    ADMUX = _BV(MUX3) | _BV(MUX2);
  #else
    ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  #endif  

  delay(2); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Start conversion
  while (bit_is_set(ADCSRA,ADSC)); // measuring

  uint8_t low  = ADCL; // must read ADCL first - it then locks ADCH  
  uint8_t high = ADCH; // unlocks both

  long result = (high<<8) | low;

  result = 1125300L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000
  return result; // Vcc in millivolts
}

The Reciever

An Arduino Nano with the code below gets the data sent by the sensors and parses it in to something for the Raspberry Pi to pick up and deal with.

#include <Manchester.h>

/*
  try different speeds using this constants, your maximum possible speed will 
  depend on various factors like transmitter type, distance, microcontroller speed, ...

  MAN_300 0
  MAN_600 1
  MAN_1200 2
  MAN_2400 3
  MAN_4800 4
  MAN_9600 5
  MAN_19200 6
  MAN_38400 7
*/

#define RX_PIN 2

void setup() {

  man.setupReceive(RX_PIN, MAN_300);
  man.beginReceive();
  Serial.begin(9600);
}

void loop() {

  if (man.receiveComplete()) {
    
    uint16_t m = man.getMessage();
    man.beginReceive(); //start listening for next message right after you retrieve the message
    
    int rxdata = m >> 6;

   int unit_ID = m << 10 >> 12;
   
   int D_type = m << 14 >> 14;
   
   if (D_type==0){    // If it is a cap volt packet
   int capV = rxdata*10;
   Serial.print(unit_ID);
   Serial.print(",");
   Serial.print(D_type);
   Serial.print(",");
   Serial.println(capV); 

  }
   
   
  else if (D_type==1){ //if it is TempC data packet
   int tempC = rxdata - 50; 
   Serial.print(unit_ID);
   Serial.print(",");
   Serial.print(D_type);
   Serial.print(",");
   Serial.println(tempC); 
   }
 
  }
}

The code on the raspberry pi requires Python 3 and the pySerial library. Very easy to do. The code I have on it so far only prints what the arduino sends it via the USB serial and also saves it to a file.

import serial

ser=serial.Serial('/dev/ttyUSB0',9600)

f=open("data.txt","a")
while 1:
        rxd = ser.readline()
        print(rxd.decode("utf-8"))
        f.write(rxd.decode("utf-8"))

Here is an example of what the python script receives on the raspberry Pi.
Further stuff will be done, like adding data to mySQL tables and then having the PHP front end.

9,0,3650
9,1,21
9,0,3650
9,1,21
9,0,3650
9,1,21

The 9 is the Unit's ID (so can have 16 sensors in those 4 bits).
The second value in each line is the "data type". 0 is the capacitor voltage (mV). 1 is the temperature.
The last value is the data...the mV or the Temp for example.

I used a process of bit shifting to form data packets with ID values for each sensor and a Data type indicator (2 bits).

More detail on it here...but basically...
Sensor ID variable + Data from sensor + sensor type -> One 16 bit unsigned int.

Large image showing how I formed packets to send...this was my learning intent of the project. Making my own "packets".

Ok. So already run in to a problem.

The transmitter is well under-powered (I think).
The 4V is not enough to get any decent range...we are talking less than 10m

I am ordering some 12V solar panels and then going to double up on the supercaps (to make 11V caps).

Seeming as the caps will now be 1/(1/4 +1/4) = capacitance of 2F and not the 4F...a small hit but for the power boost the transmitter...it is needed!
Will probably just use a Schottky diode to keep 5V to the Attiny85...that or a voltage divider to power a small 5V supercap.