maximkulkin / esp-homekit

Apple HomeKit accessory server library for ESP-OPEN-RTOS
MIT License
1.11k stars 170 forks source link

Can anybody help me to get a "Garagedoor-Opener" work ? #24

Closed TMD-Burger closed 6 years ago

TMD-Burger commented 6 years ago

Hello!

1st: Awesome job to get the HomeKit run native on ESP8266 !!

Thank´s for that!

My Problem:

I want to bring up a garagedoor accessory to simple digital in/outs... But i have big problems to understand this api-style coding.

Ok, this is my very first try, and i always get "error: 'homekit_permissions_write'": Can anybody help me to get this Task running?

Big thanks !

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

#include <homekit/homekit.h>
#include <homekit/characteristics.h>
#include "wifi.h"

static void wifi_init() {
    struct sdk_station_config wifi_config = {
        .ssid = WIFI_SSID,
        .password = WIFI_PASSWORD,
    };

    sdk_wifi_set_opmode(STATION_MODE);
    sdk_wifi_station_set_config(&wifi_config);
    sdk_wifi_station_connect();
}

#define SENSOR_PIN 4

void garage_identify(homekit_value_t _value) {
    printf("GarageLock identify\n");
}

homekit_characteristic_t name = HOMEKIT_CHARACTERISTIC_(NAME, "Garage");
homekit_characteristic_t currdoor = HOMEKIT_CHARACTERISTIC_(CURRENT_DOOR_STATE, 0);
homekit_characteristic_t currlock = HOMEKIT_CHARACTERISTIC_(LOCK_CURRENT_STATE, 0);
homekit_characteristic_t tardoor = HOMEKIT_CHARACTERISTIC_(TARGET_DOOR_STATE, 0);
homekit_characteristic_t tarlock = HOMEKIT_CHARACTERISTIC_(LOCK_TARGET_STATE, 0);
homekit_characteristic_t detect = HOMEKIT_CHARACTERISTIC_(OBSTRUCTION_DETECTED, false);

void garage_sensor_task(void *_args) {
    gpio_set_pullup(SENSOR_PIN, false, false);

    while (1) {
        int open = gpio_read(SENSOR_PIN);

        if (open) {            
            homekit_characteristic_notify(&currdoor, HOMEKIT_UINT8(0));

        } else {
            homekit_characteristic_notify(&currdoor, HOMEKIT_UINT8(1));
        }

        vTaskDelay(3000 / portTICK_PERIOD_MS);
    }
}

void garage_sensor_init() {
    xTaskCreate(garage_sensor_task, "Garage Sensor", 256, NULL, 2, NULL);
}

homekit_accessory_t *accessories[] = {
    HOMEKIT_ACCESSORY(.id=1, .category=homekit_accessory_category_garage, .services=(homekit_service_t*[]){
        HOMEKIT_SERVICE(ACCESSORY_INFORMATION, .characteristics=(homekit_characteristic_t*[]){
            &name,
            HOMEKIT_CHARACTERISTIC(MANUFACTURER, "Maxim&i"),
            HOMEKIT_CHARACTERISTIC(SERIAL_NUMBER, "037A2BABF19D"),
            HOMEKIT_CHARACTERISTIC(MODEL, "GarageLock"),
            HOMEKIT_CHARACTERISTIC(FIRMWARE_REVISION, "0.1"),
            HOMEKIT_CHARACTERISTIC(IDENTIFY, garage_identify),
            NULL
        }),
        HOMEKIT_SERVICE(GARAGE_DOOR_OPENER, .primary=true, .characteristics=(homekit_characteristic_t*[]){
            &name,
            &currdoor,
            &currlock,
            &tardoor,
            &tarlock,
            &detect,                           
            NULL
        }),
        NULL
    }),
    NULL
};

homekit_server_config_t config = {
    .accessories = accessories,
    .password = "111-11-111"
};

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

    wifi_init();
    garage_sensor_init();
    homekit_server_init(&config);
}
renssies commented 6 years ago

I'm happy to help here.

First, it looks like there is an error in the library for the LOCK_TARGET_STATE characteristic and since it's included in your code, the compiler would throw that error. I've fixed it in this branch (which will soon be merged into the esp-homekit repo): https://github.com/renssies/esp-homekit/tree/permissions_write_fix

Second, the target lock state and current lock state are optional characteristics. You don't have to include these for a simple garage door opener if you don't intend to also control the lock. If you remove those, you don't need to use my branch.

Third, I see that you do gpio_set_pullup(SENSOR_PIN, false, false) everytime you read the sensor. As far as I know, you only have to do this once. So you can do it in void user_init(void)

I hope that helps fix the issue

TMD-Burger commented 6 years ago

Many thanks for your help! to first: you are right, for a normal opener (switch) there is no lock requierd, but... let me explain: we have a manual door with a electric lock and this "custom-homekit-device" should •get position of door (open/close) •set lock (lock/unlock) •and display this as a garage-door accessory(icon) on ios...

to second: i have delete out the lock-service, it compile now, but the monitor output shows :


>>> HomeKit: [Client 4] Get Accessories
!!! HomeKit: Write value too large
!!! HomeKit: Write value too large
!!! HomeKit: Write value too large
!!! HomeKit: Write value too large
!!! HomeKit: Write value too large
!!! HomeKit: Write value too large
>>> HomeKit: [Client 4] Closing client connection```

hmmm..... ;-)
renssies commented 6 years ago

Hmm, this is something in the JSON library. Maybe @maximkulkin knows a bit more about that

maximkulkin commented 6 years ago

Ok, the error is because of the following: when outputting JSON, it gets buffer of some size allocated, which it fills with JSON info and when buffer fills up, it flushes it into network as HTTP chunk. This error is because a single write to the buffer is larger than buffer size so even if the buffer is empty, it can not take the payload. I can rewrite it to split buffer into parts, but the question is: why does this happen? Characteristics are integers and booleans, a smallest buffer ever allocated for JSON output is 256, there should be no way this is happening.

Can you try removing "&" char from accessory manufacturer string?

TMD-Burger commented 6 years ago

Hello maxim! Thank you for this great work here ! (renssies too !) ;-)

i try... but: after a lot of "try and error" (this is what i can do very good, hehe), i have read and compare a lot of debug-outputs... the fix for this scenario (characteristic "LOCK_CURRENT_STATE & Lock Target State" was to edit the file "...../homekit/src/json.c", line 207:

void _do_write() {
        json_write(json, "%1.15g", x);
    }

to

void _do_write() {
        json_write(json, "%ld", x);
    }

now the accessory & service pairs successfull... (without debug mode, with debug=1, there were many memory-errors and a reboot-loop)

next step, is to get/set the correct "Update Characteristics function", and yes, "try & error" ! ;-)

thank you !

maximkulkin commented 6 years ago

Not sure the reason, but line src/json.c:207 already reads as json_write(json, "%ld", x);

TMD-Burger commented 6 years ago

Sorry, it was too late yesterday... I mean src/json.c:237 -->

 json_write(json, "%1.15g", x);

But i think if a "real" float value has to write to json, it gives problems.

Why are the "min/max/step-values" are declared as float? (homekit/characteristics.h:784):

#define HOMEKIT_CHARACTERISTIC_LOCK_CURRENT_STATE HOMEKIT_APPLE_UUID2("1D")
#define HOMEKIT_DECLARE_CHARACTERISTIC_LOCK_CURRENT_STATE(_value, ...) \
    .type = HOMEKIT_CHARACTERISTIC_LOCK_CURRENT_STATE, \
    .description = "Lock Current State", \
    .format = homekit_format_uint8, \
    .permissions = homekit_permissions_paired_read \
                 | homekit_permissions_notify, \
    .min_value = (float[]) {0}, \
    .max_value = (float[]) {3}, \
    .min_step = (float[]) {1}, \
    .valid_values = { \
        .count = 4, \
        .values = (uint8_t[]) { 0, 1, 2, 3 }, \
    }, \
    .value = HOMEKIT_UINT8_(_value), \
##__VA_ARGS__

here is the article of a hap-handbook:

bildschirmfoto 2018-02-04 um 08 51 22

maximkulkin commented 6 years ago

Min/max/step values are declared as float because of this page in HAP spec: hap-float-values

maximkulkin commented 6 years ago

I tried to reproduce your accessory and found a problem. First, I haven't seen that problem that required me to modify printing floats in src/json.c. Maybe there is a bug somewhere (even in vsnprintf()). What version of esp-open-rtos do you use? Mine is commit 4715d5e8 from Dec 30.

The problem with your code is that you try to re-use NAME characteristic in both ACCESSORY_INFO and GARAGE_DOOR_OPENER services. This is not allowed. All services and characteristic should have unique IIDs. In that state iOS controller successfully pairs, requests characteristics and immediately unpairs. After replacing one of &name, to HOMEKIT_CHARACTERISTIC(NAME, "Garage") everything pairs successfully.

TMD-Burger commented 6 years ago

Hello again!

To the last Problems: After looking for reasons why the "floats" are not correct writes to json, a (re)-download of • esp-open-rtos (SuperHouse) • esp-homekit-demo (maximkulkin) has not brought the expected result, but after a

"export PRINTF_SCANF_FLOAT_SUPPORT=1"

it works now.. :-)

To the required functions: After a bit of probing what services are the best choice for this "custom-device", i had split the garage-door-opener into 3 services: • LOCK_MECHANISM (lock for door1 & door2) • OCCUPANCY_SENSOR (door1) • OCCUPANCY_SENSOR (door2) because the doors open/close manually and a door-service has a "target" characteristic required, wich is not necessary.

here the "homkit relevant" code (the gpio stuff comes later...)

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

#include <homekit/homekit.h>
#include <homekit/characteristics.h>
#include "wifi.h"

static void wifi_init() {
    struct sdk_station_config wifi_config = {
        .ssid = WIFI_SSID,
        .password = WIFI_PASSWORD,
    };

    sdk_wifi_set_opmode(STATION_MODE);
    sdk_wifi_station_set_config(&wifi_config);
    sdk_wifi_station_connect();
}

void garage_identify(homekit_value_t _value) {
    printf("Garage identify\n");
}

void update_state();

void on_update(homekit_characteristic_t *ch, homekit_value_t value, void *context) {
    update_state();
}

homekit_characteristic_t torCur = HOMEKIT_CHARACTERISTIC_(LOCK_CURRENT_STATE, 0);
homekit_characteristic_t torTar = HOMEKIT_CHARACTERISTIC_(LOCK_TARGET_STATE, 0,
.callback=HOMEKIT_CHARACTERISTIC_CALLBACK(on_update));

homekit_characteristic_t occTor1 = HOMEKIT_CHARACTERISTIC_(OCCUPANCY_DETECTED, 0);
homekit_characteristic_t occTor2 = HOMEKIT_CHARACTERISTIC_(OCCUPANCY_DETECTED, 0);                          

void update_state() {       
    uint8_t newstate = torTar.value.int_value;      
    if (newstate == 1) {
        printf("Locked!\n");
        /* here comes in the function for close Lock */         
    }   
    if (newstate == 0) {
        printf("Unlocked!\n");
        /* here comes in the function for open Lock */      
    }   
    torCur.value = torTar.value;
    homekit_characteristic_notify(&torCur, torCur.value);   
}

homekit_accessory_t *accessories[] = {
    HOMEKIT_ACCESSORY(
        .id=1,
        .category=homekit_accessory_category_other,
        .services=(homekit_service_t*[]) {
            HOMEKIT_SERVICE(
                ACCESSORY_INFORMATION,
                .characteristics=(homekit_characteristic_t*[]) {
                    HOMEKIT_CHARACTERISTIC(NAME, "Garage"),
                    HOMEKIT_CHARACTERISTIC(MANUFACTURER, "DiY"),
                    HOMEKIT_CHARACTERISTIC(SERIAL_NUMBER, "0012345"),
                    HOMEKIT_CHARACTERISTIC(MODEL, "GarageLocker"),
                    HOMEKIT_CHARACTERISTIC(FIRMWARE_REVISION, "0.1"),
                    HOMEKIT_CHARACTERISTIC(IDENTIFY, garage_identify),
                    NULL
                },
            ),
            HOMEKIT_SERVICE(
                LOCK_MECHANISM,
                .primary=true,
                .characteristics=(homekit_characteristic_t*[]) {
                    HOMEKIT_CHARACTERISTIC(NAME, "Tore"),
                    &torCur,
                    &torTar,
                    NULL
                },
            ),
            HOMEKIT_SERVICE(
                OCCUPANCY_SENSOR,                
                .characteristics=(homekit_characteristic_t*[]) {
                    HOMEKIT_CHARACTERISTIC(NAME, "Tor1"),
                    &occTor1,
                    NULL
                },
            ),
            HOMEKIT_SERVICE(
                OCCUPANCY_SENSOR,                
                .characteristics=(homekit_characteristic_t*[]) {
                    HOMEKIT_CHARACTERISTIC(NAME, "Tor2"),
                    &occTor2,
                    NULL
                },
            ),
            NULL
        },
    ),
    NULL
};

homekit_server_config_t config = {
    .accessories = accessories,
    .password = "111-11-111"
};

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

    wifi_init();

    homekit_server_init(&config);
}

Thanks you guys again! :-)) It´s so great to have the possibility to create a low-cost device with such functionality!

Korcsog commented 6 years ago

@TMD-Burger could you send me the final accessory file?

TMD-Burger commented 6 years ago

@Korcsog : Hi!

Sorry for my "delay", but i´am a very "busy man" ! ;-)

Ok, here is my final code.

#include <stdio.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 <queue.h>
#include <string.h>

#include <homekit/homekit.h>
#include <homekit/characteristics.h>
#include "wifi.h"
#include "pwm.h"

const gpio_inttype_t int_any = GPIO_INTTYPE_EDGE_ANY;
const int tor1_sensor_pin = 4;
const int tor2_sensor_pin = 5;
const int motion_sensor_pin = 13;

bool armed = false;
bool tor1_closed = false;
bool tor1_progress = false;

bool tor2_closed = false;
bool tor2_progress = false;
bool motion_progress = false;

// HomeKit
static void wifi_init() {
    struct sdk_station_config wifi_config = {
        .ssid = WIFI_SSID,
        .password = WIFI_PASSWORD,
    };

    sdk_wifi_set_opmode(STATION_MODE);
    sdk_wifi_station_set_config(&wifi_config);
    sdk_wifi_station_connect();
}

void garage_identify(homekit_value_t _value) {
    //printf("Garage identify\n");
}

void update_state();

void on_update(homekit_characteristic_t *ch, homekit_value_t value, void *context) {
    update_state();
}

homekit_characteristic_t torCur = HOMEKIT_CHARACTERISTIC_(LOCK_CURRENT_STATE, 0);
homekit_characteristic_t torTar = HOMEKIT_CHARACTERISTIC_(LOCK_TARGET_STATE, 0,.callback=HOMEKIT_CHARACTERISTIC_CALLBACK(on_update));

homekit_characteristic_t occTor1 = HOMEKIT_CHARACTERISTIC_(CONTACT_SENSOR_STATE, 0, .callback=HOMEKIT_CHARACTERISTIC_CALLBACK(on_update));
homekit_characteristic_t occTor2 = HOMEKIT_CHARACTERISTIC_(CONTACT_SENSOR_STATE, 0, .callback=HOMEKIT_CHARACTERISTIC_CALLBACK(on_update));

homekit_characteristic_t motion = HOMEKIT_CHARACTERISTIC_(MOTION_DETECTED, false);
homekit_characteristic_t motion_stat = HOMEKIT_CHARACTERISTIC_(STATUS_ACTIVE, false);

void update_state() {
    uint8_t newstate = torTar.value.int_value;

    uint8_t tor1_state = gpio_read(tor1_sensor_pin);
    if (tor1_state == 0){
        occTor1.value.int_value = 1;
    } else {
        occTor1.value.int_value = 0;
    }

    uint8_t tor2_state = gpio_read(tor2_sensor_pin);
        if (tor2_state == 0){
                occTor2.value.int_value = 1;
        } else {
                occTor2.value.int_value = 0;
        }

    if (newstate == 1) {
        if (tor1_closed && tor2_closed){
            armed = true;
            homekit_characteristic_notify(&motion_stat, HOMEKIT_BOOL(true));
            //printf("Locked!\n");
            pwm_set_duty(3500); // 3700
            pwm_start();
            torCur.value = torTar.value;
            homekit_characteristic_notify(&torCur, torCur.value);
        } else {
            homekit_characteristic_notify(&torCur, HOMEKIT_UINT8(2));
        }
    }
    if (newstate == 0) {
        armed = false;
        homekit_characteristic_notify(&motion_stat, HOMEKIT_BOOL(false));
        //printf("Unlocked!\n");
        pwm_set_duty(7900); //7800
        pwm_start();
        torCur.value = torTar.value;
        homekit_characteristic_notify(&torCur, torCur.value);
    }

}

homekit_accessory_t *accessories[] = {
    HOMEKIT_ACCESSORY(
        .id=1,
        .category=homekit_accessory_category_other,
        .services=(homekit_service_t*[]) {
            HOMEKIT_SERVICE(
                ACCESSORY_INFORMATION,
                .characteristics=(homekit_characteristic_t*[]) {
                    HOMEKIT_CHARACTERISTIC(NAME, "Garage"),
                    HOMEKIT_CHARACTERISTIC(MANUFACTURER, "Stefan Burger"),
                    HOMEKIT_CHARACTERISTIC(SERIAL_NUMBER, "01"),
                    HOMEKIT_CHARACTERISTIC(MODEL, "Garagen Controller"),
                    HOMEKIT_CHARACTERISTIC(FIRMWARE_REVISION, "1.0"),
                    HOMEKIT_CHARACTERISTIC(IDENTIFY, garage_identify),
                    NULL
                },
            ),
            HOMEKIT_SERVICE(
                LOCK_MECHANISM,
                .primary=true,
                .characteristics=(homekit_characteristic_t*[]) {
                    HOMEKIT_CHARACTERISTIC(NAME, "Tore"),
                    &torCur,
                    &torTar,
                    NULL
                },
            ),
            HOMEKIT_SERVICE(
                CONTACT_SENSOR,
                .characteristics=(homekit_characteristic_t*[]) {
                    HOMEKIT_CHARACTERISTIC(NAME, "Tor1"),
                    &occTor1,
                    NULL
                },
            ),
            HOMEKIT_SERVICE(
                CONTACT_SENSOR,
                .characteristics=(homekit_characteristic_t*[]) {
                    HOMEKIT_CHARACTERISTIC(NAME, "Tor2"),
                    &occTor2,
                    NULL
                },
            ),
        HOMEKIT_SERVICE(
            MOTION_SENSOR,
            .characteristics=(homekit_characteristic_t*[]) {
                HOMEKIT_CHARACTERISTIC(NAME, "Bewegungsmelder"),
                    &motion,
                    &motion_stat,
                    NULL
                },
            ),
            NULL
        },
    ),
    NULL
};

homekit_server_config_t config = {
    .accessories = accessories,
    .password = "111-11-111"
};

void tor1_ISR(uint8_t sensor_pin){
    if (!tor1_progress){

       while(1) {
        tor1_progress = true;
        uint8_t state = gpio_read(sensor_pin);
        if (state == 0) {
            tor1_closed = false;
            homekit_characteristic_notify(&occTor1, HOMEKIT_UINT8(1));
        }
        if (state == 1) {
            tor1_closed = true;
            homekit_characteristic_notify(&occTor1, HOMEKIT_UINT8(0));
        }
        tor1_progress = false;
        //printf("tor1 detect %d\r\n", state);
        break;
       }
    }
}

void tor2_ISR(uint8_t sensor_pin){
    if (!tor2_progress){
       while(1) {
        tor2_progress = true;
        uint8_t state = gpio_read(sensor_pin);
        if (state == 0) {
            tor2_closed = false;
            homekit_characteristic_notify(&occTor2, HOMEKIT_UINT8(1));
        }
        if (state == 1) {
            tor2_closed = true;
            homekit_characteristic_notify(&occTor2, HOMEKIT_UINT8(0));
        }
        tor2_progress = false;
        //printf("tor2 detect %d\r\n", state);
        break;
       }
    }
}

void motion_ISR(uint8_t motion_sensor_pin){
   if (!motion_progress){
    while(1) {
        motion_progress = true;
        uint8_t state = gpio_read(motion_sensor_pin);
        if (state == 1) {
            if ((armed) || (tor1_closed && tor2_closed)) {
                homekit_characteristic_notify(&motion, HOMEKIT_BOOL(true));
            }
        }
        if (state == 0) {
            homekit_characteristic_notify(&motion, HOMEKIT_BOOL(false));
        }
        motion_progress = false;
        //printf("motion detect %d\r\n", state);
        break;
    }
   }
}

void user_init(void) {
   sdk_system_update_cpu_freq(80);

   gpio_enable(tor1_sensor_pin, GPIO_INPUT);
   gpio_enable(tor2_sensor_pin, GPIO_INPUT);
   gpio_enable(motion_sensor_pin, GPIO_INPUT);
   gpio_set_interrupt(tor1_sensor_pin, int_any, tor1_ISR);
   gpio_set_interrupt(tor2_sensor_pin, int_any, tor2_ISR);
   gpio_set_interrupt(motion_sensor_pin, int_any, motion_ISR);

   uint8_t pins[1];
   pins[0] = 2;
   pwm_init(1, pins, false);
   pwm_set_freq(50);
   pwm_set_duty(7900);
   pwm_start();

   uart_set_baud(0, 115200);

   wifi_init();

   homekit_server_init(&config);
}

To my first version, i have changed form stepper to a simple rc-servo. (pwm output). it work´s really great!

As a "Tip": If you use a "NodeMCU-Dev-Board": Connect GPIO15/D8 via Resistor to GND ! If the MCU got into WDT-Reset, the Board restarts automatically, otherwise the system stops!

My Test´s about "Performance" (change to CPU-Freq 160MHZ) are not very successfull, because i think there is not a really performance plus (mainly for faster connecting/processing) versus System stability.

Here is a picture of the finished "product"…

bildschirmfoto 2018-08-03 um 08 07 37

Have a nice Time, bye ! ;-)