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.

Dyson Pure Hot+Cool
#21
Resident script:23: attempt to call field 'sha512' (a nil value)
stack traceback:

Sha512 is missing in my encdec.
Reply
#22
Try this one: https://dl.openrb.com/pkg/luaencdec_20180912_mxs.ipk
Reply
#23
(15.07.2019, 13:27)admin Wrote: Send GET request to https://api.cp.dyson.com/v1/provisioning...e/manifest after the first one to get device list. You need to decode the first result using JSON and then pass AccountTongueassword values as Basic auth. See last example in HTTP docs: http://w3.impa.br/~diego/software/luasocket/http.html

Yes i found why there was no return data. I have to use the v2 api for newer devices:

Code:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
https = require 'ssl.https' require('json') socket.http.TIMEOUT = 5 local cUsername = 'xxxx@xxxxxx.com' local cPassword = 'xxxxxxxx' -- get session info local cBody1 = '{"Email":"' .. cUsername .. '","Password":"' .. cPassword ..'"}' local cReq1 = {} local cUrl1 = 'https://api.cp.dyson.com/v1/userregistration/authenticate?country=NL' result1 = https.request({     url = cUrl1,     method = 'POST',     headers = {       ['content-length'] = #cBody1,       ['content-type'] = 'application/json'     },     source = ltn12.source.string(cBody1),     sink = ltn12.sink.table(cReq1) }) if result1 and cReq1 then   cReq1 = table.concat(cReq1)   cReq1 = json.pdecode(cReq1)   cAccount = nil   cPassword = nil   if cReq1 then     if cReq1.Account and cReq1.Password then       cAccount = cReq1.Account       cPassword = cReq1.Password     end   end   --log(cAccount,cPassword) else   log(result1) end mime = require("mime") local cAuth = "Basic " .. mime.b64(cAccount..":"..cPassword) local cReq2 = {} local cUrl2 = 'https://api.cp.dyson.com/v2/provisioningservice/manifest'   -- v2 for new devices result2, c, h = https.request({   url = cUrl2,   method = 'GET',   headers = {       ['authorization'] = cAuth   },   sink = ltn12.sink.table(cReq2) }) --log(result2,c,h) log(cReq2)

But the next question, how to decode the password to use it in my mqtt client. Original code from https://github.com/CharlesBlonde/libpure...k/utils.py:
Code:
12345678910111213141516171819202122232425
def decrypt_password(encrypted_password):     """Decrypt password.     :param encrypted_password: Encrypted password     """     key = b'\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10' \           b'\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f '     init_vector = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \                   b'\x00\x00\x00\x00'     cipher = AES.new(key, AES.MODE_CBC, init_vector)     json_password = json.loads(unpad(         cipher.decrypt(base64.b64decode(encrypted_password)).decode('utf-8')))     return json_password["apPasswordHash"]
Reply
#24
Do you have this encrypted password in plain text? It should be encoded with base64. Can you send it via PM so I can test the decoding locally?
Reply
#25
(26.09.2019, 08:07)admin Wrote: Do you have this encrypted password in plain text? It should be encoded with base64. Can you send it via PM so I can test the decoding locally?

send by PM
Reply
#26
Thanks, here's decryption function, data contents is what you sent me via PM.
Code:
123456789101112131415161718192021222324252627
data = '...' -- create key key = {} for i = 1, 32 do   key[ #key + 1 ] = i end key = string.char(unpack(key)) -- decrypt data data = require('encdec').base64dec(data) aes = require('user.aes') aes_256_cbc, err = aes:new(key, nil, aes.cipher(256, 'cbc'), { iv = string.rep('\0', 16) }, nil, 0) data = aes_256_cbc:decrypt(data) -- unpad data len = string.byte(data, #data) + 1 data = data:sub(1, -len) -- decode json data, err = require('json').pdecode(data) if type(data) == 'table' then   pass = data.apPasswordHash end log(pass)

Use aes.lua from this post: https://forum.logicmachine.net/showthrea...7#pid12807
Save it as user library named aes
Reply
#27
many thanks, connecting with this output works!
Reply
#28
Yes it works, i can switch the Dyson off, with the follow command (code is not perfect, but you can use it as start for yourself):

Code:
12
require('user.nit_dyson')    runCommand(5,CMD_OFF)

My library
Code:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
--[[ use getDevices('email@email.com','password') to get the encrypted password and fill it in dysons[] so you can use this offline. --]] -- product types DYSON_PURE_COOL_LINK_TOUR = '475' DYSON_PURE_COOL_LINK_DESK = '469' DYSON_PURE_HOT_COOL_LINK_TOUR = '455' DYSON_360_EYE = 'N223' DYSON_PURE_HOT_COOL_2018 = '527' --HP04 Dyson Pure Hot+Cool 2018 (244289-01) CMD_OFF = '{"fpwr":"OFF"}' -- all dyson devices dysons =     {} -- room(not used), ip, port(default 1883), type, username/serial, password dysons[5] = {'Room', '192.168.x.xxx', 1883, DYSON_PURE_HOT_COOL_2018, 'A1B-EU-KNAXXXXX', 'verylongencryptedpasswordfromgetdevices'} -- get devices from web portal function getDevices(iPortalUser, iPortalPwd)   https = require 'ssl.https'   require('json')   socket.http.TIMEOUT = 5   local cUsername = iPortalUser   local cPassword = iPortalPwd   -- get session info   local cBody1 = '{"Email":"' .. cUsername .. '","Password":"' .. cPassword ..'"}'   local cReq1 = {}   local cUrl1 = 'https://api.cp.dyson.com/v1/userregistration/authenticate?country=NL'   result1 = https.request({       url = cUrl1,       method = 'POST',       headers = {         ['content-length'] = #cBody1,         ['content-type'] = 'application/json'       },       source = ltn12.source.string(cBody1),       sink = ltn12.sink.table(cReq1)   })   if result1 and cReq1 then     cReq1 = table.concat(cReq1)     cReq1 = json.pdecode(cReq1)     cAccount = nil     cPassword = nil     if cReq1 then       if cReq1.Account and cReq1.Password then         cAccount = cReq1.Account         cPassword = cReq1.Password       end     end     --log(cAccount,cPassword)   else     log(result1)   end   mime = require("mime")   local cAuth = "Basic " .. mime.b64(cAccount..":"..cPassword)   local cReq2 = {}   local cUrl2 = 'https://api.cp.dyson.com/v2/provisioningservice/manifest'   -- v2 for new devices   result2, c, h = https.request({     url = cUrl2,     method = 'GET',     headers = {       ['authorization'] = cAuth     },     sink = ltn12.sink.table(cReq2)   })   --log(result2,c,h)   log(cReq2) end -- decrypt password function decryptPwd(iPwd)   data = iPwd   -- create key   key = {}   for i = 1, 32 do     key[ #key + 1 ] = i   end   key = string.char(unpack(key))   -- decrypt data   data = require('encdec').base64dec(data)   aes = require('user.aes')   aes_256_cbc, err = aes:new(key, nil, aes.cipher(256, 'cbc'), { iv = string.rep('\0', 16) }, nil, 0)   data = aes_256_cbc:decrypt(data)   -- unpad data   len = string.byte(data, #data) + 1   data = data:sub(1, -len)   -- decode json   data, err = require('json').pdecode(data)   if type(data) == 'table' then     pass = data.apPasswordHash   end     --log(pass)   return pass end -- run dyson command function runCommand(IDevice, iCmd)   broker = dysons[IDevice][2]   port = dysons[IDevice][3]   producttype = dysons[IDevice][4]   username = dysons[IDevice][5]   password = dysons[IDevice][6]     -- after this initialization, you'll have to use the hashed password to connect to the fan. The hashed password is a base64 encoded of the sha512 of the password   passwordDec = decryptPwd(password)   topicCmd     = producttype .. '/' .. username .. '/command'   mqtt = require('mosquitto')   client = mqtt.new()   client.ON_CONNECT = function(status, rc, err)     if status then       payload = '{"msg":"STATE-SET", "time":"'..os.date("%Y-%m-%dT%H:%M:%SZ")..'", "data":'..iCmd..'}'       client:publish(topicCmd, payload)     end   end   client.ON_PUBLISH = function()     client:disconnect()   end   client:login_set(username, passwordDec)   client:connect(broker, port)   client:loop_forever()  end

You can also make an event handler like:
Code:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
require('user.nit_dyson') broker = '192.168.x.xx' port = 1883 -- default port is 1883 username = 'A1B-EU-KNAXXXXX' -- device serial password = 'longpassword' passwordDec = decryptPwd(password) producttype = DYSON_PURE_HOT_COOL_2018 topic         = producttype .. '/' .. username .. '/status/current' topicCmd     = producttype .. '/' .. username .. '/command' mqtt = require('mosquitto') client = mqtt.new() client.ON_CONNECT = function(status, rc, err)   if status then     log('connect ok')     client:subscribe(topic)         --payload = '{"msg":"STATE-SET", "time":"'..os.date("%Y-%m-%dT%H:%M:%SZ")..'", "data":{"fpwr":"OFF"}}'     --client:publish(topicCmd, payload)       else     log('connect error', rc, err)   end end client.ON_MESSAGE = function(mid, topic, data)   log('message', topic, data) end client.ON_PUBLISH = function()   --log('publish')     --client:disconnect() end client.ON_LOG = function()   --log('log') end client.ON_DISCONNECT = function()   --log('disconnect') end client:login_set(username, passwordDec) client:connect(broker, port) client:loop_forever()
Reply
#29
more codes:
Code:
123456789101112
CMD_ON         = '{"fpwr":"ON"}' CMD_OFF     = '{"fpwr":"OFF"}' CMD_AUTO_ON     = '{"auto":"ON"}' CMD_AUTO_OFF     = '{"auto":"OFF"}' CMD_NIGHT_ON     = '{"nmod":"ON"}' CMD_NIGHT_OFF    = '{"nmod":"OFF"}' CMD_HEAT_ON     = '{"hmod":"HEAT"}' CMD_HEAT_OFF     = '{"hmod":"OFF"}' -- not sure if its right, but it works function CMD_HEAT(iTemp) -- integer celcius     return '{"hmax":"'..2932+((iTemp-20)*10)..'"}' --2932=20 2952=22 2982=25 3012=28 enz end
Reply
#30
(12.07.2019, 10:00)admin Wrote: https://dl.openrb.com/pkg/libmosquitto_1.6.3-1_mxs.ipk
https://dl.openrb.com/pkg/luamosquitto_0.3-2_mxs.ipk

where to find this ipk file: https://dl.openrb.com/pkg/luamosquitto_0.3-2_mxs.ipk
Reply
#31
https://dl.openrb.com/pkg/libmosquitto_1.6.3-1_mxs.ipk
https://dl.openrb.com/pkg/luamosquitto_0.3-5_mxs.ipk
Reply
#32
(19.07.2019, 12:28)admin Wrote: Try this one: https://dl.openrb.com/pkg/luaencdec_20180912_mxs.ipk
 Where to find the latest version for LM 2020.07 RC3 firwmare
and also the latest mosquitto packages?
Reply
#33
You don't need this package, a newer version is already included in RC3
Reply
#34
(20.08.2020, 08:34)admin Wrote: You don't need this package, a newer version is already included in RC3

oops, i overwitten this package, do you have the latest one? with the latest one i received also an error.

So no need for luaencdec_20180912_mxs.ipk and no need for the mosquitto packages?
Reply
#35
You can reinstall the same FW and all packages will be reverted. You also need updated AES library: https://forum.logicmachine.net/showthrea...5#pid17735
Reply
#36
(20.08.2020, 08:42)admin Wrote: You can reinstall the same FW and all packages will be reverted. You also need updated AES library: https://forum.logicmachine.net/showthrea...5#pid17735

thanks, problem solved
Reply
#37
.lua   dyson.lua (Size: 5.04 KB / Downloads: 4)

After upgrading to a LM5 (HW: LM5 Lite + Ext (i.MX6) SW: 20210806) my code doesn't work

in getDevices there's no response from: https://api.cp.dyson.com/v1/userregistra...country=NL

and also the runCommand doesn't nothing.

What goes wrong? do i need to install extra libraries?
Reply
#38
I've tested your code and there's Cloudflare's 1020 "Access Denied" error. I've tried using different user agent headers but the request is still blocked. I'm not sure whether it is possible to access the authentication using a script now. There can also be a location-based blocking. You can try adding the user-agent header after content-type and check yourself:
Code:
12
['content-type'] = 'application/json', ['user-agent'] = 'Mozilla/5.0',
Reply
#39
(04.10.2021, 06:41)admin Wrote: I've tested your code and there's Cloudflare's 1020 "Access Denied" error. I've tried using different user agent headers but the request is still blocked. I'm not sure whether it is possible to access the authentication using a script now. There can also be a location-based blocking. You can try adding the user-agent header after content-type and check yourself:
Code:
12
['content-type'] = 'application/json', ['user-agent'] = 'Mozilla/5.0',

I checked this option already but it doesn't work. I see in the Dyson app that you have you enter your emailadres, after that you have to signin with a password and a code send by email. Maybe thats the change?

Another problem is that i can't send message to the Dyson on my local lan with mqtt (with the runCommand function). Any idea why this stops working after the HW en SW upgrade?
Reply
#40
Many thanks to admin to make it working again

Code:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
--[[ https://www.npmjs.com/package/homebridge-dyson-pure-cool https://github.com/lukasroegner/homebridge-dyson-pure-cool 9-10-2021 Sniff passwords with an Android phone and the Dyson link app and Netcapture (also sniff SSL traffic). You receive data like this and fill them inside the dyson devices (theres a X between username/serial and the password): .MQTT.<paho3949609190773A1B-EU-MKA0000AXn0MYFEYNRMCk7n9nCxW/F3bDFGQuLtBjQy3W4rg46ZqPc0+wkqqSVTdSVXXXXQnxc2NqgbQPABzbCe81xWQ== --]] local json = require('json') debug = false -- product types DYSON_PURE_COOL_LINK_TOUR = '475' DYSON_PURE_COOL_LINK_DESK = '469' DYSON_PURE_HOT_COOL_LINK_TOUR = '455' DYSON_360_EYE = 'N223' DYSON_PURE_HOT_COOL_2018 = '527' --HP04 Dyson Pure Hot+Cool 2018 (244289-01) CMD_ON         = { fpwr = 'ON' } CMD_OFF     = { fpwr = 'OFF' } CMD_AUTO_ON     = { auto = 'ON' } CMD_AUTO_OFF     = { auto = 'OFF' } CMD_NIGHT_ON     = { nmod = 'ON' } CMD_NIGHT_OFF    = { nmod = 'OFF' } CMD_HEAT_ON     = { hmod = 'HEAT' } CMD_HEAT_OFF     = { hmod = 'OFF' } function CMD_HEAT(iTemp) -- in graden celcius     return ({ hmax = tostring(2932+((iTemp-20)*10)) }) --2912=18 2932=20 2952=22 2982=25 3012=28 enz end -- all dyson devices dysons =     {} -- room(not used), ip, port(default 1883), type, username/serial, password dysons[1] = {'Room 1      ', '192.168.0.14', 1883, DYSON_PURE_HOT_COOL_2018, 'A1B-EU-MKA0000A', 'n0MYFEYNRMCk7n9nCxW/F3bDFGQuLtBjQy3W4rg46ZqPc0+wkqqSVTdSVXXXXQnxc2NqgbQPABzbCe81xWQ=='} -- debug logger function debugLog(iLog)   if debug then     log(iLog)   end end -- send data to dyson function mqsend(ip, username, password, prefix, cmddata)   local function datetime()     return os.date('!%Y-%m-%dT%H:%M:%SZ')   end   local ts, tu = os.microtime()   local clientid = 'lm-' .. ts .. '-' .. tu   local mq = require('mosquitto').new(clientid)   mq:login_set(username, password)   mq.ON_CONNECT = function(res, ...)     debugLog('mqtt connect status', res, ...)     if res then       local topic = prefix .. '/command'       local cmd = {         f = topic,         time = datetime(),         msg = 'STATE-SET',         data = cmddata,         ['mode-reason'] = 'LAPP',       }       mq:publish(topic, json.encode(cmd), 1)     else       mq:disconnect()     end   end   mq.ON_PUBLISH = function(mid, rc)     debugLog('published', mid, rc)     mq:disconnect()   end   mq.ON_DISCONNECT = function(...)     debugLog('mqtt disconnect', ...)   end   mq.ON_LOG = function(...)     debugLog(...)   end   local res, rc, errno = mq:connect(ip)   if not res then     debugLog('connect failed', rc, errno)     return   end   -- process mqtt messages   for i = 1, 20 do     local res, err = mq:loop()     if not res then       break     end   end end -- receive current dyson state function mqstatus(ip, username, password, prefix)   local function datetime()     return os.date('!%Y-%m-%dT%H:%M:%SZ')   end   local ts, tu = os.microtime()   local clientid = 'lm-' .. ts .. '-' .. tu   local mq = require('mosquitto').new(clientid)   local count = 0   local status = {}   mq:login_set(username, password)   mq.ON_CONNECT = function(res, ...)     log('mqtt connect status', res, ...)     if res then       local cmd = {         time = datetime(),         msg = 'REQUEST-CURRENT-STATE',       }       mq:subscribe(prefix .. '/status/current')       mq:publish(prefix .. '/command', json.encode(cmd))     else       mq:disconnect()     end   end   mq.ON_MESSAGE = function(rc, topic, payload)     payload = json.pdecode(payload)     if type(payload) ~= 'table' then       return     end     local data     if payload.msg == 'CURRENT-STATE' then       data = payload['product-state']     elseif payload.msg == 'ENVIRONMENTAL-CURRENT-SENSOR-DATA' then       data = payload['data']     end     if type(data) == 'table' then       for key, value in pairs(data) do         if value:match('^%d+$') then           value = tonumber(value)         end         status[ key ] = value       end       count = count + 1     end     if count == 2 then       mq:disconnect()     end   end   mq.ON_DISCONNECT = function(...)     debugLog('mqtt disconnect', ...)   end   local res, rc, errno = mq:connect(ip)   if not res then     debugLog('connect failed', rc, errno)     return   end   -- process mqtt messages   for i = 1, 20 do     local res, err = mq:loop()     if not res then       break     end   end   debugLog(status)   return status end -- run dyson command function runCommand(IDevice, iCmd)   broker = dysons[IDevice][2]   port = dysons[IDevice][3]   producttype = dysons[IDevice][4]   username = dysons[IDevice][5]   password = dysons[IDevice][6]     mqsend(     broker,     username,     password,     producttype..'/'..username,     iCmd     ) end -- runCommand(1,CMD_OFF) -- get dyson command function getState(IDevice)   broker = dysons[IDevice][2]   port = dysons[IDevice][3]   producttype = dysons[IDevice][4]   username = dysons[IDevice][5]   password = dysons[IDevice][6]     return mqstatus(             broker,             username,             password,             producttype..'/'..username                  ) end -- log(getState(1))
Reply


Forum Jump: