Eldat Easywave RXR09 Script

Python and python framework
WimR
Posts: 20
Joined: Saturday 03 February 2018 18:18
Target OS: NAS (Synology & others)
Domoticz version: 3.8153
Location: Belgium
Contact:

Re: Eldat Easywave RXR09 Script

Post by WimR » 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

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&param=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

Hope this will align actions and icons in Domoticz

regards

aeisenhuth
Posts: 17
Joined: Thursday 26 January 2017 22:07
Target OS: Raspberry Pi
Domoticz version:
Location: Homberg, Hessen, Germany
Contact:

Re: Eldat Easywave RXR09 Script

Post by aeisenhuth » Thursday 02 August 2018 15:24

:?: Hello WimR,
I've been to Egypt for the past few months and did not dare to change to the latest version of RX09.py and plugin.py.
Because worked well for months.
Now I am back home and will try to install the new version.
I now have a new SD card in my Pi with Raspian Stretch and latest Domoticzs beta installed.

How do I proceed now:
1. Save RX09.py in / home / pi / project / python_programs.
2. Create devices.csv in / home / pi / project / python_programs.
3. Create plugin.py in / home / pi / domoticz / plugins / Eldat_Easywave.
4. What do I do with "rx09.py -p / tmp / serialPid / dev / ttyUSB0 5331 127.0.0.1 8080 /home/pi/project/python_programs/devices.csv &" in /etc/rc.local?

What about the old lines in the rc.local:

# Add these lines to enable RX 09 Eldat USB Tranciever EasyWave
sudo modprobe cp210x
sudo sh -c 'echo 155a 1006> / sys / bus / usb-serial / drivers / cp210x / new_id'
python /home/pi/project/python_programs/rx09.py / dev / ttyUSB0 5331 &


Best regards aeisenhuth :roll:
RaspberryPI3B Domoticz beta with: Xiaomi Gateway / Amazon Echo / HUE Gateway & Osram Bulbs & ikea Bulbs/ Eldat Easywave / Homematic IP / FS-20 / CUxD / XML-API

WimR
Posts: 20
Joined: Saturday 03 February 2018 18:18
Target OS: NAS (Synology & others)
Domoticz version: 3.8153
Location: Belgium
Contact:

Re: Eldat Easywave RXR09 Script

Post by WimR » Thursday 02 August 2018 21:19

Hi Aeisenhuth,

1. Save RX09.py in /home/pi/project/python_programs. -> Yes
2. Create devices.csv in /home/pi/project/python_programs. -> Yes, but you may also copy here the existing devices.csv from your current running setup
3. Create plugin.py in /home/pi/domoticz/plugins/Eldat_Easywave. -> Yes
4. What do I do with "/home/pi/project/python_programs/rx09.py -p /tmp/serialPid /dev/ttyUSB0 5331 127.0.0.1 8080 /home/pi/project/python_programs/devices.csv &" in /etc/rc.local -> This replaces the line: python /home/pi/project/python_programs/rx09.py /dev/ttyUSB0 5331 & in /etc/rc.local

Both sudo lines on /etc/rc.local you should keep them as is

BTW my prior version is up and running for 4 months without a glitch or reboot :D

regards

Wim

aeisenhuth
Posts: 17
Joined: Thursday 26 January 2017 22:07
Target OS: Raspberry Pi
Domoticz version:
Location: Homberg, Hessen, Germany
Contact:

Re: Eldat Easywave RXR09 Script

Post by aeisenhuth » Friday 03 August 2018 11:07

Hello WimR,
thanks again for the quick reply.
So I have reinstalled everything now.
In the old devices.csv I only had your values. I did not learn my own remote controls there because I did not know exactly how it works.

Now I have only one problem after the Pi started in the terminal with the RX09.py I have created and attached a screenshot.
Maybe you have a solution for me.

Vilelen thanks in advance
Attachments
rx09_start.jpg
rx09_start.jpg (114.29 KiB) Viewed 79 times
RaspberryPI3B Domoticz beta with: Xiaomi Gateway / Amazon Echo / HUE Gateway & Osram Bulbs & ikea Bulbs/ Eldat Easywave / Homematic IP / FS-20 / CUxD / XML-API

WimR
Posts: 20
Joined: Saturday 03 February 2018 18:18
Target OS: NAS (Synology & others)
Domoticz version: 3.8153
Location: Belgium
Contact:

Re: Eldat Easywave RXR09 Script

Post by WimR » Friday 03 August 2018 11:28

Hi Aeisenhuth,

at first look it seems python isn't able to read the needed libraries -> "import" is failing.
Could you check if python has been correctly installed on your new Pi?
You'll need python 3.5.1

Also please check the line in rc.local launching the rx09.py. Does it start with "python /usr/pi/……"?

the devices.csv has a simple structure:
The Easywave transmitter identification: 2ead4f
followed by ;
and the idx it has to be attached to.

The Easywave transmitter identification will show up in the Log file whenever a transmitter is used.
If the identification isn't yet in the devices.csv it will be logged as "'Warning : unknown physical RF transmitter...."
You can than "see" what the identification of your transmitter/remote button is.
regards

Wim

Post Reply

Who is online

Users browsing this forum: No registered users and 1 guest