maximkulkin / esp-homekit-demo

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

Lock example - How to store current state in EEPROM #315

Closed wasp100 closed 4 years ago

wasp100 commented 4 years ago

Hi @maximkulkin

In your lock example, whenever I power cycle my ESP8266, it reverts to a locked state.

How do I change this so that the state is written into the EEPROM and when power-cycled, it pulls the last state?

Thanks

lrusnac commented 4 years ago

you can write and read the status to flash so that it can recall the status over the power cycles. I have an example here: https://github.com/lrusnac/esp-homekit-devices/blob/master/sonoff_s20/main.c#L91 to save it https://github.com/lrusnac/esp-homekit-devices/blob/master/sonoff_s20/main.c#L169 to read it during initialisation

cheers

maximkulkin commented 4 years ago

I think best would be some hardware sensor that can detect current state.

peros550 commented 4 years ago

I had read somewhere that writing too often to the flash storage may create issues with the memory itself on the long term, is that true ?

maximkulkin commented 4 years ago

@peros550 yes. The way nand flash works: first, you erase sector of memory, resetting all bits to 1s. When you write data, it can only flip 1s to 0s. Smallest addressable unit is 1 byte. To reuse flash memory, you need to erase the whole sector (on ESPs sector size is 4096 bytes), flipping all bits back to 1s. There is a limit on number of erases you can make before flash stops working properly (do not remember exactly, I think it is 10K erases).

peros550 commented 4 years ago

@maximkulkin many thanks for the explanation!

when we use the following function in our program :

sysparam_set_bool("device_status", device_status); // save device_status to flash

Do we erase the flash? Or otherwise, should we worry about how often do we use the above function?

lrusnac commented 4 years ago

you can read more here https://github.com/SuperHouse/esp-open-rtos/blob/master/core/include/sysparam.h#L16-L28 but it has some thought on the erase limits:

Keys and values are stored in flash using a progressive list structure which allows space-efficient storage and minimizes flash erase cycles, improving write speed and increasing the lifespan of the flash memory.

lrusnac commented 4 years ago

I agree with you in regards to a lock, you want to keep it the way it is, maybe you locked it with a key, while the power was down, so you don't want it to open once power is back up.

But you can't really do that with lights for instance, so in that case, the flash woks great for me. something like philips hue would turn on after a reboot though, so maybe that's a good way to handle it too, I just prefer it to remember the state :)

now I'm curious, what hardware are you using for the lock?

skanico commented 4 years ago

I did the same for my blinds (save current state) as I don't have a position sensor, but I didn't know for the lifecycle... maybe I'll have to optimize this.

lrusnac commented 4 years ago

I guess somebody could sacrifice a couple of esps and run some benchmarks to get a realistic number of writes

maximkulkin commented 4 years ago

To get the idea, here is datasheet for one of flash chips to get the idea. It says that it supports more than 100K erase cycles. Still, something to keep in mind. If you need to store just one bit of information frequently, do not write and erase the whole sector, you just need two bits: first is the marker bit (always 1) that signals that next bit is the data. When writing new data, just zero out both bits and then use next two bits (which initially will be both 1s). When reading, first scan to first non-zero bit and then read the data.

lrusnac commented 4 years ago

fair, but that depends on how it's actually implemented too. also 100k is a reasonable amount. it will last you 30 years if you write 10 times a day on average

wasp100 commented 4 years ago

Hi @maximkulkin I need your assistance please with the code for the Lock.

I've modified your code to allow for a separate lock and unlock. It works fine from the Home App, however, I've just discovered that when I ask Siri to lock the door, the response is

"Sorry, I wasn't able to lock your Lock. For more details, check the Home app."

I can however unlock the lock through Siri with no problem!

My code is below:

#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 <etstimer.h>
#include <esplibs/libmain.h>

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

const int relay_gpio = 12;
const int led_gpio = 2;

void lock_lock();
void lock_unlock();

void relay_write(int value) {
    gpio_write(relay_gpio, value ? 1 : 0);
}

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

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 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(relay_gpio, GPIO_OUTPUT);
    relay_write(false);
}

void lock_identify_task(void *_args) {
    for (int i=0; i<3; i++) {
        for (int j=0; j<2; j++) {
            led_write(true);
            vTaskDelay(100 / portTICK_PERIOD_MS);
            led_write(false);
            vTaskDelay(100 / portTICK_PERIOD_MS);
        }

        vTaskDelay(250 / portTICK_PERIOD_MS);
    }
    led_write(false);
    vTaskDelete(NULL);
}

void lock_identify(homekit_value_t _value) {
    printf("Lock identify\n");
    xTaskCreate(lock_identify_task, "Lock identify", 128, NULL, 2, NULL);
}

typedef enum {
    lock_state_unsecured = 0,
    lock_state_secured = 1,
    lock_state_jammed = 2,
    lock_state_unknown = 3,
} lock_state_t;

homekit_characteristic_t name = HOMEKIT_CHARACTERISTIC_(NAME, "Lock");

homekit_characteristic_t lock_current_state = HOMEKIT_CHARACTERISTIC_(
    LOCK_CURRENT_STATE,
    lock_state_unknown,
);

void lock_target_state_setter(homekit_value_t value);

homekit_characteristic_t lock_target_state = HOMEKIT_CHARACTERISTIC_(
    LOCK_TARGET_STATE,
    lock_state_secured,
    .setter=lock_target_state_setter,
);

void lock_target_state_setter(homekit_value_t value) {
    lock_target_state.value = value;

    if (value.int_value == 0) {
        lock_unlock();
    } else {        
        lock_lock();
    }
    homekit_characteristic_notify(&lock_target_state, value);
}

void lock_control_point(homekit_value_t value) {
    // Nothing to do here
}

void lock_lock() {

    relay_write(true);
    vTaskDelay(500 / portTICK_PERIOD_MS);
    relay_write(false);

    lock_current_state.value = HOMEKIT_UINT8(lock_state_secured);
    homekit_characteristic_notify(&lock_current_state, lock_current_state.value);    
}

void lock_init() {
    lock_current_state.value = HOMEKIT_UINT8(lock_state_secured);
    homekit_characteristic_notify(&lock_current_state, lock_current_state.value);
}

void lock_unlock() {
    relay_write(true);
    vTaskDelay(2000 / portTICK_PERIOD_MS);
    relay_write(false);

    lock_current_state.value = HOMEKIT_UINT8(lock_state_unsecured);
    homekit_characteristic_notify(&lock_current_state, lock_current_state.value);

}

homekit_accessory_t *accessories[] = {
    HOMEKIT_ACCESSORY(.id=1, .category=homekit_accessory_category_door_lock, .services=(homekit_service_t*[]){
        HOMEKIT_SERVICE(ACCESSORY_INFORMATION, .characteristics=(homekit_characteristic_t*[]){
            &name,
            HOMEKIT_CHARACTERISTIC(MANUFACTURER, "HaPK"),
            HOMEKIT_CHARACTERISTIC(SERIAL_NUMBER, "1"),
            HOMEKIT_CHARACTERISTIC(MODEL, "Basic"),
            HOMEKIT_CHARACTERISTIC(FIRMWARE_REVISION, "0.1"),
            HOMEKIT_CHARACTERISTIC(IDENTIFY, lock_identify),
            NULL
        }),
        HOMEKIT_SERVICE(LOCK_MECHANISM, .primary=true, .characteristics=(homekit_characteristic_t*[]){
            HOMEKIT_CHARACTERISTIC(NAME, "Lock"),
            &lock_current_state,
            &lock_target_state,
            NULL
        }),
        HOMEKIT_SERVICE(LOCK_MANAGEMENT, .characteristics=(homekit_characteristic_t*[]){
            HOMEKIT_CHARACTERISTIC(LOCK_CONTROL_POINT,
                .setter=lock_control_point
            ),
            HOMEKIT_CHARACTERISTIC(VERSION, "1"),
            NULL
        }),
        NULL
    }),
    NULL
};

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

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

void create_accessory_name() {
    uint8_t macaddr[6];
    sdk_wifi_get_macaddr(STATION_IF, macaddr);

    int name_len = snprintf(NULL, 0, "Lock-%02X%02X%02X",
                            macaddr[3], macaddr[4], macaddr[5]);
    char *name_value = malloc(name_len+1);
    snprintf(name_value, name_len+1, "Lock-%02X%02X%02X",
             macaddr[3], macaddr[4], macaddr[5]);

    name.value = HOMEKIT_STRING(name_value);
}

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

    create_accessory_name();

    wifi_config_init("lock", NULL, on_wifi_ready);
    gpio_init();
    lock_init();

}