btidey
Posts: 1641
Joined: Sun Feb 17, 2013 6:51 pm

LightwaveRF 433MHz support

Sat Jan 17, 2015 10:55 pm

Quite a few people have used my Arduino libraries for direct access to LightwaveRF 433MHz transmission and reception to control LightwaveRF devices like switches and dimmers. I had also ported them to the Spark Core wifi module. They could be used from a Raspberry Pi by sending serial commands to an Arduino.

Some people had been asking if the Raspberry Pi could be used without an intermediate Arduino.

I have posted up an experimental python library which in turn uses the excellent pigpio library to get fast real time IO on the Raspberry Pi. At this moment only the RX side is tested and functional. I am still working on the TX side but hope to have that fully working shortly.

Details and code are at

https://github.com/roberttidey/LightwaveRF

btidey
Posts: 1641
Joined: Sun Feb 17, 2013 6:51 pm

Re: LightwaveRF 433MHz support

Sun Jan 18, 2015 11:38 am

TX side of LightwaveRF library now working.

This means with the addition of cheap 433MHz TX / RX modules the Pi can receive commands form LightwaveRF remotes and the wifi Link, and can send commands direct to LightwaveRF devices.

btidey
Posts: 1641
Joined: Sun Feb 17, 2013 6:51 pm

Re: LightwaveRF 433MHz support

Wed Jan 21, 2015 2:55 pm

I have now found that the python interface to Pigpio has really too much overhead for this type of application on the Raspberry.

When idle there are about 7000 callbacks / second due to the receiver AGC and background noise. So although it works it uses up pretty much all the CPU using the python pigpio interface.

I have changed over to a c++ version still using pigpio and the utilisation is now very low (<7%) CPU.

User avatar
joan
Posts: 15660
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK

Re: LightwaveRF 433MHz support

Fri Jan 30, 2015 10:50 am

Was the port from Python to C reasonably straightforward?

I'm really wondering as a port of the pigpio Virtual Wire module from Python to C might resolve some performance issues.

Massi
Posts: 1691
Joined: Fri May 02, 2014 1:52 pm
Location: Italy

Re: LightwaveRF 433MHz support

Fri Jan 30, 2015 10:59 am

btidey wrote:I have now found that the python interface to Pigpio has really too much overhead for this type of application on the Raspberry.

When idle there are about 7000 callbacks / second due to the receiver AGC and background noise. So although it works it uses up pretty much all the CPU using the python pigpio interface.
Yes, common problem for python "sniffers".
Sorry for my noobnes, but how is LightwaveRF protocol? is it OOK?
I have changed over to a c++ version still using pigpio and the utilisation is now very low (<7%) CPU.
Good news and bad news together.
I'm really wondering as a port of the pigpio Virtual Wire module from Python to C might resolve some performance issues.
Come on, master, it's time for a proper RF class managing all types of modulation! (you know, i'm aiming my OOK weather station signal :P)
My problem is that i'm completely noob on C code (and compiling, and so on..) but at this point, after seeing my python OOK receiver burning out all my CPU, i think i have to give it a try in C

gregkontos
Posts: 3
Joined: Fri Jan 30, 2015 5:51 pm

Re: LightwaveRF 433MHz support

Fri Jan 30, 2015 6:15 pm

joan wrote:Was the port from Python to C reasonably straightforward?

I'm really wondering as a port of the pigpio Virtual Wire module from Python to C might resolve some performance issues.
Hi Joan, I tried porting your vw.py example to c++. I had some timing problems receiving the messages from the Arduino. I found that it still consumed a great deal of system resources while running. Although the excess use of system resources may have been due to faulty code... it didn't work, after all :)

The code's a bit of a mess, so I'm not sure if it will help. But below is the code that I had before I tried something different:

Code: Select all

#include "rhpi_ask.h"
#include <stdint.h>
#include <stdio.h>
#include <errno.h>
#include <deque>
#include <cstring>
#include <iostream>
#include <stdio.h>
#include <wiringPi.h>

#include "pigpio.h"

static uint8_t symbols[] =
{
    0xd,  0xe,  0x13, 0x15, 0x16, 0x19, 0x1a, 0x1c,
    0x23, 0x25, 0x26, 0x29, 0x2a, 0x2c, 0x32, 0x34
};

static int MIN_BPS=50;
static int MAX_BPS=10000;

/// The transmitter buffer in _symbols_ not data octets
uint8_t _txBuf[(RH_ASK_MAX_PAYLOAD_LEN * 2) + RH_ASK_PREAMBLE_LEN];

/// Number of symbols in _txBuf to be sent;
uint8_t _txBufLen;

uint8_t preamble[RH_ASK_PREAMBLE_LEN] = {0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x38, 0x2c};
// ie _HEADER


rhpi_ask::rhpi_ask() :
    _thisAddress(RH_BROADCAST_ADDRESS),
    _txHeaderTo(RH_BROADCAST_ADDRESS),
    _txHeaderFrom(RH_BROADCAST_ADDRESS),
    _txHeaderId(0),
    _txHeaderFlags(0),
	_speed(0),
	_mics(0)

{
	std::cout << "initializing gpio \n";
//	gpioInitialise();
	if (wiringPiSetup () < 0)
	  {
	  fprintf (stderr, "Unable to setup wiringPi: %s\n", strerror (errno)) ;
	    
	  }
	std::cout << "gpio init complete \n";
}

// Convert a 6 bit encoded symbol into its 4 bit decoded equivalent
uint8_t rhpi_ask::sym2nibble(uint8_t symbol)
{
    uint8_t i;
    uint8_t count;
    
    // Linear search :-( Could have a 64 byte reverse lookup table?
    // There is a little speedup here courtesy Ralph Doncaster:
    // The shortcut works because bit 5 of the symbol is 1 for the last 8
    // symbols, and it is 0 for the first 8.
    // So we only have to search half the table
    for (i = (symbol>>2) & 8, count=8; count-- ; i++)
	if (symbol == symbols[i]) return i;

    return 0; // Not found
}

/* From both libraries
*/
uint16_t rhpi_ask::crc_ccitt_update (uint16_t crc, uint8_t data)
{
    data ^= lo8 (crc);
    data ^= data << 4;
    
    return ((((uint16_t)data << 8) | hi8 (crc)) ^ (uint8_t)(data >> 4) 
	    ^ ((uint16_t)data << 3));
}

void writePtt(uint8_t mode)
{

}

int rhpi_ask::tickDiff(uint32_t startTick, uint32_t endTick) {
		return endTick - startTick;
}

rhpi_tx::rhpi_tx(uint16_t speed, uint8_t txPin ) :
	
	_txPin(txPin),
	_txGood(0),
	_waveId(0),
	_txBit(0),
	_txIndex(0)

{
	_speed = speed;
	memcpy(_txBuf, preamble, sizeof(preamble));
	if (_speed < MIN_BPS) {
		_speed = MIN_BPS;
	} else if (_speed > MAX_BPS) {
		_speed = MAX_BPS;
	}

	_mics = int(1000000 / _speed);

	_waveId = 0;
/* wave_add_new calls pgio_cmd_wvnew (aka 53) through c */
	_waveId = gpioWaveAddNew();

     gpioSetMode(_txPin, PI_OUTPUT);


}

bool rhpi_tx::send(const uint8_t* data, uint8_t len)
{
    uint8_t i;
    uint16_t index = 0;
    uint16_t crc = 0xffff;
    uint8_t *p = _txBuf + RH_ASK_PREAMBLE_LEN; // start of the message area
    uint8_t count = len + 3 + RH_ASK_HEADER_LEN; // Added byte count and FCS and headers to get total number of bytes

    if (len > RH_ASK_MAX_MESSAGE_LEN)
	return false;

    // Wait for transmitter to become available
    // waitPacketSent();

    // Encode the message length
    crc = crc_ccitt_update(crc, count);
    p[index++] = symbols[count >> 4];
    p[index++] = symbols[count & 0xf];

    // Encode the headers
    crc = crc_ccitt_update(crc, _txHeaderTo);
    p[index++] = symbols[_txHeaderTo >> 4];
    p[index++] = symbols[_txHeaderTo & 0xf];
    crc = crc_ccitt_update(crc, _txHeaderFrom);
    p[index++] = symbols[_txHeaderFrom >> 4];
    p[index++] = symbols[_txHeaderFrom & 0xf];
    crc = crc_ccitt_update(crc, _txHeaderId);
    p[index++] = symbols[_txHeaderId >> 4];
    p[index++] = symbols[_txHeaderId & 0xf];
    crc = crc_ccitt_update(crc, _txHeaderFlags);
    p[index++] = symbols[_txHeaderFlags >> 4];
    p[index++] = symbols[_txHeaderFlags & 0xf];

    // Encode the message into 6 bit symbols. Each byte is converted into 
    // 2 6-bit symbols, high nybble first, low nybble second
    for (i = 0; i < len; i++)
    {
	crc = crc_ccitt_update(crc, data[i]);
	p[index++] = symbols[data[i] >> 4];
	p[index++] = symbols[data[i] & 0xf];
    }

    // Append the fcs, 16 bits before encoding (4 6-bit symbols after encoding)
    // Caution: VW expects the _ones_complement_ of the CCITT CRC-16 as the FCS
    // VW sends FCS as low byte then hi byte
    crc = ~crc;
    p[index++] = symbols[(crc >> 4)  & 0xf];
    p[index++] = symbols[crc & 0xf];
    p[index++] = symbols[(crc >> 12) & 0xf];
    p[index++] = symbols[(crc >> 8)  & 0xf];

    // Total number of 6-bit symbols to send
    _txBufLen = index + RH_ASK_PREAMBLE_LEN;

    // Start the low level interrupt handler sending symbols

	transmit();
    return true;
}


void rhpi_tx::transmit()
{
    gpioPulse_t *pulses;
    uint16_t pulseIndex = 0;
    uint16_t index = 0;
    /// Bit number of next bit to send
    uint8_t _txBit;
    
    while (_txIndex < _txBufLen) {

	for (_txBit = 0; _txBit < 6; _txBit++) {
		gpioPulse_t pulse;
		if (_txBuf[_txIndex] & (1<<_txBit)) {
			pulse.gpioOff = 0;
			pulse.gpioOn  = _txBuf[_txIndex];
			pulse.usDelay = _mics;
		} else {
			pulse.gpioOff = _txBuf[_txIndex];
			pulse.gpioOn  = 0;
			pulse.usDelay = _mics;			
			
		}
		pulses[pulseIndex++] = pulse;
	
	}
	_txIndex++;
    }	    
   
  gpioWaveAddGeneric(pulseIndex,pulses);
  _waveId = gpioWaveCreate();
  gpioWaveTxSend(_waveId,PI_WAVE_MODE_ONE_SHOT);
   // send pulses in waveform

  _txGood++;
   
}


/*
void rhpi_tx::waitPacketSent() {

	while (gpioWaveTxBusy()) {
		std::this_thread::yield();
	}
}
*/
rhpi_rx::rhpi_rx() :
	_rxActive(false),
	_lastTick(0),
	_good(0),
	_bits(0),
	_messageLen(0),
	_byte(0),
    _rxBufLen(0),
	_minMics(0),
	_maxMics(0),
	_lastLevel(0),
	_promiscuous(false),
	_rxIntegrator(0),
	_rxPllRamp(0)
{
	
}

rhpi_rx& rhpi_rx::getInstance() {
		static rhpi_rx theInstance;
		return theInstance;
}

void rhpi_rx::init(uint16_t speed, uint8_t rxPin) {
	_rxPin = rxPin;	
	_speed = speed;
//	self.messages = []
//      self.bad_CRC = 0
//
	if (speed < MIN_BPS) {
		speed = MIN_BPS;
	} else if (speed > MAX_BPS) {
		speed = MAX_BPS;
	}

	_mics = int(1000000 / speed);

	float slack = 0.20;
	int slack_mics = int(slack * _mics);
	_minMics = _mics - slack_mics; // shortest legal edge
	_maxMics = (_mics + slack_mics) * 4; // longest legal edge

	_timeout = 8 * _mics / 1000;
	if (_timeout < 8) {
		_timeout = 8;
	}

	pinMode(rxPin, INPUT);
	// self.cb = pi.callback(rxgpio, pigpio.EITHER_EDGE, self._cb)
	// TODO utilize the wiringPi callback... 
	wiringPiISR(rxPin, INT_EDGE_BOTH, &callBack);
}

bool rhpi_rx::validMessage(uint8_t *message, uint8_t messageLength) {
	bool rxBufValid = false;
	uint16_t crc = 0xffff;
	std::cout << "validating message";
	    // The CRC covers the byte count, headers and user data
	    for (uint8_t i = 0; i < messageLength; i++) {
	    	crc = crc_ccitt_update(crc, message[i]);
	    }
	    if (crc != 0xf0b8) // CRC when buffer and expected CRC are CRC'd
	    {
			// Reject and drop the message

			rxBufValid = false;

	    }

	    // Extract the 4 headers that follow the message length
	    uint8_t _rxHeaderTo    = message[1];
	   // _rxHeaderFrom  = message[2];
	   // _rxHeaderId    = message[3];
	   // _rxHeaderFlags = message[4];
	    if (_promiscuous ||
			_rxHeaderTo == _thisAddress ||
			_rxHeaderTo == RH_BROADCAST_ADDRESS)
	    {

	    	rxBufValid = true;
	    }
	    return rxBufValid;
}

void rhpi_rx::insert() {
	//_rxBits == token;
	uint16_t i;
	uint16_t crc;
	bool level = digitalRead(_rxPin);
	if (level) {
			_rxIntegrator++;
	}
	if (level != _lastLevel) {
			
			_rxPllRamp += ((_rxPllRamp < RH_ASK_RAMP_TRANSITION) ? RH_ASK_RAMP_INC_RETARD : RH_ASK_RAMP_INC_ADVANCE);
			
			_lastLevel = level;
	} else {
			_rxPllRamp += RH_ASK_RAMP_INC;
	}

	if (_rxPllRamp >= RH_ASK_RX_RAMP_LEN) {
		
		_rxBits >>=1;

		if (_rxIntegrator >= 5) {
			_rxBits |= 0x800;
		}
		_rxPllRamp -= RH_ASK_RX_RAMP_LEN;
		_rxIntegrator = 0;
		
		
		if (_rxActive) {
			std::cout << "rxactive " << _bits;
			_bits++;
			if (_bits >= 12) {
				
				uint8_t this_byte = (sym2nibble(_rxBits & 0x3f)) << 4 | sym2nibble(_rxBits >> 6);
				if (this_byte == 0) {
					std::cout << "messageLen found";
					 _messageLen = this_byte;
		   			 if (_messageLen < 7 || _messageLen > RH_ASK_MAX_PAYLOAD_LEN)
		   			 {
					// Stupid message length, drop the whole thing
					std::cout << "bad message; dropping \n";
					_rxActive = false;
					return;
		   			 }
				}
				_message[_rxBufLen++] = this_byte;
				
				_byte ++;
				_bits = 0;

				if (_rxBufLen >= _messageLen) {
					_rxActive = false;
				//	gpioSetWatchdog(_rxPin, 0);
					crc = calcCrc(_message);
					if (validMessage(_message, _rxBufLen)) {
						// TODO append message to messages
						std::cout << "message added to list \n";
						_messages.push_back(_message);
					} else {
						// TODO do Something
					}
				}
			}
		} else {
			//std::cout << "rxBits " << std::hex << _rxBits;
			if (_rxBits == RH_ASK_START_SYMBOL) {
				std::cout << "start symbol received";
				_rxActive = true;
				_bits = 0;
				_rxBufLen = 0;
				//gpioSetWatchdog(_rxPin,_timeout);
			}
		}

	}
}

void rhpi_rx::insert(uint8_t bits, uint8_t level) {
	//_rxBits == token;
	uint16_t i;
	uint16_t crc;
//	std::cout << " inserting \n";
	for (i=0; i<bits; i++) {
		_rxBits >>= 1;
		
		if (level == 0) {
			_rxBits |= 0x800;
		}
	
		if (_rxActive) {
			std::cout << "rxactive " << _bits;
			_bits++;
			if (_bits >= 12) {
				
				uint8_t this_byte = (sym2nibble(_rxBits & 0x3f)) << 4 | sym2nibble(_rxBits >> 6);
				if (this_byte == 0) {
					std::cout << "messageLen found";
					 _messageLen = this_byte;
		   			 if (_messageLen < 7 || _messageLen > RH_ASK_MAX_PAYLOAD_LEN)
		   			 {
					// Stupid message length, drop the whole thing
					std::cout << "bad message; dropping \n";
					_rxActive = false;
					return;
		   			 }
				}
				_message[_rxBufLen++] = this_byte;
				
				_byte ++;
				_bits = 0;

				if (_rxBufLen >= _messageLen) {
					_rxActive = false;
				//	gpioSetWatchdog(_rxPin, 0);
					crc = calcCrc(_message);
					if (validMessage(_message, _rxBufLen)) {
						// TODO append message to messages
						std::cout << "message added to list \n";
						_messages.push_back(_message);
					} else {
						std::cout << "invalid message \n";
						// TODO do Something
					}
				}
			}
		} else {
		//	std::cout << "rxBits " << std::hex << _rxBits << "\n";
			if (_rxBits == RH_ASK_START_SYMBOL) {
				std::cout << "start symbol received";
				_rxActive = true;
				_bits = 0;
				_rxBufLen = 0;
				//gpioSetWatchdog(_rxPin,_timeout);
			}
		}
	}
	
}

void rhpi_rx::cb(void) {
	uint8_t level;
	uint32_t tick;
	uint16_t edge;
	uint16_t bitlen;
	uint8_t bits;
	tick = micros();
	if (_lastTick != 0) {
	
		level = digitalRead(_rxPin);
		if (level == PI_TIMEOUT) {
	
			// Switch watchdog off
		//	gpioSetWatchdog(_rxPin, 0);

			// Insert bad message
			insert(4, not _lastLevel);
			_good = 0;
			_rxActive = false;
		} else {
	
			edge = rhpi_ask::tickDiff(_lastTick, tick);
		//	std::cout << "edge" << edge << "\n";
			if (edge < _minMics) {
			//	std::cout << " short edge" << edge << "\n";
				_good = 0;
				_rxActive = false;
			} else if (edge > _maxMics) {
			//	std::cout << " long edge" << edge << "\n";
			//	std::cout << " max " << _maxMics << "\n";
				if (_rxActive) {
					insert(4,level);
				}
				_good = 0;
				_rxActive = false;
			} else {
				_good++;
				if (_good > 8) {
					bitlen = (100 * edge) / _mics;
                 	if  (bitlen < 140) {
						bits = 1;
					} else if (bitlen < 240) {
						bits = 2;
					} else /*if (bitlen < 340) */{
						bits = 3;
					} //else {
					//	std::cout << "too many bits";
					//	bits = 4;
					//}
                  	insert(bits, level);
				}
			}
		}
	} 
	
	
	_lastTick = tick;
	_lastLevel = level;
	
		
}

uint16_t rhpi_rx::calcCrc(uint8_t *message)
{
	 uint16_t crc = 0xFFFF;
	 int i;
	 for (i=0; i<=_messageLen; i++) {
		 crc = crc_ccitt_update(crc, message[i]);
	 }
	 return crc;
}
uint8_t* rhpi_rx::nextMessage() {
	// get Next Message from messages
	uint8_t *msg = _messages.front();
	_messages.pop_front();
	return msg;
}

bool rhpi_rx::ready() {
	// return true if length of messages queue is > 0
	if (!_messages.empty()) {
		return true;
	}
	return false;
}

bool rhpi_rx::cancelRx() {
	return false;
}

void rhpi_rx::callBack(void) {
	rhpi_rx::getInstance().cb();
	
		
}
void rhpi_rx::callBackExplicit(int gpio, int level, uint32_t tick)
{
   /*
      Need a static callback to link with C.
   */
printf("gpio %d became %d at %d\n", gpio, level, tick);
std::cout << "callBack received \n";
  
}
void rhpi_rx::callBackExplicitEx(int gpio, int level, uint32_t tick, void *user) {
	std::cout << "callBack received \n";
	 rhpi_rx *mySelf = (rhpi_rx *) user;

	mySelf->callBackExplicit(gpio, level, tick); /* Call the instance callback. */
	printf("gpio %d became %d at %d\n", gpio, level, tick);
}

void example() {
	rhpi_tx tx = rhpi_tx(2000,7);
	// rhpi_rx::getInstance().init(2000,2); pin 2 is for the wiringPi library
//	rhpi_rx rx = rhpi_rx(2000,27);
	
	uint8_t buflen = RH_ASK_MAX_MESSAGE_LEN;
	int i;
	const char *msg = "hello";
	// wiringPiISR(2, INT_EDGE_BOTH, &callBack);
	for (;;) {
		tx.send((uint8_t *)msg, strlen(msg));
//		if (rx.ready()) {
//			uint8_t* buf = rx.nextMessage();
//			for (i = 0; i < buflen; i++)
//			{
//			  std::cout << (char)buf[i];  // the received data is stored in buffer
//			}
//			std::cout << "\n";
//		}
	}
}

User avatar
joan
Posts: 15660
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK

Re: LightwaveRF 433MHz support

Fri Jan 30, 2015 7:53 pm

gregkontos wrote: ...
Hi Joan, I tried porting your vw.py example to c++. I had some timing problems receiving the messages from the Arduino. I found that it still consumed a great deal of system resources while running. Although the excess use of system resources may have been due to faulty code... it didn't work, after all :)
...
Thanks for that. Much appreciated. I'll have a play with it to see if it I can get it to work.

I'll probably need rhpi_ask.h as well.

gregkontos
Posts: 3
Joined: Fri Jan 30, 2015 5:51 pm

Re: LightwaveRF 433MHz support

Fri Jan 30, 2015 9:32 pm

No sweat... here's rhpi_ask.h. I'm sure you'll have better luck with it than I did. I'll probably post the stuff that I was able to get working and some notes in a different thread.

Out of curiosity, is pigpio using the system clock to poll at regular intervals? Or it using a timed interrupt?

Code: Select all

#define RH_ASK_MAX_PAYLOAD_LEN 77
#define RH_ASK_HEADER_LEN 4
#ifndef RH_ASK_MAX_MESSAGE_LEN
 #define RH_ASK_MAX_MESSAGE_LEN (RH_ASK_MAX_PAYLOAD_LEN - RH_ASK_HEADER_LEN - 3)
#endif
/// The size of the receiver ramp. Ramp wraps modulo this number
#define RH_ASK_RX_RAMP_LEN 160
#define RH_ASK_RX_SAMPLES_PER_BIT 8
// Ramp adjustment parameters
// Standard is if a transition occurs before RH_ASK_RAMP_TRANSITION (80) in the ramp,
// the ramp is retarded by adding RH_ASK_RAMP_INC_RETARD (11)
// else by adding RH_ASK_RAMP_INC_ADVANCE (29)
// If there is no transition it is adjusted by RH_ASK_RAMP_INC (20)
/// Internal ramp adjustment parameter
#define RH_ASK_RAMP_INC (RH_ASK_RX_RAMP_LEN/RH_ASK_RX_SAMPLES_PER_BIT)
/// Internal ramp adjustment parameter
#define RH_ASK_RAMP_TRANSITION RH_ASK_RX_RAMP_LEN/2
/// Internal ramp adjustment parameter
#define RH_ASK_RAMP_ADJUST 9
/// Internal ramp adjustment parameter
#define RH_ASK_RAMP_INC_RETARD (RH_ASK_RAMP_INC-RH_ASK_RAMP_ADJUST)
/// Internal ramp adjustment parameter
#define RH_ASK_RAMP_INC_ADVANCE (RH_ASK_RAMP_INC+RH_ASK_RAMP_ADJUST)

#define RH_ASK_PREAMBLE_LEN 8
#define RH_BROADCAST_ADDRESS 0xff
#define lo8(x) ((x)&0xff)
#define hi8(x) ((x)>>8)
// This is the value of the start symbol after 6-bit conversion and nybble swapping
#define RH_ASK_START_SYMBOL 0xb38
#include <deque>
#include <stdint.h>

void callBack(void);
void callBackExplicit(int gpio, int level, uint32_t tick);

class rhpi_ask {

public:
rhpi_ask();
uint8_t sym2nibble(uint8_t symbol);
uint16_t crc_ccitt_update(uint16_t crc, uint8_t data);

void writeTx(uint8_t mode);
void writePtt(uint8_t mode);
int tickDiff(uint32_t startTick, uint32_t endTick);

protected: 
 char _thisAddress;
 char _txHeaderTo;
 char _txHeaderFrom;
 uint8_t _txHeaderId;
 uint8_t _txHeaderFlags;
 uint8_t _speed;
 uint16_t _mics;
};

class rhpi_tx : public rhpi_ask {

 uint8_t _txPin;
 uint8_t _txIndex;
 uint16_t _txBit;
 unsigned int _waveId;
 uint16_t _txGood;
 /// Whether the transport is in promiscuous mode


public:
	rhpi_tx(uint16_t speed, uint8_t txPin);
	bool send(const uint8_t* data, uint8_t len);
	void transmit();
/*	I wanted to implement this, but found some problem with a cross-platform support for yield() or thread pausing
 * void waitPacketSent(); */
};

class rhpi_rx : public rhpi_ask {

public:
uint8_t _rxPin;
uint32_t _timeout;
uint32_t _lastTick;
bool _rxActive;
uint16_t _good;
uint8_t _bits;
uint16_t _rxBits;
uint8_t _messageLen;
uint8_t _byte;
uint8_t _rxBufLen;
uint16_t _minMics;
uint16_t _maxMics;
uint8_t _message[RH_ASK_MAX_PAYLOAD_LEN];
bool _promiscuous;
uint8_t _rxIntegrator;
uint8_t _rxPllRamp;
bool _lastLevel;

static rhpi_rx& getInstance();
void init(uint16_t speed, uint8_t rxPin);
bool validMessage(uint8_t *message, uint8_t messageLength);
void insert();
void insert(uint8_t bits, uint8_t level);
void cb();
bool ready();
bool cancelRx();
uint8_t* nextMessage();
uint16_t calcCrc(uint8_t *message);
static void callBackExplicitEx(int gpio, int level, uint32_t tick, void *user);
static void callBackExplicit(int gpio, int level, uint32_t tick);
static void callBack(void);

private:
std::deque<uint8_t*> _messages;
rhpi_rx();
rhpi_rx(const rhpi_rx&);
void operator=(const rhpi_rx&);


};

User avatar
joan
Posts: 15660
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK

Re: LightwaveRF 433MHz support

Fri Jan 30, 2015 9:35 pm

gregkontos wrote: ...
Out of curiosity, is pigpio using the system clock to poll at regular intervals? Or it using a timed interrupt?
...
Neither. It sets up a chain of DMA blocks to continually copy the gpio levels and the system microsecond clock to a cyclic buffer. The DMA hardware takes care of the timing.

btidey
Posts: 1641
Joined: Sun Feb 17, 2013 6:51 pm

Re: LightwaveRF 433MHz support

Sat Jan 31, 2015 11:24 pm

I found it fairly straightforward porting back from Python use of pigpio to C++ because the LightwaveRf libraries were originally written for Arduino using c and were just interrupt driven state machines.

I originally used the python pigpio access because that seemed the most accessible method method for Raspberry Pi users. The Tx side is fine in Python as one is just setting up a waveform and kicking it off. However, I hadn't realised the impact of the callback overhead using the Python interface for Rx reception. The problem is with typical 433MHz receivers there is a continuous background of agc based noise edges even with no real signals. These typically happen at about 7KHz. Although my RX state machine can reject these very rapidly the Python call back interface via the daemon murders the Raspberry CPU at this rate. Even with a null RX state machine the cpu was touching 100% most of the time.

The callback overhead virtually disappears when using the pigpio directly into c++ and means the libraries are very low CPU users in this environment. I realise this makes them a bit more difficult to use for many Raspberry Pi users and so I'm trying out wrapping them in a socket interface so they can be straightforwardly be accessed from python or other environments. In this sense it becomes a bit like a specialised pigpio daemon.

A downside, I believe, of using pigpio in this way is that I think it becomes exclusive to this one application. So if one wanted to use pigpio for other types of access concurrently then I don't think this would work.

What might be nice if one could define an extensible or plug-in type environment for pigpio / daemon so that one could attach specific handlers to it in a standard way within the main pigpio environment.

I note the point about generalising this to deal with different 433MHz protocols, something I had thought about. This is pretty straightforward for TX usage as one is just selecting form a different set of waveform constructors. It is a bit trickier on the RX side if one wants to auto-detect concurrently different protocol formats but still do-able.

User avatar
joan
Posts: 15660
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK

Re: LightwaveRF 433MHz support

Sat Jan 31, 2015 11:57 pm

btidey wrote: ...
What might be nice if one could define an extensible or plug-in type environment for pigpio / daemon so that one could attach specific handlers to it in a standard way within the main pigpio environment.
...
I will be adding a couple of customisable functions for the next release.

Code: Select all

/*F*/
int gpioCustom1(unsigned arg1, unsigned arg2, char *argx, unsigned count);
/*D
This function is available for user customisation.

It returns a single integer value.

. .
 arg1: >=0
 arg2: >=0
 argx: extra (byte) arguments
count: number of extra arguments
. .

Returns >= 0 if OK, less than 0 indicates a user defined error.
D*/


/*F*/
int gpioCustom2(unsigned arg1, char *argx, unsigned count,
                char *retBuf, unsigned retMax);
/*D
This function is available for user customisation.

It differs from gpioCustom1 in that it returns an array of bytes
rather than just an integer.

The returned value is an integer indicating the number of returned bytes.
. .
  arg1: >=0
  argx: extra (byte) arguments
 count: number of extra arguments
retBuf: buffer for returned bytes
retMax: maximum number of bytes to return
. .

Returns >= 0 if OK, less than 0 indicates a user defined error.

The number of returned bytes must be retMax or less.
D*/
The idea is these are defined in a file called custom.cext which the user will replace with their own implementation. The delivered custom.cext will just be a dummy implementation.

I expect that arg1 will be used as a command discriminator.

I need to use this sort of interface to allow the same framework to be used from Python, C, and pigs.

User avatar
joan
Posts: 15660
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK

Re: LightwaveRF 433MHz support

Sun Feb 01, 2015 4:21 pm

I've done a first cut of a port of the Python Virtual Wire to C. I did a straight port rather than using the gregkontos code as I wanted a C version rather than C++ (my reasoning being it's easy to go from C to C++, not quite so easy to go the other way).

I've done a quick test on the rx side and can receive messages. I haven't tried using the tx side yet.

The input side seems to use around 10% CPU regardless of the amount of static. When pumping messages through at 13.5 per second the average CPU usage was 9%.

In a 5 minute test at 3000 bps (5 byte message payload) I received 4109 good messages and 15 bad (CRC fail) messages (13.75 messages per second).

As I say it's a first cut and I will probably tidy the code at some stage.

Code: Select all

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <pigpio.h>


/*
vw.c
2015-02-01
Public Domain

gcc -o vw vw.c -lpigpio -lpthread -lrt
*/

/*
This module provides a 313MHz/434MHz radio interface compatible
with the Virtual Wire library used on Arduinos.

Untested.
*/

#define MAX_MESSAGE_BYTES 77 // Maximum message payload.

#define MIN_BPS 50    // Minimum bits per second.
#define MAX_BPS 10000 // Maximum bits per second.

#define MAX_RX_MESSAGES 100 // How many received messagas to buffer.

uint8_t _HEADER[]={0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x38, 0x2c};

#define _CTL 3 // Number of control bytes, length + CRC.

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

/*
                                                             8    3    B
preamble 48-bit   010101 010101 010101 010101 010101 010101 0001 1100 1101
length   12-bit   S1 S2
message  12*n bit S1 S2 ... S(2n-1) S(2n)
CRC      24-bit   S1 S2 S3 S4

S are the 6-bit symbols for each nibble.
*/

int _sym2nibble(int symbol)
{
   int nibble;

   for (nibble=0; nibble<16; nibble++)
   {
      if (symbol == _SYMBOL[nibble]) return nibble;
   }
   return 0;
}

int _crc_ccitt_update(int crc, int data)
{
   data = data ^ (crc & 0xFF);

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

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


/*
class tx():
*/

typedef struct
{
   int txgpio;
   int bps;
   int txbit;
   int mics;
   int wave_id;
   gpioPulse_t pulse[1024];
   int npulses;
} vw_tx_t;

vw_tx_t *tx_init(int txgpio, int bps)
{
   /*
      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.
   */

   vw_tx_t *self;

   self = calloc(1, sizeof(vw_tx_t));

   if (!self) return NULL;

   self->txbit = (1<<txgpio);

   if      (bps < MIN_BPS) bps = MIN_BPS;
   else if (bps > MAX_BPS) bps = MAX_BPS;

   self->bps = bps;

   self->mics = 1000000 / bps;

   self->wave_id = -1;

   gpioSetMode(txgpio, PI_OUTPUT);

   return self;
}

void tx_cancel(vw_tx_t *self)
{
   /*
   Cancels the wireless transmitter, aborting any message
   in progress.
   */
   if (self)
   {
      if (self->wave_id >= 0)
      {
         gpioWaveTxStop();
         gpioWaveDelete(self->wave_id);
         gpioWaveAddNew();

         self->wave_id = -1;
      }
      self->npulses = 0;
   }
}

void tx_nibble(vw_tx_t *self, int nibble)
{
   int i;

   for (i=0; i<6; i++)
   {
      if (nibble & (1<<i))
      {
         self->pulse[self->npulses].gpioOn = self->txbit;
         self->pulse[self->npulses].gpioOff = 0;
      }
      else
      {
         self->pulse[self->npulses].gpioOn = 0;
         self->pulse[self->npulses].gpioOff = self->txbit;
      }
      self->pulse[self->npulses++].usDelay = self->mics;
   }
}

int tx_byte(vw_tx_t *self, int crc, uint8_t byte)
{
   tx_nibble(self, _SYMBOL[byte>>4]);
   tx_nibble(self, _SYMBOL[byte&0x0F]);

   return _crc_ccitt_update(crc, byte);
}

int tx_put(vw_tx_t *self, uint8_t *data, int count)
{
   /*
   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.
   */

   int i, crc;

   if (count > MAX_MESSAGE_BYTES) return 0;

   tx_cancel(self);

   self->npulses = 0;

   gpioWaveAddNew();

   for (i=0; i<sizeof(_HEADER); i++) tx_nibble(self, _HEADER[i]);

   crc = tx_byte(self, 0xFFFF, count+_CTL);

   for (i=0; i<count; i++) crc = tx_byte(self, crc, data[i]);

   crc = ~crc;

   tx_byte(self, 0, crc&0xFF);
   tx_byte(self, 0, crc>>8);

   gpioWaveAddGeneric(self->npulses, self->pulse);

   self->wave_id = gpioWaveCreate();

   if (self->wave_id >= 0)
   {
      gpioWaveTxSend(self->wave_id, PI_WAVE_MODE_ONE_SHOT);
      return 1;
   }
   else return 0;
}

int tx_ready(void)
{
   /*
   Returns True if a new message may be transmitted.
   */
   return !gpioWaveTxBusy();
}

/*
class rx():
*/

typedef struct
{
   int inited;
   int rxgpio;
   int bps;
   int bad_CRC;
   int mics;
   int min_mics;
   int max_mics;
   int timeout; 
   uint32_t last_tick;
   int last_level;
   int good;
   int token;
   int in_message;
   int msgLen;
   int bitCnt;
   int byteCnt;
   int msgCnt;
   int msgReadPos;
   int msgWritePos;
   char *msgBuf[MAX_RX_MESSAGES];
   char message[MAX_MESSAGE_BYTES+_CTL];
} vw_rx_t;

void _cb(int gpio, int level, uint32_t tick, vw_rx_t *self);

vw_rx_t *rx_init(int rxgpio, int bps)
{
   /*
   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.
   */

   vw_rx_t *self;

   float slack;
   int slack_mics;

   self = calloc(1, sizeof(vw_rx_t));

   if (!self) return NULL;

   self->rxgpio = rxgpio;

   self->bad_CRC = 0;

   if      (bps < MIN_BPS) bps = MIN_BPS;
   else if (bps > MAX_BPS) bps = MAX_BPS;

   self->bps = bps;

   slack = 0.20;
   self->mics = 1000000 / bps;
   slack_mics = 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->inited = 0;
   self->last_tick = 0;
   self->good = 0;
   self->bitCnt = 0;
   self->token = 0;
   self->in_message = 0;
   self->msgLen = 0;
   self->byteCnt = 0;
   self->msgCnt = 0;
   self->msgWritePos = 0;
   self->msgReadPos = 0;

   gpioSetMode(rxgpio, PI_INPUT);

   gpioSetAlertFuncEx(rxgpio, (gpioAlertFuncEx_t)_cb, self);

   return self;
}

int rx_calc_crc(vw_rx_t *self)
{
   int i, crc;

   crc = 0xFFFF;

   for (i=0; i<self->msgLen; i++)
      crc = _crc_ccitt_update(crc, self->message[i]);
   return crc;
}

void rx_insert(vw_rx_t *self, int bits, int level)
{
   int i, byte, crc;
   char *mptr;

   for (i=0; i<bits; i++)
   {
      self->token >>= 1;

      if (level == 0) self->token |= 0x800;

      if (self->in_message)
      {
         self->bitCnt++;

         if (self->bitCnt >= 12) // Complete token.
         {
            byte = (
               _sym2nibble(self->token & 0x3f) << 4 |
               _sym2nibble(self->token >> 6));

            if (self->byteCnt == 0)
            {
               self->msgLen = byte;

               if (byte > (MAX_MESSAGE_BYTES+_CTL))
               {
                  self->in_message = 0; // Abort message.
                  return;
               }
            }

            self->message[self->byteCnt] = byte;

            self->byteCnt++;
            self->bitCnt = 0;

            if (self->byteCnt >= self->msgLen)
            {
               self->in_message = 0;
               gpioSetWatchdog(self->rxgpio, 0);

               crc = rx_calc_crc(self);

               if (crc == 0xF0B8) // Valid CRC.
               {
                  /* discard new message if buffer full */
                  if (self->msgCnt < MAX_RX_MESSAGES)
                  {
                     /* allocate space for message */
                     mptr = malloc(self->msgLen-2);
                     if (mptr)
                     {
                        memcpy(mptr, self->message, self->msgLen-2);
                        self->msgBuf[self->msgWritePos] = mptr;
                        self->msgWritePos++;
                        if (self->msgWritePos >= MAX_RX_MESSAGES)
                           self->msgWritePos = 0;
                        self->msgCnt++;
                     }
                  }
               }
               else self->bad_CRC ++;
            }
         }
      }
      else
      {
         if (self->token == 0xB38) // Start message token.
         {
            self->in_message = 1;
            gpioSetWatchdog(self->rxgpio, self->timeout);
            self->bitCnt = 0;
            self->byteCnt = 0;
         }
      }
   }
}

void _cb(int gpio, int level, uint32_t tick, vw_rx_t *self)
{
   int edge, bitlen, bits;

   if (self->inited)
   {
      if (level == PI_TIMEOUT)
      {
         gpioSetWatchdog(self->rxgpio, 0); // Switch watchdog off.

         if (self->in_message) rx_insert(self, 4, !self->last_level);

         self->good = 0;
         self->in_message = 0;
      }
      else
      {

         edge = tick - self->last_tick;

         if (edge < self->min_mics)
         {
            self->good = 0;
            self->in_message = 0;
         }
         else if (edge > self->max_mics)
         {
            if (self->in_message) rx_insert(self, 4, level);

            self->good = 0;
            self->in_message = 0;
         }
         else
         {
            self->good += 1;

            if (self->good > 8)
            {
              bitlen = (100 * edge) / self->mics;

               if      (bitlen < 140) bits = 1;
               else if (bitlen < 240) bits = 2;
               else if (bitlen < 340) bits = 3;
               else                   bits = 4;

               rx_insert(self, bits, level);
            }
         }
      }
   }

   self->last_tick = tick;
   self->last_level = level;
   self->inited = 1;
}


int rx_get(vw_rx_t *self, uint8_t *buf)
{
   /*
   Returns the next unread message.

   The next message is copied to buf and its length is
   returned as the function value (0 if no message is
   available.

   buf should be large enough to hold the longest message
   of MAX_MESSAGE_BYTES.
   */

   int msgLen;

   if (self->msgCnt)
   {
      msgLen = self->msgBuf[self->msgReadPos][0];
      if (buf) memcpy(buf, self->msgBuf[self->msgReadPos]+1, msgLen);

      /* free memory used for message */
      free(self->msgBuf[self->msgReadPos]);
      self->msgBuf[self->msgReadPos] = NULL;

      self->msgCnt--;
      self->msgReadPos++;
      if (self->msgReadPos >= MAX_RX_MESSAGES) self->msgReadPos = 0;
      return msgLen;
   }
   else return 0;
}

int rx_ready(vw_rx_t *self)
{
   /*
   Returns count of messages available to be read.
   */
   return self->msgCnt;
}

void rx_pause(vw_rx_t *self)
{
   /*
   Pauses the wireless receiver.
   */
   gpioSetAlertFuncEx(self->rxgpio, NULL, self);
   gpioSetWatchdog(self->rxgpio, 0);
}

void rx_resume(vw_rx_t *self)
{
   /*
   Resumes the wireless receiver.
   */
   self->in_message = 0;
   gpioSetAlertFuncEx(self->rxgpio, (gpioAlertFuncEx_t)_cb, self);
}

void rx_cancel(vw_rx_t *self)
{
   /*
   Cancels the wireless receiver.
   */
   gpioSetAlertFuncEx(self->rxgpio, NULL, self);
   gpioSetWatchdog(self->rxgpio, 0);
}

#define RX 11
#define TX 25

#define BPS 3000

int main(int argc, char *argv[])
{
   double start;

   vw_rx_t *rx;
   vw_tx_t *tx;

   char buf[128];

   int len;

   if (gpioInitialise() < 0) return 1;

   rx = rx_init(RX, BPS); // Specify rx gpio, and baud.
   tx = tx_init(TX, BPS); // Specify tx gpio, and baud.

   start = time_time();

   while ((time_time()-start) < 360.0)
   {
      while (!rx_ready(rx)) time_sleep(0.1);

      while (rx_ready(rx))
      {
         len = rx_get(rx, buf);
         printf("%s\n", buf);
      }
   }

   rx_cancel(rx);
   tx_cancel(tx);

   gpioTerminate();
}

Massi
Posts: 1691
Joined: Fri May 02, 2014 1:52 pm
Location: Italy

Re: LightwaveRF 433MHz support

Tue Feb 03, 2015 6:40 am

joan wrote:The input side seems to use around 10% CPU regardless of the amount of static
Impressive. Really. need to try a OOK decoder with pigpio and C..
Maybe my brand new Raspberry 2 will help :D

User avatar
joan
Posts: 15660
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK

Re: LightwaveRF 433MHz support

Tue Feb 03, 2015 8:39 am

pattagghiu wrote:
joan wrote:The input side seems to use around 10% CPU regardless of the amount of static
Impressive. Really. need to try a OOK decoder with pigpio and C..
Maybe my brand new Raspberry 2 will help :D
What is impressive is that once I removed compilation errors the port worked first time. I've tried between Pi's and between Pi's and Arduinos on the RX and TX side and it seems OK. I will tidy it up and add a few comments at some stage.

I'm assuming pigpio will work with the Pi 2. pigpio will need software changes. To the best of my current knowledge the needed changes are minor.

Massi
Posts: 1691
Joined: Fri May 02, 2014 1:52 pm
Location: Italy

Re: LightwaveRF 433MHz support

Tue Feb 03, 2015 11:27 am

I mean, i can understand overhead and compiled vs interpreted language, but we are talking about a factor 10 difference in cpu usage..
Your receiver (and my receiver) in python were killing the pi totally.
So, i have to learn (or better, remember :)) a bit of C to start my OOK receiver :)

But have i understood correctly that with a C pigpio program i'll not be able to (concurrently) use Pigpiod from python?

User avatar
joan
Posts: 15660
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK

Re: LightwaveRF 433MHz support

Tue Feb 03, 2015 12:08 pm

pattagghiu wrote:I mean, i can understand overhead and compiled vs interpreted language, but we are talking about a factor 10 difference in cpu usage.
...
But have i understood correctly that with a C pigpio program i'll not be able to (concurrently) use Pigpiod from python?
Depending on the code it's not uncommon to get a factor of a thousand times better for compiled rather than interpreted implementations.

The only function of pigpiod is to act as a dummy C program to start the pigpio library. Once the pigpio library is running any process can gain access via the socket and pipe interfaces. So rather than use the daemon you could run the C virtual wire implementation.

The latest version of pigpio (27) adds support for a couple of customisable functions (C gpioCustom1, gpioCustom2, Python custom_1, custom_2, pigs cf1, cf2, pigpiod_if custom_1, custom_2). These are intended to make it easier to add on customised functionality.

It's not documented as I only released version 27 to (hopefully) cater for the Pi 2 model.

btidey
Posts: 1641
Joined: Sun Feb 17, 2013 6:51 pm

Re: LightwaveRF 433MHz support

Fri Feb 06, 2015 10:51 pm

Joan,

Thanks for the addition of the custom extensions. They were just what I wanted.

I have now recast my c++ libraries as c routines in custom.cext. This gives the easy access from languages like python via pigpio.py and pigpiod together with the low overhead of direct access to the core pigpio.

I used just the custom_2 function and used the first arg1 effectively as a unique 'class' selector making it simpler to add further custom extensions in. The sub functions and associated parameters were then carried in the argx array.

I have updated the code and documentation for this on https://github.com/roberttidey/LightwaveRF This includes a python example using this method.

User avatar
joan
Posts: 15660
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK

Re: LightwaveRF 433MHz support

Fri Feb 06, 2015 10:59 pm

btidey wrote:Joan,

Thanks for the addition of the custom extensions. They were just what I wanted.

I have now recast my c++ libraries as c routines in custom.cext. This gives the easy access from languages like python via pigpio.py and pigpiod together with the low overhead of direct access to the core pigpio.

I used just the custom_2 function and used the first arg1 effectively as a unique 'class' selector making it simpler to add further custom extensions in. The sub functions and associated parameters were then carried in the argx array.

I have updated the code and documentation for this on https://github.com/roberttidey/LightwaveRF This includes a python example using this method.
franz has used a slightly different approach which may be of interest.

http://www.raspberrypi.org/forums/viewt ... 35#p686935

User avatar
joan
Posts: 15660
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK

Re: LightwaveRF 433MHz support

Fri Feb 06, 2015 11:16 pm

btidey wrote: ...
I have updated the code and documentation for this on https://github.com/roberttidey/LightwaveRF This includes a python example using this method.
I've just hag a quick look at the github content and am very impressed. I was a bit worried that nobody would get the point of these custom functions. It's good to see such a clear write-up.

btidey
Posts: 1641
Joined: Sun Feb 17, 2013 6:51 pm

Re: LightwaveRF 433MHz support

Fri Feb 06, 2015 11:20 pm

Yes. That is also an interesting approach building a py compatible module in c directly. Good to have two methods available.

I think the module will take over pigpio exclusively though and so is a bit trickier to share across applications.

Return to “Automation, sensing and robotics”