Raspberry Pi Virtual Wire radio messages


30 posts   Page 1 of 2   1, 2
by joan » Fri Aug 15, 2014 2:06 pm
The following Python module, vw.py, is intended to be compatible with the Arduino Virtual Wire library.

The Virtual Wire library allows for the sending of short messages between Arduinos via inexpensive 313MHz/434MHz radio modules.

vw.py has had limited testing between a Pi, a TI Launchpad (using Energia) and an Arduino Pro Mini.

pydoc vw
Code: Select all
Help on module vw:

NAME
    vw

FILE
    /home/common/code/vw.py

DESCRIPTION
    This module provides a 313MHz/434MHz radio interface compatible
    with the Virtual Wire library used on Arduinos.
   
    It has been tested between a Pi, TI Launchpad, and Arduino Pro Mini.

CLASSES
    rx
    tx
   
    class rx
     |  Methods defined here:
     | 
     |  __init__(self, pi, rxgpio, bps=2000)
     |      Instantiate a receiver with the Pi, the receive gpio, and
     |      the bits per second (bps).  The bps defaults to 2000.
     |      The bps is constrained to be within MIN_BPS to MAX_BPS.
     | 
     |  cancel(self)
     |      Cancels the wireless receiver.
     | 
     |  get(self)
     |      Returns the next unread message, or None if none is avaiable.
     | 
     |  ready(self)
     |      Returns True if there is a message available to be read.
   
    class tx
     |  Methods defined here:
     | 
     |  __init__(self, pi, txgpio, bps=2000)
     |      Instantiate a transmitter with the Pi, the transmit gpio,
     |      and the bits per second (bps).  The bps defaults to 2000.
     |      The bps is constrained to be within MIN_BPS to MAX_BPS.
     | 
     |  cancel(self)
     |      Cancels the wireless transmitter, aborting any message
     |      in progress.
     | 
     |  put(self, data)
     |      Transmit a message.  If the message is more than
     |      MAX_MESSAGE_BYTES in size it is discarded.  If a message
     |      is currently being transmitted it is aborted and replaced
     |      with the new message.  True is returned if message
     |      transmission has successfully started.  False indicates
     |      an error.
     | 
     |  ready(self)
     |      Returns True if a new message may be transmitted.

DATA
    MAX_BPS = 10000
    MAX_MESSAGE_BYTES = 77
    MIN_BPS = 50


vw.py (source)
Example usage shown in the __main__ section.
Code: Select all
#!/usr/bin/env python
"""
This module provides a 313MHz/434MHz radio interface compatible
with the Virtual Wire library used on Arduinos.

It has been tested between a Pi, TI Launchpad, and Arduino Pro Mini.
"""
# 2014-08-14
# vw.py

import time

import pigpio

MAX_MESSAGE_BYTES=77

MIN_BPS=50
MAX_BPS=10000

_HEADER=[0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x38, 0x2c]

_CTL=3

_SYMBOL=[
   0x0d, 0x0e, 0x13, 0x15, 0x16, 0x19, 0x1a, 0x1c,
   0x23, 0x25, 0x26, 0x29, 0x2a, 0x2c, 0x32, 0x34]


def _sym2nibble(symbol):
   for nibble in range(16):
      if symbol == _SYMBOL[nibble]:
         return nibble
   return 0

def _crc_ccitt_update(crc, data):

   data = data ^ (crc & 0xFF);

   data = (data ^ (data << 4)) & 0xFF;

   return (
             (((data << 8) & 0xFFFF) | (crc >> 8)) ^
              ((data >> 4) & 0x00FF) ^ ((data << 3) & 0xFFFF)
          )

class tx():

   def __init__(self, pi, txgpio, bps=2000):
      """
      Instantiate a transmitter with the Pi, the transmit gpio,
      and the bits per second (bps).  The bps defaults to 2000.
      The bps is constrained to be within MIN_BPS to MAX_BPS.
      """
      self.pi = pi

      self.txbit = (1<<txgpio)

      if bps < MIN_BPS:
         bps = MIN_BPS
      elif bps > MAX_BPS:
         bps = MAX_BPS

      self.mics = int(1000000 / bps)

      self.wave_id = None

      pi.wave_add_new()

      pi.set_mode(txgpio, pigpio.OUTPUT)


   def _nibble(self, nibble):

      for i in range(6):
         if nibble & (1<<i):
            self.wf.append(pigpio.pulse(self.txbit, 0, self.mics))
         else:
            self.wf.append(pigpio.pulse(0, self.txbit, self.mics))

   def _byte(self, crc, byte):
      self._nibble(_SYMBOL[byte>>4])
      self._nibble(_SYMBOL[byte&0x0F])
      return _crc_ccitt_update(crc, byte)

   def put(self, data):
      """
      Transmit a message.  If the message is more than
      MAX_MESSAGE_BYTES in size it is discarded.  If a message
      is currently being transmitted it is aborted and replaced
      with the new message.  True is returned if message
      transmission has successfully started.  False indicates
      an error.
      """
      if len(data) > MAX_MESSAGE_BYTES:
         return False

      self.wf = []

      self.cancel()

      for i in _HEADER:
         self._nibble(i)

      crc = self._byte(0xFFFF, len(data)+_CTL)

      for i in data:

         if type(i) == type(""):
            v = ord(i)
         else:
            v = i

         crc = self._byte(crc, v)

      crc = ~crc

      self._byte(0, crc&0xFF)
      self._byte(0, crc>>8)

      self.pi.wave_add_generic(self.wf)

      self.wave_id = self.pi.wave_create()

      if self.wave_id >= 0:
         self.pi.wave_send_once(self.wave_id)
         return True
      else:
         return False


   def ready(self):
      """
      Returns True if a new message may be transmitted.
      """
      return not self.pi.wave_tx_busy()

   def cancel(self):
      """
      Cancels the wireless transmitter, aborting any message
      in progress.
      """
      if self.wave_id is not None:
         self.pi.wave_tx_stop()
         self.pi.wave_delete(self.wave_id)
         self.pi.wave_add_new()

      self.wave_id = None

class rx():

   def __init__(self, pi, rxgpio, bps=2000):
      """
      Instantiate a receiver with the Pi, the receive gpio, and
      the bits per second (bps).  The bps defaults to 2000.
      The bps is constrained to be within MIN_BPS to MAX_BPS.
      """
      self.pi = pi
      self.rxgpio = rxgpio

      self.messages = []
      self.bad_CRC = 0

      if bps < MIN_BPS:
         bps = MIN_BPS
      elif bps > MAX_BPS:
         bps = MAX_BPS

      slack = 0.20
      self.mics = int(1000000 / bps)
      slack_mics = int(slack * self.mics)
      self.min_mics = self.mics - slack_mics       # Shortest legal edge.
      self.max_mics = (self.mics + slack_mics) * 4 # Longest legal edge.

      self.timeout =  8 * self.mics / 1000 # 8 bits time in ms.
      if self.timeout < 8:
         self.timeout = 8

      self.last_tick = None
      self.good = 0
      self.bits = 0
      self.token = 0
      self.in_message = False
      self.message = [0]*(MAX_MESSAGE_BYTES+_CTL)
      self.message_len = 0
      self.byte = 0

      pi.set_mode(rxgpio, pigpio.INPUT)

      self.cb = pi.callback(rxgpio, pigpio.EITHER_EDGE, self._cb)

   def _calc_crc(self):

      crc = 0xFFFF
      for i in range(self.message_length):
         crc = _crc_ccitt_update(crc, self.message[i])
      return crc

   def _insert(self, bits, level):

      for i in range(bits):

         self.token >>= 1

         if level == 0:
            self.token |= 0x800

         if self.in_message:

            self.bits += 1

            if self.bits >= 12: # Complete token.

               byte = (
                  _sym2nibble(self.token & 0x3f) << 4 |
                  _sym2nibble(self.token >> 6))

               if self.byte == 0:
                  self.message_length = byte

                  if byte > (MAX_MESSAGE_BYTES+_CTL):
                     self.in_message = False # Abort message.
                     return

               self.message[self.byte] = byte

               self.byte += 1
               self.bits = 0

               if self.byte >= self.message_length:
                  self.in_message = False
                  self.pi.set_watchdog(self.rxgpio, 0)

                  crc = self._calc_crc()

                  if crc == 0xF0B8: # Valid CRC.
                     self.messages.append(
                        self.message[1:self.message_length-2])
                  else:
                     self.bad_CRC += 1

         else:
            if self.token == 0xB38: # Start message token.
               self.in_message = True
               self.pi.set_watchdog(self.rxgpio, self.timeout)
               self.bits = 0
               self.byte = 0

   def _cb(self, gpio, level, tick):

      if self.last_tick is not None:

         if level == pigpio.TIMEOUT:

            self.pi.set_watchdog(self.rxgpio, 0) # Switch watchdog off.

            if self.in_message:
               self._insert(4, not self.last_level)

            self.good = 0
            self.in_message = False

         else:

            edge = pigpio.tickDiff(self.last_tick, tick)

            if edge < self.min_mics:

               self.good = 0
               self.in_message = False

            elif edge > self.max_mics:

               if self.in_message:
                  self._insert(4, level)

               self.good = 0
               self.in_message = False

            else:

               self.good += 1

               if self.good > 8:

                  bitlen = (100 * edge) / self.mics

                  if   bitlen < 140:
                     bits = 1
                  elif bitlen < 240:
                     bits = 2
                  elif bitlen < 340:
                     bits = 3
                  else:
                     bits = 4

                  self._insert(bits, level)

      self.last_tick = tick
      self.last_level = level

   def get(self):
      """
      Returns the next unread message, or None if none is avaiable.
      """
      if len(self.messages):
         return self.messages.pop(0)
      else:
         return None

   def ready(self):
      """
      Returns True if there is a message available to be read.
      """
      return len(self.messages)

   def cancel(self):
      """
      Cancels the wireless receiver.
      """
      if self.cb is not None:
         self.cb.cancel()
         self.pi.set_watchdog(self.rxgpio, 0)
      self.cb = None

if __name__ == "__main__":

   import time

   import pigpio

   import vw

   RX=11
   TX=25

   BPS=2000

   pi = pigpio.pi() # Connect to local Pi.

   rx = vw.rx(pi, RX, BPS) # Specify Pi, rx gpio, and baud.
   tx = vw.tx(pi, TX, BPS) # Specify Pi, tx gpio, and baud.

   msg = 0

   start = time.time()

   while (time.time()-start) < 300:

      msg += 1

      while not tx.ready():
         time.sleep(0.1)

      time.sleep(0.2)

      tx.put([48, 49, 65, ((msg>>6)&0x3F)+32, (msg&0x3F)+32])

      while not tx.ready():
         time.sleep(0.1)

      time.sleep(0.2)

      tx.put("Hello World #{}!".format(msg))

      while rx.ready():
         print("".join(chr (c) for c in rx.get()))

   rx.cancel()
   tx.cancel()

   pi.stop()

The following shows the waveform of a message being transmitted on gpio 25 and the waveform of the same message being received on gpio 11.
vw-1.png
vw-1.png (41.19 KiB) Viewed 10906 times
Last edited by joan on Sun Aug 17, 2014 2:48 pm, edited 1 time in total.
User avatar
Posts: 12134
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK
by asandford » Sat Aug 16, 2014 7:19 pm
Great work Joan.

The developers have EOLed VW, replacing it with RadioHead.
Posts: 1427
Joined: Mon Dec 31, 2012 12:54 pm
Location: Ealing
by joan » Sat Aug 16, 2014 8:08 pm
asandford wrote:Great work Joan.

The developers have EOLed VW, replacing it with RadioHead.

I'm not sure of the status of RadioHead. I'm fairly sure I can make software compatible with VirtualWire Public Domain. I'm not so sure about RadioHead.

Would you happen to know if RadioHead is widely used by the Arduino community?
User avatar
Posts: 12134
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK
by asandford » Sat Aug 16, 2014 8:34 pm
joan wrote:I'm not sure of the status of RadioHead. I'm fairly sure I can make software compatible with VirtualWire Public Domain. I'm not so sure about RadioHead.

On the RH page they say
Platforms

A range of platforms is supported:

Arduino and the Arduino IDE (version 1.0 to 1.5.5 and later) Including Diecimila, Uno, Mega, Leonardo, Yun etc. http://arduino.cc/, Also similar boards such as Moteino http://lowpowerlab.com/moteino/ , Anarduino Mini http://www.anarduino.com/mini/ etc.
ChipKit Uno32 board and the MPIDE development environment http://www.digilentinc.com/Products/Det ... PKIT-UNO32
Maple and Flymaple boards with libmaple and the Maple-IDE development environment http://leaflabs.com/devices/maple/ and http://www.open-drone.org/flymaple
Teensy including Teensy 3.1 built using Arduino IDE 1.0.5 with teensyduino addon 1.18 and later. http://www.pjrc.com/teensy

Other platforms are partially supported, such as Generic AVR 8 bit processors, MSP430. We welcome contributions that will expand the range of supported platforms.

So it appears they are open to development for other platforms.

joan wrote:Would you happen to know if RadioHead is widely used by the Arduino community?

It only became available in April, so it's probably too new to be widely adopted; but it offers vastly improved methods, and [IMVHO] should be used now that support for VW has officially been dropped.
Posts: 1427
Joined: Mon Dec 31, 2012 12:54 pm
Location: Ealing
by Bianco » Wed Aug 20, 2014 1:52 pm
Looks like it's what I'm looking for but I've never used a module...

Shall I download vw.py and place it in /home/common/code (which doesn't exist on my RPi for the moment) and then call it with import vw from a python program I'll have created?


(TX will be an Arduino, RX the Pi)
Posts: 40
Joined: Sun Jul 13, 2014 9:41 pm
by joan » Wed Aug 20, 2014 3:00 pm
Bianco wrote:Looks like it's what I'm looking for but I've never used a module...

Shall I download vw.py and place it in /home/common/code (which doesn't exist on my RPi for the moment) and then call it with import vw from a python program I'll have created?


(TX will be an Arduino, RX the Pi)

Put the file in the same directory as your Python program. pydoc was just showing where the file was found on my file system. It's best to give it the same name (vw.py) or just incorporate the class source in your own program.
User avatar
Posts: 12134
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK
by Bianco » Wed Aug 20, 2014 7:11 pm
Thanks.

I had nothing when the Arduino was emitting, I've got that since RX and TX are on the Pi:

Image
Posts: 40
Joined: Sun Jul 13, 2014 9:41 pm
by joan » Wed Aug 20, 2014 7:26 pm
Bianco wrote:Thanks.

I had nothing when the Arduino was emitting, I've got that since RX and TX are on the Pi:

Image

You don't need to use sudo with the pigpio Python module.

If the Arduino isn't been received (I think that's what you are saying) then check the signal on the Pi's RX gpio. One way would be to use piscope (preferably from another Linux machine rather than the Pi).
User avatar
Posts: 12134
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK
by Bianco » Wed Aug 20, 2014 8:47 pm
I'm making some progress:

Image

I got four "Hello World" (sent by the Arduino) before the bug, that number varies a lot.

Also, I have the feeling not all messages are received by the pi (as opposed to what I get from the rc-switch library I was using up to today) but I'll have to send incremental integers rather than text to check that.
Posts: 40
Joined: Sun Jul 13, 2014 9:41 pm
by joan » Wed Aug 20, 2014 8:56 pm
Bianco wrote:I'm making some progress:

Image

I got four "Hello World" (sent by the Arduino) before the bug, that number varies a lot.

Also, I have the feeling not all messages are received by the pi (as opposed to what I get from the rc-switch library I was using up to today) but I'll have to send incremental integers rather than text to check that.

I think you are using an old version of pigpio. Probably best to download the latest version (currently 20).

What does pigs pigpv report?
User avatar
Posts: 12134
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK
by Bianco » Wed Aug 20, 2014 10:00 pm
joan wrote:I think you are using an old version of pigpio. Probably best to download the latest version (currently 20).

You're right, I'm running version 17. I tried reinstalling:
Code: Select all
wget abyz.co.uk/rpi/pigpio/pigpio.zip
unzip pigpio.zip
cd PIGPIO
make
make install


Still running v17 and sudo ./x_pigpio tells me "Can't lock /var/run/pigpio.pid pigpio initialisation failed."
Posts: 40
Joined: Sun Jul 13, 2014 9:41 pm
by joan » Wed Aug 20, 2014 10:03 pm
The old daemon is still running.

sudo killall pigpiod

Probably safest to run

make install

again.
User avatar
Posts: 12134
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK
by Bianco » Wed Aug 20, 2014 10:12 pm
I'll do that tomorrow, thanks for the help.
Posts: 40
Joined: Sun Jul 13, 2014 9:41 pm
by Bianco » Thu Aug 21, 2014 2:38 pm
make wasn't doing anything (probably because the compiled files from the previous install were in the same folder?) but I got it working (placing the newly-downloaded source somewhere else) and I'm running version 20 now.
The bug doesn't appear anymore and I'm receiving everything I'm sending when they are not too far apart:

Image


That's my next question: is there some software trickery to improve the range and reliability?
Like reducing the speed (it really doesn't matter), increasing the delay between two messages, increasing the pulse length (I don't even know if it's possible with virtual wire), sending the same message a few more times, ...
Posts: 40
Joined: Sun Jul 13, 2014 9:41 pm
by joan » Thu Aug 21, 2014 2:53 pm
On the make I suppose I remove the previous zip file and PIGPIO directory without thinking before downloading a new zip.

Not too far apart? In distance or time?

You could send a message multiple times. That should increase the odds of a successful transmission. That's outside the scope of the protocol though. Only the user understands the meaning of messages and whether duplicates would cause errors.

You could change the bits per second. A lower value should be more reliable.

On the hardware side. More voltage to the transmitter, better aerials etc.
User avatar
Posts: 12134
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK
by Bianco » Thu Aug 21, 2014 3:56 pm
joan wrote:On the make I suppose I remove the previous zip file and PIGPIO directory without thinking before downloading a new zip.

I installed a Mandrake 5 on my Pentium II 15 years ago and I bouht an eeePC 701 when they came out. That was my experience with Linux until last week when I received the Pi. So, I'm still quite clumsy :oops:

Not too far apart? In distance or time?

In distance, hence my question about range after.
In time, I haven't checked but that seems fast enough.

You could send a message multiple times. That should increase the odds of a successful transmission. That's outside the scope of the protocol though. Only the user understands the meaning of messages and whether duplicates would cause errors.

I'm expecting one or two messages a day so I guess my python script will handle the multiple occurences itself.

You could change the bits per second. A lower value should be more reliable.

Is that something I'll have to setup on the emitter and the receiver?
Posts: 40
Joined: Sun Jul 13, 2014 9:41 pm
by joan » Thu Aug 21, 2014 4:21 pm
Yes, you need to set the same bits per second at both ends.

How does it compare with your current software for reliability? From about 20 feet apart at 2000 bps with a 5V transmitter I received (correct CRC) 2396 out of 3283 sent (72%) 26 byte long messages. I have no feel as to whether that is good, bad, or indifferent. In another run the success rate was 92%.

If the timings are obviously out between the two machines there might be some mileage in setting slightly higher or lower bits per second at the Pi end.

webm video
User avatar
Posts: 12134
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK
by Bianco » Thu Aug 21, 2014 11:55 pm
joan wrote:How does it compare with your current software for reliability? From about 20 feet apart at 2000 bps with a 5V transmitter

That would be interesting to precise which transmitter and which antena.

I'll try making a sketch and sript allowing me to count messages after the weekend.
Posts: 40
Joined: Sun Jul 13, 2014 9:41 pm
by Bianco » Mon Sep 01, 2014 4:53 pm
Well, I did it by hand and changed my Tx and Rx modules (for much better ones).

20 feet apart, I got every of the 500 messages I sent, same for the RCSitch library.


Then I uploaded the sketch to my barebone ATmega 328P: I didn't receive anything. The reason: it's running at 8 Mhz instead of 16 for the Arduino and the library isn't taking it into account (I don't know if it should) when setting the speed. So, sending at "1000" bps from the Arduino, I had to set the R-Pi to receive at 500.
Posts: 40
Joined: Sun Jul 13, 2014 9:41 pm
by Bianco » Fri Sep 12, 2014 10:09 am
Bianco wrote:Then I uploaded the sketch to my barebone ATmega 328P: I didn't receive anything. The reason: it's running at 8 Mhz instead of 16 for the Arduino and the library isn't taking it into account (I don't know if it should) when setting the speed. So, sending at "1000" bps from the Arduino, I had to set the R-Pi to receive at 500.

I screwed up there. Maybe I didn't chose the right board when flashing, I don't know.
Posts: 40
Joined: Sun Jul 13, 2014 9:41 pm
by JumpZero » Wed Sep 24, 2014 5:42 am
Hello,

 

I have to say a big thank you to you, Joan, for this vw.py and pigpio. It exactly matches my needs. My project uses an Arduino to send temperature measures to a Raspberry Pi, by this cheap RF transmitter/receiver. I was using the RCSwitch library because when you Google on that, it’s what comes first. It was running fine, I have done plenty of tests. However it wasn’t what I wanted, the length of the message is limited and the RFSniffer provided uses 100% cpu, not sure if it uses interrupts. Anyway as per its name the purpose of this library is to operates RF switches, not to send data. Virtual wire is made to send data, and I was happy to find it. I spent a couple of hours reading the Virtual wire and pigpio doc’s before going ahead. Then:

I installed Virtual Wire library in the Arduino IDE

Modify the wiring to meet the pin used in the software

Compiled and run on my Arduino Uno the example “Transmitter” without modification.

Downloaded, compiled, ran tests of pigpio on the Pi, ran the daemon

Copy this vw.py script, modify it by removing the transmit parts (I need to receive only)

Modify the wiring on the Pi to match the software (Rx=11)

Run the script and SUCCESS, I received the “hello” messages from the Arduino

 

It’s wonderful to have it running from the first try, no bug! I can go ahead with my project, I can send what I want, the cpu load isn’t very big, 45% (more by Python than the daemon).

Thank you again, Pigpio is a awesome piece of software, very professional.

--

Jmp0
Posts: 729
Joined: Thu Mar 28, 2013 7:35 pm
Location: 127.0.0.1
by joan » Wed Sep 24, 2014 7:39 am
JumpZero wrote:Hello,

 

I have to say a big thank you to you, Joan, for this vw.py and pigpio.
...

My first thought on seeing the post in the listings was "Oh no, what's wrong now". So I was pleasantly surprised.

Thank you.

p.s. not much you can do about the CPU usage when using Python. There is lots of radio static which has to be sampled and then ignored when checking for messages. A C version would be much lighter on resources.
User avatar
Posts: 12134
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK
by Bianco » Wed Sep 24, 2014 10:36 am
joan wrote:p.s. not much you can do about the CPU usage when using Python. There is lots of radio static which has to be sampled and then ignored when checking for messages.

A band pass filter, maybe?
Posts: 40
Joined: Sun Jul 13, 2014 9:41 pm
by JumpZero » Sat Sep 27, 2014 1:21 pm
Hi,

FYI: It works fine with Radiohead in place of Virtual Wire on the Arduino.
The code of the provided example is almost the same.
--
Jmp0
Posts: 729
Joined: Thu Mar 28, 2013 7:35 pm
Location: 127.0.0.1
by NAD » Mon Jul 20, 2015 8:57 am
Hi,

I'm really interest in this implementation, seems like what I was looking for but I have few question...

My requirement is to communicate between two or more raspberry pi (rev 2) devices. A message will be 11 ASCII characters. devices will be placed few meters apart so no need of long distant communication.

    [1]will I be able to transmit 11 characters at once ?
    [2]How the encoding and decoding is handled ? do I need to interface a HW encoder/decoder ?
    [3]I'm going to use these modules will they work with the VW ?
http://www.lankatronics.com/modules-and-sensors/rf/433mhz-rf-transmitter-and-receiver-kit.html

I'm sorry if I have done any mistakes I'm new to pi and electronics :)
Posts: 8
Joined: Tue Jun 09, 2015 4:33 pm