maximkulkin / esp-homekit-demo

Demo of Apple HomeKit accessory server library
MIT License
809 stars 233 forks source link

Blinds #241

Closed AchimPieters closed 5 years ago

AchimPieters commented 5 years ago

Hi Maxim,

I have a problem with my blinds setup, I used the sonoff_dual_blinds example to build upon. So after compiling everything is working correctly.

After adding my blinds to HomeKit the motor (see scheme below) turns left and right when I open and close the blinds. ( from 0 to 100 in 15 seconds / 100 to 0 in 15 seconds).

I can't figure out why the Up button does not work? and the 'Button' button should it not stop the up and down movement when pressed?

Maybe I have looked at it for too long and can't see the solution anymore? :weary:

Scheme

ESP8266-HomeKit-Blinds

Code

#include <stdio.h>
#include <stdlib.h>
//#include <espressif/esp_wifi.h>
//#include <espressif/esp_sta.h>>
#include <espressif/esp_common.h>
#include <esp/uart.h>
#include <esp8266.h>
#include <FreeRTOS.h>
#include <task.h>

#include <homekit/homekit.h>
#include <homekit/characteristics.h>
#include <wifi_config.h>

#include "button.h"

#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#define MIN(x, y) (((x) < (y)) ? (x) : (y))

#define POSITION_OPEN 100
#define POSITION_CLOSED 0
#define POSITION_STATE_CLOSING 0
#define POSITION_STATE_OPENING 1
#define POSITION_STATE_STOPPED 2

// number of seconds the blinds take to move from fully open to fully closed position
#define SECONDS_FROM_CLOSED_TO_OPEN 15

TaskHandle_t updateStateTask;
homekit_characteristic_t current_position;
homekit_characteristic_t target_position;
homekit_characteristic_t position_state;
homekit_accessory_t *accessories[];

// The GPIO pin that is connected to the relay on the Sonoff Dual R2
const int relay0_gpio = 12;
const int relay1_gpio = 13;
// The GPIO pin that is connected to the LED on the Sonoff Dual R2
const int led_gpio = 2;
// The GPIO pin that is oconnected to the button on the Sonoff Dual R2
const int BUTTON_GPIO = 4;
// The GPIO pin that is oconnected to the BUTTON1 header pin
const int BUTTON1_GPIO = 16;
// The GPIO pin that is oconnected to the BUTTON0 header pin
const int BUTTON0_GPIO = 14;

const int button_up = 16;
const int button_down = 14;
const int relay_up = 12;
const int relay_down = 13;

void target_position_changed();

void relay_write(int relay, bool on) {
    gpio_write(relay, on ? 1 : 0);
}

void led_write(bool on) {
    gpio_write(led_gpio, on ? 0 : 1);
}

void relays_write(int state) {
    switch (state){
    case POSITION_STATE_CLOSING:
        gpio_write(relay_up, 0);
        gpio_write(relay_down, 1);
        break;
    case POSITION_STATE_OPENING:
        gpio_write(relay_down, 0);
        gpio_write(relay_up, 1);
        break;
    default:
        gpio_write(relay_down, 0);
        gpio_write(relay_up, 0);
    }
}

void reset_configuration_task() {
    //Flash the LED first before we start the reset
    for (int i=0; i<3; i++) {
        led_write(true);
        vTaskDelay(100 / portTICK_PERIOD_MS);
        led_write(false);
        vTaskDelay(100 / portTICK_PERIOD_MS);
    }

    printf("Resetting Wifi Config\n");

    wifi_config_reset();

    vTaskDelay(1000 / portTICK_PERIOD_MS);

    printf("Resetting HomeKit Config\n");

    homekit_server_reset();

    vTaskDelay(1000 / portTICK_PERIOD_MS);

    printf("Restarting\n");

    sdk_system_restart();

    vTaskDelete(NULL);
}

void reset_configuration() {
    printf("Resetting Sonoff configuration\n");
    xTaskCreate(reset_configuration_task, "Reset configuration", 256, NULL, 2, NULL);
}

void gpio_init() {
    gpio_enable(led_gpio, GPIO_OUTPUT);
    led_write(false);

    gpio_enable(button_up, GPIO_INPUT);
    gpio_enable(button_down, GPIO_INPUT);

    gpio_enable(relay0_gpio, GPIO_OUTPUT);
    gpio_enable(relay1_gpio, GPIO_OUTPUT);
    relay_write(relay0_gpio, false);
    relay_write(relay1_gpio, false);
}

void button_callback(button_event_t event, void* context) {
        switch (event) {
        case button_event_single_press:
              break;
        case button_event_long_press:
                //reset_configuration();
                break;
        default:
                printf("Unknown button event: %d\n", event);
        }
}

void button_up_callback(button_event_t event, void* context) {
    // up button pressed
    if (position_state.value.int_value != POSITION_STATE_STOPPED){ // if moving, stop
    target_position.value.int_value = current_position.value.int_value;
    target_position_changed();
    }else{
        switch (event) {
            case button_event_single_press:
            target_position.value.int_value = POSITION_OPEN;
                target_position_changed();
                break;
            case button_event_long_press:
                //reset_configuration();
                break;
            default:
                printf("Unknown button event: %d\n", event);
        }
    }
}

void button_down_callback(button_event_t event, void* context) {
    // down button pressed
    if (position_state.value.int_value != POSITION_STATE_STOPPED){ // if moving, stop
    target_position.value.int_value = current_position.value.int_value;
    target_position_changed();
    }else{
        switch (event) {
            case button_event_single_press:
            target_position.value.int_value = POSITION_CLOSED;
                target_position_changed();
                break;
            case button_event_long_press:
                //reset_configuration();
                break;
            default:
                printf("Unknown button event: %d\n", event);
        }
    }
}

void update_state() {
    while (true) {
printf("update_state\n");
    relays_write(position_state.value.int_value);
        uint8_t position = current_position.value.int_value;
        int8_t direction = position_state.value.int_value == POSITION_STATE_OPENING ? 1 : -1;
        int16_t newPosition = position + direction;

        printf("position %u, target %u\n", newPosition, target_position.value.int_value);

        current_position.value.int_value = newPosition;
        homekit_characteristic_notify(&current_position, current_position.value);

        if (newPosition == target_position.value.int_value) {
            printf("reached destination %u\n", newPosition);
            position_state.value.int_value = POSITION_STATE_STOPPED;
        relays_write(position_state.value.int_value);
            homekit_characteristic_notify(&position_state, position_state.value);
            vTaskSuspend(updateStateTask);
        }

        vTaskDelay(pdMS_TO_TICKS(SECONDS_FROM_CLOSED_TO_OPEN * 10));
    }
}

void update_state_init() {
    xTaskCreate(update_state, "UpdateState", 256, NULL, tskIDLE_PRIORITY, &updateStateTask);
    vTaskSuspend(updateStateTask);
}

void window_covering_identify(homekit_value_t _value) {
    printf("Curtain identify\n");
}

void on_update_target_position(homekit_characteristic_t *ch, homekit_value_t value, void *context);

homekit_characteristic_t current_position = {
    HOMEKIT_DECLARE_CHARACTERISTIC_CURRENT_POSITION(POSITION_CLOSED)
};

homekit_characteristic_t target_position = {
    HOMEKIT_DECLARE_CHARACTERISTIC_TARGET_POSITION(POSITION_CLOSED, .callback=HOMEKIT_CHARACTERISTIC_CALLBACK(on_update_target_position))
};

homekit_characteristic_t position_state = {
    HOMEKIT_DECLARE_CHARACTERISTIC_POSITION_STATE(POSITION_STATE_STOPPED)
};

homekit_accessory_t *accessories[] = {
    HOMEKIT_ACCESSORY(.id=1, .category=homekit_accessory_category_window_covering, .services=(homekit_service_t*[]) {
        HOMEKIT_SERVICE(ACCESSORY_INFORMATION, .characteristics=(homekit_characteristic_t*[]) {
            HOMEKIT_CHARACTERISTIC(NAME, "Window blind"),
            HOMEKIT_CHARACTERISTIC(MANUFACTURER, "None"),
            HOMEKIT_CHARACTERISTIC(SERIAL_NUMBER, "201905261408"),
            HOMEKIT_CHARACTERISTIC(MODEL, "Blinds"),
            HOMEKIT_CHARACTERISTIC(FIRMWARE_REVISION, "0.1"),
            HOMEKIT_CHARACTERISTIC(IDENTIFY, window_covering_identify),
            NULL
        }),
        HOMEKIT_SERVICE(WINDOW_COVERING, .primary=true, .characteristics=(homekit_characteristic_t*[]) {
            HOMEKIT_CHARACTERISTIC(NAME, "Window blind"),
            &current_position,
            &target_position,
            &position_state,
            NULL
        }),
        NULL
    }),
    NULL
};

void on_update_target_position(homekit_characteristic_t *ch, homekit_value_t value, void *context) {
    target_position_changed();
}

void target_position_changed(){
    printf("Update target position to: %u\n", target_position.value.int_value);

    if (target_position.value.int_value == current_position.value.int_value) {
        printf("Current position equal to target. Stopping.\n");
        position_state.value.int_value = POSITION_STATE_STOPPED;
    relays_write(position_state.value.int_value);
        homekit_characteristic_notify(&position_state, position_state.value);
        vTaskSuspend(updateStateTask);
    } else {
        position_state.value.int_value = target_position.value.int_value > current_position.value.int_value
            ? POSITION_STATE_OPENING
            : POSITION_STATE_CLOSING;

        homekit_characteristic_notify(&position_state, position_state.value);
        vTaskResume(updateStateTask);
    }
}

homekit_server_config_t config = {
    .accessories = accessories,
    .password = "345-58-410",
    .setupId="0LK7",
};

void on_wifi_ready() {
    homekit_server_init(&config);
}

void user_init(void) {
    uart_set_baud(0, 115200);

    gpio_init();

    wifi_config_init("blinds", NULL, on_wifi_ready);
    update_state_init();

    button_config_t button_config = BUTTON_CONFIG(
        .active_level=button_active_low,
    );

    int r = button_create(BUTTON_GPIO, button_config, button_callback, NULL);
    if (r) {
        printf("Failed to initalize button (code %d)\n", r);
    }

    int u = button_create(BUTTON0_GPIO, button_config, button_down_callback, NULL);
    if (u) {
        printf("Failed to initalize button (code %d)\n", r);
    }

    int d = button_create(BUTTON1_GPIO, button_config, button_up_callback, NULL);
    if (d) {
        printf("Failed to initalize button (code %d)\n", r);
    }

}
maximkulkin commented 5 years ago

According to this invaluable reference it looks like GPIO16 does not have an interrupt support. That's why your UP button (which is connected to it) does not work. I recommend using the other unused pin - GPIO5 for that.

A few suggestions on the code:

  1. You have button_up constant and BUTTON1_GPIO define both specifying pin number for UP button. In one place you use button_up, in other - BUTTON1_GPIO. I suggest getting rid of BUTTON1_GPIO and using a more descriptive button_up constant. Same applies to DOWN and BUTTON button.
  2. You can simplify relay_write like this:
    void relays_write(int state) {
        gpio_write(relay_up, state == POSITION_STATE_OPENING);
        gpio_write(relay_down, state == POSITION_STATE_CLOSING);
    }
  3. Instead of doing this:
    int8_t direction = position_state.value.int_value == POSITION_STATE_OPENING ? 1 : -1;

    I would do this:

    int8_t direction = current_position.value.int_value < target_position.value.int_value ? 1 : -1;

    to prevent runaway. In general, I would just use current_position & target_position to control movements and would derive position_state based on the other two.

AchimPieters commented 5 years ago

Snap, I forgot about the gpio16 interrupt support. I will look in to your code suggestions.

Thank you so much!

AchimPieters commented 5 years ago

@maximkulkin

It works! thank you so much, now I have to figure out how to implement this in a real life setup for my curtains / Blinds.

giphy

Here's my working code:

#include <stdio.h>
#include <stdlib.h>
#include <espressif/esp_common.h>
#include <esp/uart.h>
#include <esp8266.h>
#include <FreeRTOS.h>
#include <task.h>
#include <homekit/homekit.h>
#include <homekit/characteristics.h>
#include <wifi_config.h>
#include "button.h"

#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#define MIN(x, y) (((x) < (y)) ? (x) : (y))

#define POSITION_OPEN 100
#define POSITION_CLOSED 0
#define POSITION_STATE_CLOSING 0
#define POSITION_STATE_OPENING 1
#define POSITION_STATE_STOPPED 2

// number of seconds the blinds take to move from fully open to fully closed position
#define SECONDS_FROM_CLOSED_TO_OPEN 15

TaskHandle_t updateStateTask;
homekit_characteristic_t current_position;
homekit_characteristic_t target_position;
homekit_characteristic_t position_state;
homekit_accessory_t *accessories[];

const int relay0_gpio = 12;
const int relay1_gpio = 13;
const int led_gpio = 2;
const int button_gpio = 4;
const int button1_gpio = 5;
const int button0_gpio = 14;

const int button_up = 5;
const int button_down = 14;
const int relay_up = 12;
const int relay_down = 13;

void target_position_changed();

void relay_write(int relay, bool on) {
        gpio_write(relay, on ? 1 : 0);
}

void led_write(bool on) {
        gpio_write(led_gpio, on ? 0 : 1);
}

void relays_write(int state) {
        gpio_write(relay_up, state == POSITION_STATE_OPENING);
        gpio_write(relay_down, state == POSITION_STATE_CLOSING);
}

void reset_configuration_task() {
        for (int i=0; i<3; i++) {
                led_write(true);
                vTaskDelay(100 / portTICK_PERIOD_MS);
                led_write(false);
                vTaskDelay(100 / portTICK_PERIOD_MS);
        }

        printf("Resetting Wifi Config\n");

        wifi_config_reset();

        vTaskDelay(1000 / portTICK_PERIOD_MS);

        printf("Resetting HomeKit Config\n");

        homekit_server_reset();

        vTaskDelay(1000 / portTICK_PERIOD_MS);

        printf("Restarting\n");

        sdk_system_restart();

        vTaskDelete(NULL);
}

void reset_configuration() {
        printf("Resetting Sonoff configuration\n");
        xTaskCreate(reset_configuration_task, "Reset configuration", 256, NULL, 2, NULL);
}

void gpio_init() {
        gpio_enable(led_gpio, GPIO_OUTPUT);
        led_write(false);

        gpio_enable(button_up, GPIO_INPUT);
        gpio_enable(button_down, GPIO_INPUT);

        gpio_enable(relay0_gpio, GPIO_OUTPUT);
        gpio_enable(relay1_gpio, GPIO_OUTPUT);
        relay_write(relay0_gpio, false);
        relay_write(relay1_gpio, false);
}

void button_up_callback(uint8_t gpio_num, button_event_t event) {
        if (position_state.value.int_value != POSITION_STATE_STOPPED) {
                target_position.value.int_value = current_position.value.int_value;
                target_position_changed();
        }else{
                switch (event) {
                case button_event_single_press:
                        target_position.value.int_value = POSITION_OPEN;
                        target_position_changed();
                        break;
                case button_event_long_press:
                        break;
                default:
                        printf("Unknown button event: %d\n", event);
                }
        }
}

void button_down_callback(uint8_t gpio_num, button_event_t event) {
        if (position_state.value.int_value != POSITION_STATE_STOPPED) {
                target_position.value.int_value = current_position.value.int_value;
                target_position_changed();
        }else{
                switch (event) {
                case button_event_single_press:
                        target_position.value.int_value = POSITION_CLOSED;
                        target_position_changed();
                        break;
                case button_event_long_press:
                        break;
                default:
                        printf("Unknown button event: %d\n", event);
                }
        }
}

void update_state() {
        while (true) {
                printf("update_state\n");
                relays_write(position_state.value.int_value);
                uint8_t position = current_position.value.int_value;
                int8_t direction = current_position.value.int_value < target_position.value.int_value ? 1 : -1;
                int16_t newPosition = position + direction;

                printf("position %u, target %u\n", newPosition, target_position.value.int_value);

                current_position.value.int_value = newPosition;
                homekit_characteristic_notify(&current_position, current_position.value);

                if (newPosition == target_position.value.int_value) {
                        printf("reached destination %u\n", newPosition);
                        position_state.value.int_value = POSITION_STATE_STOPPED;
                        relays_write(position_state.value.int_value);
                        homekit_characteristic_notify(&position_state, position_state.value);
                        vTaskSuspend(updateStateTask);
                }

                vTaskDelay(pdMS_TO_TICKS(SECONDS_FROM_CLOSED_TO_OPEN * 10));
        }
}

void update_state_init() {
        xTaskCreate(update_state, "UpdateState", 256, NULL, tskIDLE_PRIORITY, &updateStateTask);
        vTaskSuspend(updateStateTask);
}

void window_covering_identify(homekit_value_t _value) {
        printf("Curtain identify\n");
}

void on_update_target_position(homekit_characteristic_t *ch, homekit_value_t value, void *context);

homekit_characteristic_t current_position = {
        HOMEKIT_DECLARE_CHARACTERISTIC_CURRENT_POSITION(POSITION_CLOSED)
};

homekit_characteristic_t target_position = {
        HOMEKIT_DECLARE_CHARACTERISTIC_TARGET_POSITION(POSITION_CLOSED, .callback=HOMEKIT_CHARACTERISTIC_CALLBACK(on_update_target_position))
};

homekit_characteristic_t position_state = {
        HOMEKIT_DECLARE_CHARACTERISTIC_POSITION_STATE(POSITION_STATE_STOPPED)
};

homekit_accessory_t *accessories[] = {
        HOMEKIT_ACCESSORY(.id=1, .category=homekit_accessory_category_window_covering, .services=(homekit_service_t*[]) {
                HOMEKIT_SERVICE(ACCESSORY_INFORMATION, .characteristics=(homekit_characteristic_t*[]) {
                        HOMEKIT_CHARACTERISTIC(NAME, "Window blind"),
                        HOMEKIT_CHARACTERISTIC(MANUFACTURER, "None"),
                        HOMEKIT_CHARACTERISTIC(SERIAL_NUMBER, "201905261408"),
                        HOMEKIT_CHARACTERISTIC(MODEL, "Blinds"),
                        HOMEKIT_CHARACTERISTIC(FIRMWARE_REVISION, "0.7"),
                        HOMEKIT_CHARACTERISTIC(IDENTIFY, window_covering_identify),
                        NULL
                }),
                HOMEKIT_SERVICE(WINDOW_COVERING, .primary=true, .characteristics=(homekit_characteristic_t*[]) {
                        HOMEKIT_CHARACTERISTIC(NAME, "Window blind"),
                        &current_position,
                        &target_position,
                        &position_state,
                        NULL
                }),
                NULL
        }),
        NULL
};

void on_update_target_position(homekit_characteristic_t *ch, homekit_value_t value, void *context) {
        target_position_changed();
}

void target_position_changed(){
        printf("Update target position to: %u\n", target_position.value.int_value);

        if (target_position.value.int_value == current_position.value.int_value) {
                printf("Current position equal to target. Stopping.\n");
                position_state.value.int_value = POSITION_STATE_STOPPED;
                relays_write(position_state.value.int_value);
                homekit_characteristic_notify(&position_state, position_state.value);
                vTaskSuspend(updateStateTask);
        } else {
                position_state.value.int_value = target_position.value.int_value > current_position.value.int_value
                                                 ? POSITION_STATE_OPENING
                                                 : POSITION_STATE_CLOSING;

                homekit_characteristic_notify(&position_state, position_state.value);
                vTaskResume(updateStateTask);
        }
}

homekit_server_config_t config = {
        .accessories = accessories,
        .password = "345-58-410",
        .setupId="0LK7",
};

void on_wifi_ready() {
        homekit_server_init(&config);
}

void user_init(void) {
        uart_set_baud(0, 115200);

        gpio_init();

        wifi_config_init("blinds", NULL, on_wifi_ready);
        update_state_init();

        if (button_create(button_up, 0, 1000, button_up_callback)) {
                printf("Failed to initialize button up\n");
        }
        if (button_create(button_down, 0, 1000, button_down_callback)) {
                printf("Failed to initialize button down\n");
        }
}