Python plugin: HC868 Kincony Ethernet I/O intreface

Python and python framework

Moderator: leecollings

Post Reply
ramirez22
Posts: 9
Joined: Thursday 26 March 2020 2:30
Target OS: Raspberry Pi / ODroid
Domoticz version:
Contact:

Python plugin: HC868 Kincony Ethernet I/O intreface

Post by ramirez22 »

Hello.

I'm new in developing under Domoticz and python language, but it's exciting and I learn many things.
I developed a python script for acting on Kincony KC868-H16 ethernet board (16 outputs and 8 inputs).
The protocol is TCP and dialog is made by socket.
The script work well but it's not enough 'user frienly' and I try to develop a python plugin for this interface.

I try to check at each call onHeartbeat if my connection is always correct. For that, I send an init frame and wait for the answer.

Code: Select all

    def onHeartbeat(self):
        global debug, connected
        # Vérification de la connexion
        if connected:
            InitKincony()
        else:
            ConnectKincony()
        # Log
        if debug == False:
            Debug("onHeartbeat appelé")

Code: Select all

def ConnectKincony():
    global host, port, connected, socket_relais
    # ----- Connexion au serveur TCP KinCony -----
    if not connected:
        socket_relais = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        socket_relais.settimeout(2)
        try:
            socket_relais.connect((host,port))
        except socket.timeout:
            Domoticz.Log("TimeOut connexion")
            connected = False
            return
        Debug('Connexion carte KinCony')
        connected = True

Code: Select all

def InitKincony():
    global host, port, connected, socket_relais
    # ----- Initialisation communication : message 1 -----
    commande_tcp = "RELAY-SCAN_DEVICE-NOW"
    Debug("Commande TCP: " + str(commande_tcp))
    try:
        socket_relais.sendto(commande_tcp.encode(),(host,port))
    except socket.timeout:
        Domoticz.Log("TimeOut connexion")
        connected = False
        return
    # Traitement des donnees reçues
    msg_recu = socket_relais.recv(1024).decode()
    # Est-ce que le message reçu est conforme ?
    if "OK" in msg_recu:
        Debug("Init 1 OK")
    else:
        Domoticz.Log("Erreur Initialisation 1: " + str(msg_recu))
        socket_relais.close()
        connected = False
        return

    # ----- Initialisation communication : message 2 -----
    commande_tcp = "RELAY-TEST-NOW"
    Debug("Commande TCP: " + str(commande_tcp))
    try:
        socket_relais.sendto(commande_tcp.encode(),(host,port))
    except socket.timeout:
        Domoticz.Log("TimeOut connexion")
        connected = False
        return
    #traitement des donnees reçues
    msg_recu = socket_relais.recv(1024).decode()
    #Debug("Réception Init 2: " + msg_recu)
    # Est-ce que le message reçu est conforme ?
    if "HOST-TEST-START" in msg_recu:
        Debug("Init 2 OK")
    else:
        Domoticz.Log("Erreur Initialisation 2: " + str(msg_recu))
        socket_relais.close()
        connected = False
        return
    connected = True
Comments are in French, sorry (nobody's perfect :D )

I use global boolean variable to know if interface is connected

The problem is during test: I disconnect cable from interface, but the onHeartbeat don't work as expected :

Code: Select all

2020-03-26 06:59:27.281 (a) Commande TCP: RELAY-SCAN_DEVICE-NOW
2020-03-26 06:59:29.284 Error: (a) 'onHeartbeat' failed 'timeout':'timed out'.
2020-03-26 06:59:29.284 Error: (a) ----> Line 163 in '/home/pi/domoticz/plugins/Kincony/plugin.py', function onHeartbeat
2020-03-26 06:59:29.284 Error: (a) ----> Line 123 in '/home/pi/domoticz/plugins/Kincony/plugin.py', function onHeartbeat
2020-03-26 06:59:29.284 Error: (a) ----> Line 215 in '/home/pi/domoticz/plugins/Kincony/plugin.py', function InitKincony 
As you can see, the error is not generated by my code but by Domoticz. The time between shape send and error is 2 seconds.
I tryed to change the socket_relais.settimeout to 1 seconde and the result is the timeout error of the onHeartbeat function occurs after 1 second.

So I'm afraid that the onHeartbeat function watching a timeout interrupt and "catch" my socket interrupt.

I need help now because my knowledge of python and Domoticz stop here :D
I'm under Raspbian Stretch and Domoticz 4.10717 (stable). If you need more information of course, ask me :D

Many thanks.
Regards
Last edited by ramirez22 on Tuesday 31 March 2020 7:49, edited 1 time in total.
ramirez22
Posts: 9
Joined: Thursday 26 March 2020 2:30
Target OS: Raspberry Pi / ODroid
Domoticz version:
Contact:

Re: 'onHeartbeat' failed 'timeout' during timeout socket

Post by ramirez22 »

Hello,

I find another way to communicate with device, with Domoticz.Connection.
I don't know exactly how use it, but after some test, the timeout problem isn't ... a problem :D
ramirez22
Posts: 9
Joined: Thursday 26 March 2020 2:30
Target OS: Raspberry Pi / ODroid
Domoticz version:
Contact:

KC868 Plugin development

Post by ramirez22 »

Hello all.

I continue to develop my plugin for the KC868 device.
I finally can do some functional things:
  • Can pilot output
  • The board check at the first launch the state of the device (if some outputs are ON, set the correct value of Domoticz)
  • Inputs are half functional (device problem : I have information when a change low to high occur, but no information when high to low)
  • Each heartbeat time, check the state of inputs/outputs (in case of power loss on the device, outputs switch to off). Better way should be check if device always online, but it's for later
I have a problem with scenes. I want to make a general stop. So I build a scene with all my outputs to OFF.
But that don't work as expected :
1 or 2 outputs swith off, with a long delay, and that's all.
This is the log:

Code: Select all

2020-03-30 22:31:13.132 Activating Scene/Group: [test]					<-- Start of the scene
2020-03-30 22:31:13.134 Activating Scene/Group Device: a - Relais 1 (Off)		<-- First device OFF
2020-03-30 22:31:13.157 (a) onCommand appelé.Unit: 1: Parameter 'Off', Level: 99, Hue:	<-- onCommand call for first output
2020-03-30 22:31:13.157 (a) KinconyWriteOutput appelé: 'RELAY-SET-1,1,0'		<-- subfuction 'KinconyWriteOutput' call
2020-03-30 22:31:13.157 (a) RELAY-SET-1,1,0						<-- message sent throw the eth0
2020-03-30 22:31:13.184 Activating Scene/Group Device: a - Relais 2 (Off)		<-- The scene continue 
2020-03-30 22:31:13.235 Activating Scene/Group Device: a - Relais 3 (Off)
2020-03-30 22:31:13.285 Activating Scene/Group Device: a - Relais 4 (Off)
2020-03-30 22:31:13.336 Activating Scene/Group Device: a - Relais 5 (Off)
2020-03-30 22:31:13.386 Activating Scene/Group Device: a - Relais 6 (Off)
2020-03-30 22:31:13.437 Activating Scene/Group Device: a - Relais 7 (Off)
2020-03-30 22:31:13.487 Activating Scene/Group Device: a - Relais 8 (Off)
2020-03-30 22:31:13.131 Status: User: Admin initiated a scene/group command
2020-03-30 22:31:14.159 (a) onCommand appelé.Unit: 2: Parameter 'Off', Level: 99, Hue:	<-- onCommand call for 2nd output
2020-03-30 22:31:14.159 (a) KinconyWriteOutput appelé: 'RELAY-SET-1,2,0'
2020-03-30 22:31:14.159 (a) RELAY-SET-1,2,0						<-- message sent throw the eth0, but the result occur 7 seconds later
2020-03-30 22:31:15.160 (a) onCommand appelé.Unit: 3: Parameter 'Off', Level: 99, Hue:	<-- onCommand call for 2nd output
2020-03-30 22:31:15.161 (a) KinconyWriteOutput appelé: 'RELAY-SET-1,3,0'
2020-03-30 22:31:15.161 (a) RELAY-SET-1,3,0						<-- At this point, no more command are accepted to the device
2020-03-30 22:31:16.162 (a) onCommand appelé.Unit: 4: Parameter 'Off', Level: 99, Hue:
2020-03-30 22:31:16.162 (a) KinconyWriteOutput appelé: 'RELAY-SET-1,4,0'
2020-03-30 22:31:16.162 (a) RELAY-SET-1,4,0
2020-03-30 22:31:17.163 (a) onCommand appelé.Unit: 5: Parameter 'Off', Level: 99, Hue:
2020-03-30 22:31:17.163 (a) KinconyWriteOutput appelé: 'RELAY-SET-1,5,0'
2020-03-30 22:31:17.163 (a) RELAY-SET-1,5,0
2020-03-30 22:31:18.165 (a) onCommand appelé.Unit: 6: Parameter 'Off', Level: 99, Hue:
2020-03-30 22:31:18.165 (a) KinconyWriteOutput appelé: 'RELAY-SET-1,6,0'
2020-03-30 22:31:18.165 (a) RELAY-SET-1,6,0
2020-03-30 22:31:19.166 (a) onCommand appelé.Unit: 7: Parameter 'Off', Level: 99, Hue:
2020-03-30 22:31:19.166 (a) KinconyWriteOutput appelé: 'RELAY-SET-1,7,0'
2020-03-30 22:31:19.166 (a) RELAY-SET-1,7,0
2020-03-30 22:31:20.168 (a) onCommand appelé.Unit: 8: Parameter 'Off', Level: 99, Hue:
2020-03-30 22:31:20.168 (a) KinconyWriteOutput appelé: 'RELAY-SET-1,8,0'
2020-03-30 22:31:20.168 (a) RELAY-SET-1,8,0
2020-03-30 22:31:21.169 (a) onMessage appelé, message reçu: 'b'RELAY-SET-1,1,0,OK\x00''
2020-03-30 22:31:21.170 (a) Envoi commande SET OK
2020-03-30 22:31:21.170 (a) Mise à jour affichage Domoticz - Sortie: 1, état:0
2020-03-30 22:31:21.286 (a) onMessage appelé, message reçu: 'b'RELAY-SET-1,2,0,OK\x00''
2020-03-30 22:31:21.286 (a) Envoi commande SET OK
2020-03-30 22:31:21.286 (a) Mise à jour affichage Domoticz - Sortie: 2, état:0 
And the onCommand and KinconyWriteOutput functions

Code: Select all

    def onCommand(self, Unit, Command, Level, Hue):
        """
        Voir si cette fonction est appelée lors d'un dispositif esclave ?
        """
        # Log
        Debug("onCommand appelé.Unit: " + str(Unit) + ": Parameter '" + str(Command) + "', Level: " + str(Level) + ", Hue:" + str(Hue))
        # Pilotage des sorties individuellement (les sorties sont systématiquement <= 32)
        if Unit <= 32:
            self.KinconyWriteOutput(str(Unit), str(Command))
        if Level == 99:		# only for test if a timer help
            time.sleep(1)
            
    def KinconyWriteOutput(self, Output, Value):
        """
        Ecriture d'une sortie de la carte
        """
        # Log
        Debug("KinconyWriteOutput appelé: 'RELAY-SET-1," + Output + "," + ("1" if Value == "On" else "0") + "'")
        # Envoi trame
        self.KinconyTx = "RELAY-SET-1," + Output + "," + ("1" if Value == "On" else "0")
        Debug(self.KinconyTx)
        self.KinconyCnx.Send(Message=self.KinconyTx)

I don't know why this delay, and why message is not sent (1 second is large enought to send the message).

If you have idea ...
Thx for attention
ramirez22
Posts: 9
Joined: Thursday 26 March 2020 2:30
Target OS: Raspberry Pi / ODroid
Domoticz version:
Contact:

Re: Python plugin: HC868 Kincony Ethernet I/O intreface

Post by ramirez22 »

Hello.

I finally change the plugin. I don't use anymore the domoticz.connection function, but return to the socket module of python.
All work well (for the outputs of the device) except 2 things :
1 - When I loss communication, the socket answer a timeout problem and domoticz interrupt the plugin himself : I cannot make a treatment by myself.
2 - At the start of domoticz, the plugin don't start.

At this time, I only ask help for the start problem.

Here, the plugin :

Code: Select all

#
# Author: Ramirez22
#
"""
<plugin key="KinCony_KC868" name="KinCony KC868 plugin" author="Ramirez22" version="1.0.0" wikilink="test" externallink="test">
    <description>
        <h2>KinCony KC868</h2><br/>
        Carte d'entrées/sorties sur protocole TCP<br/>
        <br/>
        <h3>Fonctionnalités</h3>
        <ul style="list-style-type:square">
            <li>Création des interrupteurs en fonction de la carte selectionnée (relais activables et entrées en lecture seule)</li>
            <li>Initialisation et dialogue avec la carte</li>
            <li>Raffraichissement de l'affichage après chaque commande</li>
            <li>Vérification de la connexion toute les 10 secondes</li>
        </ul>
        <h3>Modèles supportés</h3>
        <ul style="list-style-type:square">
            <li>HC868-H16 - 16 sorties, 8 entrées</li>
            <li>HC868-H4  -  4 sorties, 4 entrées</li>
        </ul>
        <h3>Configuration</h3>
        A venir...
        <ul style="list-style-type:square">
            <li>A venir ...</li>
        </ul>
    </description>
    <params>
        <param field="Address" label="Adresse IP" width="150px" required="true" />
        <param field="Port" label="Port" width="80px" required="true" default="4196" />
        <param field="Mode1" label="Modèle" width="250px" required="true">
            <options>
                <option label="HC868-H16 (16 sorties/8 entrées)" value="16 8" />
                <option label="HC868-H4 (4 sorties/4 entrées)" value="4 4" />
            </options>
        </param>
        <param field="Mode2" label="RAZ sorties à la création/mise à jour" width="85px" required="true">
            <options>
                <option label="Non" value="False" />
                <option label="Oui" value="True" default="true" />
            </options>
        </param>
        <param field="Mode3" label="RAZ sorties à la suppression/désactivation" width="85px" required="true">
            <options>
                <option label="Non" value="False" />
                <option label="Oui" value="True" default="true" />
            </options>
        </param>
        <param field="Mode6" label="Debug" width="85px">
            <options>
                <option label="Activé" value="True" />
                <option label="Silencieux" value="False" default="true" />
            </options>
        </param>
    </params>
</plugin>
"""


import Domoticz
import os
import socket
import time
from math import ceil
debug = False


class BasePlugin:
    enabled = False


    def __init__(self):
        return



    def onStart(self):
        global debug
        # Log
        if Parameters["Mode6"] == "True":
            debug = True
        else:
            debug = False
        Debug("onStart appelé")
        # Variables et paramètres divers
        self.host = Parameters["Address"]
        self.port = int(Parameters["Port"])
        self.heartbeat_count = 1
        self.HEARTBEAT_MAX = 3
        self.nb_sorties, self.nb_entrees = (int(x) for x in Parameters["Mode1"].split())
        # Création des dispositifs (si besoin)
        if (len(Devices) == 0):
            Debug("Création dispositifs. Nombre d'entrées : " + str(self.nb_entrees) + ", nombre de sorties : " + str(self.nb_sorties))
            # Sorties
            for sortie in range(1, self.nb_sorties + 1):
                Domoticz.Device(Unit=sortie, Name="Relais " + str(sortie), TypeName="Switch", Used=1).Create()
            # Entrées
            for entree in range(1, self.nb_entrees + 1):
                Domoticz.Device(Unit=(entree+32), Name="Entree " + str(entree), Type=244, Subtype=73, Switchtype=2, Used=1).Create()
        # ~ if debug:
            # ~ DumpConfigToLog()
        # Connexion avec la carte
        self.connexion_TCP = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        self.connexion_TCP.settimeout(2)
        self.connexion_TCP.connect((self.host,self.port))	<------------------------------- HERE IS THE PROBLEM LINE
        Debug("Tentative de connexion avec la carte Kincony IP:" + self.host)
        msg_recu = self.KinconyScan()
        if ("RELAY-SCAN_DEVICE-CHANNEL_" in msg_recu) and (",OK" in msg_recu):
            Debug("Esclave présent, tentative de comm")
            msg_recu = self.KinconyTest()
            if ("HOST-TEST-START" in msg_recu):
                Debug("Comm OK")
        elif ("RELAY-SCAN_DEVICE-CHANNEL_" in msg_recu) and (",ERREUR" in msg_recu):
            Debug("Erreur de comm")
            return
        # Remise à zéro des sorties à la connexion ?
        if Parameters["Mode2"] == "True":
            Debug("Remise à zéro des sorties")
            valeurs_sorties = list()
            if self.nb_sorties == 32:
                valeurs_sorties = (0,0,0,0)
            elif self.nb_sorties == 16:
                valeurs_sorties = (0,0)
            else:
                valeurs_sorties.append(0)
            msg_recu = str(self.KinconyWriteOutputs(*valeurs_sorties))
            if ("RELAY-SET_ALL-1," in msg_recu) and (",ERROR" in msg_recu):
                Debug("Erreur de remise à zéro des sorties")
                return
        self.UpdateDomoticz(True, True)



    def onStop(self):
        # Log
        Debug("onStop appelé")
        # Remise à zéro des sorties à la suppression ?
        if Parameters["Mode3"] == "True":
            Debug("Remise à zéro des sorties")
            valeurs_sorties = list()
            if self.nb_sorties == 32:
                valeurs_sorties = (0,0,0,0)
            elif self.nb_sorties == 16:
                valeurs_sorties = (0,0)
            else:
                valeurs_sorties.append(0)
            msg_recu = str(self.KinconyWriteOutputs(*valeurs_sorties))
            if ("RELAY-SET_ALL-1," in msg_recu) and (",ERROR" in msg_recu):
                Debug("Erreur de remise à zéro des sorties")
                return
        self.UpdateDomoticz(True, True)
        # Fermeture du socket
        self.connexion_TCP.close()
        


    def onConnect(self, Connection, Status, Description):
        # Log
        Debug("onConnect appelé.")



    def onMessage(self, Connection, Data):
        # Traitement du message reçu
        self.KinconyRx = str(Data)
        # ~ Debug("onMessage appelé, message reçu: '" + self.KinconyRx + "'")
        Domoticz.Log("onMessage appelé")
        return



    def onCommand(self, Unit, Command, Level, Hue):
        Debug("onCommand appelé")
        Debug("Unit: " + str(Unit) + ": Parameter '" + str(Command) + "', Level: " + str(Level) + ", Hue:" + str(Hue))



    def onNotification(self, Name, Subject, Text, Status, Priority, Sound, ImageFile):
        # Log
        Debug("onNotification appelé")
        # ~ Debug("Name: " + Name + ",Subject: " + Subject + ",Text: " + Text + ",Status: " + Status + ",Priority: " + str(Priority) + ",Sound: " + Sound + ",Image: " + ImageFile)



    def onDisconnect(self, Connection):
        # Log
        Debug("onConnect appelé")
        # ~ Debug("onDisconnect appelé. Kincony IP:" + self.host)



    def onHeartbeat(self):
        # Log
        Debug("onHeartbeat appelé")
        """
        Debug("onHeartbeat appelé (compteur = " + str(self.heartbeat_count) + "/3)")
        Debug("nValue :" + str(Devices[1].nValue) + " sValue :" + Devices[1].sValue)
        if self.heartbeat_count >= self.HEARTBEAT_MAX:
            self.heartbeat_count = 1
            # Demande de mise à jour de Domoticz 
            self.maj_sorties_domoticz = True
            self.maj_entrees_domoticz = True
            # Test de connexion NON FONCTIONNEL car pas de timeout sur les commandes
            # Appel test de comm
            self.KinconyTest()
        self.heartbeat_count += 1
        """
        
        
    def KinconyScan(self):
        # Log
        Debug("KinconyScan - Appel : 'RELAY-SCAN_DEVICE-NOW'")
        # Envoi trame
        KinconyTx = "RELAY-SCAN_DEVICE-NOW"
        self.connexion_TCP.sendto(KinconyTx.encode(), (self.host,self.port))
        msg_recu = self.connexion_TCP.recv(256)
        msg_recu.decode()
        Debug("KinconyScan - Message reçu:'" + str(msg_recu) + "'")
        return str(msg_recu)

    
    def KinconyTest(self):
        # Log
        Debug("KinconyTest - Appel : 'RELAY-TEST-NOW'")
        # Envoi trame
        KinconyTx = "RELAY-TEST-NOW"
        self.connexion_TCP.sendto(KinconyTx.encode(), (self.host,self.port))
        msg_recu = self.connexion_TCP.recv(256)
        msg_recu.decode()
        Debug("KinconyTest -Message reçu:'" + str(msg_recu) + "'")
        return str(msg_recu)

        
        
    def KinconyReadInputs(self):
        # Log
        Debug("KinconyReadInputs - Appel: 'RELAY-GET_INPUT-1'")
        # Envoi trame
        KinconyTx = "RELAY-GET_INPUT-1"
        self.connexion_TCP.sendto(KinconyTx.encode(), (self.host,self.port))
        msg_recu = self.connexion_TCP.recv(256)
        msg_recu.decode()
        Debug("KinconyReadInputs - Message recu:" + str(msg_recu))
        return str(msg_recu)



    def KinconyReadOutputs(self):
        # Log
        Debug("KinconyReadOutputs - Appel : 'RELAY-STATE-1'")
        # Envoi trame
        KinconyTx = "RELAY-STATE-1"
        self.connexion_TCP.sendto(KinconyTx.encode(), (self.host,self.port))
        msg_recu = self.connexion_TCP.recv(256)
        msg_recu.decode()
        Debug("KinconyReadOutputs - Message reçu:'" + str(msg_recu) + "'")
        return str(msg_recu)
        
        
        
        
    def KinconyWriteOutput(self, Output, Value):
        # Log
        Debug("KinconyWriteOutput - Appel")
        # Envoi trame
        KinconyTx = "RELAY-SET-1," + Output + "," + ("1" if Value == "On" else "0")
        Debug("KinconyWriteOutput - Envoi :'" + KinconyTx + "'")
        self.connexion_TCP.send(KinconyTx.encode())
        msg_recu = self.connexion_TCP.recv(256)
        msg_recu.decode()
        Debug("KinconyWriteOutput - Message reçu:'" + str(msg_recu) + "'")
        return str(msg_recu)
        
        
        
        
    def KinconyWriteOutputs(self, *Value):
        # Log
        Debug("KinconyWriteOutputs - Appel. Paramètres : " + str(Value))
        # Contrôle cohérence paramètres / nombre de mots de sorties de la carte (nombre de sorties / 8. Si < à 8, résultat doit être égal à 1)
        Debug("KinconyWriteOutputs - Controle nb de sorties: len(Value)=" + str(len(Value)) + ", int(self.nb_sorties / 8)=" + str(ceil(self.nb_sorties / 8)))
        if len(Value) != ceil(self.nb_sorties / 8):
            Debug("KinconyWriteOutputs - Erreur : nombre de paramètres incohérents")
            return
        # Calcul des valeurs à transmettre
        KinconyTx = "RELAY-SET_ALL-1,"
        for i in range(0,len(Value)):
            KinconyTx = KinconyTx + str(Value[i]) + ","
        KinconyTx = KinconyTx[:-1]
        Debug("KinconyWriteOutputs - Envoi : '" + KinconyTx + "'")
        self.connexion_TCP.send(KinconyTx.encode())
        msg_recu = self.connexion_TCP.recv(256)
        msg_recu.decode
        Debug("KinconyWriteOutputs - Message reçu:'" + str(msg_recu) + "'")
        return str(msg_recu)




    def UpdateDomoticz(self, Inputs, Outputs):
        # Log
        Debug("UpdateDomoticz - Appel (MaJ entrées = " + ("oui" if Inputs else "Non") + " MaJ sorties = " + ("oui" if Outputs else "Non") + ")")
        if Inputs:
            # Mise à jour des entrées
            msg_recu = self.KinconyReadInputs()
            # Traitement GET entrées (lecture des entrées et mise à jour Domoticz)
            if ("RELAY-GET_INPUT-1," in msg_recu) and (",OK" in msg_recu):
                Debug("UpdateDomoticz - Réception état entrées OK " + msg_recu)
                start = msg_recu.find("RELAY-GET_INPUT-1,") + len("RELAY-GET_INPUT-1,")
                end = msg_recu.find(",OK")
                # Extraction valeur décimale des entrées (self.KinconyRx[start:end])
                # transformation en binaire et inversion des bits (^255), suppression du '0b' ([2:]
                # et complétion du mot avec des 0 pour obtenir un mot de 8 bits (zfill(8))
                etat_entrees = bin((int(msg_recu[start:end])^255))[2:].zfill(8) 
                no_bit = 7
                for entree in range(33, self.nb_entrees + 33):
                    if Devices[int(entree)].nValue != int(etat_entrees[no_bit]):
                        Debug("UpdateDomoticz - Discordance valeur entrée " + str(entree) + ", mise à jour")
                        Devices[int(entree)].Update(nValue = int(etat_entrees[no_bit]), sValue = "Open" if etat_entrees[no_bit] == 1 else "Closed")
                    no_bit -= 1
            elif ("RELAY-GET_INPUT-1," in msg_recu) and (",ERROR" in msg_recu):
                Domoticz.Log("UpdateDomoticz - Erreur réception entrée: " + msg_recu)
                return
            else:
                ("UpdateDomoticz - Erreur non définie 'Mise à jour des entrées'")
                return
        # Mise à jour des sorties
        if Outputs:
            msg_recu = self.KinconyReadOutputs()
            # Traitement STATE des sorties (lecture des sorties et mise à jour Domoticz)
            if ("RELAY-STATE-1," in msg_recu) and (",OK" in msg_recu):
                Debug("UpdateDomoticz - Réception état sorties OK : '" + msg_recu + "'")
                start = msg_recu.find("RELAY-STATE-1,") + len("RELAY-STATE-1,")
                end = msg_recu.find(",OK")
                mots = list()
                mots = (msg_recu[start:end].split(","))
                mots.reverse()
                nb_mots = len(mots)
                if nb_mots == 4:
                    for mot_en_cours in range(4):
                        etat_sorties = bin(int(mots[mot_en_cours]))[2:].zfill(8)
                        no_bit = 7
                        for sortie in range(1+(mot_en_cours*8), 9+(mot_en_cours*8)):
                            if Devices[int(sortie)].nValue != int(etat_sorties[no_bit]):
                                Debug("UpdateDomoticz - Discordance valeur sortie " + str(sortie) + ", mise à jour")
                                Devices[int(sortie)].Update(nValue = int(etat_sorties[no_bit]), sValue = "On" if etat_sorties[no_bit] == 1 else "Off")
                            no_bit -= 1
                            
                elif nb_mots == 2:
                    for mot_en_cours in range(2):
                        etat_sorties = bin(int(mots[mot_en_cours]))[2:].zfill(8)
                        no_bit = 7
                        for sortie in range(1+(mot_en_cours*8), 9+(mot_en_cours*8)):
                            if Devices[int(sortie)].nValue != int(etat_sorties[no_bit]):
                                Debug("UpdateDomoticz - Discordance valeur sortie " + str(sortie) + ", mise à jour")
                                Devices[int(sortie)].Update(nValue = int(etat_sorties[no_bit]), sValue = "On" if etat_sorties[no_bit] == 1 else "Off")
                            no_bit -= 1
                else:
                    etat_sorties = bin(int(mots[0]))[2:].zfill(8)
                    no_bit = 7
                    for sortie in range(1, self.nb_sorties+1):
                        if Devices[int(sortie)].nValue != int(etat_sorties[no_bit]):
                            Debug("UpdateDomoticz - Discordance valeur sortie " + str(sortie) + ", mise à jour")
                            Devices[int(sortie)].Update(nValue = int(etat_sorties[no_bit]), sValue = "On" if etat_sorties[no_bit] == 1 else "Off")
                        no_bit -= 1
                
            elif ("RELAY-STATE-1," in msg_recu) and (",ERROR" in msg_recu):
                Domoticz.Log("UpdateDomoticz - Erreur réception état sorties " + msg_recu)
                return
                

global _plugin
_plugin = BasePlugin()

def onStart():
    global _plugin
    _plugin.onStart()

def onStop():
    global _plugin
    _plugin.onStop()

def onConnect(Connection, Status, Description):
    global _plugin
    _plugin.onConnect(Connection, Status, Description)

def onMessage(Connection, Data):
    global _plugin
    _plugin.onMessage(Connection, Data)

def onCommand(Unit, Command, Level, Hue):
    global _plugin
    _plugin.onCommand(Unit, Command, Level, Hue)

def onNotification(Name, Subject, Text, Status, Priority, Sound, ImageFile):
    global _plugin
    _plugin.onNotification(Name, Subject, Text, Status, Priority, Sound, ImageFile)

def onDisconnect(Connection):
    global _plugin
    _plugin.onDisconnect(Connection)

def onHeartbeat():
    global _plugin
    _plugin.onHeartbeat()

    ## Generic helper functions
def DumpConfigToLog():
    for x in Parameters:
        if Parameters[x] != "":
            Domoticz.Log( "'" + x + "':'" + str(Parameters[x]) + "'")
    Domoticz.Debug("Device count: " + str(len(Devices)))
    for x in Devices:
        Domoticz.Log("Device:           " + str(x) + " - " + str(Devices[x]))
        Domoticz.Log("Device ID:       '" + str(Devices[x].ID) + "'")
        Domoticz.Log("Device Name:     '" + Devices[x].Name + "'")
        Domoticz.Log("Device nValue:    " + str(Devices[x].nValue))
        Domoticz.Log("Device sValue:   '" + Devices[x].sValue + "'")
        Domoticz.Log("Device LastLevel: " + str(Devices[x].LastLevel))
    return

def Debug(text):
    global debug
    #Domoticz.Log("Entrée dans méthode Debug")
    if (debug):
        Domoticz.Log(text)
I put a mark where the problem occurs, but all the plugin is affected : the 'Debug("onStart appelé")' call before the problematic line doesn't be executed.

And the log error during the start (I have 2 devices: '16 sorties' and 'test'):

Code: Select all

2020-04-09 08:57:32.973 Error: (16 sorties) 'onStart' failed 'OSError'.
2020-04-09 08:57:32.973 Error: (16 sorties) ----> Line 553 in '/home/pi/domoticz/plugins/KinCony/plugin.py', function onStart
2020-04-09 08:57:32.973 Error: (16 sorties) ----> Line 111 in '/home/pi/domoticz/plugins/KinCony/plugin.py', function onStart
2020-04-09 08:57:32.977 Error: (test) 'onStart' failed 'OSError'.
2020-04-09 08:57:32.977 Error: (test) ----> Line 553 in '/home/pi/domoticz/plugins/KinCony/plugin.py', function onStart
2020-04-09 08:57:32.977 Error: (test) ----> Line 111 in '/home/pi/domoticz/plugins/KinCony/plugin.py', function onStart
The error occurs only during the first start. If I go to 'Setup'-'Hardware' and just 'update' the device (without any modification), the start function work perfectly (I have error for the stop function because I try to close the socket but it don't opened, so error is logical).

I try to put time.sleep (1 second) before the socket.connect(...), but thant don't have effect.

My apologies for my bad english, I'm French and wasn't a good student... I hope I'm understandable :D
If you have any idea, I'll be interrested.

Thanks for help.
User avatar
Dnpwwo
Posts: 820
Joined: Sunday 23 March 2014 9:00
Target OS: Raspberry Pi / ODroid
Domoticz version: Beta
Location: Melbourne, Australia
Contact:

Re: KC868 Plugin development

Post by Dnpwwo »

ramirez22 wrote: Monday 30 March 2020 23:06 Hello all.

I continue to develop my plugin for the KC868 device.
I finally can do some functional things:
  • Can pilot output
  • The board check at the first launch the state of the device (if some outputs are ON, set the correct value of Domoticz)
  • Inputs are half functional (device problem : I have information when a change low to high occur, but no information when high to low)
  • Each heartbeat time, check the state of inputs/outputs (in case of power loss on the device, outputs switch to off). Better way should be check if device always online, but it's for later
I have a problem with scenes. I want to make a general stop. So I build a scene with all my outputs to OFF.
But that don't work as expected :
1 or 2 outputs swith off, with a long delay, and that's all.
This is the log:

Code: Select all

2020-03-30 22:31:13.132 Activating Scene/Group: [test]					<-- Start of the scene
2020-03-30 22:31:13.134 Activating Scene/Group Device: a - Relais 1 (Off)		<-- First device OFF
2020-03-30 22:31:13.157 (a) onCommand appelé.Unit: 1: Parameter 'Off', Level: 99, Hue:	<-- onCommand call for first output
2020-03-30 22:31:13.157 (a) KinconyWriteOutput appelé: 'RELAY-SET-1,1,0'		<-- subfuction 'KinconyWriteOutput' call
2020-03-30 22:31:13.157 (a) RELAY-SET-1,1,0						<-- message sent throw the eth0
2020-03-30 22:31:13.184 Activating Scene/Group Device: a - Relais 2 (Off)		<-- The scene continue 
2020-03-30 22:31:13.235 Activating Scene/Group Device: a - Relais 3 (Off)
2020-03-30 22:31:13.285 Activating Scene/Group Device: a - Relais 4 (Off)
2020-03-30 22:31:13.336 Activating Scene/Group Device: a - Relais 5 (Off)
2020-03-30 22:31:13.386 Activating Scene/Group Device: a - Relais 6 (Off)
2020-03-30 22:31:13.437 Activating Scene/Group Device: a - Relais 7 (Off)
2020-03-30 22:31:13.487 Activating Scene/Group Device: a - Relais 8 (Off)
2020-03-30 22:31:13.131 Status: User: Admin initiated a scene/group command
2020-03-30 22:31:14.159 (a) onCommand appelé.Unit: 2: Parameter 'Off', Level: 99, Hue:	<-- onCommand call for 2nd output
2020-03-30 22:31:14.159 (a) KinconyWriteOutput appelé: 'RELAY-SET-1,2,0'
2020-03-30 22:31:14.159 (a) RELAY-SET-1,2,0						<-- message sent throw the eth0, but the result occur 7 seconds later
2020-03-30 22:31:15.160 (a) onCommand appelé.Unit: 3: Parameter 'Off', Level: 99, Hue:	<-- onCommand call for 2nd output
2020-03-30 22:31:15.161 (a) KinconyWriteOutput appelé: 'RELAY-SET-1,3,0'
2020-03-30 22:31:15.161 (a) RELAY-SET-1,3,0						<-- At this point, no more command are accepted to the device
2020-03-30 22:31:16.162 (a) onCommand appelé.Unit: 4: Parameter 'Off', Level: 99, Hue:
2020-03-30 22:31:16.162 (a) KinconyWriteOutput appelé: 'RELAY-SET-1,4,0'
2020-03-30 22:31:16.162 (a) RELAY-SET-1,4,0
2020-03-30 22:31:17.163 (a) onCommand appelé.Unit: 5: Parameter 'Off', Level: 99, Hue:
2020-03-30 22:31:17.163 (a) KinconyWriteOutput appelé: 'RELAY-SET-1,5,0'
2020-03-30 22:31:17.163 (a) RELAY-SET-1,5,0
2020-03-30 22:31:18.165 (a) onCommand appelé.Unit: 6: Parameter 'Off', Level: 99, Hue:
2020-03-30 22:31:18.165 (a) KinconyWriteOutput appelé: 'RELAY-SET-1,6,0'
2020-03-30 22:31:18.165 (a) RELAY-SET-1,6,0
2020-03-30 22:31:19.166 (a) onCommand appelé.Unit: 7: Parameter 'Off', Level: 99, Hue:
2020-03-30 22:31:19.166 (a) KinconyWriteOutput appelé: 'RELAY-SET-1,7,0'
2020-03-30 22:31:19.166 (a) RELAY-SET-1,7,0
2020-03-30 22:31:20.168 (a) onCommand appelé.Unit: 8: Parameter 'Off', Level: 99, Hue:
2020-03-30 22:31:20.168 (a) KinconyWriteOutput appelé: 'RELAY-SET-1,8,0'
2020-03-30 22:31:20.168 (a) RELAY-SET-1,8,0
2020-03-30 22:31:21.169 (a) onMessage appelé, message reçu: 'b'RELAY-SET-1,1,0,OK\x00''
2020-03-30 22:31:21.170 (a) Envoi commande SET OK
2020-03-30 22:31:21.170 (a) Mise à jour affichage Domoticz - Sortie: 1, état:0
2020-03-30 22:31:21.286 (a) onMessage appelé, message reçu: 'b'RELAY-SET-1,2,0,OK\x00''
2020-03-30 22:31:21.286 (a) Envoi commande SET OK
2020-03-30 22:31:21.286 (a) Mise à jour affichage Domoticz - Sortie: 2, état:0 
And the onCommand and KinconyWriteOutput functions

Code: Select all

    def onCommand(self, Unit, Command, Level, Hue):
        """
        Voir si cette fonction est appelée lors d'un dispositif esclave ?
        """
        # Log
        Debug("onCommand appelé.Unit: " + str(Unit) + ": Parameter '" + str(Command) + "', Level: " + str(Level) + ", Hue:" + str(Hue))
        # Pilotage des sorties individuellement (les sorties sont systématiquement <= 32)
        if Unit <= 32:
            self.KinconyWriteOutput(str(Unit), str(Command))
        if Level == 99:		# only for test if a timer help
            time.sleep(1)
            
    def KinconyWriteOutput(self, Output, Value):
        """
        Ecriture d'une sortie de la carte
        """
        # Log
        Debug("KinconyWriteOutput appelé: 'RELAY-SET-1," + Output + "," + ("1" if Value == "On" else "0") + "'")
        # Envoi trame
        self.KinconyTx = "RELAY-SET-1," + Output + "," + ("1" if Value == "On" else "0")
        Debug(self.KinconyTx)
        self.KinconyCnx.Send(Message=self.KinconyTx)

I don't know why this delay, and why message is not sent (1 second is large enought to send the message).

If you have idea ...
Thx for attention
Couple of things:
  • You shouldn't sleep in a callback because it stalls all Python Plugins for the entire sleep time
  • The Domoticz.Connection 'send' function has a 'Delay' parameter that allows you to submit the message now to be sent later. This is there because some devices get over run and need time to process messages
Another way of handling this if your device respondes to commands is to queue messages in a list in your plugin and check the queue in the onMessage callback. If the queue has entries after your device has responded then you can send the next one.
The reasonable man adapts himself to the world; the unreasonable one persists to adapt the world to himself. Therefore all progress depends on the unreasonable man. George Bernard Shaw
voyo
Posts: 24
Joined: Monday 17 February 2020 19:16
Target OS: Raspberry Pi / ODroid
Domoticz version: beta
Location: Poland
Contact:

Re: Python plugin: HC868 Kincony Ethernet I/O intreface

Post by voyo »

ramirez22 wrote: Thursday 26 March 2020 7:32 I'm new in developing under Domoticz and python language, but it's exciting and I learn many things.
I developed a python script for acting on Kincony KC868-H16 ethernet board (16 outputs and 8 inputs).
hello,
ramirez22 - can you share your script ?
I just bought these Kincony devices (KC868-H32 + terminal board), want to play with it & domoticz :D

best regards
ramirez22
Posts: 9
Joined: Thursday 26 March 2020 2:30
Target OS: Raspberry Pi / ODroid
Domoticz version:
Contact:

Re: Python plugin: HC868 Kincony Ethernet I/O intreface

Post by ramirez22 »

Hello,

This is the script of the latest version.

Code: Select all

#
# Author: Ramirez22
#
"""
<plugin key="KinCony_KC868" name="KinCony KC868 plugin" author="Ramirez22" version="1.0.0" 
externallink="https://www.kincony.com/product/relay-controller">
    <description>
        <h2>KinCony KC868</h2><br/>
        Carte d'entrées/sorties sur protocole TCP<br/>
        <br/>
        <h3>Fonctionnalités</h3>
        <ul style="list-style-type:square">
            <li>Création des interrupteurs en fonction de la carte selectionnée (relais activables et entrées en lecture seule)</li>
            <li>Initialisation et dialogue avec la carte</li>
            <li>Raffraichissement de l'affichage après chaque commande</li>
            <li>Scrutation des entrées avec fréquence réglable</li>
        </ul>
        <h3>Modèles supportés</h3>
        <ul style="list-style-type:square">
            <li>HC868-H16 - 16 sorties, 8 entrées</li>
            <li>HC868-H4  -  4 sorties, 4 entrées</li>
        </ul>
        <h3>Configuration</h3>
        <ul style="list-style-type:square">
            <li>Adresse IP / Port</li>
        </ul>
        Saisir l'adresse du dispositif esclave ainsi que le port de communication (par défaut 4196)<br/>
        <br/>
        <ul style="list-style-type:square">
            <li>Modèle</li>
        </ul>
        Choisir le modèle de carte<br/>
        <br/>
        <ul style="list-style-type:square">
            <li>RAZsortie à la création / mise à jour / sortie / désactivation</li>
        </ul>
        Remise à zéro de toutes les sorties à la création, à la mise à jour, lors de la suppression ou de la désactivation<br/>
        <br/>
        <ul style="list-style-type:square">
            <li>Nb de dispositifs virtuels</li>
        </ul>
        Création de dispositifs virtuels. Il est possible de passer diretement des commandes via le champ 'description' du 
        dispositif. Les commandes possibles sont:<br/>
        - RELAY-SET_ALL-1,Octet3,Octet2,Octet1,Octet0 (cartes avec 32 sorties)<br/>
        - RELAY-SET_ALL-1,Octet1,Octet0 (cartes avec 16 sorties)<br/>
        - RELAY-SET_ALL-1,Octet0 (cartes avec 8 sorties et moins)<br/>
        - RELAY-SET_ONLY,Mask3,Octet3,Mask2,Octet2,Mask1,Octet1,Mask0,Octet0 (cartes avec 32 sorties)<br/>
        - RELAY-SET_ONLY,Mask1,Octet1,Mask0,Octet0 (cartes avec 16 sorties)<br/>
        - RELAY-SET_ONLY,Mask0,Octet0 (cartes avec 8 sorties et moins)<br/>
        La commande RELAY-SET_ALL-1 force toutes les sorties à la valeur décimale spécifiée (0 à 255)<br/>
        La commande RELAY-SET_ONLY, change uniquement les sorties dont le mask est à 1 (0 à 255), les autres 
        sorties restent inchangées.<br/>
        <br/>
        <ul style="list-style-type:square">
            <li>Fréquence de scrutation des entrées</li>
        </ul>
        Plus la fréquence est élevée, plus la capture des entrées est rapide et plus les évènements rapides peuvent être 
        capturés. Cependant, la charge du réseau et du CPU augmente d'autant... A adapter en fonction de vos besoins.<br/>
        <br/>
        Lien vers un site commercial : <a href="https://kincony.aliexpress.com/store/group/Smart-Controller/807891_259382457.html?spm=a2g0w.12010612.1000001.12.33545853zn9vxT" target="_blank">Aliexpress</a>
    </description>
    <params>
        <param field="Address" label="Adresse IP" width="150px" required="true" />
        <param field="Port" label="Port" width="80px" required="true" default="4196" />
        <param field="Mode1" label="Modèle" width="250px" required="true">
            <options>
                <option label="HC868-H16 (16 sorties/8 entrées)" value="16 8" />
                <option label="HC868-H4 (4 sorties/4 entrées)" value="4 4" />
            </options>
        </param>
        <param field="Mode2" label="RAZ sorties à la création/mise à jour" width="85px" required="true">
            <options>
                <option label="Non" value="False" />
                <option label="Oui" value="True" default="true" />
            </options>
        </param>
        <param field="Mode3" label="RAZ sorties à la suppression/désactivation" width="85px" required="true">
            <options>
                <option label="Non" value="False" />
                <option label="Oui" value="True" default="true" />
            </options>
        </param>
        <param field="Mode4" label="Nb de dispositifs virtuels" width="85px"  default="0"/>
        <param field="Mode5" label="Fréquence de scrutation des entrées" width="250px" required="true">
            <options>
                <option label="5 fois par secondes (200 ms)" value="2" />
                <option label="4 fois par secondes (250 ms)" value="3" default="true" />
                <option label="Env. 3 fois par secondes (300 ms)" value="4" />
                <option label="2 fois par secondes (500 ms)" value="8" />
            </options>
        </param>
        <param field="Mode6" label="Debug" width="85px">
            <options>
                <option label="Activé" value="True" />
                <option label="Silencieux" value="False" default="true" />
            </options>
        </param>
    </params>
</plugin>
"""

import Domoticz
import os, sys
import socket
import time
import threading
import select
from math import ceil

debug = False

class BasePlugin:

    def __init__(self):
        # ~ self.connexion_TCP = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        # ~ self.connexion_TCP.settimeout(1)
        self.checkInputs = threading.Thread(name="ThreadCheckInputs", target = BasePlugin.KinconyCheckInputs, args=(self,))
        return


    def onStart(self):
        """
        Appelée à la création du matériel
        Création des dispositifs d'entrées et de sorties + lancement de la communication
        """
        global debug
        # Variables et paramètres divers
        self.host = Parameters["Address"]
        self.port = int(Parameters["Port"])
        # ~ self.heartbeat_count = 1
        # ~ self.HEARTBEAT_MAX = 3
        self.nb_sorties, self.nb_entrees = (int(x) for x in Parameters["Mode1"].split())
        self.connexion_ok = False
        # Log
        if Parameters["Mode6"] == "True":
            debug = True
        else:
            debug = False
        Debug("onStart appelé")
        # Création des dispositifs (si besoin) 
        if (len(Devices) == 0):
            Debug("onStart - Création dispositifs. Nombre d'entrées : " + str(self.nb_entrees) + ", nombre de sorties : " + str(self.nb_sorties))
            # Sorties
            for sortie in range(1, self.nb_sorties + 1):
                Domoticz.Device(Unit=sortie, Name="Relais " + str(sortie), TypeName="Switch", Used=1).Create()
            # Entrées
            for entree in range(1, self.nb_entrees + 1):
                Domoticz.Device(Unit=(entree+32), Name="Entree " + str(entree), Type=244, Subtype=73, Switchtype=2, Used=1).Create()
            # S'il y a des dispositifs "virtuels"
            Nb_dispositifs_virtuels = int(Parameters["Mode4"])
            if Nb_dispositifs_virtuels > 0:
                Debug("onStart - Création de " + str(Nb_dispositifs_virtuels) + " dispositifs virtuels")
                for virtuel in range(1, Nb_dispositifs_virtuels + 1):
                    Domoticz.Device(Unit=(virtuel+64), Name="Virtuel " + str(virtuel), Type=244, Subtype=73, Switchtype=9, Used=1).Create()
        if debug:
            DumpConfigToLog()
        self.connexion_ok = self.KinconyConnexion()
        # ~ # Connexion avec la carte
        # ~ self.connexion_TCP.connect((self.host,self.port))
        # ~ Debug("onStart - Tentative de connexion avec la carte Kincony IP:" + self.host)
        # ~ msg_recu = self.KinconyScan()
        # ~ if ("RELAY-SCAN_DEVICE-CHANNEL_" in msg_recu) and (",OK" in msg_recu):
            # ~ Debug("onStart - Esclave présent, tentative de communication")
            # ~ msg_recu = self.KinconyTest()
            # ~ if ("OK" in msg_recu):
                # ~ Domoticz.Status("onStart - Communication OK avec la carte Kincony IP:" + self.host)
            # ~ else:
                # ~ Domoticz.Error("onStart - Erreur de communication: '" + msg_recu + "'")
                # ~ return
        # ~ else:
            # ~ Domoticz.Error("onStart - Erreur de communication: '" + msg_recu + "'")
            # ~ return
        # Remise à zéro des sorties à la connexion
        if self.connexion_ok:
            if Parameters["Mode2"] == "True":
                Debug("onStart - Remise à zéro des sorties")
                valeurs_sorties = list()
                if self.nb_sorties == 32:
                    valeurs_sorties = (0,0,0,0)
                elif self.nb_sorties == 16:
                    valeurs_sorties = (0,0)
                else:
                    valeurs_sorties.append(0)
                msg_recu = str(self.KinconyWriteAllOutputs(*valeurs_sorties))
            self.UpdateDomoticz(True, True)
            # Surveillance des entrées
            self.stop_thread = False
            self.checkInputs.start()


    def onStop(self):
        # Log
        Debug("onStop appelé")
        # Liste des threads en cours
        for thread in threading.enumerate():
            Debug("onStop - Le thread '" + thread.name + "' est toujours actif")
        # Fermeture du thread
        self.stop_thread = True
        Debug("onStop - Envoi de la commande d'arrêt du thread")
        # Attente que les thread soient fermés
        while (threading.active_count() > 1):
            for thread in threading.enumerate():
                if (thread.name != threading.current_thread().name):
                    Domoticz.Log("onStop - '"+thread.name+"' est toujours en éxécution. Attente fin du thread")
            time.sleep(1.0)
        # Remise à zéro des sorties à la suppression ?
        if self.connexion_ok:
            if Parameters["Mode3"] == "True":
                Debug("onStop - Remise à zéro des sorties")
                valeurs_sorties = list()
                if self.nb_sorties == 32:
                    valeurs_sorties = (0,0,0,0)
                elif self.nb_sorties == 16:
                    valeurs_sorties = (0,0)
                else:
                    valeurs_sorties.append(0)
                msg_recu = str(self.KinconyWriteAllOutputs(*valeurs_sorties))
            self.UpdateDomoticz(True, True)
            # Fermeture du socket
            self.connexion_TCP.close()
        
        
    def onCommand(self, Unit, Command, Level, Hue):
        """
        Appelée à chaque changement d'un Device. Si le device en question est d'un 
        type virtuel (n° > à 64), lecture de la commande passée en Description du Device.
        Les valeurs sont des entiers (entre 0 et 255, correspondant à la valeur d'un mot de 8 bits)
        - RELAY-SET_ALL-1,... fonctionnement standar d'une commande de ce type
        - RELAY-SET_ONLY,<mask>,<value>,... le masque permet de ne pas modifier les bits à 0 
          (ils conserveront leur état).
        """
        # Log
        Debug("onCommand appelé. Unit: " + str(Unit) + ": Parameter '" + str(Command) + "', Level: " + str(Level) + ", Hue:" + str(Hue))
        # Si la connexion n'est pas active, retour
        if not self.connexion_ok:
            Domoticz.Error("Commande impossible, connexion avec carte perdue")
            return
        # Si la connexion est OK, arrêt du thread de vérification des entrées
        self.stop_thread = True
        self.checkInputs.join()
        # Pilotage des sorties individuellement (les sorties sont systématiquement <= 32)
        if Unit <= 32:
            msg_recu = self.KinconyWriteOutput(str(Unit), str(Command))
        # Pilotage des sorties par device 'virtuel' 
        if Unit >= 65:
            valeurs_sorties = list()
            commande = Devices[Unit].Description
            Domoticz.Log("onCommand - Ordre direct : " + commande)
            # Si la commande est une commande de type SET-RELAY_ALL
            if "RELAY-SET_ALL-1," in commande:
                commande = commande.replace("RELAY-SET_ALL-1,","")
                valeurs_sorties = commande.split(',')
                msg_recu = str(self.KinconyWriteAllOutputs(*valeurs_sorties))
            # Pilotage de sorties spécifiques. Données à transmettre : masque, valeur (x nombre de mots)
            elif "RELAY-SET_ONLY," in commande:
                valeurs_initiales = list()
                valeurs_demandees = list()
                masque = list()
                temp = list()
                # Récupération de la valeur des sorties initiales
                msg_recu = self.KinconyReadOutputs()
                valeurs_initiales = [int(i) for i in msg_recu.split(',')]
                #Debug("onCommand - Valeurs initiales sorties : " + str(valeurs_initiales))
                nb_mots= len(valeurs_initiales)
                # Extraction des paramètres
                commande = commande.replace("RELAY-SET_ONLY,","")
                temp = commande.split(',')  # temp contient une liste de x masque, y valeurs
                # Vérification si le nombre de paramètres est conforme et tri des paramètres
                nb_param = len(temp)
                if (nb_param == 2 or nb_param == 4 or nb_param == 8) and nb_param//2 == nb_mots:
                    for i in range(0,nb_param,2):
                        masque.append(int(temp[i]))
                        valeurs_demandees.append(int(temp[i+1]))
                else:
                    Domoticz.Error("onCommand - Erreur : nombre de paramètres passés à " + Devices[Unit].Name + " incorrect")
                    return
                # Calcul des modifications à apporter aux sorties
                for i in range(0,nb_mots):
                    valeurs_sorties.append((masque[i] & valeurs_demandees[i]) | (~masque[i] & valeurs_initiales[i]))
                Debug("onCommand - Valeurs de sorties calculées : " + str(valeurs_sorties))
                msg_recu = str(self.KinconyWriteAllOutputs(*valeurs_sorties))
            else:
                Debug("onCommand - Commande inconnue passée à " + Devices[Unit].Name)
        # Mise à jour de Domoticz
        self.UpdateDomoticz(False, True)
        # Remise en route du thread de surveillance des entrées
        self.stop_thread = False
        self.checkInputs = threading.Thread(name="ThreadCheckInputs", target = BasePlugin.KinconyCheckInputs, args=(self,))
        self.checkInputs.start()


    def onHeartbeat(self):
        """
        Appel régulier pour tester si le thread de scrutation des entrées est toujours actif
        Le relance si ce n'est pas le cas
        """
        # Log
        Debug("onHeartbeat appelé")
        if not self.connexion_ok:
            self.connexion_ok = self.KinconyConnexion()
        if not self.connexion_ok:
            return
        # Vérification de l'activation du thread de vérification de l'état des entrées
        if not self.checkInputs.is_alive():
            # Liste des threads en cours
            for thread in threading.enumerate():
                Debug("onHeartbeat - Thread actifs : '" + thread.name + "'")
            Domoticz.Error("onHeartbeat - Le thread n'existe plus, tentative de redémarrage")
            self.stop_thread = False
            self.checkInputs = threading.Thread(name="ThreadCheckInputs", target = BasePlugin.KinconyCheckInputs, args=(self,))
            self.checkInputs.start()

        
    def KinconyScan(self):
        """
        Initialisation de la communication avec la carte Kincony
        Correspond à l'envoi de la trame TCP:
        - RELAY-SCAN_DEVICE-NOW (réponse doit contenir OK)
        """
        # Log
        Debug("KinconyScan - Appel : 'RELAY-SCAN_DEVICE-NOW'")
        # Envoi trame
        KinconyTx = "RELAY-SCAN_DEVICE-NOW"
        self.connexion_TCP.sendto(KinconyTx.encode(), (self.host,self.port))
        # Lecture du message de retour, relance si le message correspond à une ALARM de changement d'état d'une entrée
        while True:
            try:
                msg_recu = self.connexion_TCP.recv(256)
            except socket.timeout:
                Domoticz.Error("KinconyScan - Erreur de communication")
                self.connexion_TCP.close()
                self.connexion_ok = False
                return ("ERROR")
            except Exception as err:
                Domoticz.Error("KinconyScan - Erreur :" + str(err))
                self.connexion_TCP.close()
                self.connexion_ok = False
                return ("ERROR")
            msg_recu.decode()
            msg_recu = str(msg_recu)
            if "RELAY-ALARM" in msg_recu:
                Debug("KinconyScan - relecture confirmation")
                continue
            elif ("RELAY-SCAN_DEVICE" in msg_recu) and (",OK" in msg_recu):
                start = msg_recu.find("RELAY-SCAN_DEVICE")
                end = (msg_recu.find(",OK"))+3
                Debug("KinconyScan - Réception OK :'" + msg_recu[start:end] + "'")
                return(msg_recu[start:end])
            else:
                Domoticz.Error("KinconyReadInputs - Erreur")
                return ("ERROR")

    
    def KinconyTest(self):
        """
        Initialisation de la communication avec la carte Kincony
        Correspond à l'envoi de la trame RELAY-TEST-NOW 
        Réponse doit contenir HOST-TEST-START
        """
        # Log
        Debug("KinconyTest - Appel : 'RELAY-TEST-NOW'")
        # Envoi trame
        KinconyTx = "RELAY-TEST-NOW"
        self.connexion_TCP.sendto(KinconyTx.encode(), (self.host,self.port))
        # Lecture du message de retour, relance si le message correspond à une ALARM de changement d'état d'une entrée
        while True:
            try:
                msg_recu = self.connexion_TCP.recv(256)
            except socket.timeout:
                Domoticz.Error("KinconyTest - Erreur de communication")
                self.connexion_TCP.close()
                self.connexion_ok = False
                return ("ERROR")
            except Exception as err:
                Domoticz.Error("KinconyTest - Erreur :" + str(err))
                self.connexion_TCP.close()
                self.connexion_ok = False
                return ("ERROR")
            msg_recu.decode()
            msg_recu = str(msg_recu)
            if "RELAY-ALARM" in msg_recu:
                Debug ("KinconyTest - Relecture confirmation")
                continue
            elif "HOST-TEST-START" in msg_recu:
                Debug("KinconyTest - Communication OK")
                return ("OK")
            else:
                Domoticz.Error("KinconyTest - Erreur : " + msg_recu)
                return("ERROR")

        
    def KinconyReadInputs(self):
        """
        Lecture des entrées de la carte. Retourne la valeur de l'octet 
        d'entrée de la carte si OK (sous forme d'entier), "ERROR" sinon
        """
        # Log
        Debug("KinconyReadInputs - Appel: 'RELAY-GET_INPUT-1'")
        # Envoi trame
        KinconyTx = "RELAY-GET_INPUT-1"
        self.connexion_TCP.sendto(KinconyTx.encode(), (self.host,self.port))
        # Lecture du message de retour, relance si le message correspond à une ALARM de changement d'état d'une entrée
        while True:
            try:
                msg_recu = self.connexion_TCP.recv(256)
            except socket.timeout:
                Domoticz.Error("KinconyReadInputs - Erreur de communication")
                self.connexion_TCP.close()
                self.connexion_ok = False
                return ("ERROR")
            except Exception as err:
                Domoticz.Error("KinconyReadInputs - Erreur :" + str(err))
                self.connexion_TCP.close()
                self.connexion_ok = False
                return ("ERROR")

            msg_recu.decode()
            msg_recu = str(msg_recu)
            if "RELAY-ALARM" in msg_recu:
                Debug("KinconyReadInputs - relecture confirmation")
                continue
            elif ("RELAY-GET_INPUT-1," in msg_recu) and (",OK" in msg_recu):
                start = msg_recu.find("RELAY-GET_INPUT-1,")
                end = msg_recu.find(",OK")
                Debug("KinconyReadInputs - Réception état entrées OK :'" + msg_recu[start:end+3] + "'")
                start = start + len("RELAY-GET_INPUT-1,")
                return(msg_recu[start:end])
            else:
                Domoticz.Error("KinconyReadInputs - Erreur '" + msg_recu + "'")
                return ("ERROR")


    def KinconyReadOutputs(self):
        """
        Lecture des sorties de la carte. Retourne la valeur de chaque octet de sortie
        de la carte (entier, sous la forme octet3, octet2, octet1, octet0 pour une carte
        de 32 sorties, octet1, octet0 pour une carte à 16 sorties, octet0 pour les cartes
        avec 8 sorties et moins) si OK, sinon "ERROR"
        """
        # Log
        Debug("KinconyReadOutputs - Appel : 'RELAY-STATE-1'")
        # Envoi trame
        KinconyTx = "RELAY-STATE-1"
        self.connexion_TCP.sendto(KinconyTx.encode(), (self.host,self.port))
        # Lecture du message de retour, relance si le message correspond à une ALARM de changement d'état d'une entrée
        while True:
            try:
                msg_recu = self.connexion_TCP.recv(256)
            except socket.timeout:
                Domoticz.Error("KinconyReadOutputs - Erreur de communication")
                self.connexion_TCP.close()
                self.connexion_ok = False
                return ("ERROR")
            except Exception as err:
                Domoticz.Error("KinconyReadOutputs - Erreur :" + str(err))
                self.connexion_TCP.close()
                self.connexion_ok = False
                return ("ERROR")
            msg_recu.decode()
            msg_recu = str(msg_recu)
            if "RELAY-ALARM" in msg_recu:
                Debug("KinconyReadOutputs - Relecture confirmation")
                continue
            elif ("RELAY-STATE-1," in msg_recu) and (",OK" in msg_recu):
                start = msg_recu.find("RELAY-STATE-1,")
                end = msg_recu.find(",OK")
                Debug("KinconyReadOutputs - Réception état sorties OK :'" + msg_recu[start:end+3] + "'")
                start = start + len("RELAY-STATE-1,")
                return(msg_recu[start:end])
            else:
                Domoticz.Error("KinconyReadOutputs - Erreur '" + msg_recu + "'")
                return ("ERROR")
        
        
    def KinconyWriteOutput(self, Output, Value):
        """
        Ecriture d'une sortie de la carte.
        Paramètres : Output : entier correspondant au numéro de la sortie à activer
                     Value  : 0 ou 1
        Renvoie "OK" si tout s'est bien passé, ERROR dans le cas contraire
        """
        # Log
        #Debug("KinconyWriteOutput - Appel")
        # Envoi trame
        KinconyTx = "RELAY-SET-1," + Output + "," + ("1" if Value == "On" else "0")
        Debug("KinconyWriteOutput - Envoi :'" + KinconyTx + "'")
        self.connexion_TCP.sendto(KinconyTx.encode(), (self.host,self.port))
        # Lecture du message de retour, relance si le message correspond à une ALARM de changement d'état d'une entrée
        while True:
            try:
                msg_recu = self.connexion_TCP.recv(256)
            except socket.timeout:
                Domoticz.Error("KinconyWriteOutput - Erreur de communication")
                self.connexion_TCP.close()
                self.connexion_ok = False
                return ("ERROR")
            except Exception as err:
                Domoticz.Error("KinconyWriteOutput - Erreur :" + str(err))
                self.connexion_TCP.close()
                self.connexion_ok = False
                return ("ERROR")
            msg_recu.decode()
            msg_recu = str (msg_recu)
            if "RELAY-ALARM" in msg_recu:
                Debug ("KinconyWriteOutput - Relecture confirmation")
                continue
            elif ("RELAY-SET-1," in msg_recu) and (",OK" in msg_recu):
                Debug("KinconyWriteOutput - Pilotage sortie OK")
                return("OK")
            else:
                Domoticz.Error("KinconyWriteOutput - Erreur")
                return ("ERROR")
        
        
    def KinconyWriteAllOutputs(self, *Value):
        """
        Ecriture de toutes les sorties de la carte. 
        Valeurs passées en décimal correspondant à la valeur binaire d'un octet (0 à 255)
        - Si 32 sorties : 4 octets dans l'ordre 4, 3, 2, 1
        - Si 16 sorties : 2 octets dans l'ordre 2, 1
        - Si 8, 4 ou 2 sorties : 1 seul octet
        """
        # Contrôle cohérence paramètres / nombre de mots de sorties de la carte (nombre de sorties / 8. Si < à 8, résultat doit être égal à 1)
        if len(Value) != ceil(self.nb_sorties / 8):
            Domoticz.Error("KinconyWriteAllOutputs - Erreur : nombre de paramètres incohérents")
            return
        # Calcul des valeurs à transmettre
        KinconyTx = "RELAY-SET_ALL-1,"
        for i in range(0,len(Value)):
            KinconyTx = KinconyTx + str(Value[i]) + ","
        KinconyTx = KinconyTx[:-1]
        Debug("KinconyWriteAllOutputs - Envoi : '" + KinconyTx + "'")
        self.connexion_TCP.sendto(KinconyTx.encode(), (self.host,self.port))
        # Lecture du message de retour, relance si le message correspond à une ALARM de changement d'état d'une entrée
        while True:
            try:
                msg_recu = self.connexion_TCP.recv(256)
            except socket.timeout:
                Domoticz.Error("KinconyWriteAllOutputs - Erreur de communication")
                self.connexion_TCP.close()
                self.connexion_ok = False
                return ("ERROR")
            except Exception as err:
                Domoticz.Error("KinconyWriteAllOutputs - Erreur :" + str(err))
                self.connexion_TCP.close()
                self.connexion_ok = False
                return ("ERROR")
            msg_recu.decode()
            msg_recu = str (msg_recu)
            if "RELAY-ALARM" in msg_recu:
                Debug ("KinconyWriteAllOutputs - Relecture confirmation")
                continue
            elif ("RELAY-SET_ALL-1," in msg_recu) and (",OK" in msg_recu):
                Debug("KinconyWriteAllOutputs - Pilotage sorties OK")
                return("OK")
            else:
                Domoticz.Error("KinconyWriteAllOutputs - Erreur")
                return ("ERROR")


    def UpdateDomoticz(self, Inputs, Outputs):
        """
        Mise à jour de l'état des entrées/sorties de l'interface dans Domoticz
        Accepte en paramètre 2 valeurs booléennes pour vérifier les entrées et/ou les sorties
        """
        # Log
        Debug("UpdateDomoticz - Appel (MaJ entrées = " + ("oui" if Inputs else "Non") + " MaJ sorties = " + ("oui" if Outputs else "Non") + ")")
        if Inputs:
            # Mise à jour des entrées
            msg_recu = self.KinconyReadInputs()
            # Traitement GET entrées (lecture des entrées et mise à jour Domoticz)
            if ("ERROR" in msg_recu):
                Domoticz.Error("UpdateDomoticz - Erreur réception état entrées: '" + msg_recu + "'")
                return
            Debug("UpdateDomoticz - Réception état entrées OK :'" + msg_recu + "'")
            # transformation en binaire et inversion des bits (^255), suppression du '0b' ([2:]
            # et complétion du mot avec des 0 pour obtenir un mot de 8 bits (zfill(8))
            etat_entrees = bin(int(msg_recu)^255)[2:].zfill(8)
            no_bit = 7
            # Pour chaque bit du mot d'entrée lu, si la valeur diffère de Domoticz, mise à jour
            for entree in range(33, self.nb_entrees + 33):
                if Devices[int(entree)].nValue != int(etat_entrees[no_bit]):
                    Domoticz.Status("Entrée " + str(entree) + " ('" + Devices[int(entree)].Name + "') à " + str(etat_entrees[no_bit]))
                    Debug("UpdateDomoticz - Discordance valeur entrée " + str(entree) + ", mise à jour")
                    Devices[int(entree)].Update(nValue = int(etat_entrees[no_bit]), sValue = "Open" if etat_entrees[no_bit] == 1 else "Closed")
                no_bit -= 1
        # Mise à jour des sorties
        if Outputs:
            msg_recu = self.KinconyReadOutputs()
            # Traitement STATE des sorties (lecture des sorties et mise à jour Domoticz)
            if ("ERROR" in msg_recu):
                Domoticz.Error("UpdateDomoticz - Erreur réception état sorties: '" + msg_recu + "'")
                return
            Debug("UpdateDomoticz - Réception état sorties OK : '" + msg_recu + "'")
            mots = list()
            mots = msg_recu.split(",")
            mots.reverse()
            nb_mots = len(mots)
            # Extraction du nombre de mots renvoyés par la carte
            if nb_mots != 1:
                for mot_en_cours in range(nb_mots):
                    etat_sorties = bin(int(mots[mot_en_cours]))[2:].zfill(8)
                    no_bit = 7
                    for sortie in range(1+(mot_en_cours*8), 9+(mot_en_cours*8)):
                        if Devices[int(sortie)].nValue != int(etat_sorties[no_bit]):
                            Debug("UpdateDomoticz - Discordance valeur sortie " + str(sortie) + ", mise à jour")
                            Devices[int(sortie)].Update(nValue = int(etat_sorties[no_bit]), sValue = "On" if etat_sorties[no_bit] == 1 else "Off")
                        no_bit -= 1
            else:
                etat_sorties = bin(int(mots[0]))[2:].zfill(8)
                no_bit = 7
                for sortie in range(1, self.nb_sorties+1):
                    if Devices[int(sortie)].nValue != int(etat_sorties[no_bit]):
                        Debug("UpdateDomoticz - Discordance valeur sortie " + str(sortie) + ", mise à jour")
                        Devices[int(sortie)].Update(nValue = int(etat_sorties[no_bit]), sValue = "On" if etat_sorties[no_bit] == 1 else "Off")
                    no_bit -= 1
                

    def KinconyCheckInputs(self):
        """
        Vérification des entrées.
        La boucle à la fin de la vérification permet d'interrompre le cycle plus rapidement si une commande 
        de pilotage de sortie est reçue (onCommand). La fréquence de scrutation des entrées est paramétrable
        dans les options du plugin. 
        """
        Debug("KinconyCheckInputs - Lancement du thread")
        self.frequence_check = int(Parameters["Mode5"])
        Debug("Fréquence de raffraichissement : " + str(int(self.frequence_check) * 50) + " ms + temps de cycle d'environ 100 ms")
        while not self.stop_thread:
            self.UpdateDomoticz(True, False)
            for i in range(0,self.frequence_check):
                time.sleep(0.05)
                if self.stop_thread:
                    break
        Debug("KinconyCheckInputs - Arrêt du thread")


    def KinconyConnexion(self):
        # Connexion avec la carte
        self.connexion_TCP = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        self.connexion_TCP.settimeout(2)

        try:
            self.connexion_TCP.connect((self.host,self.port))
        except socket.timeout:
            Domoticz.Error("KinconyConnexion - Erreur de communication")
            self.connexion_TCP.close()
            return False
        except Exception as err:
            Domoticz.Error("KinconyConnexion - Erreur :" + str(err))
            self.connexion_TCP.close()
            return False
        Debug("KinconyConnexion - Tentative de connexion avec la carte Kincony IP:" + self.host)
        msg_recu = self.KinconyScan()
        if ("RELAY-SCAN_DEVICE-CHANNEL_" in msg_recu) and (",OK" in msg_recu):
            Debug("onStart - Esclave présent, tentative de communication")
            msg_recu = self.KinconyTest()
            if ("OK" in msg_recu):
                Domoticz.Status("KinconyConnexion - Communication OK avec la carte Kincony IP:" + self.host)
                return True
            else:
                Domoticz.Error("KinconyConnexion - Erreur de communication: '" + msg_recu + "'")
                return False
        else:
            Domoticz.Error("KinconyConnexion - Erreur de communication: '" + msg_recu + "'")
            return False


global _plugin
_plugin = BasePlugin()

def onStart():
    global _plugin
    _plugin.onStart()

def onStop():
    global _plugin
    _plugin.onStop()

def onMessage(Connection, Data):
    global _plugin
    _plugin.onMessage(Connection, Data)

def onCommand(Unit, Command, Level, Hue):
    global _plugin
    _plugin.onCommand(Unit, Command, Level, Hue)

def onHeartbeat():
    global _plugin
    _plugin.onHeartbeat()

    ## Generic helper functions
def DumpConfigToLog():
    for x in Parameters:
        if Parameters[x] != "":
            Domoticz.Log( "'" + x + "':'" + str(Parameters[x]) + "'")
    Domoticz.Debug("Device count: " + str(len(Devices)))
    for x in Devices:
        Domoticz.Log("Device:           " + str(x) + " - " + str(Devices[x]))
        Domoticz.Log("Device ID:       '" + str(Devices[x].ID) + "'")
        Domoticz.Log("Device Name:     '" + Devices[x].Name + "'")
        Domoticz.Log("Device nValue:    " + str(Devices[x].nValue))
        Domoticz.Log("Device sValue:   '" + Devices[x].sValue + "'")
        Domoticz.Log("Device LastLevel: " + str(Devices[x].LastLevel))
    return

def Debug(text):
    global debug
    if (debug):
        Domoticz.Log(text)
Sorry but comments are in french ... Google Translate is your friend :D

Settings in few words :
Communication
You must set the IP and the port of the card (4196 default).

Type
There is 2 types of card in the script : 4 inputs/4 outputs (HC868-H4) ou 8 inputs/16 outputs (HC868-H16).

Reset
There is 2 RESET settings : during the creation process (or update) and during the remove. I put it to Yes, cause I want to force outputs to OFF when I create the device or when I remove it. I'm not sure this function is fully working.... :?

Virtual devices
Its possible to create virtual device attached to the HC868. This kind of device permit to send directly command to the device through the Description field:
Force outputs to value in Word(x)
- RELAY-SET_ALL-1,Word3,Word2,Word1,Word0 (Device with 32 outputs for futur development)
- RELAY-SET_ALL-1,Word1,Word0 (Device with 16 outputs)
- RELAY-SET_ALL-1,Word0 (Device with 8 outputs or less)

Force only outputs witch mask(x) in ON to the Word(x) value
- RELAY-SET_ONLY,Mask3,Word3,Mask2,Word2,Mask1,Word1,Mask0,Word0 (Device with 32 outputs for futur development)
- RELAY-SET_ONLY,Mask1,Word1,Mask0,Word0 (Device with 16 outputs)
- RELAY-SET_ONLY,Mask0,Word0 (Device with 8 outputs or less)

Input scrutating frequency
It's possible to change the speed of the input scrutating function. I don't use the ALARM function of the card cause it's too slow for me. So I give the possibility to choose the speed (2, 3, 4 or 5 times each seconds). Of course, more ask by second = more network communication ... I set (for my use) to 4 times each second and never had problem...

Debug
Put some debug informations in the Domoticz log.

How to use
When you create the hardware, a number of devices are automaticaly created : 16 outputs (Unit 1 to 16), 8 inputs (Units 33 to 40) and a number of 'virtual devices' that you setup (Unit 65 to ...). There is an inversion between inputs state (close = off, open = on), I don't find how change that.
If you have virtual device, don't forget to put the command in the Description field.
Normaly, the heartbeat function check if all is alright (specific thread fo input reading, connxion ...). I never had problem with unplug or power off the device (all return to correct state automaticaly when power is back or network reconnect).

Of course, I recommend to you to make some test before put the HC868 in real :lol:

I hope this script can help you, tell me if you find error.

Regards
voyo
Posts: 24
Joined: Monday 17 February 2020 19:16
Target OS: Raspberry Pi / ODroid
Domoticz version: beta
Location: Poland
Contact:

Re: Python plugin: HC868 Kincony Ethernet I/O intreface

Post by voyo »

Thanks! I will give a try tonight.
Sorry but comments are in french ... Google Translate is your friend :D
merde :twisted:
I thought its only comments in code but its also interface :D I will have to translate just to start using :mrgreen:

Looks like good piece of code, good work! Is it working stable for you ? And what about hardware, any issues , especially after longer time ?
I'm not going to use it yet 'on production' , I will take probably few months till I will be using it actively, for now it will be just a playground on my desk.

I will probably do my own changes, improvements etc, do you mind ? I will share code later with you of course.
Finally, I will need more than only 32 relays, so I'm planning to buy more of these devices, depending if everything will be working ok.
ramirez22
Posts: 9
Joined: Thursday 26 March 2020 2:30
Target OS: Raspberry Pi / ODroid
Domoticz version:
Contact:

Re: Python plugin: HC868 Kincony Ethernet I/O intreface

Post by ramirez22 »

merde :twisted:
:lol:
I'm sorry, but I never translate code because when I post here, I was alone and don't think someone will be interested by it :roll:
Next time, I will code directly in English, I promise :lol:

Except this kind of error (and it's not a problem with this plugin):

Code: Select all

Error: CheckAuthToken(b46aa99c318032fa608613cf556e59c6_YzA0ZGI5NjAtYzNjNi00NjI2LWI2NzctYzE2NGMyYzVlOGI3) : session id not found
my log page is clear.

The last error was commnuication errors due to electrical shutdown or manual disconnection (I made some cleanning in the ethernet cables ... :D ) :

Code: Select all

2020-08-16 07:05:30.038 Error: (Arrosage) KinconyReadInputs - Erreur de communication
2020-08-16 07:05:30.038 Error: (Arrosage) UpdateDomoticz - Erreur réception état entrées: 'ERROR'
2020-08-16 07:05:33.148 Error: (Arrosage) KinconyConnexion - Erreur de communication
2020-08-16 07:05:43.315 Error: (Arrosage) KinconyConnexion - Erreur :[Errno 111] Connection refused
2020-08-16 07:05:52.997 Error: (Arrosage) onHeartbeat - Le thread n'existe plus, tentative de redémarrage
2020-08-16 07:05:55.546 Error: (Arrosage) KinconyReadInputs - Erreur de communication
2020-08-16 07:05:55.547 Error: (Arrosage) UpdateDomoticz - Erreur réception état entrées: 'ERROR'
Meanings:
- comm error
- error receiving input state
- thread killed, restarting
I don't know exactly why I had "Erno 111", it's not my program (but a plugin error... never occur again).

About the hardware, I have the Ethernet model and never had any problem with it.

I have 2 differents curents sources (24Vdc for water valves and 230Vac for pump) and not problem (I use a contactor and not active pump directly).

Of course, I will interested by all improvements you can done. This "plugin" is only a base and everybody can modify it.

Regards.
voyo
Posts: 24
Joined: Monday 17 February 2020 19:16
Target OS: Raspberry Pi / ODroid
Domoticz version: beta
Location: Poland
Contact:

Re: Python plugin: HC868 Kincony Ethernet I/O intreface

Post by voyo »

hi ramirez22,

Finally I had time for some more playing. FYI - I will be using this device "in production" probably in few months, or even a year (I'm building a house), so I'm not in much rush ;) Main purpose will be to control all lights (and other devices as well).

In general - almost everything works OKish. I was able to update relay outputs with Domoticz (but updating status of device in Domoticz when I changed state 'externally' , with button terminal in my case - was not working. I have HC868-H32, and only 16 relays and 8 inputs was added.
I have quite busy Domoticz server running on old RBPI device, so to be honest - it is putting some load when I run plugin with 200ms interval frequency to checking inputs.
Resetting (or not resetting) status of relays during startup/reset - is working well (you were worrying about that), in my case I had to set it to "no" - as I will be using it mainly to control lights, so I don't want to turn off lights during Domoticz restart.

What I did so far -
- uploaded and your code on Github, I hope you don't mind , let me know if I may make it public
- translated comments and interface to English , this will allow others to further improvements more easily ;)
- implemented correct support for HC868-H32 device (32 outputs, 6 inputs)
- added reaction (update state of relay in Domoticz) when relay state was changed externally (with terminal button or 433Mhz RC)
- added options to set checking input frequency to 5s and 10s

I don't like this pulling input status, but well - this is only way to make it work. I was playing a little with these inputs, and in my case device is reporting input status with quite random delay, from 0 to 4 seconds or sometimes (rarely) even not at all ! While when I checked input status manually (with RELAY-GET_INPUT order) it was always returning status correctly. I don't know the reason, IMHO this looks like a bug in firmware.

Update state of relays - I'm not happy with current implementation, because I made it very simply, in your heartbeat/UpdateDomoticz call, thus it have the same delay as checking inputs.

Also, I reached out to Kincony support, asking about this problem with unreliable and delayed RELAY-ALARM message, and whether we ask very quickly for GET_INPUT will not break anything in long run (like ie. wearing out flash memory). I will let you know if/when I get any reply from them.
kincony
Posts: 2
Joined: Thursday 19 November 2020 12:45
Target OS: Raspberry Pi / ODroid
Domoticz version:
Contact:

Re: Python plugin: HC868 Kincony Ethernet I/O intreface

Post by kincony »

hello. we have write the plugin for domoticz,that support 2,4,8,16,32 channel. you can try to test.
Image
Image

how to do see details: https://www.kincony.com/kc868-smart-con ... tform.html
ramirez22
Posts: 9
Joined: Thursday 26 March 2020 2:30
Target OS: Raspberry Pi / ODroid
Domoticz version:
Contact:

Re: Python plugin: HC868 Kincony Ethernet I/O intreface

Post by ramirez22 »

Hello Voyo,

Sorry for this late answer, I was really busy last month.
I'm please that my job can help you to have functional interface (with a lot of work to adapt it I think :D )

Thanks to performed some test and posted here results.

Of course, you can post it in Github and do every thing you want with this code :
- share is a good thing to everyone (I usually use shared code for my projects so it's a good thing if I can give too)
- my knoledge of programming is limited and if you can add features, that will be perfect !
- translate it is a great idea :lol: I will put comments in english now ;)

Me too, I wasn't fully satisfy by polling inputs, but I haven't choice cause ALARM function not work as I wish. I had contact with Kincony about a firmware upgrade. I bought interface for do it, but haven't more contact to have the updated firmware (but I don't know is upgrade is about this function). I will ask them one more time :?

As I can see, Kincony develop themselve a plugin. I will try it in futur, I have no time at this moment.
I don't know if I will add more Kincony device in my house : the product is very good but the price really increased in 1 years ... but the plugin exist now and maybe ... I don't know :?:

Thanks again for the job you do !

Regards
kincony
Posts: 2
Joined: Thursday 19 November 2020 12:45
Target OS: Raspberry Pi / ODroid
Domoticz version:
Contact:

Re: Python plugin: HC868 Kincony Ethernet I/O intreface

Post by kincony »

about the ALARM message , it need short input port at least >2 seconds will be enabled. Because input is for alarm sensor use. so it need >2S sustained level to avoid false alarm.
voyo
Posts: 24
Joined: Monday 17 February 2020 19:16
Target OS: Raspberry Pi / ODroid
Domoticz version: beta
Location: Poland
Contact:

Re: Python plugin: HC868 Kincony Ethernet I/O intreface

Post by voyo »

hello!
Apologize for very long reply... It is crazy time for me. Recently I'm looking on forums (like this) only when I need some particular info...


So as I have your permission Ramirez22 - here is published code, with public access https://github.com/voyo/Domoticz_Kincony_KC868_plugin

unfortunately I did not had time to test and compare with original Kincony plugin.
But I'm afraid, if they don't change this behavior with min. 2s of input as alarm - it will be useless for my need (and probably your as well).
And I will have to stick with pooling device for information about inputs.
voyo
Posts: 24
Joined: Monday 17 February 2020 19:16
Target OS: Raspberry Pi / ODroid
Domoticz version: beta
Location: Poland
Contact:

Re: Python plugin: HC868 Kincony Ethernet I/O intreface

Post by voyo »

kincony wrote: Friday 27 November 2020 16:43 about the ALARM message , it need short input port at least >2 seconds will be enabled. Because input is for alarm sensor use. so it need >2S sustained level to avoid false alarm.
Hello Kincony,
It is great we have you here ;)
I understand this 2s is for de-bounce purpose ?
Would you consider maybe changing this 2s input ? it seems very long time.
Maybe make it as variable in firmware, allowing user to change default 2s to something as low as 50ms
?
Post Reply

Who is online

Users browsing this forum: No registered users and 1 guest