This forum uses cookies
This forum makes use of cookies to store your login information if you are registered, and your last visit if you are not. Cookies are small text documents stored on your computer; the cookies set by this forum can only be used on this website and pose no security risk. Cookies on this forum also track the specific topics you have read and when you last read them. Please confirm that you accept these cookies being set.

Somfy Gateway
#1
Hi,

Have anybody made an gateway to Somfy Tahoma ? im looking into how to control some indoor sunscreens.
Whats the best solution, API or?

BR Even Sundgot.
Reply
#2
+1

Intrested too for help integrating Somfy Open API with LM.

https://developer.somfy.com/apis-docs

PS: Can provide credentials if needed

BR
Geroege
Reply
#3
Hi, just wondering if anyone ever found a solution for this? I have a need to control some somfy blinds on a new project... Or alternatively is there any kind of gateway software/hardware to convert somfy tahoma to Mqtt, making control trivial from LM...

EDIT: So far I have this solution, but it involves a few steps... It's actually just the blinds I need to control rather than Tahoma itself. If the blind motors are the newer Somfy IO control models, It seems like the KLF-200 Gateway mentioned elsewhere on this form (a velux product but is IO-homecontrol  compatible, so will pair with the somfy motors also) might work in conjunction with this docker image that exposes it via MQTT..

It needs a bit more hardware than I'd like (to run the docker container...maybe a din rail mounted RPI...) but it might be easier than figuring out the KLF200s API, or converting the Home Assistant Python or Node JS examples to LUA... (which is beyond my coding abilities)

If anyone has a more elegant solution please shout!
Reply
#4
(29.03.2021, 20:05)Paddyb Wrote: Hi, just wondering if anyone ever found a solution for this? I have a need to control some somfy blinds on a new project... Or alternatively is there any kind of gateway software/hardware to convert somfy tahoma to Mqtt, making control trivial from LM...

EDIT: So far I have this solution, but it involves a few steps... It's actually just the blinds I need to control rather than Tahoma itself. If the blind motors are the newer Somfy IO control models, It seems like the KLF-200 Gateway mentioned elsewhere on this form (a velux product but is IO-homecontrol  compatible, so will pair with the somfy motors also) might work in conjunction with this docker image that exposes it via MQTT..

It needs a bit more hardware than I'd like (to run the docker container...maybe a din rail mounted RPI...) but it might be easier than figuring out the KLF200s API, or converting the Home Assistant Python or Node JS examples to LUA... (which is beyond my coding abilities)

If anyone has a more elegant solution please shout!

Hi, I'm currently trying to work with KLF-200, but it's kinda early version. Currently it authorizes with KLF-200 and recognizes next confirmations and notifications: GW_PASSWORD_ENTER_CFM, GW_GET_STATE_CFM, GW_GET_ALL_NODES_INFORMATION_CFM, GW_GET_ALL_NODES_INFORMATION_CFM, GW_GET_ALL_NODES_INFORMATION_NTF, GW_GET_NODE_INFORMATION_NTF, GW_GET_ALL_NODES_INFORMATION_FINISHED_NTF and GW_COMMANDS_SEND_CFM. Also there's a function for GW_COMMAND_SEND_REQ which controls devices.

Code:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
require('ssl') function slip(str)   if type(str) == 'string' then     str_table = {}     str:gsub('.', function(c) table.insert(str_table, string.byte(c)) end)   else     str_table = str   end   if str_table[1] ~= 192 then              -- to slip     temp_table = {192}     for _, v in ipairs(str_table) do       if v == 0xc0 then         table.insert(temp_table, 0xdb)         table.insert(temp_table, 0xdc)       elseif v == 0xdb then         table.insert(temp_table, 0xdb)         table.insert(temp_table, 0xdd)       else         table.insert(temp_table, v)       end     end     table.insert(temp_table, 192)   else                                     -- from slip     temp_table = {}     for i, v in ipairs(str_table) do       if v == 0xdb then         if str_table[i+1] == 0xdc then           table.insert(temp_table, 0xc0)         elseif str_table[i+1] == 0xdd then           table.insert(temp_table, 0xdb)         end       elseif v ~= 0xc0 then         table.insert(temp_table, v)       end     end   end   return temp_table end function create_frame(cmd, data, data_len)   b1 = bit.band(cmd,0xff)   b2 = bit.band(bit.rshift(cmd,8),0xff)   frame = {}   if type(data) == 'number' then     data = '' .. string.char(data)   end   if type(data) == 'string' then       data:gsub('.', function(c) table.insert(frame,string.byte(c)) end) -- Data   end   if data_len then     for i = #frame, data_len-1 do       table.insert(frame, 0)     end   end   --log('added data', frame)   frame = {b2, b1, unpack(frame)} -- Cmd   --log('added cmd', frame)   frame = {#frame+1, unpack(frame)} -- Len   --log('added len', frame)   frame = {0, unpack(frame)} -- Protocol ID   table.insert(frame, bit.bxor(unpack(frame))) -- Checksum   frame = slip(frame)   --log('added SLIP', frame)   str = string.char(unpack(frame))   loghex(str)   return str end function parse_frame(frame)   if type(frame) == 'string' then     frame = slip(frame)   end   protocolId = frame[1]   lenFrame = frame[2]   data_offset = 4   cmd = bit.lshift(frame[3], 8) + frame[4]   if cmd == 0x3001 then     log('GW_PASSWORD_ENTER_CFM', frame[5])   elseif cmd == 0x000d then     log('GW_GET_STATE_CFM', frame[data_offset + 1], frame[data_offset + 1])   elseif cmd == 0x203 then     log('GW_GET_ALL_NODES_INFORMATION_CFM', frame[data_offset + 1], frame[data_offset + 2])   elseif cmd == 0x201 then     log('GW_GET_NODE_INFORMATION_CFM', frame[data_offset + 1], frame[data_offset + 2])   elseif cmd == 0x204 then     str = string.char(unpack(frame))     node_id = frame[data_offset + 1]     name = str:sub(data_offset + 5, data_offset + 68)     position = bit.lshift(frame[data_offset + 86], 8) + frame[data_offset + 87]     fp1 = bit.lshift(frame[data_offset + 90], 8) + frame[data_offset + 91]     fp2 = bit.lshift(frame[data_offset + 92], 8) + frame[data_offset + 93]     fp3 = bit.lshift(frame[data_offset + 94], 8) + frame[data_offset + 95]     fp4 = bit.lshift(frame[data_offset + 96], 8) + frame[data_offset + 97]     if position == 0xf7ff then       position = 'nan'     end     if fp1 == 0xf7ff then       fp1 = 'nan'     end     if fp2 == 0xf7ff then       fp2 = 'nan'     end     if fp3 == 0xf7ff then       fp3 = 'nan'     end     if fp4 == 0xf7ff then       fp4 = 'nan'     end     log('GW_GET_ALL_NODES_INFORMATION_NTF', node_id, position, fp1, fp2, fp3, fp4, name)   elseif cmd == 0x210 then     str = string.char(unpack(frame))     node_id = frame[data_offset + 1]     name = str:sub(data_offset + 5, data_offset + 68)     position = bit.lshift(frame[data_offset + 86], 8) + frame[data_offset + 87]     fp1 = bit.lshift(frame[data_offset + 90], 8) + frame[data_offset + 91]     fp2 = bit.lshift(frame[data_offset + 92], 8) + frame[data_offset + 93]     fp3 = bit.lshift(frame[data_offset + 94], 8) + frame[data_offset + 95]     fp4 = bit.lshift(frame[data_offset + 96], 8) + frame[data_offset + 97]     if position == 0xf7ff then       position = 'nan'     end     if fp1 == 0xf7ff then       fp1 = 'nan'     end     if fp2 == 0xf7ff then       fp2 = 'nan'     end     if fp3 == 0xf7ff then       fp3 = 'nan'     end     if fp4 == 0xf7ff then       fp4 = 'nan'     end     log('GW_GET_NODE_INFORMATION_NTF', node_id, position, fp1, fp2, fp3, fp4, name)   elseif cmd == 0x205 then     log('GW_GET_ALL_NODES_INFORMATION_FINISHED_NTF')   elseif cmd == 0x301 then     sessionId = bit.lshift(frame[data_offset + 1], 8) + frame[data_offset + 2]     status = frame[data_offset + 3]     log('GW_COMMANDS_SEND_CFM', sessionId, status)   else     log('Unknown command', string.format('%x', cmd))     loghex(string.char(unpack(frame)))   end end function split_frames(frame)   chars = {}   frame:gsub('.', function(c) table.insert(chars, string.byte(c)) end)   frames_table = {}   flag = true   start, stop = 0, 0   for i, v in ipairs(chars) do     if v == 192 then       if flag then         start = i         flag = not flag       else         stop = i         flag = not flag       end     end     if stop > start then       table.insert(frames_table, frame:sub(start, stop))     end   end   for i, v in ipairs(frames_table) do     parse_frame(v)   end end cert = '/home/ftp/velux-cert.pem' if not sock then   ip = '192.168.0.112'   port = 51200   params = {     mode = 'client',     protocol = 'tlsv12',     certificate = cert   }         log('start')   sock = require('socket').tcp()   log('connect', sock:connect(ip, port))   sock:settimeout(1)   peer = ssl.wrap(sock, params)   log('handshake', peer:dohandshake())   peer:settimeout(1)   --log(peer:info())   --loghex(create_frame(0x3000, 'UR4nb2Apqh', 32))   peer:send(create_frame(0x3000, 'UR4nb2Apqh', 32)) -- password for WiFi   last_send = os.time() end function create_command(sessionid, user, priority, paramater, fpi1, fpi2, functionalArray, indexArrayCount, indexArray, priotiryLock, pl_0_3, pl_4_7, lockTime)   data = {}   b1 = bit.band(sessionid,0xff)   b2 = bit.band(bit.rshift(sessionid,8),0xff)   table.insert(data, b2)   table.insert(data, b1)   table.insert(data, user) -- default = 1   table.insert(data, priority) -- default = 3   table.insert(data, paramater) -- default = 0   table.insert(data, fpi1) -- default = 0   table.insert(data, fpi2) -- default = 0   b1 = bit.band(functionalArray,0xff)   b2 = bit.band(bit.rshift(functionalArray,8),0xff)   table.insert(data, b2)   table.insert(data, b1)   for i = 10, 41 do     table.insert(data, 0)   end   table.insert(data, indexArrayCount) -- default = 1   table.insert(data, indexArray) -- node id   for i = 44, 62 do     table.insert(data, 0)   end   table.insert(data, priotiryLock) -- default = 0   table.insert(data, pl_0_3) -- default = not used with priotiryLock = 0   table.insert(data, pl_4_7) -- default = not used with priotiryLock = 0   table.insert(data, lockTime) -- default = not used with priotiryLock = 0   return string.char(unpack(data)) end a, b, c, d = peer:receive('*a') --log(a, b, c, d) if #c > 0 then   loghex(c)   split_frames(c) end --[[ 0x0200 GW_GET_NODE_INFORMATION_REQ Request extended information of one specific actuator node. 0x0201 GW_GET_NODE_INFORMATION_CFM Acknowledge to GW_GET_NODE_INFORMATION_REQ. 0x0210 GW_GET_NODE_INFORMATION_NTF Acknowledge to GW_GET_NODE_INFORMATION_REQ. 0x0202 GW_GET_ALL_NODES_INFORMATION_REQ Request extended information of all nodes. 0x0203 GW_GET_ALL_NODES_INFORMATION_CFM Acknowledge to GW_GET_ALL_NODES_INFORMATION_REQ 0x0204 GW_GET_ALL_NODES_INFORMATION_NTF Acknowledge to GW_GET_ALL_NODES_INFORMATION_REQ. Holds node information 0x0205 GW_GET_ALL_NODES_INFORMATION_FINISHED_NTF Acknowledge to GW_GET_ALL_NODES_INFORMATION_REQ. No more nodes. 0x0206 GW_SET_NODE_VARIATION_REQ Set node variation. 0x0207 GW_SET_NODE_VARIATION_CFM Acknowledge to GW_SET_NODE_VARIATION_REQ. 0x0208 GW_SET_NODE_NAME_REQ Set node name. 0x0209 GW_SET_NODE_NAME_CFM Acknowledge to GW_SET_NODE_NAME_REQ. 0x020C GW_NODE_INFORMATION_CHANGED_NTF Information has been updated. 0x0211 GW_NODE_STATE_POSITION_CHANGED_NTF Information has been updated. 0x020D GW_SET_NODE_ORDER_AND_PLACEMENT_REQ Set search order and room placement. 0x020E GW_SET_NODE_ORDER_AND_PLACEMENT_CFM Acknowledge to GW_SET_NODE_ORDER_AND_PLACEMENT_REQ. 0x0300 GW_COMMAND_SEND_REQ Send activating command direct to one or more io-homecontrol nodes. 0x0301 GW_COMMAND_SEND_CFM Acknowledge to GW_COMMAND_SEND_REQ. 0x0302 GW_COMMAND_RUN_STATUS_NTF Gives run status for io-homecontrol node. 0x0303 GW_COMMAND_REMAINING_TIME_NTF Gives remaining time before io-homecontrol node enter target position. 0x0304 GW_SESSION_FINISHED_NTF Command send, Status request, Wink, Mode or Stop session is finished. 0x0305 GW_STATUS_REQUEST_REQ Get status request from one or more io-homecontrol nodes. 0x0306 GW_STATUS_REQUEST_CFM Acknowledge to GW_STATUS_REQUEST_REQ. 0x0307 GW_STATUS_REQUEST_NTF Acknowledge to GW_STATUS_REQUEST_REQ. Status request from one or more io-homecontrol nodes. 0x0308 GW_WINK_SEND_REQ Request from one or more io-homecontrol nodes to Wink. 0x0309 GW_WINK_SEND_CFM Acknowledge to GW_WINK_SEND_REQ 0x030A GW_WINK_SEND_NTF Status info for performed wink request. --]] status = grp.getvalue('32/1/7') if status and os.time() % 10 == 0 then   log('cmd')   raw_cmd = create_command(1, 1, 3, 0, 0, 0, 0xc900 + 1000 + 100 * 10, 1, 0, 0, 0, 0, 0)   -- absolute = % * 512   -- relative = 0xC900 + 1000 + +-% * 10   -- stop = 0xD200   peer:send(create_frame(0x300, raw_cmd))  -- GW_COMMANDS_SEND_REQ   last_send = os.time()   grp.write('32/1/7', false) end   if os.time() % 10 == 0 then   --peer:send(create_frame(0x0200, 0)) -- GW_GET_ALL_NODES_INFORMATION_REQ   --last_send = os.time() end if os.time() - last_send > 14 * 60 then  -- ping for keeping connection live     peer:send( create_frame(0x000c) )   last_time = os.time() end
Reply
#5
Wow! nice, I will take some time and try to understand your code, and will read it in conjunction with the official API documentation... At first glance, I understand more of what your code is doing rather than understanding the instructions in the Velux docs!... Your code is probably my rosetta stone! Thanks for posting, really appreciate it
Reply
#6
(30.03.2021, 11:30)Paddyb Wrote: Wow! nice, I will take some time and try to understand your code, and will read it in conjunction with the official API documentation... At first glance, I understand more of what your code is doing rather than understanding the instructions in the Velux docs!... Your code is probably my rosetta stone! Thanks for posting, really appreciate it

Don't forget to upload velux-cert to LM with FTP. You can take it from Node JS github or get it by yourself with instruction.
Reply
#7
(30.03.2021, 11:30)Paddyb Wrote: Wow! nice, I will take some time and try to understand your code, and will read it in conjunction with the official API documentation... At first glance, I understand more of what your code is doing rather than understanding the instructions in the Velux docs!... Your code is probably my rosetta stone! Thanks for posting, really appreciate it

I added some commands, getting statuses and moved functions to a library. Now it uses storage to save statuses, nodes and queue of commands. There's examples for event scripts but some comments in Russian, sorry =)

Attached Files
.lua   event example bit.lua (Size: 474 bytes / Downloads: 31)
.lua   event example byte.lua (Size: 145 bytes / Downloads: 23)
.lua   resident handler.lua (Size: 1.55 KB / Downloads: 29)
.lua   user.velux.lua (Size: 10.49 KB / Downloads: 29)
Reply
#8
(01.04.2021, 13:29)Snoolik Wrote:
(30.03.2021, 11:30)Paddyb Wrote: Wow! nice, I will take some time and try to understand your code, and will read it in conjunction with the official API documentation... At first glance, I understand more of what your code is doing rather than understanding the instructions in the Velux docs!... Your code is probably my rosetta stone! Thanks for posting, really appreciate it

I added some commands, getting statuses and moved functions to a library. Now it uses storage to save statuses, nodes and queue of commands. There's examples for event scripts but some comments in Russian, sorry =)

Great work @Snoolik! I have tried to get it working now. Seems its working 99%... I get Unknown command 211.. In the docs I cannot find 211 0xD3 or something siliar. It's not in the list in user.velux.lua either.. Any idea what this is? This command is from Velux to client right? I'll try to plough trough the python version from Velux to see if I can sort it out.

I'm running firmware 0.2.0.0.71.0.

Update:

Seems I was a little bit hasty.. its 0x211 and its GW_NODE_STATE_POSITION_CHANGED_NTF. Handler is not implemented. I will look into it.

best regards
Thomas

Attached Files Thumbnail(s)
   
Reply


Forum Jump: