Integrate Dyson Pure Cool Link

Hi, is anybody here who can help please?

Hi Mr. Burns,

I still haven’t figured out my problem in my post above yours but I can try and help you with a few things. First, I did not have to modify mqtt.cfg to get one of my Dyson HPO2s to work with Openhab Before jumping into this though, have you verified your MQTT setup is working properly? If you are using files instead of PaperUI, can you post your .things and .items files?

Best,
Paul

Hi everyone,

I’m hoping someone can help me get 2 Dyson Pure’s operating with Openhab. The one I have setup is working well. Thanks for the effort with this binding.

I can confirm that this still works for my PH02 455 purchased new in 2021.
The 2FA login for the app does not affect this server/cloud-less solution.
But, I do think it is a prerequisite that the phone app has connected via 2FA once.

And remember, as this threw me off for a while, the MQTT broker is the fan itself!
When reading the MQTT binding doc, it states that a broker like Mosquitto or similar is needed.
It is not for this solution. :slight_smile:

Could you explain how you found the password?

Hi,

could someone help me with this topic? I used the prepare.py to setup my dyson. I have a dyson purifier hot+cool hp07. I copied the dysonapbroker.things to my openhab and it looks like this.

Bridge mqtt:broker:dysonApBroker [ host="192.168.178.113", secure=false, username="-PEA0", password="xy/g8M8BcwGlujxUwnhxdHRxrzNasZKHabCs6mvo5pXAlx6g/lx+nTCH4Y6VEFjBXRLL9ZYl633xfPkYM5xCgA==", qos=1]
{
    Thing topic dysonAp {
    Channels:
        Type string : status "Status"   [ stateTopic="455/-PEA0/status/current" ]
        Type string : command "Command" [ commandTopic="455/-PEA0/command" ]
    }
}

but it seems the broker is not working. The Status is “Bridge offline”
I dont know which password i should use. My WIFI PW or the numbers behind the filter 1234:5678?
Because when i use getPwdHash.py and my WIFI PW the hash is different to the numbers behind the filter.
Any suggestions?

OH shows me this error

Thing ‘mqtt:broker:dysonApBroker’ changed from OFFLINE (COMMUNICATION_ERROR): CONNECT failed as CONNACK contained an Error Code: CLIENT_IDENTIFIER_NOT_VALID. to OFFLINE

I just got my new shiny Dyson Purifier Hot+Cool™ Formaldehyde.

Used Wireshark to work out username, password and clientID. Can connect via MQTT.fx locally and see status messages by the way the topics for this mode are:

527E/serial/status/current
527E/serial/command

But…I can’t get openhab to connect…getting the following error:

Thing 'mqtt:broker:dysonApBroker' changed from OFFLINE to OFFLINE (COMMUNICATION_ERROR): CONNECT failed as CONNACK contained an Error Code: NOT_AUTHORIZED.

Any ideas?

Thing config below:

Bridge mqtt:broker:dysonApBroker [ host=“192.168.xx.xx”, secure=false, clientID=“clientid”, username=“serial”, password=“secret”, qos=1]
{
Thing topic dysonAp {
Channels:
Type string : status “Status” [ stateTopic=“527E/serial/status/current” ]
Type string : command “Command” [ commandTopic=“527E/serial/command” ]
}
}

Sorry for not chiming in before, but my Dyson (Camping) setup has been out of reach since August. :slight_smile:
Here is what worked this summer: (sampled from various sources, not my original work, thanks this thread & the internet!). Collected for a one-post summing-up source.

  1. Not 100% sure, but I think one has to do the 2FA Dyson phone app login at least once.
  2. Use the WiFi password from the sticker on you unit. I will use my actual values here so you can verify. Consider that low risk. WiFi password yvatwlfs
  3. Now run the following python hashing program:
import base64
import hashlib

# Ask for the password
pwd = input("Product WiFi Password (e.g.: adgjsfhk):")

# Transfer password to hash version
hash = hashlib.sha512()
hash.update(pwd.encode('utf-8'))
pwd_hash = base64.b64encode(hash.digest()).decode('utf-8')

# Print out password hash
print(pwd_hash)
pi@raspberrypi:~ $ python dysonhash.py 
Product WiFi Password (e.g.: adgjsfhk):yvatwlfs
Zu6rHzM9mOVdqy3FKLiPfUzWL5/gfCXn8R4G3eJ99wv6tEhf56+kIbcRGyXMMi0Cz6h5Dvl1DdEvhFAcR8wHUQ==
pi@raspberrypi:~ $

Then, you dyson.items file becomes: (remember: the fan is the broker!)

Bridge mqtt:broker:dyson455broker [ host="192.168.1.7", port="1883", secure=false, username="PT4-EU-PDA6549A", password="Zu6rHzM9mOVdqy3FKLiPfUzWL5/gfCXn8R4G3eJ99wv6tEhf56+kIbcRGyXMMi0Cz6h5Dvl1DdEvhFAcR8wHUQ==", qos=1 ] 
{
    Thing topic dyson455  {
    Channels:
        Type string : dyson_status [ stateTopic="455/PT4-EU-PDA6549A/status/current", postCommand="true" ]
        Type string : dyson_request [ commandTopic="455/PT4-EU-PDA6549A/command", postCommand="true" ]

         }
}

.things file:

// Dyson Pure Cool Link Items


String Dyson455_Topic_Status_Current "[%s]" {channel="mqtt:topic:dyson455broker:dyson455:dyson_status"}
 
String Dyson455_Request "[%s]" {channel="mqtt:topic:dyson455broker:dyson455:dyson_request"}


//VIRTUAL ITEMS
Number Dyson455_Tact "Temperature [%.2f °C]"         <temperature>  
Number Dyson455_Hact "Humidity [%.0f %%]"            <humidity>    
Number Dyson455_Pact "P-Value [%d]"                  <smoke>   
Number Dyson455_Vact "V-Value [%d]"                  <smoke>  
Number Dyson455_Fmod "Mode"                          <switch>   
Switch Dyson455_Rhtm "Contin. Measurement"           <line>    
Number Dyson455_Fnsp "Fan Speed [%d]"                <flow>   
Number Dyson455_Qtar "Quality Target"                <smoke>
Switch Dyson455_Oson "Turning"                       <incline>
Switch Dyson455_Nmod "Night Mode"                    <moon>
Number Dyson455_Filf "Remaining Filter Hours [%d h]" <sewerage>
Number Dyson455_Fnst "Fan Activity [%d]"             <fan>     
Number Dyson455_Hmod "Heater Mode"                   <switch>
Number Dyson455_Hmax "Target Temperature [%.1f °C]"  <temperature>
Number Dyson455_Hsta "Heating Activity [%d]"         <radiator>
Switch Dyson455_Ffoc "Heater Focus"                  <fan>

.sitemap

  Frame label="Dyson vifte" {
    Switch   item=Dyson455_Fmod label="Mode" mappings=[0="OFF", 1="FAN", 2="AUTO"]
    Setpoint item=Dyson455_Fnsp label="Fan Speed [%d]" minValue=1 maxValue=10 step=1 visibility=[Dyson455_Fmod > 0]
    Default  item=Dyson455_Oson label="Turning"                                      visibility=[Dyson455_Fmod > 0]
    Default  item=Dyson455_Ffoc label="Air flow Focus"                               visibility=[Dyson455_Fmod > 0]
    Switch   item=Dyson455_Hmod label="Heater Mode" mappings=[0="COOL", 1="HEAT"]    visibility=[Dyson455_Fmod > 0]
    Setpoint item=Dyson455_Hmax label="Target Temperature [%.0f °C]" minValue=1 maxValue=37 step=1 visibility=[Dyson455_Fmod > 0]
    Switch   item=Dyson455_Qtar label="Quality Target" mappings=[1="LOW", 3="AVERAGE", 4="HIGH"] visibility=[Dyson455_Fmod > 0]
    Default  item=Dyson455_Tact label="Temperature [%.1f °C]"                        visibility=[Dyson455_Fmod > 0]
    Default  item=Dyson455_Hact label="Humidity [%.0f %%]"                           visibility=[Dyson455_Fmod > 0]
    Default  item=Dyson455_Pact label="P-Value [%d]"                                 visibility=[Dyson455_Fmod > 0]
    Default  item=Dyson455_Vact label="V-Value [%d]"                                 visibility=[Dyson455_Fmod > 0]
    Default  item=Dyson455_Rhtm label="Contin. Measurement"                          visibility=[Dyson455_Fmod > 0]
    Default  item=Dyson455_Nmod label="Night Mode"                                   visibility=[Dyson455_Fmod > 0]
    Default  item=Dyson455_Filf label="Remaining Filter Hours"                       visibility=[Dyson455_Fmod > 0]
    Default  item=Dyson455_Fnst label="Fan Activity [%d]"                            visibility=[Dyson455_Fmod > 0] 
    Default  item=Dyson455_Hsta label="Heater Activity [%d]"                         visibility=[Dyson455_Fmod > 0]
  }

.rules

import java.text.SimpleDateFormat
//import java.util.TimeZone

// Rules for Dyson Pure Cool Link (455)

var SimpleDateFormat simpleDF = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS")

//val DateTimeType simpleDF = DateTimeType.valueOf("yyyy-MM-dd'T'HH:mm:ss.SSS")

// Start of STATE-SET msg
var String startStateSetCmdStr = "{\"msg\":\"STATE-SET\",\"time\":"
// Variable to store String used as command
var String cmdStr

// TOPIC "455/<id>/status/current" ############################################
    // msg "ENVIRONMENTAL-CURRENT-SENSOR-DATA" ############
    // msg "CURRENT-STATE" ################################
    // msg "STATE-CHANGE" #################################
rule "455/<id>/status/current"
when
    Item Dyson455_Topic_Status_Current received update
then
    val String content = Dyson455_Topic_Status_Current.state.toString()
    val String msg = transform("JSONPATH", "$.msg", content)

    //logWarn("DysonUpdate",msg)
    if (msg == "ENVIRONMENTAL-CURRENT-SENSOR-DATA") {
        val String tact_str = transform("JSONPATH", "$.data.tact", content)
        if (tact_str != "OFF") {
            var double tact = Float.parseFloat(tact_str)
            tact = (tact - 2731.5)/10
            postUpdate(Dyson455_Tact, tact as Number)
        }
        val String hact_str = transform("JSONPATH", "$.data.hact", content)
        if (hact_str != "OFF") {
            val int hact = Integer.parseInt(hact_str)
            postUpdate(Dyson455_Hact, hact as Number)
        }
        val int pact = Integer.parseInt(transform("JSONPATH", "$.data.pact", content))
        postUpdate(Dyson455_Pact, pact as Number)
        val String vact_str = transform("JSONPATH", "$.data.vact", content)
        if (vact_str != "INIT") {
            val int vact = Integer.parseInt(vact_str)
            postUpdate(Dyson455_Vact, vact as Number)
        }
    } else if (msg == "CURRENT-STATE") {
        val String fmod_str = transform("JSONPATH", "$.product-state.fmod", content)
        switch fmod_str {
            case "AUTO": postUpdate(Dyson455_Fmod, 2 as Number)
            case "FAN": postUpdate(Dyson455_Fmod, 1 as Number)
            case "OFF": postUpdate(Dyson455_Fmod, 0 as Number)
        }
        if (transform("JSONPATH", "$.product-state.rhtm", content) == "ON") {
           // logWarn("Postupdate","ON")
            postUpdate(Dyson455_Rhtm, ON)
        } else {
            //logWarn("Postupdate","OFF")
            postUpdate(Dyson455_Rhtm, OFF)
        }
        val String fnsp = transform("JSONPATH", "$.product-state.fnsp", content)
        if (fnsp == "AUTO") {
            postUpdate(Dyson455_Fnsp, 11 as Number)
        } else {
            postUpdate(Dyson455_Fnsp, Integer.parseInt(fnsp) as Number)
        }
        val int qtar = Integer.parseInt(transform("JSONPATH", "$.product-state.qtar", content))
        postUpdate(Dyson455_Qtar, qtar as Number)
        if (transform("JSONPATH", "$.product-state.oson", content) == "ON") {
            postUpdate(Dyson455_Oson, ON)
        } else {
            postUpdate(Dyson455_Oson, OFF)
        }
        if (transform("JSONPATH", "$.product-state.nmod", content) == "ON") {
            postUpdate(Dyson455_Nmod, ON)
        } else {
            postUpdate(Dyson455_Nmod, OFF)
        }
        val int filf = Integer.parseInt(transform("JSONPATH", "$.product-state.filf", content))
        postUpdate(Dyson455_Filf, filf as Number)
        if (transform("JSONPATH", "$.product-state.fnst", content) == "FAN") {
            postUpdate(Dyson455_Fnst, 1)
        } else {
            postUpdate(Dyson455_Fnst, 0)
        }
        val String hmod_str = transform("JSONPATH", "$.product-state.hmod", content)
        switch hmod_str {
            case "AUTO": postUpdate(Dyson455_Hmod, 2 as Number)
            case "HEAT": postUpdate(Dyson455_Hmod, 1 as Number)
            case "OFF": postUpdate(Dyson455_Hmod, 0 as Number)
        }
        val String hmax_str = transform("JSONPATH", "$.product-state.hmax", content)
        if (hmax_str != "OFF") {
            var double hmax = Float.parseFloat(hmax_str)
            hmax = (hmax - 2731.5)/10
            postUpdate(Dyson455_Hmax, hmax as Number)
        }
        if (transform("JSONPATH", "$.product-state.hsta", content) == "HEAT") {
            postUpdate(Dyson455_Hsta, 1)
        } else {
            postUpdate(Dyson455_Hsta, 0)
        }
        if (transform("JSONPATH", "$.product-state.ffoc", content) == "ON") {
            postUpdate(Dyson455_Ffoc, ON)
        } else {
            postUpdate(Dyson455_Ffoc, OFF)
        }
    } else if (msg == "STATE-CHANGE") {
        val String fmod_str = transform("JSONPATH", "$.product-state.fmod[1]", content)
        switch fmod_str {
            case "AUTO": postUpdate(Dyson455_Fmod, 2 as Number)
            case "FAN": postUpdate(Dyson455_Fmod, 1 as Number)
            case "OFF": postUpdate(Dyson455_Fmod, 0 as Number)
        }
        if (transform("JSONPATH", "$.product-state.rhtm[1]", content) == "ON") {
            postUpdate(Dyson455_Rhtm, ON)
        } else {
            postUpdate(Dyson455_Rhtm, OFF)
        }
        val String fnsp = transform("JSONPATH", "$.product-state.fnsp[1]", content)
        if (fnsp == "AUTO") {
            postUpdate(Dyson455_Fnsp, 11 as Number)
        } else {
            postUpdate(Dyson455_Fnsp, Integer.parseInt(fnsp) as Number)
        }
        val int qtar = Integer.parseInt(transform("JSONPATH", "$.product-state.qtar[1]", content))
        postUpdate(Dyson455_Qtar, qtar as Number)
        if (transform("JSONPATH", "$.product-state.oson[1]", content) == "ON") {
            postUpdate(Dyson455_Oson, ON)
        } else {
            postUpdate(Dyson455_Oson, OFF)
        }
        if (transform("JSONPATH", "$.product-state.nmod[1]", content) == "ON") {
            postUpdate(Dyson455_Nmod, ON)
        } else {
            postUpdate(Dyson455_Nmod, OFF)
        }
        val int filf = Integer.parseInt(transform("JSONPATH", "$.product-state.filf[1]", content))
        postUpdate(Dyson455_Filf, filf as Number)
        if (transform("JSONPATH", "$.product-state.fnst[1]", content) == "FAN") {
            postUpdate(Dyson455_Fnst, 1)
        } else {
            postUpdate(Dyson455_Fnst, 0)
        }
        val String hmod_str = transform("JSONPATH", "$.product-state.hmod[1]", content)
        switch hmod_str {
            case "AUTO": postUpdate(Dyson455_Hmod, 2 as Number)
            case "HEAT": postUpdate(Dyson455_Hmod, 1 as Number)
            case "OFF": postUpdate(Dyson455_Hmod, 0 as Number)
        }
        val String hmax_str = transform("JSONPATH", "$.product-state.hmax[1]", content)
        if (hmax_str != "OFF") {
            var double hmax = Float.parseFloat(hmax_str)
            hmax = (hmax - 2731.5)/10
            postUpdate(Dyson455_Hmax, hmax as Number)
        }
        if (transform("JSONPATH", "$.product-state.hsta[1]", content) == "HEAT") {
            postUpdate(Dyson455_Hsta, 1)
        } else {
            postUpdate(Dyson455_Hsta, 0)
        }
        if (transform("JSONPATH", "$.product-state.ffoc[1]", content) == "ON") {
            postUpdate(Dyson455_Ffoc, ON)
        } else {
            postUpdate(Dyson455_Ffoc, OFF)
        }
    }
end
//#############################################################################

// REQUEST "455/<id>/command"##################################################
    // msg "REQUEST-CURRENT-STATE" ########################
rule "Request Current State every 30 seconds"
when
    Time cron "0/30 * * * * ?"
then
    // Create current TimeStamp as UTC
    //simpleDF.setTimeZone(TimeZone.getTimeZone("UTC"))
    var String timeStampUtc = simpleDF.format(new java.util.Date())
    cmdStr = "{\"msg\":\"REQUEST-CURRENT-STATE\","
    cmdStr = cmdStr + " \"time\":\"" + timeStampUtc + "Z\"}"
    //logWarn("DysonUpdateRequest",cmdStr)
    Dyson455_Request.sendCommand(cmdStr)
end

    // msg "SET-STATE" ####################################
rule "455/<id>/command SET-STATE fmod"
when
    Item Dyson455_Fmod received command
then
    // Create current TimeStamp as UTC
    ////simpleDF.setTimeZone(TimeZone.getTimeZone("UTC"))
    val String timeStampUtc = simpleDF.format(new java.util.Date())
    // Cast fmod to String representation
    var String fmod = "OFF"
    switch receivedCommand {
        case 2: fmod = "AUTO"
        case 1: fmod = "FAN"
    }
    // Create JSON for commanding and send it
    cmdStr = startStateSetCmdStr + "\"" + timeStampUtc + "Z\","
    cmdStr = cmdStr + "\"mode-reason\":\"LAPP\","
    cmdStr = cmdStr + "\"data\":{\"fmod\":\"" + fmod + "\"}}"
    //logWarn("Dyson455_Request",cmdStr)
    Dyson455_Request.sendCommand(cmdStr)
end

rule "455/<id>/command SET-STATE  fnsp"
when
    Item Dyson455_Fnsp received command
then
    // Create current TimeStamp as UTC
    //simpleDF.setTimeZone(TimeZone.getTimeZone("UTC"))
    var String timeStampUtc = simpleDF.format(new java.util.Date())
    // Cast fnsp to String representation
    val String fnsp = receivedCommand.toString()
    // Create JSON for commanding and send it
    cmdStr = startStateSetCmdStr + "\"" + timeStampUtc + "Z\","
    cmdStr = cmdStr + "\"mode-reason\":\"LAPP\","
    cmdStr = cmdStr + "\"data\":{\"fnsp\":\"" + fnsp + "\"}}"
    logWarn("DysonFanSpeedChange",fnsp)
    Dyson455_Request.sendCommand(cmdStr)
end

rule "455/<id>/command SET-STATE  qtar"
when
    Item Dyson455_Qtar received command
then
    // Create current TimeStamp as UTC
    ////simpleDF.setTimeZone(TimeZone.getTimeZone("UTC"))
    val String timeStampUtc = simpleDF.format(new java.util.Date())
    // Cast qtar to String representation
    val String qtar = receivedCommand.toString()
    // Create JSON for commanding and send it
    cmdStr = startStateSetCmdStr + "\"" + timeStampUtc + "Z\","
    cmdStr = cmdStr + "\"mode-reason\":\"LAPP\","
    cmdStr = cmdStr + "\"data\":{\"qtar\":\""+ qtar + "\"}}"
    Dyson455_Request.sendCommand(cmdStr)
end

rule "455/<id>/command SET-STATE  oson"
when
    Item Dyson455_Oson received command
then
    // Create current TimeStamp as UTC
    ////simpleDF.setTimeZone(TimeZone.getTimeZone("UTC"))
    val String timeStampUtc = simpleDF.format(new java.util.Date())
    // Cast oson to String representation
    var String oson = "OFF"
    if (receivedCommand == ON) oson = "ON"
    // Create JSON for commanding and send it
    cmdStr = startStateSetCmdStr + "\"" + timeStampUtc + "Z\","
    cmdStr = cmdStr + "\"mode-reason\":\"LAPP\","
    cmdStr = cmdStr + "\"data\":{\"oson\":\"" + oson + "\"}}"
    Dyson455_Request.sendCommand(cmdStr)
end

rule "455/<id>/command SET-STATE  rhtm"
when
    Item Dyson455_Rhtm received command
then
    // Create current TimeStamp as UTC
    ////simpleDF.setTimeZone(TimeZone.getTimeZone("UTC"))
    val String timeStampUtc = simpleDF.format(new java.util.Date())
    // Cast rhtm and fmod to String representation
    var String fmod = "OFF"
    switch Dyson455_Fmod.state {
        case 2: fmod = "AUTO"
        case 1: fmod = "FAN"
    }
    var String rhtm = "OFF"
    if (receivedCommand == ON) rhtm = "ON"
    // Create JSON for commanding and send it
    cmdStr = startStateSetCmdStr + "\"" + timeStampUtc + "Z\","
    cmdStr = cmdStr + "\"mode-reason\":\"LAPP\","
    cmdStr = cmdStr + "\"data\":{\"fmod\":\"" + fmod + "\""
    cmdStr = cmdStr + "\"rhtm\":\"" + rhtm + "\"}}"
    Dyson455_Request.sendCommand(cmdStr)
end

rule "455/<id>/command SET-STATE  nmod"
when
    Item Dyson455_Nmod received command
then
    // Create current TimeStamp as UTC
    ////simpleDF.setTimeZone(TimeZone.getTimeZone("UTC"))
    val String timeStampUtc = simpleDF.format(new java.util.Date())
    // Cast nmod to String representation
    var String nmod = "OFF"
    if (receivedCommand == ON) nmod = "ON"
    // Create JSON for commanding and send it
    cmdStr = startStateSetCmdStr + "\"" + timeStampUtc + "Z\","
    cmdStr = cmdStr + "\"mode-reason\":\"LAPP\","
    cmdStr = cmdStr + "\"data\":{\"nmod\":\"" + nmod + "\"}}"
    Dyson455_Request.sendCommand(cmdStr)
end
rule "455/<id>/command SET-STATE hmod"
when
    Item Dyson455_Hmod received command
then
    // Create current TimeStamp as UTC
    ////simpleDF.setTimeZone(TimeZone.getTimeZone("UTC"))
    val String timeStampUtc = simpleDF.format(new java.util.Date())
    // Cast hmod to String representation
    var String hmod = "OFF"
    switch receivedCommand {
        case 1: hmod = "HEAT"
    }
    // Create JSON for commanding and send it
    cmdStr = startStateSetCmdStr + "\"" + timeStampUtc + "Z\","
    cmdStr = cmdStr + "\"mode-reason\":\"LAPP\","
    cmdStr = cmdStr + "\"data\":{\"hmod\":\"" + hmod + "\"}}"
    Dyson455_Request.sendCommand(cmdStr)
end

rule "455/<id>/command SET-STATE  hmax"
when
    Item Dyson455_Hmax received command
then
    // Create current TimeStamp as UTC
    ////simpleDF.setTimeZone(TimeZone.getTimeZone("UTC"))
    val String timeStampUtc = simpleDF.format(new java.util.Date())
    // Cast hmax to String representation
    var double hmax = Float.parseFloat(receivedCommand.toString())
    hmax = (hmax * 10) + 2731.5
    val int hmax_int = Integer.parseInt(Math::round(hmax).toString())
    // Create JSON for commanding and send it
    cmdStr = startStateSetCmdStr + "\"" + timeStampUtc + "Z\","
    cmdStr = cmdStr + "\"mode-reason\":\"LAPP\","
    cmdStr = cmdStr + "\"data\":{\"hmax\":\"" + hmax_int.toString() + "\"}}"
    Dyson455_Request.sendCommand(cmdStr)
end

rule "455/<id>/command SET-STATE  ffoc"
when
    Item Dyson455_Ffoc received command
then
    // Create current TimeStamp as UTC
    ////simpleDF.setTimeZone(TimeZone.getTimeZone("UTC"))
    val String timeStampUtc = simpleDF.format(new java.util.Date())
    // Cast ffoc to String representation
    var String ffoc = "OFF"
    if (receivedCommand == ON) ffoc = "ON"
    // Create JSON for commanding and send it
    cmdStr = startStateSetCmdStr + "\"" + timeStampUtc + "Z\","
    cmdStr = cmdStr + "\"mode-reason\":\"LAPP\","
    cmdStr = cmdStr + "\"data\":{\"ffoc\":\"" + ffoc + "\"}}"
    Dyson455_Request.sendCommand(cmdStr)
end
//#############################################################################

Thank you for your setup, I have this running in node-red in order to construct the json mqtt messages. It should be possible with transformations as well, but using a rule as you did is also quite effective :slight_smile:.

For the 2FA I used the Python script “get_devices.py” from GitHub - shenxn/libdyson: Python library for dyson devices. It retrieves all devices in your cloud account and provides the password for local access to the mqtt server built into the Dyson. You may need to remove the device from your phone app first if the script produces an error. This was the only way for me to get a local password as there are no stickers with the wifi password anymore on newer machines.

I also noticed that the mqtt server in my Dyson does not accept the openHAB client ID, however if you enter something yourself in the thing settings it will connect. Hopefully this will get you up to speed in integrating the Dyson into openHAB.

1 Like

Thanks a lot for the instruction.
Tested that and a Dyson HP04 Type 527 did react, but extreme unstable.
Connection got lost all couple of seconds.

After a review of the log files and referring to

I changed the thing to:

Bridge mqtt:broker:dyson527broker
[
host = “your_HostIP”,
port = “1883”,
secure = false,
clientID = “your_clientID”,
username = “your_username”,
password = “your_password”,
enableDiscovery = false,
qos = 1
]

followed by the channel description as suggested to switch off an AutoDiscovery for the
Dyson who acts as broker in this case.

The add of “enableDiscovery = false,” made the trick. The connection is stable and does give back
values all 30 seconds.

Just a note: If you have a 527-Type just replace 455 from the example by 527.

Hope this hint helps if you a running in a similar issue.