Re: Eldat Easywave RXR09 Script
Posted: Sunday 01 April 2018 17:10
Hi aeisenhuth,
i finally got time to get a working solution.
I've adapted both RX09.py and the plugin.py.
Both will query first the "SwithTypeVal" Device Attribute to determine if it concerns a "Venitian Blind EU" before executing a command.
The bad news is it requires the latest Beta version: Version: 3.9116
I've also added some parameters to the startup of RX09.py:
The full startup is now:
rx09.py -p /tmp/serialPid /dev/ttyUSB0 5331 127.0.0.1 8080 /home/pi/project/python_programs/devices.csv &
the -p option + first parameter allows the PID of the process to be stored
follows:
Device, Port which the process is monitoring, Domoticz IP address, Domoticz port (for http) and the location of devices.csv
New Version RX09.py
The new version of plugin.py
Hope this will align actions and icons in Domoticz
regards
i finally got time to get a working solution.
I've adapted both RX09.py and the plugin.py.
Both will query first the "SwithTypeVal" Device Attribute to determine if it concerns a "Venitian Blind EU" before executing a command.
The bad news is it requires the latest Beta version: Version: 3.9116
I've also added some parameters to the startup of RX09.py:
The full startup is now:
rx09.py -p /tmp/serialPid /dev/ttyUSB0 5331 127.0.0.1 8080 /home/pi/project/python_programs/devices.csv &
the -p option + first parameter allows the PID of the process to be stored
follows:
Device, Port which the process is monitoring, Domoticz IP address, Domoticz port (for http) and the location of devices.csv
New Version RX09.py
Code: Select all
# based upon: https://github.com/richardkchapman/serproxy/blob/master/serproxy.py
#
# Python version of serproxy for Easywave (RX09) v20180330
#
# Python 3.5.1
# Windows 10, Pi 4.59. V7+
#
# Accept requests by socket (TCP/IP Raw) and forward to Serial Port
# Return Serial response to socket
#
# Original Author: Richard K Chapman
# Adaptations: WimR
#
# Date: 2018 01 12 Original version
# 2018 02 28 Added read csv with link: switch id TO domoticz idx
# Read serial line and add to log as 'respons'
# 2018 03 16 Added 'C' telegram mapped to 'Stop' (for devices Venitian Blinds EU)
# 19 Added Dict for Commands
# Made path to devices.csv relative
# Added counter for devices table
# 30 Added different behaviour for Blinds. SwitchType will be checked first
#
from socket import *
from threading import Thread
import logging
import serial
import time
import sys
import os
import csv
import json, urllib, urllib2
ser = None
allClients = []
BUFSIZ = 1024
serialDevice = None
def serialReader():
global ser
global allClients
global serialDevice
global domoticzIP
global domoticzPort
global pairingFile
line = []
oldLine = ''
oldTime = time.time()
allDevices = {}
CommandMap = {'A':'On','B':'Off','C':'Stop','D':'Off'}
CommandMapBlind = {'A':'Off','B':'On','C':'Stop','D':'Off'}
while 1:
try:
# configure the serial connections (the parameters differs on the device you are connecting to)
ser = serial.Serial(
port=serialDevice,
baudrate=57600,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
bytesize=serial.EIGHTBITS
)
ser.isOpen()
logging.info('Serial port opened %s', ser)
# load device table from csv file
logging.info('Device table being loaded in dictionnary.')
ifile = open(pairingFile, "rU")
logging.info('Device table being opened.')
reader = csv.reader(ifile, delimiter=";")
allDevices = dict([(row[0],row[1]) for row in reader])
ifile.close()
logging.info('Device table being closed.')
logging.info('Device table loaded in dictionnary. Number of devices loaded: %d ' % len(allDevices))
# main listener loop
while 1:
out = ser.read(1)
line.append(out)
# wait till '\r' as end of line char
if out == '\r':
myLine = ''.join(line)
myTime = time.time()
logging.info('Serial respons : %s', myLine)
# if myLine starts with REC :split line use second part as key in dict and lauch json call
if myLine[0:3] == 'REC':
if allDevices.get(myLine[4:10]) is None:
# transmitter not in devices.csv
logging.info('Warning : unknown physical RF transmitter: %s Please add to device table with corresponding idx', myLine[4:10])
else:
# Check with Domoticz what kind of switch we are dealing with
result_load = json.load(urllib2.urlopen("http://%s:%s/json.htm?type=devices&rid=%s" % ( domoticzIP, domoticzPort, allDevices.get(myLine[4:10])), timeout=5))
if result_load["result"][0]["SwitchTypeVal"] is None:
logging.info("Warning : Key : %s is linked to idx: %s but its not a Switch "% (myLine[4:10],allDevices.get(myLine[4:10])))
else:
if result_load["result"][0]["SwitchTypeVal"] == 15:
myCommand = CommandMapBlind.get(myLine[11])
else:
myCommand = CommandMap.get(myLine[11])
# known physical RF transmitter linked to switch
if myCommand is None:
logging.info('Warning : unknown Command from physical transmitter: ' + myLine[11])
else:
logging.info('Key : ' + myLine[4:10] + ' Mapped to : ' + allDevices.get(myLine[4:10]) + ' Telegram : ' + myLine[11] + ' mapped to Command : ' + myCommand)
# switches may bounce and emit rapidly 2 to 5 times the same command: debounce by comparing content and time lapse (more than 1 sec apart)
if myLine <> oldLine or myTime - oldTime > 1:
# transfer received command to Domoticz
result_load = json.load(urllib2.urlopen("http://%s:%s/json.htm?type=command¶m=switchlight&idx=%s&switchcmd=%s&level=0" % (domoticzIP, domoticzPort, allDevices.get(myLine[4:10]), myCommand), timeout=5))
logging.info('Respons domoticz : %s', str(result_load))
# arm debounce mechanism
oldTime = myTime
oldLine = myLine
else:
# respons is not from physical switch
# write line to all clients
for elem in line:
for client in allClients:
client.send(elem)
# Clear line
line = []
except:
# Wait a while then try again
logging.debug('Error on serial port')
if ser is not None:
ser.close()
ser = None
time.sleep(10)
def handler(clientsock,addr):
global ser
global allClients
logging.info('connected from: %s', addr)
allClients.append(clientsock)
while 1:
data = clientsock.recv(BUFSIZ)
if not data:
break
if not (ser is None):
ser.write(data)
logging.info('Message received: %s ', data)
logging.info('disconnected from: %s', addr)
allClients.remove(clientsock)
clientsock.close()
def mainProgram():
global serialDevice
global port
global domoticzIP
global domoticzPort
global pairingFile
if len(sys.argv)>=8 and sys.argv[1] == '-p':
writePidFile(sys.argv[2])
del sys.argv[2]
del sys.argv[1]
if len(sys.argv) != 6:
print ('usage: ' + sys.argv[0] + ' device port domoticzIP domoticzPort pairingFile')
exit()
print ('Logfile: /tmp/SerProxy.Log')
logging.basicConfig(filename='/tmp/SerProxy.Log', filemode='w', format='%(asctime)s %(levelname)s:%(message)s', level=logging.DEBUG)
serialDevice=sys.argv[1]
port=int(sys.argv[2])
domoticzIP=sys.argv[3]
domoticzPort=sys.argv[4]
pairingFile=sys.argv[5]
print ('Version Serial Proxy: RX09v20180330')
print ('Serial Device: ' + sys.argv[1])
print ('Serial Port: ' + sys.argv[2])
print ('Domoticz IP: ' + sys.argv[3])
print ('Domoticz Port: ' + sys.argv[4])
print ('Pairing File: ' + sys.argv[5])
logging.info('Version Serial Proxy: RX09v20180330')
logging.info('Serial Device: ' + sys.argv[1])
logging.info('Serial Port: ' + sys.argv[2])
logging.info('Domoticz IP: ' + sys.argv[3])
logging.info('Domoticz Port: ' + sys.argv[4])
logging.info('Pairing File: ' + sys.argv[5])
Thread(target=serialReader).start()
ser = None
allClients = []
listenAddr = ('', port)
try:
serversock = socket(AF_INET, SOCK_STREAM)
serversock.bind(listenAddr)
serversock.listen(2)
logging.info('waiting for connection')
while 1:
clientsock, addr = serversock.accept()
Thread(target=handler, args=(clientsock, addr)).start()
except KeyboardInterrupt:
if not ser is None:
ser.close()
os._exit(0)
def writePidFile(pidfile):
pid = str(os.getpid())
f = open(pidfile, 'w')
f.write(pid)
f.close()
if __name__=='__main__':
mainProgram()
The new version of plugin.py
Code: Select all
# Domoticz Easywave Python Plugin using Serial Proxy
#
# Tested on:
# Domoticz: Version: 3.9116
# DSM: Version: 6.1
# SerialProxy: Windows 10, Python 3.5.1
# Raspberry Pi 4.9.59-v7+, Python 2.7.13
#
# Type "C" or "D" devices not tested, handling incomming messages from Easywave transmitters not implemented (SerProxy fires of JSON)
#
#
# Easywave protocol is used by brands Niko & Eldat to communicate between RF transmitters and recievers (868.30Mhz)
#
# This plugin requires Eldat RTR09 USB transceiver on Serial Proxy
#
# Two models exist: 64 virtual switches (RX09E5026-02-01K)
# 128 virtual switches (RX09E5031-02-01K)
#
# TCP communication will be setup to Serial Proxy
# Serial Proxy will transfer communication to and from Tranceiver proper
#
# Basic Easywave protocol: send: TXP,Switch_identifier,Command
# response: OK or ERROR to indicate correctly formed send string
#
# receiving: REC,Unique_key,Command
#
# Switch_identifier: two bytes HEX, transceiver ties a hardcoded Unique_key for each Switch_identifier
# Unique_key: six bytes HEX
# Command: A,B,C,D depending on transmitter/receiver e.g. Switch A=On, B=Off, Gong A=Ring
#
# On creating the hardware All the Virtual switches will be created as devices (currently only simple On/Of switches)
#
# Socket communication based upon: https://pymotw.com/2/socket/tcp.html
#
# Author: WimR
#
# Date: 2018 01 12
# 03 16 : added 'Blinds' : assumption: Simple On/Off Switch can be changed via Switches/Select switch/Edit/Select type from 'On/Off' switch into 'Venetian Blind EU'
# Commands are 'On', 'Off', 'Stop' will be translated into A,B,C Easywave telegrams
# 28 : added 'Ventan Blinds EU' will use different telegram order in respect to default order: Standard A,B,C, Venitian Blinds EU: B,A,C
#
# <param field="SerialPort" label="Serial Port" width="150px" required="true" default="COM3" />
"""
<plugin key="Easywave" name="Easywave (Niko - Eldat)" author="WimR" version="0.4.0" wikilink="http://www.domoticz.com/wiki/plugins/plugin.html" externallink="http://www.eldat.de/produkte/_div/rx09e_sp_en.pdf">
<params>
<param field="Mode1" label="Model" width="250px">
<options>
<option label=" 64 Switches (RX09E5026-02-01K)" value="64"/>
<option label="128 Switches (RX09E5031-02-01K)" value="128" default="64" />
</options>
</param>
<param field="Mode2" label="IP Address Serial Proxy" width="250px" required="true" default="127.0.0.1" />
<param field="Mode3" label="Port" width="150px" required="true" default="5331" />
<param field="Mode6" label="Debug" width="75px">
<options>
<option label="True" value="Debug"/>
<option label="False" value="Normal" default="true" />
</options>
</param>
</params>
</plugin>
"""
import Domoticz
import sys
import socket
import time
def onStart():
i = 0
Domoticz.Log("onStart called")
if Parameters["Mode6"] == "Debug":
Domoticz.Debugging(1)
# if first time create all possible Switch devices
if (len(Devices) == 0):
# Create Virtual Switches (Number depending USB model)
# Mapping: Hex code id of Virutal Switch [0-7F] will be mapped to INT(Hex) + 1 as Unit [1-128]
for i in range(0, int(Parameters["Mode1"])):
Domoticz.Device(Name="EasyWave TX - " + format( i , '02X'), Unit=i+1, TypeName="Switch").Create()
Domoticz.Log("Device " + format( i , '02X') + " created.")
Domoticz.Log("Plugin has " + str(len(Devices)) + " devices associated with it.")
DumpConfigToLog()
return
def onStop():
Domoticz.Debug("onStop called")
def onConnect(Connection, Status, Description):
Domoticz.Debug("onConnect called")
def onMessage(Connection, Data, Status, Extra):
Domoticz.Debug("onMessage called")
return
def onCommand(Unit, Command, Level, Hue):
# define expected Succes response from Serial
response_ok = "OK\r" # '
# define dict Domoticz Command : Easywave telegram type e.g.: 'On' -> 'A'
telegram = {'On':'A','Off':'B','Stop':'C'}
telegram_blinds = {'On':'B','Off':'A','Stop':'C'}
update_value= {'On':1,'Off':0,'Stop':0}
# Create a TCP/IP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Connect the socket to the port where the server is listening
Domoticz.Log("onMessage called using socket params: " + Parameters["Mode2"] + " " + str(Parameters["Mode3"]))
server_address = (str(Parameters["Mode2"]), int(Parameters["Mode3"]))
sock.connect(server_address)
try:
# Send data
# If Command received from Virtual Switch send command
Domoticz.Log("onCommand called for Unit " + str(Unit) + " : Hex : " + str(format( Unit -1 , '02X')) + ": Parameter '" + str(Command) + "', Level: " + str(Level) + ": SwitchType : " +str(Devices[Unit].SwitchType))
if telegram.get(Command) is None:
Domoticz.Log("Unknown Command: " + Command)
else:
# Build Easywave telegram coding pending SwitchType: 15 = Ventian Blinds EU
if Devices[Unit].SwitchType == 15:
message = "TXP," + str(format( Unit -1 , '02X')) + "," + telegram_blinds.get(Command) + '\r\n'
else:
message = "TXP," + str(format( Unit -1 , '02X')) + "," + telegram.get(Command) + '\r\n'
Domoticz.Log("Telegram: " + message)
sock.sendall(str.encode(message))
# Look for the response before updating
amount_received = 0
amount_expected = 3
time.sleep(1)
while amount_received < amount_expected:
data = sock.recv(16)
amount_received += len(data)
if repr(data)[1:6] == repr(response_ok)[0:5]:
Domoticz.Debug("Response OK")
Devices[Unit].Update(update_value.get(Command),Command)
else:
Domoticz.Log("Device not Updated. Serial Response: " + repr(data))
finally:
message = None
return
def onNotification(Name, Subject, Text, Status, Priority, Sound, ImageFile):
Domoticz.Log("Notification: " + Name + "," + Subject + "," + Text + "," + Status + "," + str(Priority) + "," + Sound + "," + ImageFile)
def onDisconnect(Connection):
Domoticz.Log("RX09 Disconnected")
return
def onHeartbeat():
return True
# Generic helper functions
def DumpConfigToLog():
for x in Parameters:
if Parameters[x] != "":
Domoticz.Debug( "'" + x + "':'" + str(Parameters[x]) + "'")
Domoticz.Debug("Device count: " + str(len(Devices)))
for x in Devices:
Domoticz.Debug("Device: " + str(x) + " - " + str(Devices[x]))
Domoticz.Debug("Device ID: '" + str(Devices[x].ID) + "'")
Domoticz.Debug("Device Name: '" + Devices[x].Name + "'")
Domoticz.Debug("Device nValue: " + str(Devices[x].nValue))
Domoticz.Debug("Device sValue: '" + Devices[x].sValue + "'")
Domoticz.Debug("Device LastLevel: " + str(Devices[x].LastLevel))
return
regards