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
#1
Has anyone connected a Dyson Pure Hot+Cool 2018 (244289-01) or maybe another Dyson product that used the same protocol?


https://shop.dyson.nl/dyson-ventilatoren...-244289-01
Reply
#2
There are some Python libraries for control: https://github.com/CharlesBlonde/libpurecoollink

Internally MQTT is used which is supported by LM, but you need to get credentials before MQTT connection is possible.
Reply
#3
(15.02.2019, 08:34)admin Wrote: There are some Python libraries for control: https://github.com/CharlesBlonde/libpurecoollink

Internally MQTT is used which is supported by LM, but you need to get credentials before MQTT connection is possible.

I already installed this device on my local network and have the right credentials for the Dyson app. How to start?
Reply
#4
Use standard MQTT example without encryption. Username is device serial number, password value are device credentials.

This resident script will connect to MQTT and subscribe to status topic. See logs tab for output result.

Code:
-- 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'

broker = '192.168.1.1' -- default port is 1883
username = '...' -- device serial
password = '...' -- device credentials
producttype = DYSON_PURE_HOT_COOL_LINK_TOUR

topic = producttype .. '/' .. username .. '/status'

mqtt = require('mosquitto')
client = mqtt.new()

client.ON_CONNECT = function(status, rc, err)
  if status then
    log('connect ok')
    client:subscribe(topic)
  else
    log('connect error', rc, err)
  end
end

client.ON_MESSAGE = function(mid, topic, data)
  log('message', topic, data)
end

client:login_set(username, password)
client:connect(broker)
client:loop_forever()
Reply
#5
where to find the mosquitto module?

Resident script:19: module 'mosquitto' not found:
no field package.preload['mosquitto']
no file './mosquitto'
no file 'Library mosquitto'
no file 'Library mosquitto'
no file 'Library mosquitto.so'
stack traceback:
[C]: in function 'require'
Reply
#6
Install these packages:

https://dl.openrb.com/pkg/libmosquitto_1.6.3-1_imx6.ipk
https://dl.openrb.com/pkg/luamosquitto_0.3-2_imx6.ipk
Reply
#7
(12.07.2019, 09:47)admin Wrote: Install these packages:

https://dl.openrb.com/pkg/libmosquitto_1.6.3-1_imx6.ipk
https://dl.openrb.com/pkg/luamosquitto_0.3-2_imx6.ipk

and for a lm4?
Reply
#8
https://dl.openrb.com/pkg/libmosquitto_1.6.3-1_mxs.ipk
https://dl.openrb.com/pkg/luamosquitto_0.3-2_mxs.ipk
Reply
#9
yes, packages are installed.

But how to find the password (device credentials) or is it possible to generate it?
Reply
#10
I think i need to get device information over HTTPS:
https://github.com/CharlesBlonde/libpure...k/dyson.py


Code:
https = require 'ssl.https'
require('json')
socket.http.TIMEOUT = 5
local cUsername = 'xxxxxxxxx@xxxxxxxx.nl'
local cPassword = 'xxxxxxxxxxxxxx'

-- get session info
local cBody = '{"Email":"' .. cUsername .. '","Password":"' .. cPassword ..'"}'
local cReq = {}
local cUrl1 = 'https://api.cp.dyson.com/v1/userregistration/authenticate?country=NL'
result1 = https.request({
    url = cUrl1,
    method = 'POST',
    headers = {
      ['content-length'] = #cBody,
      ['content-type'] = 'application/json'
    },
    source = ltn12.source.string(cBody),
    sink = ltn12.sink.table(cReq)
})
if result1 and cReq then
  log(cReq)
  cAuth = cReq
else
  log(result1)
end


result:

Code:
* table:
[1]
  * string: {"Account":"xxxxxxx-xxxxxxx-xxxxxxx-xxxxxxx-xxxxxxx","Password":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}

How to get the account and password value in the following GET request to receive al devices from: /provisioningservice/manifest
Reply
#11
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
Reply
#12
(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

i added Account and Password settings to the code below, but i only get a 403 error.


Code:
mime = require("mime")
r, c = https.request {
  url = 'https://api.cp.dyson.com/v1/provisioningservice/manifest',
  method = 'GET',
  headers = { authentication = "Basic " .. mime.b64("xxxxxxxxx:xxxxxxxxxxxx") }
}
log(r,c)
Reply
#13
Can you send your email/password via PM so I can test locally?
Reply
#14
(16.07.2019, 07:59)admin Wrote: Can you send your email/password via PM so I can test locally?
done
Reply
#15
Thanks, turns out that example is incorrect. Correct header name should be authorization, not authentication. You also need to add sink table to the GET request so you can retrieve response data.
Reply
#16
(16.07.2019, 09:09)admin Wrote: Thanks, turns out that example is incorrect. Correct header name should be authorization, not authentication. You also need to add sink table to the GET request so you can retrieve response data.

Did you receive a response with a device in it?

Code:
mime = require("mime")
local cReq2 = {}
local cUrl2 = 'https://api.cp.dyson.com/v1/provisioningservice/manifest'
result2, c, h = https.request({
  url = cUrl2,
  method = 'GET',
  headers = {
      ['authorization'] = "Basic " .. mime.b64(cAccount..":"..cPassword)
  },
  sink = ltn12.sink.table(cReq2)
})
log(result2,c,h)
log(cReq2)

Now my response is a correct http request (code 200), but no data in cReq2
Reply
#17
When I tested with your account it returned empty device array []
Reply
#18
mmm something must be wrong, because i have one device in my account. I can control this device by the app with the same credentials.
Reply
#19
My model is HP04, inside is a code on a label, maybe i can use this because i read:
(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 written on manual and on the fan label)

How to do this encryption in lua, in the Original python code i see:


Code:
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
#20
Try this for SHA512 + BASE64 encoding:
Code:
require('encdec')

password = '123456'
password = encdec.sha512(password, true) -- binary form
password = encdec.base64enc(password)

Python code that you've posted does password decryption.
Reply


Forum Jump: