simont77 / fakegato-history

Module to emulate Elgato Eve history
MIT License
167 stars 15 forks source link

Eve Aqua - Decoded schedules :-) #90

Open n0rt0nthec4t opened 4 years ago

n0rt0nthec4t commented 4 years ago

So, after some playing around, I've managed to decode the schedules from Eve Aqua. My WIP code is below. hoping someone finds this useful to create a PR for fakegato. As I don't use homebridge, thats not on my radar. Also, decoded more of the "supercharacteristic" for getting info

Still have more of the commands to decode, but the hard part about the schedules is done

This is set with firmware ID of 1208 (the wiki example, is for firmware 1051)


       service.getCharacteristic(Characteristic.EveSetConfiguration).on("set", (value, callback) => {
                    //console.log("DEBUG: Characteristic.EveSetConfiguration: ", decodeEveData(value));

                    // Loop through set commands passed to us
                    var programs = [];
                    var valHex = decodeEveData(value);
                    var index = 0;
                    while (index < valHex.length) {
                        // first byte is command
                        // second byte is size of data for command
                        command = valHex.substr(index, 2);
                        size = parseInt(valHex.substr(index + 2, 2), 16) * 2;
                        data = valHex.substr(index + 4, parseInt(valHex.substr(index + 2, 2), 16) * 2);

                        switch(command) {
                            case "2e" : {
                                // flow rate in ml/M
                                var flowrateLS = EveHexStringToNumber(data) * 60; // flow rate in ml/M
                                break;
                            }

                            case "2f" : {
                                // reset timestamp in seconds since EPOCH
                                var timestamp = (EPOCH_OFFSET + EveHexStringToNumber(data));
                            }

                            case "44" : {
                                // schedule on/off ???
                                console.log(command, data);
                                break;
                            }

                            case "45" : {  
                                // programs
                                var index2 = parseInt(data.substr(0, 2), 16) * 2;
                                var programcount = 0;
                                while (index2 < data.length) {
                                    switch (data.substr(index2, 2)) {
                                        case "08" : {
                                            // No program defined
                                            programs.push({"id": programcount, "type": data.substr(index2, 2), "days": []});  // no program
                                            programcount++;
                                            index2 +=2;
                                            break;
                                        }

                                        case "0a" :
                                        case "0b" : {
                                            // Program defined
                                            var schedules = [];
                                            for (var index3 = 0; index3 < parseInt(data.substr(index2 + 2, 2), 16) && parseInt(data.substr(index2 + 2, 2), 16) != 8; index3++)
                                            {
                                                // schedules appear to be a 32bit word
                                                // after swapping 16bit words
                                                // 1st 16bits = end time 
                                                // 2nd 16bits = start time
                                                // starttime decode
                                                // bit 1-5 specific time or sunrise/sunset 05 = time, 07 = sunrise/sunset
                                                // if sunrise/sunset
                                                //      bit 6, sunrise = 1, sunset = 0
                                                //      bit 7, before = 1, after = 0
                                                //      bit 8 - 16 - minutes for sunrise/sunset
                                                // if time 
                                                //      bit 6 - 16 - minutes from 00:00
                                                //   
                                                // endtime decode
                                                // bit 1-5 specific time or sunrise/sunset 01 = time, 03 = sunrise/sunset
                                                // if sunrise/sunset
                                                //      bit 6, sunrise = 1, sunset = 0
                                                //      bit 7, before = 1, after = 0
                                                //      bit 8 - 16 - minutes for sunrise/sunset
                                                // if time 
                                                //      bit 6 - 16 - minutes from 00:00
                                                var start = parseInt(data.substr(index2 + 4 + (index3 * 8), 4).match(/[a-fA-F0-9]{2}/g).reverse().join(''), 16);
                                                var end = parseInt(data.substr(index2 + 4 + ((index3 * 8) + 4), 4).match(/[a-fA-F0-9]{2}/g).reverse().join(''), 16);
                                                //schedules.push(data.substr(index2 + 4 + (index3 * 8), 8).match(/[a-fA-F0-9]{2}/g).reverse().join(''));

                                                // decode start time
                                                var start_min = null;
                                                var start_hr = null;
                                                var start_offset = null;
                                                var start_sunrise = null;

                                                var end_min = null;
                                                var end_hr = null;
                                                var end_offset = null;
                                                var end_sunrise = null;
                                                if ((start & 0x1f) == 5) {
                                                    // specific time
                                                    start_min = (start >>> 5) % 60;   // Start minute
                                                    start_hr = ((start >>> 5) - start_min) / 60;    // Start hour
                                                    start_offset = ((start >>> 5) * 60);    // Seconds since 00:00
                                                    //console.log("Start:", start_hr, start_min, start_offset);
                                                } else if ((start & 0x1f) == 7) {
                                                    // sunrise/sunset
                                                    start_sunrise = ((start >>> 5) & 0x01);    // 1 = sunrise, 0 = sunset
                                                    start_offset = ((start >>> 6) & 0x01 ? ~((start >>> 7) * 60) + 1 : (start >>> 7) * 60);   // offset from sunrise/sunset (plus/minus value)
                                                    //console.log("Start:", (start_sunrise ? "sunrise" : "sunset"), start_offset);
                                                } 

                                                // decode end time
                                                if ((end & 0x1f) == 1) {
                                                    // specific time
                                                    end_min = (end >>> 5) % 60;   // End minute
                                                    end_hr = ((end >>> 5) - end_min) / 60;    // End hour
                                                    end_offset = ((end >>> 5) * 60);    // Seconds since 00:00
                                                    //console.log("end:", hr, min, offset);
                                                } else if ((end & 0x1f) == 3) {
                                                    end_sunrise = ((start >>> 5) & 0x01);    // 1 = sunrise, 0 = sunset
                                                    end_offset = ((start >>> 6) & 0x01 ? ~((start >>> 7) * 60) + 1 : (start >>> 7) * 60);   // offset from sunrise/sunset (plus/minus value)
                                                    //console.log("end:", (end_sunrise ? "sunrise" : "sunset"), end_offset);
                                                }

                                                schedules.push({"start" : (start_sunrise == null ? "time" : (start_sunrise ? "sunrise" : "sunset")), "offset": start_offset, "duration" : (end_offset - start_offset)});
                                            }
                                            programs.push({"id": programcount, "type": data.substr(index2, 2), "days": [], "schedules": schedules});
                                            programcount++;
                                            index2 += 2 + (index3 * 8);
                                            break;
                                        }

                                        default : {
                                            index2 += 2;
                                            break;
                                        }

                                    }
                                }
                                break;
                            }

                            case "46" : {
                                // active days across programs
                                var unknown = EveHexStringToNumber(data.substr(0, 6));   // Unknown data for first 6 bytes
                                var daynumber = (EveHexStringToNumber(data.substr(8, 6)) >>> 4);

                                // bit masks for active days mapped to programm id
                               /* var mon = (daynumber & 0x7);
                                var tue = ((daynumber >>> 3) & 0x7)
                                var wed = ((daynumber >>> 6) & 0x7)
                                var thu = ((daynumber >>> 9) & 0x7)
                                var fri = ((daynumber >>> 12) & 0x7)
                                var sat = ((daynumber >>> 15) & 0x7)
                                var sun = ((daynumber >>> 18) & 0x7) */

                                programs.forEach(program => {
                                    var index = 0;
                                    var daysofweek = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"];
                                    while (index <= 18) {
                                        if (((daynumber >>> index) & 0x7) == program.id) {
                                            program.days.push(daysofweek[index / 3]);
                                        }
                                        index +=3
                                    }
                                });
                                break;
                            }

                            case "47" : {
                                // Water details or time stamp???
                                console.log(command, data)
                                break;
                            }

                            case "48" : {
                                // Suspension scenes on/off
                                // data = 000000000000 - off
                                // data = ??
                                console.log(command, data);
                                break;
                            }

                            case "4b" : {
                                // suspension scene trigger from HomeKit
                                // data = 000000000000 - pause for today
                                // data = a00500000000 - pause for today and tomorrow
                                break;
                            }

                            default : {
                                console.log("unknown:", command, data);
                                break;
                            }
                        }

                        index += 4 + size;  // Move to next command accounting for header size of 4 bytes
                    }

                    if (programs.length != 0) {
                        programs.forEach(program => {
                            console.log(program);
                        });
                    }

                    callback();
                });
                break;
            }
`
simont77 commented 4 years ago

Nice work! Actually, it is beyond fakegato-history to implement also the schedule, but it would be nice if you can update the wiki describing the decoded protocol.

ctschach commented 4 years ago

So how is triggering the schedule? Is this the device itself?

n0rt0nthec4t commented 4 years ago

So how is triggering the schedule? Is this the device itself?

Yep, that is my assumption. The schedules are setup in the Eve app and transferred to the device. There must be another command which syncs the sunset/sunrise times to the device. I’m assuming this as not sure how the device would know that otherwise. Plus, a couple of commands I haven’t decoded yet which I think contains time information.

n0rt0nthec4t commented 4 years ago

Nice work! Actually, it is beyond fakegato-history to implement also the schedule, but it would be nice if you can update the wiki describing the decoded protocol.

Yep, see what I can document in the wiki once finished full decoding and testing to send own developed schedules back to the Eve app

HomeKidd commented 3 years ago

So how is triggering the schedule? Is this the device itself?

Yep! Accurate timekeeping in embedded systems is a very common thing. The Eve app sends the current time sometimes so the device can maintain the internal "clock" very well πŸ˜„ I'm also using a similar method for timestamps: on boot i get the NTP time and the ESP8266 than maintains the time automatically. Sometimes the clock gets off by 1-2 seconds, so when E863F121 characteristic get a new time value from the Eve app, i can adjust the clock πŸ˜„ Also the NTP task runs itself every hour for safety πŸ˜„

@n0rt0nthec4t Have you got a full documentation for the Schedule functions? Currently i'm struggling with it πŸ˜… But my main goal is to skip the homebridge part, so I want to make it in C language and running it natively πŸ˜„ Currently I've just finished the Water consumption, Flow rate and Last Watering / Watering for part, so these are running correctly but cant get the Schedule working, even with the HomeKit Accessory Simulator 😩

Can you send me some examples what a proper data looks like (for Eve config get/set characteristics)? homekid92@gmail.com

n0rt0nthec4t commented 3 years ago

@HomeKidd sorry, haven't been coding much recently. Trying to workout a few more of the aqua commands, specifically for setting a schedule, but haven't gotten that working correctly.

One funny thing when using fakehistory, if I put this against an irrigation system with multiple valves (zones), teh eve app shows the configuredname characteristic as blanks, however, without connect to fake history, it shows things correctly. So thinking its either a eve app bug OR eve stores the aqua name outside of the normal HomeKit configuredname characteristic

And you don't publish your sourcecode on your GitHub, so yeah. Be great if you can share all your code