Lua code to read data via websocket

Hey guys,

I have a Daikin Altherma Hybrid heating system with a LAN adaptor which can be read via Websockets. I was thinking of using the Virtual Sensor plugin in combination with some Lua code started by Reactor as a quick and easy way to build an Daikin app. Would this be possible and indeed a simple way and if so could anyone help me out with some pointers or code to help me here?

I include an example in Python:

from websocket import create_connection
import json, datetime, time, urllib2, urllib
ts = time.time()
timestamp = datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S')
 
ws = create_connection("ws://192.168.2.4/mca")
 
ws.send("{\"m2m:rqp\":{\"op\":2,\"to\":\"/[0]/MNAE/1/Sensor/TankTemperature/la\",\"fr\":\"/TarmoTest\",\"rqi\":\"xijub\"}}")
result1 =  json.loads(ws.recv())
ws.send("{\"m2m:rqp\":{\"op\":2,\"to\":\"/[0]/MNAE/1/Operation/TargetTemperature/la\",\"fr\":\"/TarmoTest\",\"rqi\":\"yssyq\"}}")
result2 = json.loads(ws.recv())
 
#print("Received temp '%s'" % result1)
#print("Received target '%s'" % result2)
 
tankTemperature = result1["m2m:rsp"]["pc"]["m2m:cin"]["con"]
tankTargetTemperature = result2["m2m:rsp"]["pc"]["m2m:cin"]["con"]
 
urllib2.urlopen("http://xxxxx.yy/recv.php?timestamp=" + urllib.quote_plus(str(timestamp)) + "&type=daikinTankTemp&measurementValue=" + str(tankTemperature))
urllib2.urlopen("http://xxxxx.yy/recv.php?timestamp=" + urllib.quote_plus(str(timestamp)) + "&type=daikinTankTargetTemp&measurementValue=" + str(tankTargetTemperature))
 
ws.close()

Hi,
Websocket is possible with Lua. I have done plug-in for Vallox ventilation machines.

The code is available here: GitHub - Vpowgh/VVMachine: Vallox ventilation machine plug-in for Vera
(WS in it is based on @reneboer plug-in GitHub - reneboer/vera-Harmony-Hub: Vera and openLuup plugin to intergrate with Harmony Hub.)

If you need to send just predetermined fixed messages the code can be simplified. Otherwise need to have those checksum etc. calculations included.

1 Like

@vpow or anyone with some basic understanding of websockets which I lack…:wink:

I have been trying for some days to access my Daikin unit to read some basic temperature value but fail and need some help.

My code:

local socket = require("socket")
local wsheader = ""
local hdr_ok = false
local value =""
local host1 = "192.168.1.252"
local host2 = host1.."/mca"

local wsheader = 'GET / HTTP/1.1\r\nHost: ' .. host2 .. '\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\nUpgrade: websocket\r\nConnection: Upgrade\r\n\r\n'

sock = socket.tcp()
local _,err = sock:connect(host1,80)
if err then
   return "1+"..err
end
local _,err = sock:settimeout(7,'t')
if err then
    return "2+"..err
end
local _,err = sock:send(wsheader)
if err then
    return "3+"..err
end
repeat
	local line,err = sock:receive('*l')
	if err then
        return "4+"..err
	end
	if line == "HTTP/1.1 101 Switching Protocols" then
			hdr_ok = "1"
	else
	    if line ~= nil
	    then
    	    value = value.."      "..line
    	end
    end
until line == ''
if not hdr_ok then
    return "5+"..value
end
if err then
    return "6+"..err
end
local n,err = sock:send("{\"m2m:rqp\":{\"op\":2,\"to\":\"/[0]/MNAE/1/Sensor/OutdoorTemperature/la\",\"fr\":\"/DaikinRead\",\"rqi\":\"xijub\"}}")
if err then
    return "7+"..err
end
repeat
    local result,err = sock:receive('*l')
    if err then
       return "8+"..err
    end
    local i, j = string.find(result,"con")
    if i > 1 then
        local value = string.sub(i+5,i+8)
    end
until result == ''
local _,err = sock:close()
if err then
    return "9+"..err
end
return value

And that gives me as result:
"5+ HTTP/1.1 400 Bad request Connection: keep-alive Content-Length: 0 Date: Mon, 18 May 2020 19:19:38 +0000 Keep-Alive: timeout=15, max=65535 "

Which basically means that my unit seems unable to switch to websockets or I am making a silly mistake somewhere…

I tried to use host2 on the command but that gives me error 500.

Using any websocket test to 192.168.1.252/mca works however. I am probable doing something silly somewhere but after strugling for a few days now I a calling for some help.

Any clues anyone??

I thought i would ask just incase, have you turned on run unsafe Lua in the vera settings. users &info - security.

Thanks for the suggestion @EICid, I didnot know that setting, just enabled it and tried again but same result…

I assume you have working way to get the wanted data from Daikin unit through websockets. Whether it is that Python in the first post or there is some way to use a browser or something else. Now, just capture the traffic between the PC and the Daikin unit. Use for example Wireshark or browser developer tools or whatever else tool.

Then just replicate the traffic with Lua. It’s picky, all spaces and newlines must be correct.

I would highly recommend to leave Vera alone on initial development. Install Lua and some nice editor like Scite to your computer. After everything works on your PC, then move code to Vera.

1 Like

Indeed. I use ZeroBrane Studio as an IDE. You won’t find a better Lua development environment.

1 Like

Took me about 20 different attempts with code to send a http request with json body. After i turn that setting on it turns out that nearly every one of them worked. Wish you luck

1 Like

I’ve also got a WebSocket library for Lua that works both sync, and more importantly async (very responsive) when assisted by SockProxy.

2 Likes

Well, I have an app and also some Node-red code which works so I will try the wireshark route firts. Thanks!

Say, on the host header you sending the version of the host with path, that’s not correct. Should be hostname or IP only there. (you’re sending host2 but it should be host1)

Edit: And the path following the GET should be /mca if that’s your real URL.

Form:

GET /mca HTTP/1.1
Host: 192.168.1.252
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Upgrade: websocket
Connection: Upgrade

@rigpapa Thanks! That helped! Unfortunately, I still donot get the 101 but 404 Not Found…

To be clear, the path is /mca indeed and I use that now. So did the working node-red app I could sniff with Wireshark. I noticed the protocol being http though instead of ftp but I guess the send command takes care of that automatically.

Edited: Got one step further, solved the 404 so now trying to get the real data. Thanks for your support guys!!!

1 Like

Well guys,

thanks for your help and support but I will give up on this project unless some of you has a simple solution. The code to use the websocket is really too much low level for me. All I need is the command in my string processed and read the temperature but it seems that the example code I use to create my script (with many thanks to @akbooer and @Vpow) is too complex for my limited knowledge and skills.

Thanks. HansW

1 Like

To follow up, I took another approach with an old Raspberry Pi. Installed Node-Red and used the websocket nodes to connect to the Daikin unit which worked like a charm!

HansW

2 Likes

Fantastic! Glad you have a good solution.

The right tool for the right job perhaps.

I think the moral is that if something seems hard, there’s probably a better way to do it. Especially true in the HA world.

1 Like

There might not be a simple solution. Unless @rigpapa includes websocket to Sitesensor with ability to send user defined messages and way to parse responses :wink:

Just to conlude why your Lua code was not working. The http handshake part switching protocol to websocket is fine as noticed. However sending the payload with

sock:send("{\"m2m:rqp\":{\"op\":2,\"to\":\"/[0]/MNAE/1/Sensor/TankTemperature/la\",\"fr\":\"/TarmoTest\",\"rqi\":\"xijub\"}}")

will not work, you have to convert it first to websocket frame, i.e. add control bits, mask key and then mask the whole payload.
If we now use the Wireshark method it is easy to see how this happens. Using Python and capturing the message we’ll get the result:
ws1

The first six bytes are the ones to be added, the actual payload is the rest:
ws2

Now, the websocket requires that masking key is random, but maybe in your case a fixed key would work. In that case it is easy to just copy the bytes for websocket message and put them to Lua code:

sock:send(string.char(0x81,0xe1,0x32,0xc7,0x70,0x58,0x49,0xe5,0x1d,0x6a,0x5f,0xfd,0x2,0x29,0x42,0xe5,0x4a,0x23,0x10,0xa8,0x0,0x7a,0x8,0xf5,0x5c,0x7a,0x46,0xa8,0x52,0x62,0x10,0xe8,0x2b,0x68,0x6f,0xe8,0x3d,0x16,0x73,0x82,0x5f,0x69,0x1d,0x94,0x15,0x36,0x41,0xa8,0x2,0x77,0x66,0xa6,0x1e,0x33,0x66,0xa2,0x1d,0x28,0x57,0xb5,0x11,0x2c,0x47,0xb5,0x15,0x77,0x5e,0xa6,0x52,0x74,0x10,0xa1,0x2,0x7a,0x8,0xe5,0x5f,0x0c,0x53,0xb5,0x1d,0x37,0x66,0xa2,0x3,0x2c,0x10,0xeb,0x52,0x2a,0x43,0xae,0x52,0x62,0x10,0xbf,0x19,0x32,0x47,0xa5,0x52,0x25,0x4f))

If the random mask is required, then you need to add creating random mask and masking payload to to you code.

1 Like

Wow, Vpow, thanks a lot, I do appreciate this. I was hoping there would be an easy way out which you confirmed. Unfortunately I switched to another solution route and programmed the whole thing high level in node-red which workes like a charm now. But I will keep your solution in mind and possibly try it later.

Thanks, Hans

1 Like