maximkulkin / esp-homekit-demo

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

Add Elgato Eve's History capability #121

Open dezdeepblue opened 6 years ago

dezdeepblue commented 6 years ago

Adding Elgato Eve's history capability would be great. https://github.com/simont77/fakegato-history did the same for the Homebridge https://github.com/nfarina/homebridge.

maximkulkin commented 5 years ago

@dezdeepblue It is not quite obvious from repository you mentioned what a "history" feature is. Could you elaborate that?

dezdeepblue commented 5 years ago

I'm not sure if you saw the functionality of Elgato Eve's app in regard to some of its sensors. For example check https://www.evehome.com/en/eve-degree. There are screenshots of the app on the page. Having their HomeKit accessories in the Eve app, not only it shows the current state of the sensor (Temperature, Humidity, etc.) it shows the historical data from each parameter in a graph. I'm not sure if it could be achieved just by a specific characteristic, but by checking the https://github.com/simont77/fakegato-history it could be seen that it seems to be more than just a characteristic. I mean it isn't just an app-side feature(As I checked with current HomeKit temperature sensor example) and the HomeKit device needs to log its data (SPIFFS, or on the cloud, Gdrive, Dropbox, etc.) and provide it somehow to the app. I thought the Fakegato-History could help, but I don't have enough programming skills to contribute and don't know where to start.

peros550 commented 5 years ago

I am also very interested in having historical data of my sensors. Don't know if Homekit provides this kind of functionality. It would be great if it does.

Right now, I have set up a function that sends data to ThingSpeak server every 10 minutes and I use a different app or a browser to view the data.

Here is the code I'm using to send temp & humidity values:

Initializing values:

#define USE_THINGSPEAK 1
#define WEB_SERVER "api.thingspeak.com"
#define API_KEY  "XXXXXXXXXXXXXXXXXX" //Your provided key goes here
#define FIELD1 "field1=" 
#define FIELD2 "field2=" 
#define WEB_PORT "80"
#define WEB_PATH "/update?api_key="
#define THINGSPEAK_INTERVAL 600000 //interval every 10 minutes. Change it to your liking

volatile float old_humidity_value = 0.0, old_temperature_value = 0.0 //these values must be updated by another function not shown here. Normally, that function is the one reading sensor data from your sensors.  

ETSTimer things_timer;

The main ThingSpeak function:

void call_things_process()
{

    int successes = 0, failures = 0;
    printf("HTTP get task starting...\r\n");

    while(1) {

        const struct addrinfo hints = {
            .ai_family = AF_UNSPEC,
            .ai_socktype = SOCK_STREAM,
        };
        struct addrinfo *res;

        printf("Running DNS lookup for %s...\r\n", WEB_SERVER);
        int err = getaddrinfo(WEB_SERVER, WEB_PORT, &hints, &res);

        if (err != 0 || res == NULL) {
            printf("DNS lookup failed err=%d res=%p\r\n", err, res);
            if(res)
            freeaddrinfo(res);
            failures++;
            break;
        }

#if LWIP_IPV6
        {
            struct netif *netif = sdk_system_get_netif(0);
            int i;
            for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) {
                printf("  ip6 %d state %x\n", i, netif_ip6_addr_state(netif, i));
                if (!ip6_addr_isinvalid(netif_ip6_addr_state(netif, i)))
                    printf("  ip6 addr %d = %s\n", i, ip6addr_ntoa(netif_ip6_addr(netif, i)));
            }
        }
#endif

        struct sockaddr *sa = res->ai_addr;
        if (sa->sa_family == AF_INET) {
            printf("DNS lookup succeeded. IP=%s\r\n", inet_ntoa(((struct sockaddr_in *)sa)->sin_addr));
        }
#if LWIP_IPV6
        if (sa->sa_family == AF_INET6) {
            printf("DNS lookup succeeded. IP=%s\r\n", inet6_ntoa(((struct sockaddr_in6 *)sa)->sin6_addr));
        }
#endif

        int s = socket(res->ai_family, res->ai_socktype, 0);
        if(s < 0) {
            printf("... Failed to allocate socket.\r\n");
            freeaddrinfo(res);
           failures++;
            break;
        }

        printf("... allocated socket\r\n");

        if(connect(s, res->ai_addr, res->ai_addrlen) != 0) {
            close(s);
            freeaddrinfo(res);
            printf("... socket connect failed.\r\n");
            failures++;
            break;
        }

        printf("... connected\r\n");
        freeaddrinfo(res);

        char temp2[10];
        snprintf(temp2, sizeof(temp2),"%2.2f", old_temperature_value );
        printf("%s \n",temp2);
        printf(temp2);

        char hum1[6];
        snprintf(hum1,sizeof(hum1), "%2.2f", old_humidity_value );
        printf("%s \n",hum1 );

        char req[300]="GET " WEB_PATH API_KEY "&" FIELD1;
        strncat(req,temp2,10);
         strncat(req,"&",2);
         strncat(req,FIELD2,20);
         strncat(req,hum1,6);
         strncat(req," HTTP/1.1\r\nHost: "WEB_SERVER"\r\nUser-Agent: esp-open-rtos/0.1 esp8266\r\nConnection: close\r\n\r\n",250);

        printf("%s \n",req );

        if (old_temperature_value ==0) 
        {
            break;
        }

         printf("%s \n",req );

        if (write(s, req, strlen(req)) < 0) {
            printf("... socket send failed\r\n");
            close(s);
            failures++;
            break;
        }
        printf("... socket send success\r\n");

        static char recv_buf[128];
        int r;
        do {
            bzero(recv_buf, 128);
            r = read(s, recv_buf, 127);
            if(r > 0) {
                printf("%s", recv_buf);
            }
        } while(r > 0);

        printf("... done reading from socket. Last read return=%d errno=%d\r\n", r, errno);
        close(s);
        printf("successes = %d failures = %d\r\n", successes, failures);        
        printf("\r\nEnding!\r\n");

        break;
    }
}

The following code should go on your on_wifi_ready function

void on_wifi_ready() {
other code  

sdk_os_timer_setfn(&things_timer, call_things_process, NULL);
//sdk_os_timer_disarm(&things_timer);
sdk_os_timer_arm(&things_timer, THINGSPEAK_INTERVAL, 1);
}

If you try the code, I would really appreciate some feedback. It normally runs fine. However, If the accessory is not paired first, then it may fail to pair! That's why initializing the things_timer may need to take place on a later stage after accessory has paired.

maximkulkin commented 5 years ago

From fakegato-history repository I see that they use custom characteristics and base64 serialized data: https://github.com/simont77/fakegato-history/blob/master/fakegato-history.js#L84-L137

That could work but surely it will work only with Elgato application.

dezdeepblue commented 5 years ago

I'm not sure how the Fakegato-history on Homebridge works, either the sensor only works on the Eve app or someway it is also working with the Home app. I need to re-setup my RPi and Homebridge to check that. However, I think it could be added to a multiple-sensor device to make it work with both apps, just like what @peros550 did in https://github.com/peros550/esp-homekit-multiple-sensors. Isn't it @peros550 ?

NorthernMan54 commented 5 years ago

I have several homebridge plugins that use fakegato-history, and for example a temperature sensor works like this. In the home app, it appears as just a regular temperature sensor, but in the Eve app it also shows historical graphs of the temperature, which is very cool.

In the implementation you create a couple of additional characteristics that are used to communicate that historical data on request. Also you will need to store/cache on the esp8266 the historical records until they are asked for by the app. In homebridge, we are using either the local file system or google drive to cache the records until requested. On the esp8266 this would be a major consideration, as you are likely to be updating the history cache every 10 minutes. In memory memory may not be a good user experience as you would loose the records during a device restart, and writing to the local flash storage may not be desired.

HomeKidd commented 4 years ago

@maximkulkin This is why homekit data needs to be implemented 😄 Yes, Elgato Eve using custom characteristics and a tons of HomeKit data type. Also the accessory must support timekeeping because every data entry contains the current time (current time is calculated, not just a timestamp)! I've all of these custom characteristics implemented to esp-homekit and also i'm still reverse-engineering the custom characteristics for Eve products 😄

At the moment using TLV8 data type, the History is showing up, but it definitely needs HomeKit data to store all the History.

IMG_8685

Kristian8606 commented 4 years ago

Has anyone made any progress with historical data?