diff --git a/.gitignore b/.gitignore index 0d20b64..08dc9de 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ *.pyc +/dist/ +/build/ +/*.egg-info diff --git a/README.md b/README.md index 18dc959..a0e995c 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,39 @@ -# Python files for RaspyRFM +# Python module & examples for RaspyRFM +![RaspyRFM](img/raspberry-rfm69-ii_6_z4.jpg?raw=true "RasyRFM") + +#Installation +```sh +sudo su +apt-get install git-core python-dev +apt-get install python-pip + +cd #folder in which setup.py is located +pip install -e . +``` + +#Using python module and set parameters +```python +import RasyRFM +rfm = RaspyRFM.RaspyRFM() +rfm.SetParams( + Freq = 868.300, #MHz center frequency + ModulationType = rfm69.FSK, #modulation + Datarate = 9.992, #kbit/s baudrate + Deviation = 19.042, #kHz frequency deviation + SyncPattern = [0xc6, 0x26, 0xc6, 0x26], #syncword + Bandwidth = 100, #kHz bandwidth + RssiThresh = -105, #dBm RSSI threshold + TXPower = 13, #dBm + Preamble = 2, #bytes +) +``` ## connair.py -emulate a gateway for controlling RC socket. Compatible to "Brennenstuhl Brematic", Intertechno "ITGW-433", "ConnAir" +emulate a gateway for controlling RC sockets via the app power-switch. Compatible to "Brennenstuhl Brematic", Intertechno "ITGW-433", "ConnAir" see https://power-switch.eu/ ## emoncms.py -receive lacrosse-sensors and post them to the open energy monitor, see https://openenergymonitor.org/ +receive lacrosse-sensors with the RaspyRFM and post them to the open energy monitor, see https://openenergymonitor.org/ ## fs20tx.py controlling FS20 RX sockets @@ -16,19 +44,20 @@ sudo ./fs20tx
## intertechno.py controlling remote control sockets ```sh -intertechno on|off -intertechno <12 symbols tristate code> -intertechno <26 bit address> <1 goup bit> <4 bit unit> on|off -intertechno <32 bit code> +rcpulse on|off #control old intertechno sockets +rcpulse <12 symbols tristate code> #control old intertechno sockets +rcpulse <26 bit address 0|1> <1 goup bit 0|1> <4 bit unit 0|1> on|off #control intertechno self learning +rcpulse <32 bit code 0|1> #control intertechno and compatible (HAMA, REV) +rcpulse <5 DIP 0|1> on|off #control Brennenstuhl RC1000 usage example: -intertechno A 1 1 on -intertechno 0000FFFF0FFF -interttechno 11110000111100001111000010 0 1110 on -interttechno 11110000111100001111000010010000 +./rcpulse A 1 1 on +./rcpulse 0000FFFF0FFF +./rcpulse 11110000111100001111000010 0 1110 on +./rcpulse 11110000111100001111000010010000 ``` ## lacrosse.py -receiving temperature sensors +receiving lacrosse temperature sensors IT29-T, IT35-T, ... ```sh sudo ./lacrosse.py RFM69 found on CS 1 @@ -44,19 +73,5 @@ La crosse {'batlo': False, 'AFC': 308, 'init': False, 'T': (19.5, 'C'), 'RSSI': La crosse {'batlo': False, 'AFC': 376, 'init': False, 'T': (19.7, 'C'), 'RSSI': -103, 'RH': (57, '%'), 'ID': '24'} ``` -## rcs1000.py -controll Brennenstuhl RC1000 RC sockets -```sh -sudo ./rcs1000.py <5xDIPSWITCH> on|off -``` - -## rfm69.py -interfacing the RFM69 - -# Installation - * [Install GPIO](http://sourceforge.net/projects/raspberry-gpio-python/) -Version has to be >= 0.5.4 - * [Install SPI for Python](http://www.100randomtasks.com/simple-spi-on-raspberry-pi) - ## Product [Module RaspbyRFM Seegel Systeme](http://www.seegel-systeme.de/produkt/raspyrfm-ii/) diff --git a/RaspyRFM/__init__.py b/RaspyRFM/__init__.py new file mode 100644 index 0000000..0cfa381 --- /dev/null +++ b/RaspyRFM/__init__.py @@ -0,0 +1 @@ +from .raspyrfm import * diff --git a/RaspyRFM/raspyrfm.py b/RaspyRFM/raspyrfm.py new file mode 100644 index 0000000..4e8160a --- /dev/null +++ b/RaspyRFM/raspyrfm.py @@ -0,0 +1,558 @@ +import RPi.GPIO as GPIO +import spidev +import threading +import time + +FXOSC = 32E6 +FSTEP = FXOSC / (1<<19) + +#------ Raspberry RFM Module connection ----- +# RaspyRFM single module +# Connect to pins 17-26 on raspberry pi +#-------------------------------------------------# +# Raspi | Raspi | Raspi | RFM69 | RFM12 | PCB con # +# Name | GPIO | Pin | Name | Name | Pin # +#-------------------------------------------------# +# 3V3 | - | 17 | 3.3V | VDD | 1 # +# - | 24 | 18 | DIO1 | FFIT | 2 # only when PCB jumper closed +# MOSI | 10 | 19 | MOSI | SDI | 3 # +# GND | - | 20 | GND | GND | 4 # +# MISO | 9 | 21 | MISO | SDO | 5 # +# - | 25 | 22 | DIO0 | nIRQ | 6 # +# SCKL | 11 | 23 | SCK | SCK | 7 # +# CE0 | 8 | 24 | NSS | nSEL | 8 # +# CE1 | 7 | 26 | DIO2 | nFFS | 10 # only when PCB jumper closed +#-------------------------------------------------# + +# RaspyRFM twin module with 10-pin connector +# Connect to pins 17-26 on raspberry pi +#-------------------------------------------------# +# Raspi | Raspi | Raspi | RFM69 | RFM12 | PCB con # +# Name | GPIO | Pin | Name | Name | Pin # +#-------------------------------------------------# +# 3V3 | - | 17 | 3.3V | VDD | 1 # +# - | 24 | 18 | DIO0_2| FFIT | 2 # +# MOSI | 10 | 19 | MOSI | SDI | 3 # +# GND | - | 20 | GND | GND | 4 # +# MISO | 9 | 21 | MISO | SDO | 5 # +# - | 25 | 22 | DIO0_1| nIRQ | 6 # +# SCKL | 11 | 23 | SCK | SCK | 7 # +# CE0 | 8 | 24 | NSS1 | nSEL | 8 # +# CE1 | 7 | 26 | NSS2 | nFFS | 10 # +#-------------------------------------------------# + +# RaspyRFM twin module with 12-pin connector +# Connect to pins 15-26 on raspberry pi +#-------------------------------------------------# +# Raspi | Raspi | Raspi | RFM69 | RFM12 | PCB con # +# Name | GPIO | Pin | Name | Name | Pin # +#-------------------------------------------------# +# - | 22 | 15 | DIO2_2| | 1 # +# - | 23 | 16 | DIO2_1| | 2 # +# 3V3 | - | 17 | 3.3V | VDD | 3 # +# - | 24 | 18 | DIO0_2| FFIT | 4 # +# MOSI | 10 | 19 | MOSI | SDI | 5 # +# GND | - | 20 | GND | GND | 6 # +# MISO | 9 | 21 | MISO | SDO | 7 # +# - | 25 | 22 | DIO0_1| nIRQ | 8 # +# SCKL | 11 | 23 | SCK | SCK | 9 # +# CE0 | 8 | 24 | NSS1 | nSEL | 10 # +# CE1 | 7 | 26 | NSS2 | nFFS | 12 # +#-------------------------------------------------# + +#RFM69 registers +#common registers +RegFifo = 0x00 +RegOpMode = 0x01 +RegDataModul = 0x02 +RegBitrateMsb = 0x03 +RegBitrateLsb = 0x04 +RegFdevMsb = 0x05 +RegFdevLsb = 0x06 +RegFrfMsb = 0x07 +RegFrfMid = 0x08 +RegFrfLsb = 0x09 +RegOsc1 = 0x0A +RegAfcCtrl = 0x0B +RegListen1 = 0x0D +RegListen2 = 0x0E +RegListen3 = 0x0F +RegVersion = 0x10 +#TX registers +RegPaLevel = 0x11 +RegPaRamp = 0x12 +RegOcp = 0x13 +#RX registers +RegLna = 0x18 +RegRxBw = 0x19 +RegAfcBw = 0x1A +RegOokPeak = 0x1B +RegOokAvg = 0x1C +RegOokFix = 0x1D +RegAfcFei = 0x1E +RegAfcMsb = 0x1F +RegAfcLsb = 0x20 +RegFeiMsb = 0x21 +RegFeiLsb = 0x22 +RegRssiConfig = 0x23 +RegRssiValue = 0x24 +#IRQ & pin mapping registers +RegDioMapping1 = 0x25 +RegDioMapping2 = 0x26 +RegIrqFlags1 = 0x27 +RegIrqFlags2 = 0x28 +RegRssiThresh = 0x29 +RegRxTimeout1 = 0x2A +RegRxTimeout2 = 0x2B +#packet engine registers +RegPreambleMsb = 0x2C +RegPreambleLsb = 0x2D +RegSyncConfig = 0x2E +RegSyncValue1 = 0x2F +RegPacketConfig1 = 0x37 +RegPayloadLength = 0x38 +RegNodeAdrs = 0x39 +RegBroadcastAdrs = 0x3A +RegAutoModes = 0x3B +RegFifoThresh = 0x3C +RegPacketConfig2 = 0x3D +RegTemp1 = 0x4E +RegTemp2 = 0x4F +RegTestLna = 0x58 +RegTestDagc = 0x6F +RegTestAfc = 0x71 + +InterPacketRxDelay = 4 #Bitposition +RestartRx = 2 +AutoRxRestartOn = 1 +AesOn = 0 + +#Modulation type +OOK = 1 +FSK = 0 + +#DcFree +DcFree_None = 0 +DcFree_Manchester = 1 +DcFree_Whitening = 2 + +#RFM69 modes +MODE_SLEEP = 0 +MODE_STDBY = 1 +MODE_FS = 2 +MODE_TX = 3 +MODE_RX = 4 + +#DIO packet mode +DIO0_PM_CRC = 0 +DIO0_PM_PAYLOAD = 1 +DIO0_PM_SYNC = 2 +DIO0_PM_RSSI = 3 +DIO0_PM_SENT = 0 +DIO0_PM_TXDONE = 1 +DIO0_PM_PLLLOCK = 3 + +class RaspyRFM(threading.Thread): + @staticmethod + def Test(cs): + spi = spidev.SpiDev() + spi.open(0, cs) + spi.max_speed_hz = 5000 + #Testing presence of module + err = False + for i in range(0, 8): + spi.xfer2([(RegSyncValue1 + i) | 0x80, 0x55]) + test = spi.xfer2([(RegSyncValue1 + i), 0x00])[1] + if test != 0x55: + err = True + break + temp = spi.xfer2([(RegSyncValue1 + i) | 0x80, 0xAA]) + test = spi.xfer2([(RegSyncValue1 + i), 0x00])[1] + if test != 0xAA: + err = True + break + spi.close() + return not err + + + def __init__(self, cs = 0, gpio_int = 25): + if not self.Test(cs): + print("ERROR! RFM69 not found") + return + + self.__event = threading.Event() + self.__spi = spidev.SpiDev() + self.__spi.open(0, cs) + self.__spi.max_speed_hz=int(5E6) + self.__gpio_int = gpio_int + self.__mutex = threading.Lock() + self.__syncsize = 4 + self.__fifothresh = 32 + + print("RFM69 found on CS " + str(cs)) + GPIO.setmode(GPIO.BCM) + GPIO.setup(gpio_int, GPIO.IN) + GPIO.add_event_detect(gpio_int, GPIO.RISING, callback=self.__RfmIrq) + + self.__SetMode(MODE_STDBY) + config = {} + + #SET DEFAULTS + config[RegOpMode] = 0x04 + config[RegDataModul] = 0x00 + config[RegBitrateMsb] = 0x1A + config[RegBitrateMsb + 1] = 0x0B + config[RegFdevMsb] = 0x00 + config[RegFdevMsb + 1] = 0x52 + config[RegFrfMsb] = 0xE4 + config[RegFrfMsb + 1] = 0xC0 + config[RegFrfMsb + 2] = 0x00 + config[RegOsc1] = 0x41 + config[RegAfcCtrl] = 0x00 + config[0x0C] = 0x02 # reserved + config[RegListen1] = 0x92 + config[RegListen2] = 0xF5 + config[RegListen3] = 0x20 + config[RegVersion] = 0x24 + config[RegPaLevel] = 0x9F + config[RegPaRamp] = 0x09 + config[RegOcp] = 0x1A + config[0x17] = 0x9B # reserved + config[RegLna] = 0x88 + config[RegRxBw] = 0x55 + config[RegAfcBw] = 0x8B + config[RegOokPeak] = 0x40 + config[RegOokAvg] = 0x80 + config[RegOokFix] = 0x06 + config[RegAfcFei] = 0x00 + config[RegAfcMsb] = 0x00 + config[RegAfcLsb] = 0x00 + config[RegFeiMsb] = 0x00 + config[RegFeiLsb] = 0x00 + config[RegRssiConfig] = 0x02 + config[RegDioMapping1] = 0x00 + config[RegDioMapping2] = 0x05 + config[RegIrqFlags1] = 0x80 + config[RegIrqFlags2] = 0x10 + config[RegRssiThresh] = 0xE4 + config[RegRxTimeout1] = 0x00 + config[RegRxTimeout2] = 0x00 + config[RegPreambleMsb] = 0x00 + config[RegPreambleLsb] = 0x00 + config[RegSyncConfig] = 0x98 + config[RegPacketConfig1] = 0x10 + config[RegPayloadLength] = 0x40 + config[RegNodeAdrs] = 0x00 + config[RegBroadcastAdrs] = 0x00 + config[RegAutoModes] = 0 + config[RegFifoThresh] = 0x8F + config[RegPacketConfig2] = 0x02 + config[RegTemp1] = 0x01 + config[RegTemp2] = 0x00 + config[RegTestLna] = 0x1B + config[RegTestDagc] = 0x30 #low beta 0 + config[RegTestAfc] = 0x00 + + config[RegPacketConfig1] = 0x00 #Fixed length, CRC off, no adr + + for key in config: + self.__WriteReg(key, config[key]) + + self.ModeStandBy() + threading.Thread.__init__(self) + print("Init complete.") + + def run(self): + while True: + time.sleep(0.5) + + def __RfmIrq(self, ch): + self.__event.set() + + def __WriteReg(self, reg, val): + temp = self.__spi.xfer2([(reg & 0x7F) | 0x80, val & 0xFF]) + + def __WriteRegWord(self, reg, val): + self.__WriteReg(reg, (val >> 8) & 0xFF) + self.__WriteReg(reg + 1, val & 0xFF) + + def __SetReg(self, reg, mask, val): + temp = self.ReadReg(reg) & (~mask) + temp |= val & mask + self.__WriteReg(reg, temp) + + def __SetDioMapping(self, dio, mapping): + if ((dio >= 0) and (dio <=3)): + self.__SetReg(RegDioMapping1, 0xC0 >> (dio * 2), mapping << (6 - dio * 2)) + elif (dio == 5): + self.__SetReg(RegDioMapping2, 0x03 << 4, mapping << 4) + + def __SetMode(self, mode): + self.__WriteReg(RegOpMode, mode << 2) + self.__mode = mode + while ((self.ReadReg(RegIrqFlags1) & (1<<7)) == 0): + pass + + def ReadReg(self, reg): + temp = self.__spi.xfer2([reg & 0x7F, 0x00]) + return temp[1] + + def ReadFifoBurst(self, len): + temp = self.__spi.xfer2([0x00] + [0x00] * len) + return temp[1:] + + def WriteFifoBurst(self, data): + self.__spi.xfer2([0x80] + list(data)) + + def ReadRegWord(self, reg): + temp = self.__spi.xfer2([reg & 0x7F, 0x00, 0x00]) + return (temp[1] << 8) | (temp[2]) + + def ReadRssiValue(self): + return self.ReadReg(RegRssiValue) + + def ModeStandBy(self): + self.__SetMode(MODE_STDBY) + + def SetParams(self, **params): + self.__mutex.acquire() + self.__event.set() + for key in params: + value = params[key] + if key == "Freq": + fword = int(round(value * 1E6 / FSTEP)) + self.__WriteReg(RegFrfMsb, fword >> 16) + self.__WriteReg(RegFrfMid, fword >> 8) + self.__WriteReg(RegFrfLsb, fword) + + elif key == "TXPower": + pwr = int(value + 18) + self.__WriteReg(RegPaLevel, 0x80 | (pwr & 0x1F)) + + elif key == "Datarate": + rate = int(round(FXOSC / (value * 1000))) + self.__WriteRegWord(RegBitrateMsb, rate) + + elif key == "Deviation": + dev = int(round(value * 1000 / FSTEP)) + self.__WriteRegWord(RegFdevMsb, dev) + + elif key == "ModulationType": + self.__SetReg(RegDataModul, 0x18, value << 3) + + elif key == "ModulationsShaping": + self.__SetReg(RegDataModul, 0x03, value) + + elif key == "SyncPattern": + conf = 0 + self.__syncsize = len(value) + if (len(value)) > 0: + conf = ((len(value) - 1) & 0x07) << 3 + conf |= 1<<7 + else: + conf = 1<<6 + self.__WriteReg(RegSyncConfig, conf) + for i, d in enumerate(value): + self.__WriteReg(RegSyncValue1 + i, d) + + elif key == "Bandwidth": + RxBw = FXOSC / value / 1000 / 4 + e = 0 + while (RxBw > 32) and (e < 7): + e += 1 + RxBw /= 2 + RxBw = RxBw / 4 - 4 + RxBw = max(RxBw, 0) + m = int(RxBw) + self.__SetReg(RegRxBw, 0x1F, m<<3 | e) + + elif key == "AfcBandwidth": + RxBw = FXOSC / value / 1000 / 4 + e = 0 + while (RxBw > 32) and (e < 7): + e += 1 + RxBw /= 2 + RxBw = RxBw / 4 - 4 + RxBw = max(RxBw, 0) + m = int(RxBw) + self.__SetReg(RegAfcBw, 0x1F, m<<3 | e) + + elif key == "Preamble": + self.__WriteRegWord(RegPreambleMsb, value) + + elif key == "LnaGain": + self.__SetReg(RegLna, 0x07, value) + + elif key == "RssiThresh": + th = -(value * 2) + self.__WriteReg(RegRssiThresh, th) + + elif key == "Dagc": + self.__WriteReg(RegDagc, value) + + elif key == "AfcFei": + self.__WriteReg(RegAfcFei, value) + + elif key == "Callback": + self.__Callback = value + + elif key == "DcFree": + self.__SetReg(RegPacketConfig1, 3<<5, value<<5) + + elif key == "OokThreshType": + self.__SetReg(RegOokPeak, 3<<6, value<<6) + + elif key == "OokFixedThresh": + self.__WriteReg(RegOokFix, value) + + elif key == "OokPeakThreshDec": + self.__SetReg(RegOokPeak, 7<<0, value) + + else: + print("Unrecognized option >>" + key + "<<") + + self.ModeStandBy(); + self.__mutex.release() + + def __WaitInt(self): + self.__event.clear() + if GPIO.input(self.__gpio_int): + return + while not self.__event.wait(0.5): + if GPIO.input(self.__gpio_int): + break + + def WhitenHope(self, data): + lfsr = 0x3fe + for i, d in enumerate(data): + data[i] = data[i] ^ ((lfsr >> 2) & 0xFF) + #roll LFSR + for j in range(8): + if ((lfsr >> 5) ^ lfsr) & 0x10 != 0: + lfsr |= 1<<0 + lfsr <<= 1 + lfsr &= 0x3ff + + def WhitenTI(self, data): + lfsr = 0x1ff + for i, d in enumerate(data): + data[i] = data[i] ^ (lfsr & 0xFF) + for i in range(8): + if ((lfsr >> 5) ^ lfsr) & 0x01 != 0: + lfsr |= 1<<9 + lfsr >>= 1 + + def SendPacket(self, data): + self.__mutex.acquire() + self.__event.set() + self.ModeStandBy() + + #flush FIFO + status = self.ReadReg(RegIrqFlags2) + while (status & 0x40 == 0x40): + self.ReadReg(RegFifo) + status = self.ReadReg(RegIrqFlags2) + + self.__WriteReg(RegPayloadLength, 0) #unlimited length + self.__WriteReg(RegFifoThresh, 0x80 | self.__fifothresh) #start TX with 1st byte in FIFO + self.__SetDioMapping(0, DIO0_PM_SENT) #DIO0 -> PacketSent + self.__SetMode(MODE_TX) + + l = min(len(data), 64) + while True: + self.WriteFifoBurst(data[:l]) + data = data[l:] + if len(data) == 0: + break + while True: + status = self.ReadReg(RegIrqFlags2) + if (status & (1<<5)) == 0: #below fifothresh + l = min(len(data), self.__fifothresh) + break + if (status & (1<<7)) == 0: #space for at least 1 bytearray + l = 1 + break + + self.__WaitInt() + self.ModeStandBy() + self.__mutex.release() + + def ReadFifoWait(self, length): + ret = [] + while length > 0: + flags = self.ReadReg(RegIrqFlags2) + if ((flags & (1<<5)) != 0) and (length >= 32): #FIFO level? + ret += self.ReadFifoBurst(self.__fifothresh) + length -= self.__fifothresh + if (flags & (1<<6)) != 0: #FIFO not empty? + ret.append(self.ReadReg(RegFifo)) + length -= 1 + return ret + + def GetNoiseFloor(self): + self.__mutex.acquire() + #save values + rssithresh = self.ReadReg(RegRssiThresh) + ookthresh = self.ReadReg(RegOokFix) + sync = self.ReadReg(RegSyncConfig) + + self.__WriteReg(RegRssiThresh, 240) + self.__WriteReg(RegSyncConfig, 1<<6) #no sync, always fill FIFO + self.__WriteReg(RegPayloadLength, 0) #unlimited length + self.__SetMode(MODE_RX) + thresh = 40 + while True: + self.__WriteReg(RegOokFix, thresh) + for i in range(150): + b = self.ReadFifoWait() + if b != 0: + thresh += 1 + break; + if i == 149: + break; + + #restore registers + self.__WriteReg(RegRssiThresh, rssithresh) + self.__WriteReg(RegOokFix, ookthresh) + self.__WriteReg(RegSyncConfig, sync) + self.ModeStandBy() + self.__mutex.release() + return thresh + + def __StartRx(self): + self.__SetDioMapping(2, 1) #DIO2 -> DATA + self.__mutex.acquire() + while True: + self.__WriteReg(RegPayloadLength, 0) #unlimited length + self.__WriteReg(RegFifoThresh, self.__fifothresh) + if self.__syncsize > 0: + self.__SetDioMapping(0, DIO0_PM_SYNC) #DIO0 -> SyncAddress + else: + self.__SetDioMapping(0, DIO0_PM_RSSI) #DIO0 -> RSSI + self.__SetMode(MODE_RX) + self.__mutex.release() + self.__WaitInt() + self.__mutex.acquire() + if self.__mode == MODE_RX: + break; + + def StartRx(self, cb): + self.__StartRx() + cb() + self.ModeStandBy() + self.__mutex.release() + + def ReceivePacket(self, length): + self.__StartRx() + result = self.ReadFifoWait(length) + + rssi = -self.ReadReg(RegRssiValue) / 2 + afc = self.ReadReg(RegAfcMsb) << 8 + afc = afc | self.ReadReg(RegAfcLsb) + + if afc >= 0x8000: + afc = afc - 0x10000 + + self.ModeStandBy() + self.__mutex.release() + return (result, rssi, afc) diff --git a/apps/bistate24.py b/apps/bistate24.py new file mode 100755 index 0000000..a6c9e46 --- /dev/null +++ b/apps/bistate24.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python2.7 + +import re + +def Decode(pulses): + if len(pulses) != 50: + return + + sym = "" + s = 0 + for p in pulses: + if (p >= 200) and (p <= 450): + sym += 's' + s += p + elif (p >= 600) and (p <= 1350): + sym += 'l' + s += p + else: + sym += '?' + + code = "" + for i in range(24): + if sym[:2] == 'sl': + code += "0" + elif sym[:2] == "ls": + code += "1" + else: + return + sym = sym[2:] + + return ("bistate24", code, int(round(s / (24.0 * 4 + 1)))) + +def Encode(args): + code = ' '.join(args) + if re.match("^[01]{24}$", code): + data = [] + d = 0 + pos = 4 + for c in code: + d |= (0x8 if c == '0' else 0xe) << pos + if pos == 0: + data.append(d) + d = 0 + pos = 4 + else: + pos = 0 + + data += [0x80, 0x00, 0x00, 0x00] + return (data, 5, 330) + + elif re.match("^([01]{20}) ([A])$", code): + g = re.match("^([01]{20}) ([ABCD])$", code).groups() + bits = g[0] + if g[1] == 'A': + bits += '0001' + elif g[1] == 'B': + bits += '0010' + elif g[1] == 'C': + bits += '0100' + elif g[1] == 'D': + bits += '1000' + return Encode([bits]) diff --git a/apps/connair.py b/apps/connair.py new file mode 100755 index 0000000..67e70c7 --- /dev/null +++ b/apps/connair.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python2.7 + +import socket +import RaspyRFM +import sys + +UDP_IP = "0.0.0.0" +UDP_PORT = 49880 +HELLO_MESSAGE = "HCGW:VC:Seegel Systeme;MC:RaspyRFM;FW:1.00;IP:0.0.0.0;;" + +sock = socket.socket(socket.AF_INET, # Internet + socket.SOCK_DGRAM) # UDP +sock.bind((UDP_IP, UDP_PORT)) + +rfm = RaspyRFM.RaspyRFM() +rfm.SetParams( + Freq = 433.92, + TXPower = 13, + ModulationType = rfm69.OOK, + SyncPattern = [], + ) + +print("Listening...") +while True: + data, addr = sock.recvfrom(1024) # buffer size is 1024 bytes + print("received message from " + addr[0] + ': ' + str(data)) + print(sock) + msg = str(data).split(":") + + if msg[0] == "SEARCH HCGW": + print("Hello message") + sock.sendto(HELLO_MESSAGE, addr) + + if msg[0] == "TXP": + msg[1] = msg[1].replace(";", "") + cmd = msg[1].split(",") + rep = int(cmd[2]) + pauselen = int(cmd[3]) + steplen = int(cmd[4]) + numpulse = int(cmd[5]) + pulsedata = cmd[6:] + pulsedata[numpulse * 2 - 1] = int(pulsedata[numpulse * 2 - 1]) + pauselen / steplen + + bindata = [] + bit = True + numbit = 0 + bitleft = 0 + for i in range(numpulse * 2): + for bits in range(int(pulsedata[i])): + if bitleft == 0: + bitleft = 8 + bindata.append(0x00) + + bindata[len(bindata) - 1] <<= 1 + if bit: + bindata[len(bindata) - 1] |= 0x01 + bitleft -= 1 + bit = not bit + for i in range(bitleft): + bindata[len(bindata) - 1] <<= 1 + + rfm.SetParams(Datarate = 1000.0 / steplen) + rfm.SendPacket(bindata * rep) diff --git a/apps/it32.py b/apps/it32.py new file mode 100644 index 0000000..abe5d64 --- /dev/null +++ b/apps/it32.py @@ -0,0 +1,48 @@ +import re + +def Decode(pulses): + if len(pulses) != 130: + return + + sym = "" + s = 0 + for p in pulses: + if (p >= 150) and (p <= 500): + s += p + sym += 's' + elif (p >= 1000) and (p <= 1500): + s += p + sym += 'l' + else: + sym += '?' + + it = "" + temp = sym + for i in range(32): + if temp[:4] == 'sssl': + it += "0" + elif temp[:4] == "slss": + it += "1" + else: + return + temp = temp[4:] + return("it32", it, int(round(s / (32.0 * 8 + 1)))) + +def Encode(args): + code = ' '.join(args) + if re.match("^[01]{32}$", code): + data = [0x08, 0x00] #sync + for c in args[0]: + if c == '0': + data.append(0xA0) + elif c == '1': + data.append(0x82) + data += [0x80, 0x00, 0x00, 0x00, 0x00] + return (data, 6, 275) + + elif re.match("^[01]{26} ([0][1-9]|[1-9]|[1][0-6]) (on|off)$", code): + g = re.match("^([01]{26}) ([0][1-9]|[1-9]|[1][0-6]) (on|off)$", code).groups() + bits = g[0] + '0' + bits += '1' if g[2] == 'on' else '0' + bits += "{0:04b}".format(int(g[1]) - 1) + return Encode([bits]) \ No newline at end of file diff --git a/lacrosse.py b/apps/lacrosse.py similarity index 80% rename from lacrosse.py rename to apps/lacrosse.py index 82b0b92..909fcd1 100755 --- a/lacrosse.py +++ b/apps/lacrosse.py @@ -1,19 +1,18 @@ #!/usr/bin/env python2.7 -from rfm69 import Rfm69 -import rfm69 +import RaspyRFM import sensors from sensors import rawsensor import sys import time import threading -if Rfm69.Test(1): +if RaspyRFM.RaspyRFM.Test(1): print("Found RaspyRFM twin") - rfm = Rfm69(1, 24) #when using the RaspyRFM twin -elif Rfm69.Test(0): + rfm = RaspyRFM.RaspyRFM(1, 24) #when using the RaspyRFM twin +elif RaspyRFM.RaspyRFM.Test(0): print("Found RaspyRFM single") - rfm = Rfm69() #when using a single single 868 MHz RaspyRFM + rfm = RaspyRFM.RaspyRFM() #when using a single single 868 MHz RaspyRFM else: print("No RFM69 module found!") exit() @@ -21,7 +20,7 @@ else: rfm.SetParams( Freq = 868.30, #MHz center frequency Datarate = 9.579, #kbit/s baudrate - ModulationType = rfm69.FSK, #modulation + ModulationType = RaspyRFM.FSK, #modulation Deviation = 30, #kHz frequency deviation SyncPattern = [0x2d, 0xd4], #syncword Bandwidth = 150, #kHz bandwidth diff --git a/apps/rcpulse.py b/apps/rcpulse.py new file mode 100755 index 0000000..e051fe0 --- /dev/null +++ b/apps/rcpulse.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python2.7 + +import RaspyRFM +import sys +import time +import it32 +import tristate +import bistate24 +from argparse import ArgumentParser + +parser = ArgumentParser() +parser.add_argument("-t", "--timebase", type=int, help=u"timebase in \u03bcs") +parser.add_argument("-r", "--repeats", type=int, help=u"number of repetitions") +parser.add_argument("code", nargs='*', help="code, e. g. '000000000FFF', 'A 1 2 on' or '10111100011101011111111110001110'") +args = parser.parse_args() + +protos = [ + it32, + tristate, + bistate24, +] + +txdata = None +if len(args.code) > 0: + txdata = None + for proto in protos: + data = proto.Encode(args.code) + if data: + txdata = data + break + + if txdata is None: + print("invalid code!") + exit() + +rfm = RaspyRFM.RaspyRFM() +rfm.SetParams( + Freq = 433.92, #MHz + Datarate = 20.0, #kbit/s + Bandwidth = 200, #kHz + SyncPattern = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F], + RssiThresh = -105, #dBm + ModulationType = RaspyRFM.OOK, + OokThreshType = 1, #peak thresh + OokPeakThreshDec = 3, +) + +if txdata: + rfm.SetParams( + SyncPattern = [], + Datarate = 1000.0 / (args.timebase if args.timebase else txdata[2]) + ) + rep = (args.repeats if args.repeats else txdata[1]) + rfm.SendPacket(txdata[0] * rep) + print("Code sent!") + exit() + +def Decode(pulses): + for i in range(len(pulses)): + pulses[i] *= 50 + + dec = None + for proto in protos: + dec = proto.Decode(pulses) + if dec: + print(dec) + + if not dec: + print("Len " + str(len(pulses)) + ": " + str(pulses)) + +while True: + data = rfm.ReceivePacket(260) + s = "" + + pulsecount = 7 + glitchcount = 0 + bit = True + pulses = [] + + for d in data[0]: + s += format(d, '08b') + mask = 0x80 + while mask > 0: + newbit = (d & mask) > 0 + + if glitchcount > 0: + glitchcount += 1 + if newbit == bit: + pulsecount += glitchcount + glitchcount = 0 + else: + if glitchcount == 3: + pulses.append(pulsecount) + if pulsecount > 50: + Decode(pulses) + pulses = [] + bit = newbit + pulsecount = 3 + glitchcount = 0 + else: + if newbit == bit: + pulsecount += 1 + else: + glitchcount = 1 + + mask >>= 1 diff --git a/sensors.py b/apps/sensors.py similarity index 100% rename from sensors.py rename to apps/sensors.py diff --git a/apps/tristate.py b/apps/tristate.py new file mode 100755 index 0000000..a9fd9e9 --- /dev/null +++ b/apps/tristate.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python2.7 + +import re + +def Decode(pulses): + if len(pulses) != 50: + return + + sym = "" + s = 0 + for p in pulses: + if (p >= 200) and (p <= 450): + sym += 's' + s += p + elif (p >= 600) and (p <= 1350): + sym += 'l' + s += p + else: + sym += '?' + + code = "" + temp = sym + for i in range(12): + if temp[:4] == 'slsl': + code += "0" + elif temp[:4] == "lsls": + code += "1" + elif temp[:4] == "slls": + code += "f" + else: + return + temp = temp[4:] + + return ("tristate", code, int(round(s / (12.0 * 8 + 1)))) + +def encodeBits(val, num): + result = '' + for i in range(num): + if (val & 0x01): + result += 'F' + else: + result += '0' + val >>= 1 + return result + +def Encode(args): + code = ' '.join(args) + if re.match("^[01Ff]{12}$", code): + data = [] + for c in code: + if c == '0': + data.append(0x88) + elif c == '1': + data.append(0xEE) + elif c in ['F', 'f']: + data.append(0x8E) + data += [0x80, 0x00, 0x00, 0x00] #sync + + return (data, 5, 360) + + elif re.match('^[A-P] [1-4] [1-4] (on|off)$', code): + g = re.match('^([A-P]) ([1-4]) ([1-4]) (on|off)$', code).groups() + tristate = "" + tristate += encodeBits(ord(g[0]) - ord('A'), 4) #housecode + tristate += encodeBits(ord(g[2]) - 1, 2) #channel + tristate += encodeBits(ord(g[1]) - 1, 2) #group + tristate += "0F" + tristate += 'FF' if g[3] == 'on' else 'F0' + return Encode([tristate]) + + elif re.match('^([01]{5}) ([1-4]) (on|off)$', code): #Brennenstuhl + g = re.match('^([01]{5}) ([1-4]) (on|off)$', code).groups() + tristate = "" + for c in g[0]: + tristate += '0' if c == '1' else 'F' + for i in range(4): + tristate += '0' if int(g[1]) - 1 == i else 'F' + tristate += 'F' + tristate += '0F' if g[2] == 'on' else 'F0' + return Encode([tristate]) + + elif re.match('^[1-4] [1-4] (on|off)$', code): + g = re.match('^([1-4]) ([1-4]) (on|off)$', code).groups() + tristate = "" + for i in range(4): + tristate += "0" if int(g[0]) - 1 == i else "F" + for i in range(4): + tristate += "0" if int(g[1]) - 1 == i else "F" + tristate += "FFF" + tristate += 'F' if g[2] == 'on' else '0' + return Encode([tristate]) \ No newline at end of file diff --git a/culemu.py b/culemu.py index 89684d4..2a07bc0 100755 --- a/culemu.py +++ b/culemu.py @@ -67,7 +67,7 @@ class Connection(threading.Thread): lines = str.splitlines(data) for line in lines: - #print("FROM FHEM: " + line) + print("FROM FHEM: " + line) if line == 'V': self.sendHost("V 1.0 CULEMU") @@ -146,12 +146,9 @@ cul.start() class RxThread(threading.Thread): def __init__(self, rfm): self.__rfm = rfm - rfm.SetParams( - CallbackSync = self.__callback - ) threading.Thread.__init__(self) - def __callback(self): + def callback(self): lfsr = 0x1ff frame = rfm.ReadFifoWait(1) len = frame[0] ^ 0xFF #invert due to whitening @@ -192,4 +189,4 @@ rxthread.daemon = True rxthread.start() while True: - data = rfm.ReceivePacket(0) + data = rfm.StartRx(rxthread.callback) diff --git a/hamarx.py b/hamarx.py deleted file mode 100755 index dc1a10d..0000000 --- a/hamarx.py +++ /dev/null @@ -1,77 +0,0 @@ -#!/usr/bin/env python2.7 - -from rfm69 import Rfm69 -import rfm69 -import sys -import time -#import types -#import os - -rfm = Rfm69() -rfm.SetParams( - Freq = 433.944, - Datarate = 4.0, #1 / 250E-06 / 1000, - Bandwidth = 24000, - SyncPattern = [0x00, 0x08, 0x00], - RssiThresh = -80, - ModulationType = rfm69.OOK - ) - -def staff(byte): - res = 0 - res |= (byte & 1<<7) >> 4 - res |= (byte & 1<<5) >> 3 - res |= (byte & 1<<3) >> 2 - res |= (byte & 1<<1) >> 1 - return res - -def decode(bindata): - netdata = [0x00, 0x00, 0x00, 0x00] - for i in range(0, 64, 2): - if (bindata[i / 8] >> (i % 8)) & 0x01 == (bindata[i / 8] >> (i % 8 + 1)) & 0x01: - print "Error", i, hex(bindata[i / 8]), hex(bindata[i / 8] >> (i % 8)) - for i in range(4): - netdata[i] = staff(bindata[i * 2]) << 4 | staff(bindata[i * 2 + 1]) - - print "decode: ", - for i in range(4): - print "{0:{fill}2x}".format(netdata[i], fill='0'), - print "" - -while True: - data = rfm.ReceivePacket(60) - zcount = 0 - bindata = [] - binval = 0 - binmask = 0x80 - for d in data[0]: - rawmask = 0x80 - while (rawmask > 0) and (len(bindata) < 8): - if (d & rawmask) > 0: - if zcount == 1: - binval |= binmask - binmask >>= 1 - - if zcount == 5: - binmask >>= 1 - - if zcount == 11: - print "Received pause" - - if zcount == 41: - print "SYNC" - - zcount = 0 - else: - zcount += 1 - rawmask >>= 1 - - if binmask == 0: - bindata.append(binval) - binmask = 0x80 - binval = 0 - - if len(bindata) == 8: - decode(bindata) - break; - diff --git a/intertechno.py b/intertechno.py deleted file mode 100755 index 500ea04..0000000 --- a/intertechno.py +++ /dev/null @@ -1,91 +0,0 @@ -#!/usr/bin/env python2.7 - -import rfm69 -from rfm69 import Rfm69 -import xx2262 -import it32 -import sys -import re - -def encodeBits(val, num, pol, inv): - result = '' - for i in range(num): - if (val & 0x01) ^ inv: - result += 'F' - else: - result += pol - val >>= 1 - return result - -def usage(): - print "usage:" - print "intertechno on|off" #12-digit code 12 * ['0' | '1' | 'f'] - print "intertechno <12 symbols tristate code>" - print "intertechno <26 bit address> <1 goup bit> <4 bit unit> on|off" - print "intertechno <32 bit code>" - print "Examples:" - print "intertechno A 1 1 on" - print "intertechno 0000FFFF0FFF" - print "intertechno 11110000111100001111000010 0 1110 on" - print "intertechno 11110000111100001111000010010000" - sys.exit(1) - -if __name__ == "__main__": - import sys - cmd = "" - data = None - for i in range(1, len(sys.argv)): - cmd += " " + sys.argv[i] - cmd = cmd.strip() - - if re.match('^[01]{32}$', cmd) is not None: - data = it32.MakeFrame(cmd, 5) - datarate = 4 - - if re.match('^[01]{26} [01] [01]{4} (on|off)$', cmd) is not None: - tmp = cmd[0:26] + cmd[27] - if cmd[-2:] == 'on': - tmp += '1' - else: - tmp += '0' - tmp += cmd[29:33] - data = it32.MakeFrame(tmp, 5) - datarate = 1/275E-6/1000 - - if re.match('^[A-P] [1-4] [1-4] (on|off)$', cmd) is not None: - housecode = ord(cmd[0]) - ord('A') - itstr = '' - itstr += encodeBits(housecode, 4, '0', False) - - ch = ord(cmd[4]) - 1 - itstr += encodeBits(ch, 2, '0', False) - - group = ord(cmd[2]) - 1 - itstr += encodeBits(group, 2, '0', False) - - itstr += '0F' - - if cmd[-2:] == 'on': - itstr += 'FF' - else: - itstr += 'F0' - - data = xx2262.MakeFrame(itstr, 5) - datarate = 2.66666666 - - if re.match('^[01Ff]{12}$', cmd) is not None: - data = xx2262.MakeFrame(cmd, 5) - datarate = 2.66666666 - -if data is not None: - rfm = Rfm69() - rfm.SetParams( - Freq = 433.92, - Datarate = datarate, #2.666666, - TXPower = 13, - ModulationType = rfm69.OOK, - SyncPattern = [] - ) - rfm.SendPacket(data) -else: - usage() diff --git a/it32.py b/it32.py deleted file mode 100644 index b453b3e..0000000 --- a/it32.py +++ /dev/null @@ -1,10 +0,0 @@ - -def MakeFrame(code, rep): - data = [0x08, 0x00] #sync - for c in code: - if c == '0': - data.append(0xA0) - elif c == '1': - data.append(0x82) - data += [0x80, 0x00, 0x00, 0x00, 0x00] - return data * rep diff --git a/logilightrx.py b/logilightrx.py deleted file mode 100755 index 357f253..0000000 --- a/logilightrx.py +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env python2.7 - -from rfm69 import Rfm69 -import rfm69 -import sys -import time -#import types -#import os - -rfm = Rfm69() -rfm.SetParams( - Freq = 433.92, - Datarate = 1 / 275E-06 / 1000, - Bandwidth = 4000, - SyncPattern = [0x80, 0x00, 0x00, 0x00], - RssiThresh = -80, - ModulationType = rfm69.OOK - ) - -def Decode(bitpos, data): - frame = 0 #space for decoded logilink frame - for i in range(bitpos, bitpos + 24 * 4, 4): - bitpattern = (data[i / 8] << 8) | (data[i / 8 + 1]) - bitpattern <<= i % 8 - bitpattern &= 0xF000 - frame <<= 1 - if bitpattern == 0xe000: - frame |= 1 - elif bitpattern == 0x8000: - pass - else: - return - systemcode = frame >> 4 - onoff = (frame >> 3) & 0x01 - ch = frame & 0x07 - return systemcode, onoff, ch - -while True: - data = rfm.ReceivePacket(60) - zcount = 0 - binstr = "" - bitcount = 0 - #print "received raw data:", data[0] - sync = 0 - for bit in range(len(data[0]) * 8): - sync <<= 1 - sync |= ((data[0][bit / 8] >> (7 - (bit % 8)))) & 0x01 - sync &= 0xFFFFFFFF - if sync == 0x80000000: #sync found in frame - if (bit >= 24 * 4 + 32 - 1): #logilinkframe has 24 bit, 1 logilink-bit = 4 raw-bits; + 32 raw bits sync - res = Decode(bit - 24 * 4 - 32 + 1, data[0]) - if res is not None: - print "Systemcode", res[0], "onoff", res[1], "ch", res[2] diff --git a/logilighttx.py b/logilighttx.py deleted file mode 100755 index 45fb7ea..0000000 --- a/logilighttx.py +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env python2.7 - -from rfm69 import Rfm69 -import rfm69 -import sys - -if len(sys.argv) != 5: - print "usage: logiloghttx.py " - print "Example: logilighttx.py 65565 7 1 4" - sys.exit(1) - -rfm = Rfm69() -rfm.SetParams( - Freq = 433.92, - Datarate = 2.666666, - TXPower = 13, - ModulationType = rfm69.OOK, - SyncPattern = [] - ) - -#Frame generation -def MakeFrame(systemcode, onoff, channel, rep): - data = systemcode << 4 | onoff << 3 | channel - frame = [0x00] * 12 - for i in range(24): - if (data & (0x800000>>i)): - nibble = 0xE0 - else: - nibble = 0x80 - frame[i / 2] |= nibble >> (4 * (i % 2)) - - frame += [0x80, 0x00, 0x00, 0x00] #sync - - return frame * rep - -data = MakeFrame(int(sys.argv[1]), int(sys.argv[2]), int(sys.argv[3]), int(sys.argv[4])) -rfm.SendPacket(data) diff --git a/rcs1000.py b/rcs1000.py deleted file mode 100755 index 244e711..0000000 --- a/rcs1000.py +++ /dev/null @@ -1,63 +0,0 @@ -#!/usr/bin/env python2.7 - -import xx2262 -import sys -import re -import rfm69 -from rfm69 import Rfm69 - -def usage(): - print "usage:", sys.argv[0], "<5xDIPSWITCH> on|off" - print "Example:", sys.argv[0], "brennenstuhl 00101 2 on" - sys.exit(1) - -if len(sys.argv) != 4: - usage() - -str = '' - -if re.match('[01]{5}$', sys.argv[1]) is None: - print "Invalid dipswitches" - usage() - -for c in sys.argv[1]: - if c == '1': - str += '0' - else: - str += 'F' - -if re.match('[1-4]$', sys.argv[2]) is None: - print "Invalid channel" - usage() - -ch = ord(sys.argv[2][0]) - ord('1') -for i in range(4): - if i == ch: - str += '0' - else: - str += 'F' - -str += 'FF' - -if sys.argv[3] == 'on': - str += 'F' -else: - str += '0' - -print "Sending", str - -data = xx2262.MakeFrame(str, 8) - -if not Rfm69.Test(0): - print "RFM69 not found!" - exit() - -rfm = Rfm69() -rfm.SetParams( - Freq = 433.92, - Datarate = 2.666666, - TXPower = 13, - ModulationType = rfm69.OOK, - SyncPattern = [] -) -rfm.SendPacket(data) diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..4c656a5 --- /dev/null +++ b/setup.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python2.7 +import setuptools + +setuptools.setup( + name="RaspyRFM", + version="1.1", + author="S. Seegel", + author_email="post@seegel-systeme.de", + description="Package for interfacing the RaspyRFM module", + packages=["RaspyRFM"], + license="MIT", + zip_safe=False, + install_requires=[ + "spidev", + "RPi.GPIO" + ] +) + diff --git a/xx2262.py b/xx2262.py deleted file mode 100755 index 14320db..0000000 --- a/xx2262.py +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env python2.7 - -from rfm69 import Rfm69 -import rfm69 -import re - -#Frame generation -def MakeFrame(code, rep): - data = [0x80, 0x00, 0x00, 0x00] #sync - b = 0; - data = [] - for c in code: - if c == '0': - data.append(0x88) - elif c == '1': - data.append(0xEE) - elif c == 'F' or c == 'f': - data.append(0x8E) - data += [0x80, 0x00, 0x00, 0x00] #sync - - return data * rep - -def usage(): - print "usage: xx2262.py " #12-digit code 12 * ['0' | '1' | 'f'] - print "Example: sudo ./intertechno.py 0FF0F0F00FF0" - sys.exit(1) - -if __name__ == "__main__": - import sys - if len(sys.argv) != 2: - usage() - - if re.match('[01fF]{12}$', sys.argv[1]) is None: - print "Invalid code" - usage() - - data = MakeFrame(sys.argv[1], 5) - - rfm = Rfm69() - rfm.SetParams( - Freq = 433.92, - Datarate = 2.666666, - TXPower = 13, - ModulationType = rfm69.OOK, - SyncPattern = [] - ) - - rfm.SendPacket(data)