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