Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for dmaker.fan.p18 and dmaker.fan.p33 #19

Open
helgek opened this issue Apr 13, 2024 · 79 comments
Open

Support for dmaker.fan.p18 and dmaker.fan.p33 #19

helgek opened this issue Apr 13, 2024 · 79 comments

Comments

@helgek
Copy link

helgek commented Apr 13, 2024

Hi,

I have here a dmaker.fan.p18 and a dmaker.fan.p33 (Mi Smart Standing Fan 2 and Mi Smart Standing Fan 2 Pro). Difference between the two is that one has a battery, otherwise they should be the same. The specs differ slightly though:

https://home.miot-spec.com/s/dmaker.fan.p18
https://home.miot-spec.com/spec?type=urn:miot-spec-v2:device:fan:0000A005:dmaker-p18:1

https://home.miot-spec.com/s/dmaker.fan.p33
https://home.miot-spec.com/spec?type=urn:miot-spec-v2:device:fan:0000A005:dmaker-p33:1

Screenshots of the app show that there are differences regarding accessible controls through the app. The control name for speed adjustment differs:

(dmaker.fan.p18 / Mi Smart Standing Fan 2)
Screenshot_2024-04-13-02-48-56-74

(dmaker.fan.p33 / Mi Smart Standing Fan 2 Pro)
Screenshot_2024-04-13-02-50-21-79

These are pictures of the dmaker.fan.p33 / Mi Smart Standing Fan 2 Pro version (for the other one I haven't done the sull teardown yet but I could see inside that it's an ESP-WROOM-02D for both):

image

image

Unlike the Air Purifier devices that are already supported in this project these fans don't seem to have an easily accessible UART port so one probably has to flash directly on the pins of the ESP-WROOM-02D.

I checked plenty of contacts combinations for continuity but for the ESP-WROOM-02D I could only find test points for IO13 and IO15 on the backside of the PCB (marked in the pictures above) + 3.3V and GND. My hope is that IO13 and IO15 would work if I use them in the ESPHome config:

uart:
tx_pin: GPIO15
rx_pin: GPIO13
baud_rate: 115200

For Air Purifier 4 Lite the config looks like this:

uart:
tx_pin: GPIO17
rx_pin: GPIO16
baud_rate: 115200

and the docucmentation (ESP32-­WROOM­-32D - https://www.espressif.com/sites/default/files/documentation/esp32-wroom-32d_esp32-wroom-32u_datasheet_en.pdf) states this :

image

This is from the ESP-WROOM-02D documentation ( https://www.espressif.com/sites/default/files/documentation/esp-wroom-02u_esp-wroom-02d_datasheet_en.pdf ):

image

image

image

As a next step I will try backing up the original firmware and flashing the chip with ESPHome, starting with only a basic config and as a next step I would try if e.g. a switch added to the config would work. I'd be happy though if - in the meantime - maybe one of the pros here could share their opinion if these things I've summarized here make sense. Thank you!

@helgek
Copy link
Author

helgek commented Apr 13, 2024

Spend a night shift and I have good news: I was both able to do a backup of the original firmware, flash ESPHome and then I tried one test control in the config (power on/off) and the device does react to it 👍

This is the original firmware:

mi-smart-standing-fan-2-pro--dmaker.fan.p33-orignal-firmware.zip

The ESPHome log for this device shows many commands sent from the MCU to the ESP8266/ESP-WROOM-02D which should be additionally helpful to further complete the config with all possible controls.

This is the working ESPHome config with the first working controls (most essential controls already included):

external_components:
  source: github://dhewg/esphome-miot@main

esphome:
  name: mi-smart-standing-fan
  friendly_name: Xiaomi Mi Smart Standing Fan 2 Pro
  comment: Xiaomi Mi Smart Standing Fan 2 Pro (dmaker.fan.p33)
  project:
    name: "dhewg.esphome-miot"
    version: "dmaker.fan.p33"

esp8266:
  board: esp_wroom_02
  framework:
    version: recommended

logger:
  level: DEBUG

api:
  #encryption:
  #  key: !secret api_encryption_key
  reboot_timeout: 0s

ota:
  password: !secret ota_password

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  ap:
    password: !secret wifi_ap_password

captive_portal:


uart:
  tx_pin: GPIO15
  rx_pin: GPIO13
  baud_rate: 115200


miot:
  id: miot_main

button:
  - platform: "miot"
    miot_siid: 2
    miot_aiid: 1
    name: "Toggle Power"
    icon: mdi:power

switch:
  - platform: "miot"
    miot_siid: 2
    miot_piid: 1
    name: "Power"
    icon: mdi:power
  - platform: "miot"
    miot_siid: 4
    miot_piid: 1
    name: "Indicator Light"
    icon: mdi:power
  - platform: "miot"
    miot_siid: 5
    miot_piid: 1
    name: "Notification Sound"
    icon: mdi:power
  - platform: "miot"
    miot_siid: 7
    miot_piid: 1
    name: "Child Lock"
    icon: mdi:power

select:
  - platform: "miot"
    miot_siid: 6
    miot_piid: 1
    name: "Fan Blades turning direction"
    options:
      0: "Auto"
      1: "Left"
      2: "Right"
  - platform: "miot"
    miot_siid: 2
    miot_piid: 2
    name: "Fan Level"
    options:
      1: "Level 1"
      2: "Level 2"
      3: "Level 3"
      4: "Level 4"
  - platform: "miot"
    miot_siid: 2
    miot_piid: 3
    name: "Mode"
    options:
      0: "Straight Wind"
      1: "Natural Wind"

@scrampker
Copy link

I just bought the non-pro version based on this post and looking to flash the ESP. Did you end up soldering pig-tails to the chip to flash via USB-to-serial adapter?

If this works, this will be the only full-local smart tower fan that I'm aware of, and I have had nearly 7 of them. So far they all require cloud API integrations.

@scrampker
Copy link

Sure is a shame that these pins cannot be used :/

image

@scrampker
Copy link

I wired up to io13 and 15 on the back per your previous post, and I'm hoping that I can piggyback on the VDD5 and GND front the other controller in my above photo. I just need to dig out my serial to USB adapter and fiddle with it. Any tips? Would be so cool to build a full guide on this.

@scrampker
Copy link

I built up a little mount to hold pins/wires up against the ESP, and when I set my USB-to-Serial adapter to 3.3v and plug in, my port disappears immediately from my Windows PC, and the fan board just keeps chirping, about once every second.

My next test will be with the 12v plugged into the board. I was hoping to avoid this, just in case it could shoot it into my serial adapter.... somehow

image

@helgek
Copy link
Author

helgek commented May 13, 2024

@scrampker you should try it only using UART USB device connection, incl power coming from UART. This worked well for me. I used FTDI Chip based UART like this one https://amzn.eu/d/2w40Qh4 . Experience with it was much more stable than other noname chip UART that I used before. I'm traveling until the end of the week, I thought I had already posted the instructions how I successfully put the ESP into bootloader mode but it doesn't seem to be the case. I'll post it later. I think I kept notes about it.

@scrampker
Copy link

Oh that would be really cool. I'm using https://www.amazon.com/dp/B09F3196FB?psc=1&ref=ppx_yo2ov_dt_b_product_details which looks to be using a proper FTDI chipset. I tested a loopback to make sure it's working properly.

When I connect everything WITH the 12v base attached, the serial adapter does not crash. I believe what is happening is that the 3.3v line is pulling too much power for my serial adapter -- due to all the extra components being powered off that rail.

That being said, I tried using the UART0 TXD/RXD pins, and also the IO13/15 pins you reference in the yaml, but nothing seems to stick. It's been 3+ years since I've programmed an ESP device from a serial header, so I could be screwing up any number of things.

Your notes would be amazing, exactly which pins you used for each step. I'm trying to ground IO0 like it says to flash firmware, but no dice. Could even be that I'm not making sufficient contact with the pads. Did you solder directly to the chip, or were the underside contacts enough for everything?

Thanks so much!

@scrampker
Copy link

I finally got it connected. Here's what seemed to work, but I'm not 100% sure if I had a bad connection for other attempts or not.

  • Hook up per-spec UART0 TXD/RXD, IO13/15 for RTS/CTS, 3.3v and GND.
  • Use ESPHome Web to flash the 'starter' -- which worked, but didn't have a Wi-Fi config afterwards... partial success. No serial output. This MAY have been due to forgetting to remove the flash-pin loop from IO0 to GND.
  • Re-flash, using Powershell (Admin) ESPTool, which was successful, but of course since your YAML didn't use the standard pins, I didn't see any serial output. (also I hadn't removed the flash pin yet.)
  • Pull power and flash pin-to-gnd.
  • Give power and watch it come up on the network, discovered by hass.

Looking good so far. Time to re-assemble.

@helgek
Copy link
Author

helgek commented May 13, 2024

Congrats! Glad you got it to work. My yaml is work in progress as you probably noted already, it's not on top of my priority list at the moment since there are some other projects I want to complete before, but I will definitely continue on it in the later course of this year. Of course I'm happy if others join the yaml configuration work.

@helgek
Copy link
Author

helgek commented May 13, 2024

Because you asked how I flashed the chip. I bought PCBite from Sensepeek some time ago. I'm not capable of soldering and using this amazing equipment I was able to flash more than 10 devices, some of them had ESP pins which were extremely difficult to reach due to the PCB design. PCBite saved me. It's such a well designed toolset.

@scrampker
Copy link

Dang, that's pretty cool. Never seen it before, which is why I 3D printed my own pin-holder. I also was not confident in soldering such small pads without damaging the connection to the traces.

Word of warning to anyone else doing this.. the nut that attaches the motor housing to the base which allows it to tilt, (the tensioner,) is made out of extremely soft metal and stripped instantly. I have to find a suitable replacement at the hardware store. My guess is it was made out of aluminum.

@scrampker
Copy link

I do have to say, this build quality and features of this fan are pretty weak when you look at the sale price of $159. It's actually horrendous, and unforgiveable.

I have several of these $139 Dreo Pedestal fans that oscillate left/right and up/down, with temperature, and better algorithms for natural-feeling airflow. The only reason I don't have 20 of them in my house is because I have to rely on the cloud to control them. https://www.amazon.com/dp/B0BSH7YKHT?psc=1&ref=ppx_yo2ov_dt_b_product_details

All that being said, I'm very pleased to have flashed this ESP and have 100% local control. Not relying on the internet, or some greedy company that wants to charge for their API access.

@scrampker
Copy link

@helgek by chance do you know if the miot module can define an actual fan component, versus 'select'? The reason being is that certain automations and especially my hubitat integration have a hard time controlling the speed. It's not seen as a legit fan. More of a jumble of various entities.

@cristianchelu
Copy link
Contributor

@scrampker I think I can answer that. esphome-miot doesn't (yet) have a fan component implemented, but since ESPHome 2024.3 there's a Template Fan you can use to hack something together.

It's as barebones as it can be and there's no state feedback support (i.e. register state change when pressing physical buttons) but may be workable via the On State trigger and friends.

fan:
  - platform: template
    name: "Virtual Fan"
    on_state:
      - lambda: |-
          // id(my_miot_speed_select).set_index(x->speed) or something?
    on_speed_set: # etc

I've ordered a non-pro standing fan 2 too and it seems fun to try my hand at a miot fan component, but don't hold your breath.

I'd love to hear it if you manage something workable with the above 😃

@scrampker
Copy link

Thanks! I was tinkering with something along these lines, but I'm not too familiar with the way this all pieces together. I assume I'd need to add some code to the miot base for fan. My quick attempts to build outputs and whatever haven't worked yet. Hopefully I'll figure out something.

@w-marco
Copy link

w-marco commented Jul 18, 2024

@scrampker would you mind sharing the 3D Print STL files for the Dupont holder?
I want to flash mine too (successfully converted my Air Purifier 4 Lite today as a first trial, now onto the fan), and would love to use the Dupont holder you designed for it.

@cristianchelu
Copy link
Contributor

cristianchelu commented Jul 19, 2024

@w-marco You can search the Thingiverse and other sharing platforms for "ESP8266 programming jig", I see there are several options.

I've also received my dmaker.fan.p18 yesterday and managed to get a good config working, hopefully this will cut some of your work :)

#28

(change the external-components: - source to a branch that has the fix for the OTA platform until it's merged to main.)

Edit:
What's also important for ESP8266 devices from what I've seen, is to disable the esphome serial debug output on UART1. It helps greatly with stability in the comms with the MCU.

logger:
  level: DEBUG
  # Important: Disable UART1 logging to avoid hardware errors on main UART0
  baud_rate: 0

@scrampker
Copy link

I can send you mine a bit later if you really want. I did search a bit but I must suck at it and didn't find an easy print. Otherwise I wouldn't have made mine. If you know of a good one please let us know.

@scrampker
Copy link

Mine definitely isn't perfect. It could hold the pins in tension better if the holes were angled some.

@w-marco
Copy link

w-marco commented Jul 20, 2024

Yeah, I also couldn’t find one where I am certain it will fit, so adding yours here is probably a good idea!

@cristianchelu
Copy link
Contributor

@scrampker I managed to get the fan component working via Template Fan and, as expected, it's an ugly mess, but it works.

The fan component can control the device, and device updates by button press / entity changes are updated in the fan too.

Also, I've shoehorned the "Natural / Direct" breeze modes as presets. To make this work, I've had to keep updating the template_fan preset_mode state, as the normal esphome fan behavior is to clear the preset whenever a manual speed/oscillation change is made :/

You can check out the full working yaml here for dmaker.fan.p18: cristianchelu@1801e60 , I'm sure it can be adapted to other fan types without much change.

Hopefully, in the future the Template Fan component will get new powers so we can maybe say which number/select/switch entities control which part of the fan instead of this spaghetti, like:

# My daydream
fan:
  - platform: template
    entities:
      power: my_miot_power_switch
      speed: my_miot_power_number
      preset: my_miot_mode_select
      oscillation: my_miot_oscillation_switch

Seeing how often devices break the xiaomi specifications (integer 0/1 instead of booleans, one write-only property for left/right instead of two actions, etc) I'm not sure how a miot fan platform would even look in order to be flexible.

If you have any ideas or successes I'd be happy to hear them!

And, last but not least, some eye candy:

Screenshot from 2024-07-20 17-57-20
Screenshot from 2024-07-20 17-57-03

@w-marco
Copy link

w-marco commented Jul 21, 2024

@w-marco You can search the Thingiverse and other sharing platforms for "ESP8266 programming jig", I see there are several options.

I've also received my dmaker.fan.p18 yesterday and managed to get a good config working, hopefully this will cut some of your work :)

Unfortunately none of the prints from Thingiverse seem to fit the orientation this ESP Chip is placed in the p18, but once I have the 3D Print files from @scrampker I will happily try out your config for the fan and report back!

@scrampker
Copy link

Here's the latest version I had.
programming guide v7.zip

@scrampker
Copy link

@scrampker I managed to get the fan component working via Template Fan and, as expected, it's an ugly mess, but it works.

Pretty cool man, I was just able to test this yaml and pretty smooth sailing. Does anyone know if we can use the original repo yet?

@scrampker
Copy link

scrampker commented Jul 21, 2024 via email

@cristianchelu
Copy link
Contributor

I'll have to be keep playing with that yaml because it seems to turn the fan right back on every time I power it off

On that branch I have another commit simplifying things a bit after I've discovered you can just id(fan).make_call() instead of turn_off() / turn_on().

If after you power off the fan it tries to set the speed anyway, the automation there starts with auto call = id(template_fan).turn_on(). Try this patch and see if it works:

  - platform: "miot"
    id: "speed_level"
    miot_siid: 2
    miot_piid: 10
    name: "Fan Speed"
    icon: "mdi:speedometer"
    min_value: 1
    max_value: 100
    step: 1
    unit_of_measurement: "%"
    on_value:
      - lambda: |-
-         auto call = id(template_fan).turn_on();
+         auto call = id(template_fan).make_call();
          call.set_speed(x);
          call.set_preset_mode(id(mode).state.c_str());
          call.perform();

@scrampker
Copy link

I'm a dummy -- totally glossed over the fact that you were using the d18 and I have the d33 'pro' version and in testing I copied your yaml entirely. After throwing back some of the code I had previously, AND making the patch you pasted, it seems to work great.

Doesn't appear to be turning back on right away. Awesome! I really need to dig deeper into these yaml configurations, and the baseline code. Just getting started with truly customizing HA or ESPHome stuffs. I also have a bunch of 8266 units I was using some hacked code to operate linear actuators to open windows, and should get those over on ESPHome as well.

@scrampker
Copy link

Yay, the notification sound disabling actually sticks now. That's been obnoxious. Seems to work exactly as I'd expect. The added bonus of the left/right control is actually interesting. I wasn't aware that it was even capable of that. I knew it had a little servo from the tear-down, but that's really cool.

Wild thought... is the servo index/left-right position known or controllable? Would be so cool to use occupancy data to tell the fan where to position itself and then set the oscillation angle range.

@cristianchelu
Copy link
Contributor

Awesome, glad to hear it! When you're happy with the config for the p33 you could add a PR for it so we can close this issue :)

Last I heard from the maintainer, he was swamped with other things, but he'll take a look when he has time, and in the meantime people can still benefit from the config in the PR.

As for the left-right position, I can only speak for the p18, but the MCU knows and doesn't expose that in the miot-spec, bummer. I've tried wild poking of a few random SIID/PIIDs (Calling esphome.standing_fan_mcu_command with get_properties [random-int] [random-int] from HA) but had no success; there's 65k possibilities at the end of the day, if there's an undocumented property the engineers used to debug it.

Off-topic

When first plugged in, the fan goes full-tilt to the right for a couple of seconds to zero the angle (e.g. even if it started full left it should be back in a known position) and then back to center.

To hack it, you could delete the oscillation switch from the config, tape over the button, and emulate the same behavior in the ESP. Make a note of how many steps you have for a full swing, then at startup spam enough button.press: pan_left to get yourself in a known position, and Bob's your uncle.

(You could even do the same thing for the "natural breeze" algorithm. Add a template number input for the speed control for the intensity, and each 1-5s calculate a speed based on a pseudo-random math function of your choosing.)

@scrampker
Copy link

Steps are probably better than timing, but that's likely something I'd play with using another tool. Was just thinking it would be cool if somehow we could get that angle.

@cristianchelu
Copy link
Contributor

@dhewg I was just opening ESPHome when you tagged me 😄

INFO ESPHome 2024.12.2
INFO Reading configuration /config/purifier.yaml...
INFO Generating C++ source...
INFO Updating https://github.com/espressif/[email protected]
INFO Compiling app...
Processing purifier (board: esp32dev; framework: espidf; platform: https://github.com/pioarduino/platform-espressif32.git#51.03.07)

[...]

In file included from src/esphome/components/miot/fan/miot_fan.cpp:1:
src/esphome/components/miot/fan/miot_fan.cpp: In lambda function:
src/esphome/components/miot/fan/miot_fan.cpp:35:144: error: could not convert 'this->esphome::miot::MiotFan::<anonymous>.esphome::fan::Fan::direction' from 'esphome::fan::FanDirection' to 'bool'
   35 |       ESP_LOGV(TAG, "MCU reported reverse direction %" PRIu32 ":%" PRIu32 " is: %u", this->direction_siid_, this->direction_piid_, ONOFF(this->direction));
src/esphome/core/log.h:86:91: note: in definition of macro 'esph_log_v'
   86 |   esp_log_printf_(ESPHOME_LOG_LEVEL_VERBOSE, tag, __LINE__, ESPHOME_LOG_FORMAT(format), ##__VA_ARGS__)
      |                                                                                           ^~~~~~~~~~~
src/esphome/components/miot/fan/miot_fan.cpp:35:7: note: in expansion of macro 'ESP_LOGV'
   35 |       ESP_LOGV(TAG, "MCU reported reverse direction %" PRIu32 ":%" PRIu32 " is: %u", this->direction_siid_, this->direction_piid_, ONOFF(this->direction));
      |       ^~~~~~~~
src/esphome/components/miot/fan/miot_fan.cpp:35:132: note: in expansion of macro 'ONOFF'
   35 |       ESP_LOGV(TAG, "MCU reported reverse direction %" PRIu32 ":%" PRIu32 " is: %u", this->direction_siid_, this->direction_piid_, ONOFF(this->direction));
      |                                                                                                                                    ^~~~~
*** [.pioenvs/purifier/src/esphome/components/miot/fan/miot_fan.cpp.o] Error 1
========================= [FAILED] Took 16.71 seconds =========================
fan:
  - platform: "miot"
    name: "Fan"
    state:
      miot_siid: 2
      miot_piid: 1
    speed:
      miot_siid: 9
      miot_piid: 2
      min_value: 450
      max_value: 2000
      step: 1
    preset_modes:
      miot_siid: 2
      miot_piid: 4
      options:
        0: "Auto"
        1: "Sleep"
        2: "Favorite"
        3: "Manual"

I'll also test with the smart standing fan 2 soonish

@dhewg
Copy link
Owner

dhewg commented Feb 12, 2025

Of course its' %s for the direction, just like for state. (Why didn't I hit that warning?)

@cristianchelu
Copy link
Contributor

@dhewg also,

And I'm not sure if that's due to my older python/HA version, but my old automations don't work anymore with the fan component. There doesn't seem to be a set fan mode to favorite nor set favorite speed to x action anymore, as was the case with the single components.

There's the Fan: Set preset mode and Fan: Set percentage at least in my 2025.02 instance. I'm not sure these are what you're referring to:

action: fan.set_preset_mode
target:
  entity_id: fan.standing_fan_smart_standing_fan_2
data: {}
# -- and --
action: fan.set_percentage
data: {}
target:
  entity_id: fan.standing_fan_smart_standing_fan_2

And yes, it would be very nice if we could keep the individual sensors/entities and have the fan as an addition, with their states synchronizing automatically. Or is this already the case in your branch?

@dhewg
Copy link
Owner

dhewg commented Feb 12, 2025

I don't see those two actions, so that's fine, an upgrade would solve it for my setup.

But no, the fan component is supposed to replace the single components.
Currently the whole listeners thingy is written so every siid/piid pair can only occur once.
And if there're replacements for all the features (like the two actions mentioned) I don't see a reason to rewrite the code to allow multiple listeners.

It kinda sucks w.r.t. automation backwards compatibility, but I'd argue that's the nature of HA, I had to redo my automations lots of times over the years...

@dhewg
Copy link
Owner

dhewg commented Feb 12, 2025

Updated HA, and indeed, the actions are there now.

While talking about backwards compatibility, the set preset mode action wants a string... So if I rename e.g. Favorite to Favourite on the yaml side, it'll break on the HA side...

@dhewg
Copy link
Owner

dhewg commented Feb 17, 2025 via email

@cristianchelu
Copy link
Contributor

@dhewg I'm still receiving the direction error.

Cleaned build files and fetched fresh:

In file included from src/esphome/components/miot/fan/miot_fan.cpp:1:
src/esphome/components/miot/fan/miot_fan.cpp: In lambda function:
src/esphome/components/miot/fan/miot_fan.cpp:38:144: error: could not convert 'this->esphome::miot::MiotFan::<anonymous>.esphome::fan::Fan::direction' from 'esphome::fan::FanDirection' to 'bool'
   38 |       ESP_LOGV(TAG, "MCU reported reverse direction %" PRIu32 ":%" PRIu32 " is: %s", this->direction_siid_, this->direction_piid_, ONOFF(this->direction));
src/esphome/core/log.h:86:91: note: in definition of macro 'esph_log_v'
   86 |   esp_log_printf_(ESPHOME_LOG_LEVEL_VERBOSE, tag, __LINE__, ESPHOME_LOG_FORMAT(format), ##__VA_ARGS__)
      |                                                                                           ^~~~~~~~~~~
src/esphome/components/miot/fan/miot_fan.cpp:38:7: note: in expansion of macro 'ESP_LOGV'
   38 |       ESP_LOGV(TAG, "MCU reported reverse direction %" PRIu32 ":%" PRIu32 " is: %s", this->direction_siid_, this->direction_piid_, ONOFF(this->direction));
      |       ^~~~~~~~
src/esphome/components/miot/fan/miot_fan.cpp:38:132: note: in expansion of macro 'ONOFF'
   38 |       ESP_LOGV(TAG, "MCU reported reverse direction %" PRIu32 ":%" PRIu32 " is: %s", this->direction_siid_, this->direction_piid_, ONOFF(this->direction));
      |                                                                                                                                    ^~~~~
Compiling .pioenvs/standing-fan/src/esphome/components/number/number_traits.cpp.o
*** [.pioenvs/standing-fan/src/esphome/components/miot/fan/miot_fan.cpp.o] Error 1
========================= [FAILED] Took 10.75 seconds =========================
# https://home.miot-spec.com/spec/dmaker.fan.p18

external_components:
  source: github://dhewg/esphome-miot@fan
  refresh: 0s

# .. snip

fan:
  - platform: "miot"
    name: "Fan"
    state:
      miot_siid: 2
      miot_piid: 1
    speed:
      miot_siid: 2
      miot_piid: 10
      min_value: 1
      max_value: 100
      step: 1
    preset_modes:
      miot_siid: 2
      miot_piid: 3
      options:
        0: "Normal"
        1: "Nature"
    oscillating:
      miot_siid: 2
      miot_piid: 4

@dhewg
Copy link
Owner

dhewg commented Feb 17, 2025 via email

@cristianchelu
Copy link
Contributor

@dhewg Compiles now.
Speed 1% to 100% works, oscillation works, preset mode works, ON/OFF works.

The only small issue I see is an error with unknown preset '' when changing other states:

OFF -> ON

[12:10:23][D][fan:021]: 'Fan' - Setting:
[12:10:23][D][fan:024]:   State: ON
[12:10:23][D][miot:163]: Queuing MCU command 'set_properties 2 1 true'
[12:10:23][E][miot.fan:112]: Unknown preset mode ''
[12:10:23][V][miot:109]: Received MCU message 'get_down'
[12:10:25][V][miot:213]: Sending reply 'down set_properties 2 1 true' to MCU
[12:10:25][V][miot:109]: Received MCU message 'result 2 1 0 '
[12:10:25][V][miot:213]: Sending reply 'ok' to MCU
[12:10:25][V][miot:109]: Received MCU message 'properties_changed 2 1 true 2 6 0'
[12:10:25][V][miot.fan:018]: MCU reported state 2:1 is: ON
[12:10:25][D][fan:120]: 'Fan' - Sending state:
[12:10:25][D][fan:121]:   State: ON
[12:10:25][D][fan:123]:   Speed: 48
[12:10:25][D][fan:126]:   Oscillating: YES

ON -> OFF

[12:11:30][D][fan:021]: 'Fan' - Setting:
[12:11:30][D][fan:024]:   State: OFF
[12:11:30][D][miot:163]: Queuing MCU command 'set_properties 2 1 false'
[12:11:30][E][miot.fan:112]: Unknown preset mode ''
[12:11:30][V][miot:109]: Received MCU message 'get_down'
[12:11:30][V][miot:213]: Sending reply 'down set_properties 2 1 false' to MCU
[12:11:30][V][miot:109]: Received MCU message 'result 2 1 0 '
[12:11:30][V][miot:213]: Sending reply 'ok' to MCU
[12:11:30][V][miot:109]: Received MCU message 'properties_changed 2 1 false 2 6 0'
[12:11:30][V][miot.fan:018]: MCU reported state 2:1 is: OFF
[12:11:30][D][fan:120]: 'Fan' - Sending state:
[12:11:30][D][fan:121]:   State: OFF
[12:11:30][D][fan:123]:   Speed: 48
[12:11:30][D][fan:126]:   Oscillating: YES
[12:11:30][D][fan:132]:   Preset Mode: Nature

Preset Nature -> Preset Normal:

[12:11:52][D][fan:021]: 'Fan' - Setting:
[12:11:52][D][fan:036]:   Preset Mode: Normal
[12:11:52][D][miot:163]: Queuing MCU command 'set_properties 2 3 0'
[12:11:52][V][miot:109]: Received MCU message 'get_down'
[12:11:52][V][miot:213]: Sending reply 'down set_properties 2 3 0' to MCU
[12:11:52][V][miot:109]: Received MCU message 'result 2 3 0 '
[12:11:52][V][miot:213]: Sending reply 'ok' to MCU
[12:11:52][V][miot:109]: Received MCU message 'properties_changed 2 3 0'
[12:11:52][V][miot.fan:044]: MCU reported preset mode 2:3 is: 0
[12:11:52][D][fan:120]: 'Fan' - Sending state:
[12:11:52][D][fan:121]:   State: OFF
[12:11:52][D][fan:123]:   Speed: 48
[12:11:52][D][fan:126]:   Oscillating: YES
[12:11:52][D][fan:132]:   Preset Mode: Normal

Set speed:

[12:12:17][D][fan:021]: 'Fan' - Setting:
[12:12:17][D][fan:024]:   State: ON
[12:12:17][D][fan:030]:   Speed: 63
[12:12:17][D][miot:163]: Queuing MCU command 'set_properties 2 1 true'
[12:12:17][D][miot:163]: Queuing MCU command 'set_properties 2 10 63'
[12:12:17][E][miot.fan:112]: Unknown preset mode ''
[12:12:17][V][miot:109]: Received MCU message 'get_down'
[12:12:17][V][miot:213]: Sending reply 'down set_properties 2 1 true' to MCU
[12:12:17][V][miot:109]: Received MCU message 'result 2 1 0 '
[12:12:17][V][miot:213]: Sending reply 'ok' to MCU
[12:12:17][V][miot:109]: Received MCU message 'properties_changed 2 1 true 2 6 0'
[12:12:17][V][miot.fan:018]: MCU reported state 2:1 is: ON
[12:12:17][D][fan:120]: 'Fan' - Sending state:
[12:12:17][D][fan:121]:   State: ON
[12:12:17][D][fan:123]:   Speed: 48
[12:12:17][D][fan:126]:   Oscillating: YES
[12:12:17][D][fan:132]:   Preset Mode: Normal

Oscillation ON -> OFF:

[12:13:56][D][fan:021]: 'Fan' - Setting:
[12:13:56][D][fan:027]:   Oscillating: NO
[12:13:56][D][miot:163]: Queuing MCU command 'set_properties 2 4 false'
[12:13:56][E][miot.fan:112]: Unknown preset mode ''
[12:13:56][V][miot:109]: Received MCU message 'get_down'
[12:13:56][V][miot:213]: Sending reply 'down set_properties 2 4 false' to MCU
[12:13:56][V][miot:109]: Received MCU message 'result 2 4 0 '
[12:13:56][V][miot:213]: Sending reply 'ok' to MCU
[12:13:56][V][miot:109]: Received MCU message 'properties_changed 2 4 false'
[12:13:56][V][miot.fan:031]: MCU reported oscillating 2:4 is: OFF
[12:13:56][D][fan:120]: 'Fan' - Sending state:
[12:13:56][D][fan:121]:   State: ON
[12:13:56][D][fan:123]:   Speed: 63
[12:13:56][D][fan:126]:   Oscillating: NO
[12:13:56][D][fan:132]:   Preset Mode: Normal

@cristianchelu
Copy link
Contributor

@dhewg The purifier also works, and the 1% to 100% speeds correctly set 450-2k rpm.

however... I'm not sure how to best add the config.

fan:
  - platform: "miot"
    name: "Fan"
    state:
      miot_siid: 2
      miot_piid: 1
    speed: 
      miot_siid: 9
      miot_piid: 2 # Favourite speed
      min_value: 450
      max_value: 2000
      step: 1
    preset_modes:
      miot_siid: 2
      miot_piid: 4
      options:
        0: "Auto"
        1: "Sleep"
        2: "Favorite"
        3: "Manual"

There is a target Manual Speed input, a target Favourite Speed input, and a true Motor speed sensor.
Which input, if any, affects the motor speed depends on which mode is selected.

So I can set the target fan speed to 69%, set the preset mode to "Favourite" and all is good.
When I change the mode to "Auto" or "Manual", I can play with the UI slider all I want, it doesn't change the motor speed, and any real-time updates are lost to the UI.

For "Auto" and "Sleep" the speed should basically be read-only, but I'm not sure we can do that in HA UI.

For good UX, I would say set the mode to favourite when a user changes the speed (that old automation branch would come in handy).

Or we could just leave it as is...

Thoughts?

@dhewg
Copy link
Owner

dhewg commented Feb 17, 2025 via email

@cristianchelu
Copy link
Contributor

I think all we can do atm is to work around it. A cleaner solution would
be if the fan component could handle such dependencies.

Gotcha.

In the meantime, I realized I can fix my issue today, we have actions for fans:

fan:
  - platform: "miot"
    # ...
    preset_modes:
      miot_siid: 2
      miot_piid: 4
      options:
        0: "Auto"
        1: "Sleep"
        2: "Favorite"
        3: "Manual"
    on_speed_set:
      then:
        select.set:
          id: preset_mode
          option: Favorite

select:
  - platform: "miot"
    miot_siid: 2
    miot_piid: 4
    id: preset_mode
    name: "Mode"
    options:
      0: "Auto"
      1: "Sleep"
      2: "Favorite"
      3: "Manual"

aand... while writing this I realized there might be another bug, or I'm misunderstanding expected behavior. I forgot to delete the old components when I added the fan, but I have no compile error, and I can't spot any runtime errors either. Maybe because I'm missing the very first logs due to the OTA logger.

The components are visible in HA, controllable from HA, but their state is always unknown.

I guess I could always use then: lambda: id(miot).queue_command("set_properties 2 4 2) directly or make the select internal: True.

The 3rd, the speed sensor, is different though. At least for my purifier
that's the fan's real rpm, so that's an additional sensor, independent
of the fan's inputs.

Correct, exactly the same for me.

And my speed settings are 0-14, so it's another
unit altogether. Yours are 450-2000 though, does that map closely to the
rpm

Yes, it does map. With the normal spin-up transition delay and small fluctuations, it's within a couple rpm to any target I set (when Favorite/Manual are separate number components).

The very latest commits also work, BTW, no more missing preset errors on changing states.

@dhewg
Copy link
Owner

dhewg commented Feb 17, 2025 via email

@cristianchelu
Copy link
Contributor

cristianchelu commented Feb 17, 2025

But does that config even work? I think you should get an error when a second id 2:4 registers a listener.

That's what is surprising to me too.

It's not newly unavailable entities still living in cache, they're still live, interactive, actually-device-controlling :)

Their current state is just never updated, presumably because the fan properties_changed listeners are registered before the individual listeners.

Edit: Fair point, I could just use the fan itself; it didn't cross my mind. Thanks!

Favorite speed and Mode in the below screenshots are affected:

Image Image

@dhewg
Copy link
Owner

dhewg commented Feb 17, 2025 via email

@cristianchelu
Copy link
Contributor

Hmm...

If it is possible to set a percentage speed manually without disabling the preset mode, create a switch or service action to represent the mode.

By this reasoning, the smart fans in this issue should not have a preset mode.
The "Natural breeze" setting only introduces some (sort of 1D Perlin noise) variation in the output RPM, with the base speed remaining controllable. "Direct breeze" just turns this noise off.

There's no other "eco"/"smart"/autopilot option available.

And for the purifiers, this would mean that the only true presets would be "Auto" and "Sleep"(minimum). Otherwise, we could keep "Favourite" and "Manual" as presets but not have speed control in the fan entity? Leave them as separate inputs?

I'm more confused now.

But, to get back on track, I think the fan component/branch is usable in its current state for these 2 devices, and we can spin off a new discussion/issue about how to tackle more "complex" devices like purifiers?

@dhewg
Copy link
Owner

dhewg commented Feb 18, 2025 via email

@cristianchelu
Copy link
Contributor

@dhewg

I've had a chance to play around with the latest code, yes it does make sense.

I have a few separate comments:

Manual preset

I've chosen Manual mode for the manual fan preset. Whatever preset I am in, changing the speed correctly sets it back to manual mode and I am in control. Put it back to a preset, the device is in control, nice!

However, what I'm missing is precise fan speed reporting in these modes.

I know it's complicating things further, but would it be possible to have the speed reporting PIID be separate from the speed setting PIID? Or at least, in the non-manual presets make the speed null, as having an incorrect speed displayed is worse in my view than having none displayed at all.

The Manual speed PIID on this device is a bit interesting (read: annoying), there's two cases I've just now understood:

  1. The Mode PIID is set to manual. Now the Manual Speed PIID acts as the target speed input for the device. The fan spins within a few RPM of this target exactly, and the device remembers the precise speed when polled.
  2. The Mode PIID is set to anything else. Now the Manual Speed PIID acts as an output, BUT, it only reports 4 discrete speeds: 450, 900, 1500, 2000rpm, corresponding to the coarse fan levels 1-4, when the actual fan speed is in-between.
    • (If I set it to anything in this mode, the fan speed changes briefly, but in ~1 second it bounces back to whatever rpm the currently active mode wants)

It also doesn't help that the manual speed PIID is only polled once per minute, when the actual motor speed PIID is notified real time by the mcu.

So, for any preset, the displayed speed is wildly out of touch with what I can hear the fan do.

Re: favourite vs manual being the same,

I've left "Favourite" as a selectable fan preset as well, with its corresponding Favourite speed piid as an independent control in the device menu.

I guess if we have "auto" and "min/sleep", we could have a user definable "just right" preset, besides the "manual" temporary override. (e.g. "cooking-when-others-are-sleeping" :D )

speed reporting bug

[13:02:08][V][miot:109]: Received MCU message 'result 9 4 0 1050'
[13:02:08][V][miot.fan:027]: MCU reported speed 9:4 is: 601 (raw: 1050)
[13:02:08][D][fan:120]: 'Fan' - Sending state:
[13:02:08][D][fan:121]:   State: ON
[13:02:08][D][fan:123]:   Speed: 601
[13:02:08][D][fan:132]:   Preset Mode: Auto

The reported speed should be the raw speed in this device's case. There may be 1-100% mapped to 450-2000rpm in the "set" direction, but in the "report" direction there's no need for the inverse. I'm not sure if this is new or I just didn't spot it in the beginning. The 1050 rpm reported here is in line with the "Motor Speed" ground truth sensor.

possible device bug

Most likely not something we can fix in this project, but I've seen some weird errors from time to time. It seems that if the MCU has some properties_changed notifications at the same time we have commands for it, it errors out and skips processing the command...

From what I've seen very likely to happen when we queue the 3 set_properties commands on fan mode change.

[13:17:55][V][miot:213]: Sending reply 'down none' to MCU
[13:17:55][D][fan:021]: 'Fan' - Setting:
[13:17:55][D][fan:036]:   Preset Mode: Auto
[13:17:55][D][miot:163]: Queuing MCU command 'set_properties 2 4 0'
[13:17:55][V][miot:109]: Received MCU message 'get_down'
[13:17:55][V][miot:213]: Sending reply 'down set_properties 2 4 0' to MCU
[13:17:55][V][miot:109]: Received MCU message 'properties_changed 2 2 0'
[13:17:55][V][miot.text_sensor:011]: MCU reported text sensor 2:2 is: 0
[13:17:55][V][text_sensor:013]: 'Device Fault': Received new state 0
[13:17:55][D][text_sensor:064]: 'Device Fault': Sending state 'No Faults'
[13:17:55][V][miot:213]: Sending reply 'ok' to MCU
[13:17:55][V][miot:109]: Received MCU message 'error "command error" -5001'
[13:17:55][E][miot:400]: MCU command error -5001: command error
[13:17:55][V][miot:213]: Sending reply 'ok' to MCU
[13:17:55][V][miot:109]: Received MCU message 'get_down'
[13:21:09][V][miot:109]: Received MCU message 'get_down'
[13:21:09][V][miot:213]: Sending reply 'down set_properties 9 4 1039' to MCU
[13:21:09][V][miot:109]: Received MCU message 'properties_changed 2 5 1'
[13:21:09][V][miot.select:011]: MCU reported select 2:5 is: 1
[13:21:09][D][select:015]: 'Fan Level': Sending state Low (index 0)
[13:21:09][V][miot:213]: Sending reply 'ok' to MCU
[13:21:09][V][miot:109]: Received MCU message 'error "command error" -5001'
[13:21:09][E][miot:400]: MCU command error -5001: command error
[13:21:09][V][miot:213]: Sending reply 'ok' to MCU
[13:21:09][V][miot:109]: Received MCU message 'get_down'

@dhewg
Copy link
Owner

dhewg commented Feb 19, 2025

That Manual Speed PIID behavior sounds funky and is different on my device. In my case you have a 0-14 range, and that is exactly what is reported back. If the device is in any other mode it doesn't report back anything different as in your case. If the favorite speed piid acting in the same way?

Your device's behavior is even problematic for HA, if you set a value of 3 and it a bit later it reports back 5, that yields all kinds of issues, like never ending automations triggering all the time.

So we can't just make it report other values or even units (like the raw value) there for the same reason, but I've had the same idea with just unsetting the speed if the device in not in manual mode. It's just consistent gui wise and a less confusing. Same when the device is off, reporting a speed makes it look like it could be on or in manual mode.

So let's just not report the speed in those cases. I've just implemented exactly that, how does that look?

@dhewg
Copy link
Owner

dhewg commented Feb 19, 2025

As for the -5001 error/bug, I get that too, but only in verbose mode, not in debug mode. I didn't dig too deep, but it looks like a sdk or even hardware bug, where using one uart affects the other. The more you use one the more likely it affects the other or something.

@scrampker
Copy link

I'm still using the template fan config tossed together a few months ago. What benefit is there to switch over to a proper fan config?

@dhewg
Copy link
Owner

dhewg commented Feb 19, 2025

It's a reusable component so we don't have to spam all that ugly template code into all the configs ;)

But it also allows us to fine-tune the behavior, like the template/speed issue discussed on the last few comments.

I've refactored the standing fan configs for this new components too, so feel free to jump in, test and report back:
https://github.com/dhewg/esphome-miot/blob/fan/config/dmaker.fan.p18.yaml

@dhewg
Copy link
Owner

dhewg commented Feb 19, 2025

@cristianchelu while refactoring dmaker.fan.p18 I noticed lambda: id(miot_main).queue_command("set_properties 2 9 1");. Why a lamba? Doesn't miot_action_args: "1" work here?

@cristianchelu
Copy link
Contributor

That Manual Speed PIID behavior sounds funky and is different on my device.

Yes, it's very weird, but at least we understand it now. Also keep in mind that this is Manual not Favorite. My Favorite PIID, even though it's 450-200 instead of 0-14, stays stable no matter what the fan is doing or what mode it's in. Manual is all over the place.

Your device's behavior is even problematic for HA, if you set a value of 3 and it a bit later it reports back 5, that yields all kinds of issues, like never ending automations triggering all the time.

It only reports back a 5 if the mode is no longer manual. When you set it to 3 and the fan component also sets the preset to manual, all is stable.

Only later when/if something else changes the mode from manual to auto, would the speed reported rightfully change (even if only in coarse values).

So let's just not report the speed in those cases. I've just implemented exactly that, how does that look?

Much better, but in an ideal world, I would still expect it to report the speed I know it has access to regardless of the mode. Still in percentages, but mapped from 450-2000 instead of 0-14. Small steps, though...

As for the -5001 error/bug, I get that too, but only in verbose mode, not in debug mode

Good to know, thanks!

While refactoring dmaker.fan.p18 I noticed lambda: id(miot_main).queue_command("set_properties 2 9 1");. Why a lamba?
Doesn't miot_action_args: "1" work here?

The command is set_properties, not execute_action :)
It's exactly as bass ackwards as it sounds, one PIID masquerading as two AIIDs.

The reported speed should be the raw speed in this device's case. There may be 1-100% mapped to 450-2000rpm in the "set" direction, but in the "report" direction there's no need for the inverse. I'm not sure if this is new or I just didn't spot it in the beginning. The 1050 rpm reported here is in line with the "Motor Speed" ground truth sensor.

@dhewg just in case you missed this in my comment ^

@dhewg
Copy link
Owner

dhewg commented Feb 19, 2025

If your piid 9:4 and/or 9:2 is too funky you may as well use 9:9 with a range of 1-11 for the fan's manual mode? Sure, it's less granular, but that range should be more than sufficient for a daily driver.

Much better, but in an ideal world, I would still expect it to report the speed I know it has access to regardless of the mode. Still in percentages, but mapped from 450-2000 instead of 0-14. Small steps, though...

I know what you mean, but I don't think that's how HA's fan is designed. A set speed percentage implies manual mode. If the device is off, and e.g. the sleep preset is set, and you just set a percentage, it turns the device on - that's not our fan component.

just in case you missed this in my comment ^

Nope:

So we can't just make it report other values or even units (like the raw value)

There're two levels of conversions:

  1. ours, a la (max-min)/step
  2. HA's, where step1 gets converted to a percentage, as visible in the gui

We always need to set and get the same unit, why do you think we need to mix units here?

@cristianchelu
Copy link
Contributor

There're two levels of conversions:

ours, a la (max-min)/step
HA's, where step1 gets converted to a percentage, as visible in the gui
We always need to set and get the same unit, why do you think we need to mix units here?

It's only the logs for the intermediate value that are wrong, you're right, in HA all is good. I saw:

[13:02:08][V][miot.fan:027]: MCU reported speed 9:4 is: 601 (raw: 1050)

1%(HA) = 450 (MCU)
~10%(HA) = 601 (MCU)
~39%(HA) = 1050 (MCU)
100%(HA) = 2000 (MCU)

Or from a more complete log:

# -------------- User sets 71% fan speed in HA
[04:54:50][D][fan:021]: 'Fan' - Setting:
[04:54:50][D][fan:024]:   State: ON
# -------------- wrong value
[04:54:50][D][fan:030]:   Speed: 1102 
[04:54:50][D][miot:163]: Queuing MCU command 'set_properties 2 4 3'
# -------------- right value
[04:54:50][D][miot:163]: Queuing MCU command 'set_properties 9 4 1551' 

[04:55:28][D][sensor:093]: 'Motor Speed': Sending state 1556.00000 rpm with 0 decimals of accuracy

[04:55:29][D][fan:120]: 'Fan' - Sending state:
[04:55:29][D][fan:121]:   State: ON
[04:55:29][D][fan:123]:   Speed: 1102
# -------------- User sees 71% fan speed in HA

I guess we could live with it or log directly in %, but this is what caused me the initial confusion.

@dhewg
Copy link
Owner

dhewg commented Feb 19, 2025

Oh, I see. Well, sorry, my painted picture isn't complete either ;)

If you really wanna know, a yaml of:

fan:
  - platform: "miot"
    name: "Fan"
    speed: 
      min_value: 450
      max_value: 2000

means that our fan components maps that 450-2000 range onto 1-1551 (0=off). Why? Because esphome/HA only has a speed_count, no min nor steps, only natural values starting from 1 without gaps.

So that esphome fan speed value of 601 is min + value * steps - 1 on the wire, so 450 + 601 * 1 - 1 = 1050.
And 1050 is 100/1550*601=38.77%

Which doesn't directly map to the motor's real percentage either... One could argue that 2000 is 100% and 450 is 22.5%, but as 1-449 isn't possible the percentage is mapped onto the valid 450-2000 range, so with that 450 is 0.065%.

And all of that crap is why I asked for Can you check if that range works as expected with the fan component, please? I'm not sure if I got the calculations correct in all the places for such a minimum value....

Happy now? :P

@dhewg
Copy link
Owner

dhewg commented Feb 19, 2025

In any case, to me the current state looks pretty good.

Well, considering what HA's fan component expects. There're different ways we might want it to behave (e.g. the standing's fan breeze mode a preset mode), but it just isn't designed like that.

With that in mind, how are your devices 'feeling' with this state? In other words: ready to merge?

@cristianchelu
Copy link
Contributor

And all of that crap is why I asked for Can you check if that range works as expected with the fan component, please? I'm not sure if I got the calculations correct in all the places for such a minimum value....

Yeah, I understood the math; I guess I could have explained what I saw was wrong better -- words are hard sometimes, sorry for that.

And, functionally, it does work as intended, as I said, as tested by setting the speed in HA to 1%, 50%, and 100% and observing the Motor speed sensor, again in HA.

I'll try to rephrase:
Only the logs in ESPHome appear wrong to a casual user for any fan that has a speed range starting at > 1.

Do with that info as you wish :) .

With that in mind, how are your devices 'feeling' with this state?

The p18 standing fan without presets LGTM.

The purifier with its manual/presets, when the thing is OFF and there is auto/sleep shown selected, if you press the power button (not selecting a percentage speed), it still clears the preset ad turns on to Manual mode in its last selected speed, instead of turning on to the shown preset mode.

Your choice if you want to do anything about that.

@dhewg
Copy link
Owner

dhewg commented Feb 19, 2025

Don't worry, glad we're on the same page now ;) I get that the values look wrong/confusing. I'll leave the logs as-is, as the used value is useful for debugging too. And the raw/on-wire value is also there ;)

The purifier with its manual/presets, when the thing is OFF and there is auto/sleep shown selected, if you press the power button (not selecting a percentage speed), it still clears the preset ad turns on to Manual mode in its last selected speed, instead of turning on to the shown preset mode.

Indeed, didn't notice that. I think that's a esphome bug: esphome/esphome#8277

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants