fakegato-history
fakegato-history copied to clipboard
Eve Aqua - Decoded schedules :-)
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;
}
`
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.
So how is triggering the schedule? Is this the device itself?
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.
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
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)? [email protected]
@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