Aller au contenu
Cardane

Utilisation du KLF-200

Recommended Posts

@Dragoniacs ah oui, l'API est très bien documentée maintenant. Au départ l'API n'était pas du tout officielle et donc non documentée. C'est en trouvant des bouts de code à gauche et à droite que je sis parvenu à faire le VD pour des actions simples.

Quand Velux a réalisé que pas mal de monde utilisait cette api, ils ont décidé de la revoir, de le compléter et de le documenter.

Problème, il n'a plus rien à voir avec l'ancien, et donc pas possible de simplement adapter mon VD, il faut tout réécrire, et je ne suis même pas certain que la HC2 supporte le protocole demandé.

J'ai trouvé des exemples en Python, mais comme je n'ai plus fait de développement  depuis 20 ans, je ne connais pas python :-)  donc pour l'instant je rame un peu :-)

 

Partager ce message


Lien à poster
Partager sur d’autres sites

@CardaneMerci pour tes réponses.

Mince alors, il va me falloir trouver un prgrammeur pour pouvoir aller plus loin.....

 

Je lance un appel à volontaire :D

Partager ce message


Lien à poster
Partager sur d’autres sites

@Cardane tu as ouvert un post sur le forum officiel ?

Moi j'ai envoyé un mail au support. Ils ont répondu que c'était une bonne idée et qu'ils transmettrai aux développeurs...

Partager ce message


Lien à poster
Partager sur d’autres sites

@Dragoniacsnon, ca fait longtemps que je ne ma fais plus d'illusion avec le support, et si même ils faisaient quelque chose, ce serait encore un plugin minimum, qui marchera une fois sur deux ;-)

Je suis en train de regarder comment faire, je vais bosser dessus ce weekend, c'est juste le temps qui me manque

Modifié par Cardane

Partager ce message


Lien à poster
Partager sur d’autres sites

@Cardane J'ai commencé à étudier les scripts de la nouvelle API. J'ai fait la mise à jour de mon KLF200 et j'ai commencé à tester.

 

Mon idée est de mettre le script python sur un serveur local et de le lancer par la HC2. Cela me parait plus simple que de le convertir en LUA, surtout avec mon niveau...

Donc j'ai refait le script exemple pour faire des tests. J'ai tout mis les modules python dans un seul et j'ai enlevé le code en trop. Pour les tests je mets le n° de la scène en dur, ensuite il faudra le traiter en paramètre.

 

Le problème maintenant, c'est comment démarrer ce script... J'essaie sur mon Synology mais sans succès pour l'instant... je n'y connais rien en Python, et je ne progresse pas tant vite.

 

Donc, si quelqu'un a des idées, je suis preneur :60:

 

Voici mon script python qui lance la scène n° 1 :

## ======================================================
## Script pour demarrer une scene de la box Velux KLF-200
## ------------------------------------------------------
## Version 0.1.1 - Last update : 03.11.18 by Christian R.
## ======================================================

import ssl, socket, time, struct
from time import sleep

# ===============================================================================
# Variables
# ===============================================================================

SCENE_ID        = 1

KLF200_ADDRESS  = "192.168.0.12"
PASSWORD        = "Velux123"
PORT            = 51200
LoopDelay       = 1

# ===============================================================================
# slip
# ===============================================================================

END     = b"\xC0"    # SLIP escape character as per RFC 1055
ESC     = b"\xDB"    # SLIP escape character as per RFC 1055
ESC_END = b"\xDC"    # SLIP escape character as per RFC 1055
ESC_ESC = b"\xDD"    # SLIP escape character as per RFC 1055


def slip_pack(inputFrame):    
    data = inputFrame
    data = data.replace(ESC, ESC + ESC_ESC)
    data = data.replace(END, ESC + ESC_END)
    return END + data + END

def slip_unpack(inputFrame):
    data = inputFrame
    if(data[0:1]==END and data[-1:]==END):
        data = data.replace(ESC + ESC_END, END)
        data = data.replace(ESC + ESC_ESC, ESC)
        return data[1:-1]
    else:
        print("Error: No SLIP frame!\n")
        return inputFrame # error -> return input

# ===============================================================================
# toolbox
# ===============================================================================

def getIndex(sourceDict, value):
    return (k for k, v in sourceDict.items() if v == value).__next__()

def toHex(s):
    return ":".join("{:02x}".format(c) for c in s)

# ===============================================================================
# ActivateScene
# ===============================================================================

def process_connection(conn):
    conn.settimeout(10.0) # 10 sec
    
    print("Send valid password")
    conn.write(bytes(ST_GW_PASSWORD_ENTER_REQ(PASSWORD)))
    print("Received: ", toHex(slip_unpack(conn.recv())), "\n")
    
    time.sleep(LoopDelay)

    print("Activate Scene with ID = ", SCENE_ID)
    conn.write(bytes(ST_GW_ACTIVATE_SCENE_REQ(bSceneID=SCENE_ID)))
    print("Received: ", toHex(slip_unpack(conn.recv())))
    

def main():
    sock = socket.socket(socket.AF_INET)
    sock.settimeout(10.0)

    context = ssl.SSLContext(ssl.PROTOCOL_TLS)
    context.check_hostname = False
    #accept self-signed certificate
    context.verify_mode = ssl.CERT_NONE

    conn = context.wrap_socket(sock, server_hostname=KLF200_ADDRESS)

    try:
        conn.connect((KLF200_ADDRESS, PORT))
        process_connection(conn)
    except BaseException as e:
        raise(e)
    finally:
        conn.close()

# ===============================================================================
# klf200api
# ===============================================================================

GW_ACTIVATE_SCENE_REQ                       =  0x0412
GW_ACTIVATE_SCENE_CFM                       =  0x0413

GW_PASSWORD_ENTER_REQ                       =  0x3000
GW_PASSWORD_ENTER_CFM                       =  0x3001

dictPriorityLevel = {
    0: 'Human Protection',
    1: 'Environment Protection',
    2: 'User Level 1',
    3: 'User Level 2',
    4: 'Comfort Level 1',
    5: 'Comfort Level 2',
    6: 'Comfort Level 3',
    7: 'Comfort Level 4',
}

dictCommandOriginator = {
    0x00: "LOCAL_USER",  # // User pressing button locally on actuator
    0x01: "USER",  # // User Remote control causing action on actuator
    0x02: "RAIN",  # // Sensor
    0x03: "TIMER",  # // Sensor
    0x04: "SECURITY",  # // SCD controlling actuator
    0x05: "UPS",  # // UPS unit
    0x06: "SFC",  # // Smart Function Controller
    0x07: "LSC",  # // Lifestyle Scenario Controller
    0x08: "SAAC",  # // Stand Alone Automatic Controls
    0x09: "WIND",  # // Wind detection
    0x10: "MYSELF",  # // Used when an actuator decides to move by itself
    0xFE: "AUTOMATIC_CYCLE",  # // Used in context with automatic cycle;
    0xFF: "EMERGENCY"  # // Used in context with emergency or security commands,
    # // -this command originator should never be disabled
}

dictVelocity = {
    0: 'DEFAULT',
    1: 'SILENT',
    2: 'FAST',
    255: 'VELOCITY_NOT_AVAILABLE', #Only used in status reply
}

# ===============================================================================
    
class ST_GW_FRAME:
    def __init__(self, Command):
        self.DataLength    = 0
        self.Command       = Command
        self.binary_output = b"";

    def __bytes__(self):
        self.binary_output = struct.pack("BB", 0, self.DataLength + 3)
        self.binary_output += struct.pack(">H", self.Command)
        self.binary_output += self.pack_data()
        self.binary_output += struct.pack("B", self.calc_crc())
        return slip_pack(self.binary_output)
    
    def calc_crc(self):
        crc = 0
        for sym in self.binary_output:
            crc = crc ^ int(sym)
        return crc

    def pack_data(self):
        return b""


class ST_GW_ACTIVATE_SCENE_REQ (ST_GW_FRAME):
    def __init__(self,
                 wSessionID         = 0x1234,
                 CommandOriginator  = 'USER',
                 PriorityLevel      = 'User Level 2',
                 bSceneID           = 0,
                 Velocity           = 'DEFAULT'):
        ST_GW_FRAME.__init__(self, GW_ACTIVATE_SCENE_REQ)
        self.DataLength         = 6
        self.wSessionID         = wSessionID
        self.bCommandOriginator = getIndex(dictCommandOriginator, CommandOriginator)
        self.bPriorityLevel     = getIndex(dictPriorityLevel, PriorityLevel)
        self.bSceneID           = bSceneID
        self.bVelocity          = getIndex(dictVelocity, Velocity)
        

    def pack_data(self):
        ret = struct.pack(">H", self.wSessionID)
        ret += bytes([self.bCommandOriginator])
        ret += bytes([self.bPriorityLevel])
        ret += bytes([self.bSceneID])
        ret += bytes([self.bVelocity])
        return ret


class ST_GW_PASSWORD_ENTER_REQ (ST_GW_FRAME):
    def __init__(self, Password):
        ST_GW_FRAME.__init__(self, GW_PASSWORD_ENTER_REQ)
        self.DataLength = 32
        self.Password   = Password

    def pack_data(self):
        binary_data = bytes(self.Password,encoding='ascii')
        binary_len = len(binary_data)
        ret = binary_data[:self.DataLength if binary_len > self.DataLength else binary_len]
        while binary_len < self.DataLength:
            ret += b'\x00'
            binary_len = binary_len + 1
        return ret

# ===============================================================================
# Start script 
# ===============================================================================
    
main()
print("Finished")

 

Partager ce message


Lien à poster
Partager sur d’autres sites

Je précise que ce script fonctionne très bien sur mon Mac depuis IDLE.

Je cherche des idées pour : quel est le meilleur endroit où poser le script afin de pouvoir le lancer depuis la HC2 avec le n° de la scène en paramètre.

  • J'ai un raspberry dans un tiroir
  • J'ai un mac mini qui pourrait faire serveur
  • J'ai un Synology avec Python installé dessus

Peut-être que @Steven, non sûr que Steven a une idée pour moi :D

 

Merci d'avance pour vos idées.

Partager ce message


Lien à poster
Partager sur d’autres sites

@ikillou il doit y avoir des exemples sur le forum de scripts qui sont démarrés à partir de la HC2 sur un Syno, je vais regarder dès que j'ai un peu de temps.

L'idéal pour moi est de convertir en LUA, mais comme je ne connais pas Python, faut déjà que je comprenne ce que ca fait...  mais passer par un script serait déjà un moindre mal

 

Par contre, si le script contient tes paramètres, je te conseille vivement de très vite changer le mot de passe du KLF car ils ont tous le même par défaut, donc si quelqu'un passe devant chez toi il n'aura aucun mal à ouvrir tes velux ;)

 

 

Partager ce message


Lien à poster
Partager sur d’autres sites

Je vois que ça avance :)

Je vous encourage à trouver un système juste en lua, ça m'arrangerait bien :60:

Je ne peux pas vous aider, je ne connais pas Python et je débute encore en lua.....

Partager ce message


Lien à poster
Partager sur d’autres sites
il y a 48 minutes, Cardane a dit :

@ikillou il doit y avoir des exemples sur le forum de scripts qui sont démarrés à partir de la HC2 sur un Syno, je vais regarder dès que j'ai un peu de temps.

L'idéal pour moi est de convertir en LUA, mais comme je ne connais pas Python, faut déjà que je comprenne ce que ca fait...  mais passer par un script serait déjà un moindre mal

 

Par contre, si le script contient tes paramètres, je te conseille vivement de très vite changer le mot de passe du KLF car ils ont tous le même par défaut, donc si quelqu'un passe devant chez toi il n'aura aucun mal à ouvrir tes velux ;)

 

 

:) j'ai mis le mot de passe par défaut dans le script ici comme exemple, non, ce n'est pas le miens, t'inquiète...

Démarrer le script depuis la HC2 n'est pas mon problème actuellement, ça viendra après :), je cherche des idées pour héberger le script Python... Je me suis battu cet après-midi avec mon Syno qui ne veux pas accepter le module SSL... je vais continuer... pfff

 

Partager ce message


Lien à poster
Partager sur d’autres sites

Salut @ikillou...  bon, j'ai essayé le script que tu m'as envoyé, mais ca ne marche pas...  je n'ai pas de message d'erreur spécifique, mais rien ne se passe...  j'ai 9 programmes enregistrés sur mon klf, mais aucun ne s'active en utilisant ce script....  tu as une idée ? le retour du du recv() est 00:01:00:04:00, mais je ne sais pas à quoi il correspond. Est-ce que tu as le même ?

Partager ce message


Lien à poster
Partager sur d’autres sites

Quand j'execute le script sur IDLE sur mon Mac, j'ai ce retour dans le shell :

 

Send valid password
Received:  00:04:30:01:00:35 

Activate Scene with ID =  1
Received:  00:06:04:13:00:12:34:37
Finished
>>> 

 

Par contre, sur syno, il ne fonctionne pas à cause de SSL. Le problème est de lui dire quel environnement Python il doit prendre, il prend souvent la version par défaut qui est la 2.7 sans SSL...

J'essaie de comprendre comment fonctionne Python que je ne connais pas...

Sinon, tu es sur PC ou Mac ?

 

Moi, j'ai installé la dernière version de Python depuis leur site, et SSL fonctionne directement sur Mac, mais je n'ai pas testé sur PC...

Partager ce message


Lien à poster
Partager sur d’autres sites

Je viens de tester sur un PC Win10 avec Python 3.71 64bits que je viens d'installer. ça fonctionne la même chose...

 

L'update du firmware a bien fonctionné sur ton KLF200 ? N'hésite pas à faire un reboot du KLF avant de faire des tests, car j'ai eu des problèmes de connexion et en fait après un reboot, c'était bon... pfff

Partager ce message


Lien à poster
Partager sur d’autres sites

oui, l'update du klf est ok, j'y ai accès via l'interface web comme avant.
Moi je reçois en premier lieu : 
00:04:30:01:01:34
et ensuite :
00:04:00:00:0c:08

je suis aussi sur Mac avec la dernière version d'IDLE, je vais vérifier si j'ai la dernière version de Python

Partager ce message


Lien à poster
Partager sur d’autres sites

même problème que toi sur Syno, je ne vois pas comment le diriger vers la version avec SSL

Partager ce message


Lien à poster
Partager sur d’autres sites
Il y a 9 heures, Cardane a dit :

oui, l'update du klf est ok, j'y ai accès via l'interface web comme avant.
Moi je reçois en premier lieu : 
00:04:30:01:01:34
et ensuite :
00:04:00:00:0c:08

je suis aussi sur Mac avec la dernière version d'IDLE, je vais vérifier si j'ai la dernière version de Python

Fais attention à ne plus être connecté dans l’interface web en même temps, car il y a toujours qu’une seule connexion simultanée...

Partager ce message


Lien à poster
Partager sur d’autres sites

Bon, je me suis remis dessus aujourd'hui. Cela fonctionne avec le script sur mon Mac serveur.

Je lance le script python depuis un VD sur la HC2, ça fonctionne parfaitement.

 

Sur le Synology, cela ne fonctionne toujours pas à cause du package SSL.

Je suis en train d'installer un Raspberry Pi, on verra comment ça fonctionne...

 

Je vous tiens au jus...

Partager ce message


Lien à poster
Partager sur d’autres sites

Bon, j'ai fait un nouveau script python que j'ai mis sur un raspberry. Cela fonctionne nickel.

Donc, j'ai fait une scène (ou un VD) sur la HC2 qui lance le script du raspberry en passant les paramètres IPKLF200, password, SceneID.

 

Cela fonctionne très bien, on peut voir le retour du script dans un debug.

 

@Cardane Si tu veux faire des tests, j'ai partagé mon raspberry sur le net, donc tu pourrais lancer mon script depuis le net. Il faudrait juste ouvrir un port TCP dans ton routeur afin que mon script puisse accéder à ton KLF...

Le script python se trouve en fichier joint. Sur le raspberry, j'ai installé Apache configuré pour accepter les scripts cgi (c'est pour cela que le script doit se trouver dans le répertoire "cgi-bin").

 

Il se lance comme ceci

http://192.168.xxx.xxx/cgi-bin/klf200.py?IP=192.168.xxx.xxx&PW=velux123&ID=1

L'URL de la version web de mon script n'est dispo que via MP, si désirée :2:

 

Depuis un VD, on peut faire un bouton par action comme ceci :

local ip_module = fibaro:get(fibaro:getSelfId(), "IPAddress")
KLF200 = Net.FHttp(ip_module)
response = KLF200:GET("/cgi-bin/klf200.py?IP=192.168.xxx.xxx&PW=velux123&ID=1")
fibaro:debug(response)
fibaro:call(fibaro:getSelfId(), "setProperty", "ui.Label1.value", "Véranda 1 monté...");	

 

Je reste à dispo pour tout complément d'information...

 

klf200.zip

Modifié par ikillou
script corrigé

Partager ce message


Lien à poster
Partager sur d’autres sites

Est ce que cela pourrait fonctionner en direct sur la HC2 sans raspberry ?

Partager ce message


Lien à poster
Partager sur d’autres sites

Oui, mais il faut convertir le script Python en Lua, ce qui n'est pas évident...

C'est pour cela que j'ai choisi la version simple pour l'instant...

Avec les bons dévs qu'il y a ici, on devrait y arriver.

 

Partager ce message


Lien à poster
Partager sur d’autres sites

@Dragoniacs j'ai commencé quelque chose, mais loin, très loin d'être fini, et pour le moment je suis en déplacement, donc pas moyen de faire avancer le truc. En plus, le script réalisé par @ikillou fonctionne bien mais j'ai toujours un code d'erreur en retour. J'ai pas eu le temps de faire de nouveaux tests, c'est un problème de mot de passe, mais je ne vois pas. Pour l'instant ca tourne sur mon Mac et un Raspberry, mais ce n'est pas la solution optimale.

 

En plus comme il faut faire du SSL, je ne sais plus si ca marche ou pas avec la HC2 ( @Lazer tu aurais l'info ? est-ce que maintenant on peut faire du SSL dans un VD et dans une scène ?) 

Comme je suis absent pour pas mal de temps, je ne pourrai m'y remettre que fin décembre...  on ne sait jamais, comme cadeau de Noël :D

Partager ce message


Lien à poster
Partager sur d’autres sites

Regarde le VD de l'aspirateur Xiaomi de @ADN182 je crois qu'il utilise du SSL, ça peut être une source d'inspiration :

 

 

 

 

Partager ce message


Lien à poster
Partager sur d’autres sites

Hello, 

 

Je me souviens plus si j'utilise la library SSL dans mon VD Xiaomi :huh: mais pk ne pas faire un appel HTTPS depuis une scène.  Il me semble que cela fonctionne.

 

Ou alors peut-être reverse proxy http to https à voir si ça fonctionne

 

J'utilse ce code qui fonctionne : 

fibaro:debug("start")
-- Appel HTTPS
local http = net.HTTPClient()
http:request("https://api.darksky.net/forecast/xxxxxx/5.901,5.41?lang=fr&units=ca&exclude=minutely,hourly,flags", {
	success = function(response)
		if response.status == 200 then
			if response.data and response.data ~= "" then
				--fibaro:debug('Retour : '.. response.data)
				local jsonTable = json.decode(response.data)
          		fibaro:setGlobal("DarkSkyJson", response.data)
 			else
				fibaro:debug("Error : empty response data")
			end
		else
			fibaro:debug("Erreur : status=" .. tostring(response.status) .." Message : " .. tostring(response.data))
		end
	end,
	error = function(err)
		fibaro:debug("Erreur : " .. err)
	end,
	options = {
		method = 'GET',
		timeout = 5000,
	}
})

 

 

Modifié par ADN182
  • Like 1

Partager ce message


Lien à poster
Partager sur d’autres sites

×