Yacubane / esp32-arduino-matter

Matter IoT protocol library for ESP32 working on Arduino framework
Apache License 2.0
298 stars 29 forks source link

WindowCovering Example #24

Open weidingerc opened 1 year ago

weidingerc commented 1 year ago

Hi there, has anyone experience how to setup a window covering device. I tried to modify the LightOnOff Example, but the window covering device always shows as "Not responding". Maybe my initial config is invalid or the update process has to be done differently. I also could not find any example online.

daniel-frenkel commented 1 year ago

@weidingerc

Have you figured it out?

weidingerc commented 1 year ago

Unfortunately not

Yacubane commented 1 year ago

I may look at this. Which Matter Controller are you using?

weidingerc commented 1 year ago

HomePod mini as Border Router ESP32 as controller (https://www.amazon.de/dp/B071P98VTG?psc=1&ref=ppx_yo2ov_dt_b_product_details)

Yacubane commented 1 year ago

Unfortunately I would be able to test it only on Google Home platform. But I will try anyway

Sustainable-Me commented 8 months ago

Our window shade controller just bit the big one and i was going to replace it with a matter/eps32 by modifying the LightOnOff Example (Matter project # 3 for me :) ) through google home wifi. Anybody have luck moving this forward?

weidingerc commented 7 months ago

Any luck getting it to work?

Robothaler commented 7 months ago

@weidingerc The hint worked with my code, the Matter device is not marked with "Not responding". You will find this already incorporated in my code. https://github.com/espressif/esp-matter/issues/343#issuecomment-1531494497

Unfortunately my programming skills are not good, so I have difficulties to finalize my project.

I want to control belt winders, but I don't know how to redirect the Matter functions to my code. Maybe someone can give me a push in the right direction.

Here is my current code: https://github.com/Robothaler/BeltWinder-Matter/tree/dev

weidingerc commented 7 months ago

@Robothaler Thanks a lot, I got it working with the additional control clusters in your code. But what I still did not manage was to just have a "Default lifting" window covering that just can "open/close" and nothing more.

Robothaler commented 7 months ago

@weidingercI'm pleased that you're now one step further. With my code, the Apple Home and EVE app only show me a roller shutter with up/down and stop function. You may also be interested in how the WindowCovering type can be changed. Take a look at the following entry: https://github.com/espressif/esp-matter/issues/280#issuecomment-1488907563

The required line is the following: window_covering_device::config_t wcd_config(static_cast<uint8_t>(chip::app::Clusters::WindowCovering::EndProductType::kRollerShutter));

How do you control your roller shutter? In other words, how do you transfer the position change to your code? Frankly, I have not yet understood the Matter functions. In my code, I want to pass the new position from the Home app to my newPosition function to the newPercentage variable. Maybe you can help me at this point. Also, I can't see any custom DEBUG output in the serial output. I have already changed this to the following format:

ESP_LOGD(TAG, "window_covering_device cluster created with cluster_id %d", cluster); I still can't see the output!

weidingerc commented 7 months ago

using the on_attribute_update function for getting the values from my iPhone home app. Do not wonder, I have a lot of serial print function for debugging :D

I have used a fixed value "11" to check for the attribute_id as the code you sent adds custom clusters to the window covering and I was not able to get the correct value by readme manual, so I have just printed the value by the serial monitor and now use it to check if it is the device created. What I also was not able to change yet is the value range between closed and opened state, it seems that it default to the range from 0 (open) to 10000 (closed).

// Listener on attribute update requests.
static esp_err_t on_attribute_update(attribute::callback_type_t type, uint16_t endpoint_id, uint32_t cluster_id,
                                     uint32_t attribute_id, esp_matter_attr_val_t *val, void *priv_data) 
{
  if (type == attribute::PRE_UPDATE) 
  {
    Serial.print("Update on endpoint id: ");
    Serial.print(endpoint_id);
    Serial.print(" cluster: ");
    Serial.print(cluster_id);
    Serial.print(" attribute id: ");
    Serial.print(attribute_id);
    Serial.println(" value: ");
    Serial.println(val->val.u16);
  }

  if (type == attribute::PRE_UPDATE)
  {
    Serial.println("IN 1");
    if (endpoint_id == endpoint_id_window_covering_1)
    {
      Serial.println("IN 2");
      if (cluster_id == CLUSTER_ID_WINDOW_COVERING)
      {
        Serial.println("IN 3");
        if (attribute_id == 11)
        {
          Serial.println("IN 4");

          // VALUES 0 open vs 10000 closed
          auto new_state = val->val.u16;
          Serial.print("Callback1 Value: ");
          Serial.println(new_state);

          .... update here with your function
        }

}
weidingerc commented 7 months ago

@Robothaler Thanks, I‘ll try this different window covering config once I have time again ;-P

Sustainable-Me commented 7 months ago

I have also had some luck with the matter window covering device -- A simple system controlling multiple shades, one at a time, using a single esp32, a timer for the intermediate shade positions, and limit switches for full open/close. I'm not a programmer, I only play one in retirement ;)

I haven't had a chance to work carefully through the recent comments, yet for me stumbling upon the "int val->val.i" in the static esp_err_t on_attribute_update routine was the key moving from the "bool val->val.b" in the two existing on/off device examples to a variable slider for the shade positions. val.i returns the % closed (which is 10000 - the % open value in the google home slider). Will try the "val->val.u16" as suggested as that seems more logical and should match the GH slide.

Neither the get- or set- attributes, nor the matter shade functions are working for me either - it would be great to learn more about the matter shade controller's inner workings...

However, The current problem I need to solve for intermediate shade positions before implementation is: when moving the slider slowly in the GH app, I am getting multiple values for the open % (val->val.i) along the way. Thus, moving from say 20% to 80% open is returning intermediate values such as 42%, 57%, and 67% before my finger stops moving the slider at 80%. It shouldn't be too hard to figure this out with a simple timer.

Having said that, I wonder if there is a matter function out there that takes care of this automatically?? For full open/close of the shades, the limit switches work.

I thought it might be useful to attach my minimalist Arduino ide code skeleton which commissions a two shade slider group in GH. Perhaps the experts can chime in what matter functions are missing and how to use them... Minimal_window_covering.zip

weidingerc commented 7 months ago

@weidingercI'm pleased that you're now one step further. With my code, the Apple Home and EVE app only show me a roller shutter with up/down and stop function. You may also be interested in how the WindowCovering type can be changed. Take a look at the following entry: espressif/esp-matter#280 (comment)

The required line is the following: window_covering_device::config_t wcd_config(static_cast<uint8_t>(chip::app::Clusters::WindowCovering::EndProductType::kRollerShutter));

How do you control your roller shutter? In other words, how do you transfer the position change to your code? Frankly, I have not yet understood the Matter functions. In my code, I want to pass the new position from the Home app to my newPosition function to the newPercentage variable. Maybe you can help me at this point. Also, I can't see any custom DEBUG output in the serial output. I have already changed this to the following format:

ESP_LOGD(TAG, "window_covering_device cluster created with cluster_id %d", cluster); I still can't see the output!

This line you suggested only leads to "Device not responding" again. I guess I'll stay at the code for Glider shades from 0 to 100% to open and close my shades.

Robothaler commented 6 months ago

I have also had some luck with the matter window covering device -- A simple system controlling multiple shades, one at a time, using a single esp32, a timer for the intermediate shade positions, and limit switches for full open/close. I'm not a programmer, I only play one in retirement ;)

I haven't had a chance to work carefully through the recent comments, yet for me stumbling upon the "int val->val.i" in the static esp_err_t on_attribute_update routine was the key moving from the "bool val->val.b" in the two existing on/off device examples to a variable slider for the shade positions. val.i returns the % closed (which is 10000 - the % open value in the google home slider). Will try the "val->val.u16" as suggested as that seems more logical and should match the GH slide.

Neither the get- or set- attributes, nor the matter shade functions are working for me either - it would be great to learn more about the matter shade controller's inner workings...

However, The current problem I need to solve for intermediate shade positions before implementation is: when moving the slider slowly in the GH app, I am getting multiple values for the open % (val->val.i) along the way. Thus, moving from say 20% to 80% open is returning intermediate values such as 42%, 57%, and 67% before my finger stops moving the slider at 80%. It shouldn't be too hard to figure this out with a simple timer.

Having said that, I wonder if there is a matter function out there that takes care of this automatically?? For full open/close of the shades, the limit switches work.

I thought it might be useful to attach my minimalist Arduino ide code skeleton which commissions a two shade slider group in GH. Perhaps the experts can chime in what matter functions are missing and how to use them... Minimal_window_covering.zip

@Sustainable-Me I have solved the problem of intermediate values when dragging the slider with a new function. The incoming values are buffered there and forwarded to the function for determining the position after a filter time. This works quite well for my case. Here is my current code, of course you still have to create the appropriate global variables.

` // buffering new target values

void bufferNewPosition() {
unsigned long currentTime = millis();

// The very first new target value received
if (newPercentageReceived && !bufferedValueSaved) {
    bufferedPercentage = newPercentage;
    DEBUGPRINTLN2("[BELTWINDER] bufferNewPosition: New percentage received - " + String(newPercentage) + "%");
    DEBUGPRINTLN2("[BELTWINDER] bufferNewPosition: First value buffered - " + String(bufferedPercentage) + "%");

    bufferedValueSaved = true;

    lastUpdateTime = currentTime;
    DEBUGPRINTLN2("[BELTWINDER] lastUpdateTime was 0 will be changed to: New timestamp - " + String(lastUpdateTime) + "ms");
    return;
    }

// A new target value received within filter duration
if (newPercentageReceived && bufferedValueSaved && currentTime - lastUpdateTime <= filterDuration) {

  if (newPercentage == bufferedPercentage) {
    DEBUGPRINTLN2("[BELTWINDER] bufferNewPosition: New percentage is equal to buffered Percentage - " + String(newPercentage) + "%");
  } else {
    bufferedPercentage = newPercentage;
    bufferedValueSaved = true;
    return; 
    }
} else if (newPercentageReceived && bufferedValueSaved && currentTime - lastUpdateTime > filterDuration){
  // Time has expired, use the last buffered value
    DEBUGPRINTLN2("[BELTWINDER] bufferNewPosition: Time limit for buffering exceeded. Use last buffered value for new position.");
    filteredPercentage = bufferedPercentage;

    DEBUGPRINTLN2("[BELTWINDER] bufferNewPosition: Filtered percentage - " + String(filteredPercentage));

    lastUpdateTime = 0; // Set lastUpdateTime to 0 for the next update
    newPercentageReceived = false;
    bufferedValueSaved = false;
    newPosition(filteredPercentage);
}

}`

palsbo commented 4 months ago

I made a Matter device with 3 windowCovering endpoints. It controls 3 Somfy blinds and I can open, close at set to x% opening from Google Home. It is build using Arduino IDE with esp32_arduino_matter library.

It works fine, but II do have a few problems. The conversion from the matter device to Google Home (and other apps) "Up" to be position 100% and down to be 0%. So when I want to position to 20% open, I get 80% open. I tried to do a manual convertion by calculating the opening percentage top 100-value. This works, but on power up, all blinds go down to fully extended position. not good en a power failure situation when the storm is on!.

Either I need to invert the action between the matter device and google home, or I need to be able to set start position to fully extended and then calculate 100-value.

Any idea is apreciated. Regards erik

Robothaler commented 4 months ago

@palsbo Hi, you can find the solution in my repository: BeltWinder-Matter

You have to use "Mode" and change the motordirection:

// Attribute: Id 23 Mode
  set_window_covering_mode(window_covering_endpoint_id, Mode::kMotorDirectionReversed);

To submit the new mode I've created this function:

void set_window_covering_mode(chip::EndpointId endpoint, chip::app::Clusters::WindowCovering::Mode newMode)
{
    esp_matter_attr_val_t modeValue;
    modeValue = esp_matter_int8(static_cast<int8_t>(newMode));

    esp_err_t result = attribute::update(window_covering_endpoint_id, WINDOW_COVERING_CLUSTER_ID, WINDOW_COVERING_ATTRIBUTE_ID_MODE, &modeValue);

    if (result == ESP_OK)
    {
        DEBUGPRINTLN2("[BELTWINDER] Window covering Mode updated successfully");
        chip::DeviceLayer::PlatformMgr().LockChipStack();
        DEBUGPRINTLN2("[BELTWINDER] Current Mode: "); ModePrint(ModeGet(window_covering_endpoint_id));
        chip::DeviceLayer::PlatformMgr().UnlockChipStack();
    }
    else
    {
        DEBUGPRINTLN2("[BELTWINDER] Failed to update window covering Mode");
        // Handle error if needed
    }
}

I hope this will help you.

palsbo commented 4 months ago

Thanks again. I ran into a few problems.

I needed to change a bit, as I am using an array og endpoints. In the function, I changed the updae call from "window_covering_endpoint_id" to just "endpoint" (from the header) - is this correct? Then I get the error: "Mode has not been declared".

This is my modified function: (sorry - I can't get the code formater here to work!)

`

void set_window_covering_mode(chip::EndpointId endpoint, chip::app::Clusters::WindowCovering::Mode newMode) { const uint32_t WINDOW_COVERING_CLUSTER_ID = WindowCovering::Id; const uint32_t WINDOW_COVERING_ATTRIBUTE_ID_MODE = WindowCovering::Attributes::Mode::Id;

esp_matter_attr_val_t modeValue; modeValue = esp_matter_int8(static_cast(newMode));

esp_err_t result = attribute::update(endpoint, WINDOW_COVERING_CLUSTER_ID, WINDOW_COVERING_ATTRIBUTE_ID_MODE, &modeValue);

if (result == ESP_OK) { Serial.println("Window covering Mode updated successfully"); chip::DeviceLayer::PlatformMgr().LockChipStack(); chip::DeviceLayer::PlatformMgr().UnlockChipStack(); } else { // Handle error if needed } } `

And this is my initialization code: in setup():

` node::config_t node_config; node_t node = node::create(&node_config, on_attribute_update, on_identification); endpoint_t endpoint[NUM_BLINDS]; cluster_t *cluster[NUM_BLINDS]; cluster::window_covering::feature::lift::config_t lift[NUM_BLINDS]; cluster::window_covering::feature::position_aware_lift::config_t position_aware_lift[NUM_BLINDS];

window_covering_device::config_t window_covering_device_config; for (int i = 0; i < NUM_BLINDS; i++) { endpoint[i] = window_covering_device::create(node, &window_covering_device_config, ENDPOINT_FLAG_NONE, NULL); if (endpoint[i] != NULL) { cluster[i] = cluster::get(endpoint[i], WindowCovering::Id); cluster::window_covering::feature::lift::add(cluster[i], &lift[i]); cluster::window_covering::feature::position_aware_lift::add(cluster[i], &position_aware_lift[i]); set_window_covering_mode(endpoint::get_id(endpoint[i], Mode::kMotorDirectionReversed); if (devs[i]) { devs[i]->endpoint_id = endpoint::get_id(endpoint[i]); devs[i]->cluster_id = cluster::get_id(cluster[i]); } } }

// Setup DAC (this is good place to also set custom commission data, passcodes etc.) esp_matter::set_custom_dac_provider(chip::Credentials::Examples::GetExampleDACProvider());

esp_matter::start(on_device_event); `

I have included the following in the start:

`

include "Matter.h"

include <app/server/OnboardingCodesUtil.h>

include <credentials/examples/DeviceAttestationCredsExample.h>

using namespace chip; using namespace chip::app::Clusters; using namespace esp_matter; using namespace esp_matter::endpoint; `

Is anything missing?

Erik