Send sensor data over 433MHZ from one Raspberry Pi to another

Jul 31, 2015

In this blog post I give a python example how sensor data can be sent from one Raspberry Pi to another over 433MHZ. However, this python example is just a proof of concept :-).

To achieve that I'm using the pi_switch and tentacle_pi library.

In this tutorial I'm using a BMP180 sensor. This is a handy barometric sensor whose pressure and temperature values can be accessed over the I2C protocol.

So what do we need to for this tutorial?

This tutorial is structured as follows:

I2C Configuration

Step 1

First, we should configure I2C on the Raspberry Pi that accesses the BMP180 sensor data. In short we must install two packages over apt-get:

  • i2c-tools
    • tools for debugging, driver development and accessing data on I2C sensors
  • libi2c-dev
    • I2C header files which are needed for compiling c drivers

Open a terminal and install these dependencies:

sudo apt-get install i2c-tools libi2c-dev

Step 2

Secondly, we need to add the modules i2c-bcm2708 and i2c-dev in the /etc/modules file.

Briefly speaking this file contains the kernel modules that should be loaded at boot time.

Open the file /etc/modules and add these two lines:

i2c-bcm2708
i2c-dev

Step 3

Thirdly, open the /boot/config.txt file and add these two lines:

dtparam=i2c1=on
dtparam=i2c_arm=on

The /boot/config.txt file contains the various system configuration parameters it can be compared to a BIOS configuration on a conventional PC.

Finally, add the user pi to the i2c group. This makes it possible to run I2C programs without sudo:

sudo usermod -a -G i2c pi

Step 4

Reboot the Pi.

Step 5

After rebooting we should test if I2C is working properly.

Enter the following command:

i2cdetect -y 1

The output should look like this:

0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --  

The command i2cdetect -y 1 shows all connected I2C devices on the bus 1.

Now we are ready to install tentacle_pi.

I2C Driver Software

If you haven't installed tentacle_pi then it's time to install it. Tentacle Pi is small library that supports some I2C sensors among others the BMP180.

So enter the following command on the Raspberry Pi:

sudo pip install tentacle_pi

Hardware Setup

Connecting the BMP180 sensor should be straightforward.

Here's the wiring setup:

  • Blue wire connects both SDA pins (Signal data line).
  • Purple wire connects both SCL pins (Signal clock line).
  • Red wire connects both 3.3V pins.
  • Black wire connects both ground pins.
Raspberry Pi - BMP180

Testing the BMP180 Driver

Let's see if we can read the sensor data from the BMP180 sensor.

Here's an example program that prints the pressure, temperature and the altitude values on the terminal.

from tentacle_pi.BMP180 import BMP180
import time
bmp = BMP180(0x77, "/dev/i2c-1")


for x in range(0, 3):
  print "temperature: %0.1f" % bmp.temperature()
  print "pressure: %s" % bmp.pressure()
  print "altitude: %0.1f" % bmp.altitude()
  print
  time.sleep(2)

As a result we should see three times a output that looks like this one here:

temperature: 24.1
pressure: 92689
altitude: 745.1

Sending and Receiving 433MHZ RF signals

For installing pi-switch and how to send and receive simple data over RF 433 please read my last blog post :-).

If that works we are ready to implement a simple frame format.

Frame format

To distinguish the received sensor values like pressure and temperature from each other we need a simple frame format that includes a meta data header.

Unfortunately the sendDecimal method in the RCSwitchSender class is limited to 4-byte integer values.

But in our case 4 bytes will be enough to send sensor data and a small meta data header.

Frame Structure

We use the following frame structure:

 |1 byte | 2 byte | 3 byte | 4 byte |
 |-------------frame----------------|
 |---meta-data----|---sensor-data---|

First byte of the meta data header stores information about the sender and the second byte the sensor type.

We use the following sensor type values to distinguish the pressure and temperature value:

  • type of the temperature sensor is 0
  • type of the pressure sensor is 1

Encoding / Decoding sensor values

To encode and decode the float sensor values we apply the Q12.3 format with a sign bit.

                        bit
 | 0  |1|2|3|4|5|6|7|8|9|10|11|12|  13  |  14  |  15  |
 |sign|------integer--part-------|----fractial-part---|

The range of the Q12.3 format (with sign bit) is [-4095.875, 4095.875] with a resolution of 0.125.

Converting a float value to Q12.3 with a sign bit is pretty simple:

  • multiply by 2^3
  • round to the nearest integer
  • take the absolute value
  • set the sign bit if the float value if negative

Here's an example to_q_12_3_1() python function:

def to_q_12_3_1(f):
    sign = int(f < 0)
    n = f * (2 ** 3)
    q = int(abs(n))
    return (sign<<15) | q

Converting a Q12.3 value back to a float value works as follows:

  • extract the sign bit from the value q
  • map the sign bit to 1 if zero otherwise to -1
  • extract the number part from value q
  • convert the number part to a float
  • multiply the float value with 2^-3 and multiply with the sign value

For converting a Q12.3 value we use the function from_q_12_3_1():

def from_q_12_3_1(q):
    sign = q & 0x8000 # extract sign bit
    if sign:
      sign = -1
    else:
      sign = 1

    number = q & 0x7FFF
    f = float(number)
    return f * ( 2 ** (-3)) * sign

Creating / Reading Frames

What is missing right now are functions to create and read frames.

Here's an example make_frame() function for the sender.

def make_frame(pi_id, data_type, sensor_value):
  q_num = to_q_12_3_1(sensor_value) # 2 bytes
  meta_data = (pi_id << 8) | data_type # 2 bytes
  frame = meta_data << 16 # 4 bytes
  frame |= q_num
  return frame

On the receiver we need a corresponding read_frame() function.

Here's the appropriate read_frame() function:

def read_frame(frame):
  meta_data =  (frame & 0xffff0000) >> 16  # etract first two bytes and shift
  pi_id = (meta_data & 0xff00) >> 8 # extract first byte and shift
  sensor_type = (meta_data & 0x00ff) # extract second byte
  sensor_data = frame & 0x0000ffff # extract third and fourth byte
  sensor_value = from_q_12_3_1(sensor_data)
  return pi_id, sensor_type, sensor_value

Software Setup

Finally, we are ready to implement the receiver and sender.

Instead of explaining all the details I just give the complete code :-).

Sender Code

# -*- coding: utf-8 -*-
"""
@author     Alexander Rüedlinger <a.rueedlinger@gmail.com>
@date       30.07.2015

"""

import time
from pi_switch import RCSwitchSender
from tentacle_pi.BMP180 import BMP180

bmp = BMP180(0x77, "/dev/i2c-1")

sender = RCSwitchSender()
sender.enableTransmit(0) # use WiringPi pin 0

sensors = {
    0: 'Temperature',
    1: 'Pressure'
}

get_data = {
    0: bmp.temperature,
    1: lambda: bmp.pressure() / 1000.0 # convert Pa values to kPa
}

pi_id = 1


def to_q_12_3_1(f):
    sign = int(f < 0)
    n = f * (2 ** 3)
    q = int(abs(round(n)))
    return (sign<<15) | q

def make_frame(pi_id, data_type, sensor_value):
    q_num = to_q_12_3_1(sensor_value) # 2 bytes
    meta_data = (pi_id << 8) | data_type # 2 bytes
    frame = meta_data << 16 # 4 bytes
    frame |= q_num
    return frame

num = 0
while True:
    sensor_value_fun = get_data[num]
    sensor_value = sensor_value_fun()
    print("send sensor data: %s" % sensors[num])
    print("send sensor value: %s" % sensor_value)
    frame = make_frame(pi_id, num, sensor_value)
    print("send frame: %s" % frame)
    sender.sendDecimal(frame, 32)
    time.sleep(5)
    num = (num+1) % 2

Receiver Code

# -*- coding: utf-8 -*-
"""
@author     Alexander Rüedlinger <a.rueedlinger@gmail.com>
@date       30.07.2015

"""

from pi_switch import RCSwitchReceiver

receiver = RCSwitchReceiver()
receiver.enableReceive(2)

num = 0

sensors = {
    0: 'Temperature',
    1: 'Pressure'
}

sensor_units = {
    0: 'Celsius',
    1: 'kPa'
}


def from_q_12_3_1(q):
    sign = q & 0x8000
    if sign:
        sign = -1
    else:
        sign = 1

    v = q & 0x7FFF
    f = float(v)
    return f * ( 2 ** (-3)) * sign


def read_frame(frame):
    meta_data =  (frame & 0xffff0000) >> 16  # extract first two bytes and shift
    pi_id = (meta_data & 0xff00) >> 8 # extract first byte and shift
    sensor_type = (meta_data & 0x00ff) # extract second byte
    sensor_data = frame & 0x0000ffff # extract third and fourth byte
    sensor_value = from_q_12_3_1(sensor_data)
    return pi_id, sensor_type, sensor_value

while True:
    if receiver.available():
      received_value = receiver.getReceivedValue()

      if received_value:
          num += 1
          print("Received[%s]:" % num)
          print("data packet: %s" % received_value)

          pi_id,  sensor_type, sensor_value = read_frame(received_value)

          sensor = sensors[sensor_type]
          unit = sensor_units[sensor_type]

          print("data from pi: %s" % pi_id)
          print("%s: %s %s" % (sensor, sensor_value, unit))
          print("%s / %s bit" % (received_value, receiver.getReceivedBitlength()))
          print("Protocol: %s" % receiver.getReceivedProtocol())
          print("")

      receiver.resetAvailable()

pi-switch: Send and receive 433MHZ signals with a Raspberry Pi

Jul 31, 2015

Recently I was asked if I could wrap the receiver functions from the rc-switch library in the pi_switch python module.

So here you have it :-).

pi_switch wraps the rc-switch receiver functions in a proxy class called RCSwitchReceiver.

Here is a short tutorial how to send data from one Raspberry Pi to another via pi-switch. No C, no ugly C++, just Python code.

Hardware Setup

  • two Raspberry Pi's
  • 433mhz rf link kit, see.

Raspberry Pi - Sender

Raspberry Pi - Sender

Wiring setup:

  • Yellow / data signal wire to pin 11
  • Black / ground wire to ground pin 6
  • Red / power wire to 5V pin 2

By the way here is a great guide to the GPIO header and pins of the Raspberry Pi.

Raspberry Pi - Receiver

Raspberry Pi - Receiver

Wiring setup:

  • Yellow / data signal wire to pin 13
  • Black / ground wire to ground pin 6
  • Red / power wire to 5V pin 2

Software Setup

Dependencies

As mentioned before we're using pi_switch.

We need to install the following dependencies:

  • wiringPi
  • python-boost
  • python-dev
  • python-pip

In a first step we install wiringPi:

git clone git://git.drogon.net/wiringPi
cd wiringPi
./build

In a next step we should update the Raspberry Pi:

sudo apt-get update

The next step could take some time. Run the following command and grab a cup of coffee :-).

sudo apt-get install python-dev libboost-python-dev python-pip

pi_switch

Now, we're ready to install pi_switch using pip:

sudo pip install pi_switch

If you've already installed pi_switch you can upgrade using this command below:

sudo pip install pi_switch --upgrade

Code: Sending / receiving data

So let's start sending some data from one Pi to another Pi :-).

Here is the code for the Raspberry Pi that is receiving the data:

Raspberry Pi - Receiver

# -*- coding: utf-8 -*-
"""
@author     Alexander Rüedlinger <a.rueedlinger@gmail.com>
@date       31.07.2015

"""


from pi_switch import RCSwitchReceiver

receiver = RCSwitchReceiver()
receiver.enableReceive(2)

num = 0

while True:
    if receiver.available():
        received_value = receiver.getReceivedValue()
        if received_value:
            num += 1
            print("Received[%s]:" % num)
            print(received_value)
            print("%s / %s bit" % (received_value, receiver.getReceivedBitlength()))
            print("Protocol: %s" % receiver.getReceivedProtocol())
            print("")

        receiver.resetAvailable()

And here is the code for the Raspberry Pi that is sending the data.

Raspberry Pi - Sender

# -*- coding: utf-8 -*-
"""
@author     Alexander Rüedlinger <a.rueedlinger@gmail.com>
@date       31.07.2015

"""
import time
from pi_switch import RCSwitchSender

sender = RCSwitchSender()
sender.enableTransmit(0) # use WiringPi pin 0

num = 1
while True:
        time.sleep(2)
        sender.sendDecimal(num, 24)
        num += 1

Limitations

There is unfortunately one limitation. It is not possible to send and receive data within one program.

Source: https://github.com/ninjablocks/433Utils

Anyways, you can still run a program that sends data and another program that receives data on a single Pi! Yeah, unreliable inter-process communication over 433 MHZ :-).

Conclusion

Basically you have the tools to implement a simple communication protocol on top of pi_switch.

Please let me know what you're doing with pi_switch and share your projects with others :-).

And of course please contribute to pi_switch and other raspberry pi software projects.

Enjoy and happy hacking.

Cheers,

Alexander

Introducing Tentacle Pi

Mar 4, 2015

In the last blog post I wrote that I'm starting my own little open source adventure called Tentacle Pi.

I hope others will benefit from the Tentacle Pi library :-).

Right know three sensors are supported:

You can easily install the library using pip. Pip can be installed using the command below:

sudo apt-get install python-pip

To install Tentacle Pi you can run this command here:

sudo pip install tentacle_pi

Here are some example python programs how you can use the Tentacle Pi library.

AM2315

import time
from tentacle_pi.AM2315 import AM2315
am = AM2315(0x5c,"/dev/i2c-1")

for x in range(0,10):
    temperature, humidity, crc_check = am.sense()
    print "temperature: %0.1f" % temperature
    print "humidity: %0.1f" % humidity
    print "crc: %s" % crc_check
    print
    time.sleep(2

BMP180

from tentacle_pi.BMP180 import BMP180
import time
bmp = BMP180(0x77,"/dev/i2c-1")


for x in range(0,10):
        print "temperature: %0.1f" % bmp.temperature()
        print "pressure: %s" % bmp.pressure()
        print "altitude: %0.1f" % bmp.altitude()
        print
        time.sleep(2)

TSL2561

from tentacle_pi.TSL2561 import TSL2561
import time

tsl = TSL2561(0x39,"/dev/i2c-1")
tsl.enable_autogain()
tsl.set_time(0x00)

for x in range(0,10):
    print "lux %s" % tsl.lux()
    print
    time.sleep(3)

Enjoy and happy hacking :-) !

« Prev 1 2 3 Next »