Last active
November 3, 2024 23:35
-
-
Save pshirshov/28c57ff4e3074df91a599dfc0e171c44 to your computer and use it in GitHub Desktop.
myenergi udp protocol: reverse engineering
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import std.mem; | |
import std.sys; | |
import std.core; | |
using MyDevice = u32; | |
// convention: | |
// _lowercase - zero certainty what is that | |
// _CamelCase - have an idea what is that | |
// CamelCase - we are certain what is that | |
// lowercase - not a field, impl detail | |
struct HarviClamp { | |
std::assert(std::mem::read_unsigned($, 1) == 8, "harvi record starts with 0x8"); | |
padding[1]; | |
u8 _wtf1; // ? 17-33-49 | |
std::assert(_wtf1 == 17 || _wtf1 == 33 || _wtf1 == 49, "wtf1"); | |
u8 ClampType; | |
u8 _wtf2; // ? varies but always is either an increasing sequence or 0-0-0 | |
s16 Power_Watts; | |
s16 Current_CentiAmps; | |
std::print(" CT tpe={} pw={} amps={}", ClampType, Power_Watts, Current_CentiAmps / 100.0); | |
std::print(" wtf1 = {}, wtf2 = {}", _wtf1, _wtf2); | |
}; | |
struct HarviRecord<auto PayloadSize, auto PayloadEnd> { | |
u32 HarviSerial [[color("00ff00")]]; | |
std::print(" HarviSerial={}", HarviSerial); | |
HarviClamp HarviClamps[while($ < PayloadEnd-2)]; | |
// varies between packets, tend to be the same within one packet (but not always). | |
// Frequently it's 0-0 for all entries in all harvis | |
// Seconds since last reading?.. | |
u16 _end_bytes[1]; | |
std::print("harvi last bytes: {}", _end_bytes); | |
}; | |
struct ZappiRecord<auto PayloadSize, auto PayloadEnd> { | |
}; | |
struct SharedRecord<auto PayloadSize, auto PayloadEnd> { | |
u32 _stable_id; | |
u32 _zero_or_wtf; | |
u32 _four; | |
u16 _device_type; | |
u16 _record_type; | |
std::print(" devtpe {}, rectpe {}", | |
_device_type, _record_type); | |
if (_record_type == 276) { | |
std::assert(PayloadSize == 30, ""); | |
u16 _voltage; | |
std::print(" voltage {}", | |
_voltage / 10.0); | |
} else if (_record_type == 8456) { | |
std::assert(PayloadSize == 42 || PayloadSize == 50, ""); | |
} else if (_record_type == 108) { | |
std::assert(PayloadSize == 38, ""); | |
} else if (_record_type == 206) { | |
std::assert(PayloadSize == 56, ""); | |
} else if (_record_type == 4360) { | |
std::assert(PayloadSize == 31, ""); | |
} else if (_record_type == 276) { | |
std::assert(PayloadSize == 30, ""); | |
} else if (_record_type == 276) { | |
std::assert(PayloadSize == 30, ""); | |
} else { | |
std::print(" UKNOWN 13584 RECORD TYPE: {} of size {}", _record_type, PayloadSize); | |
//std::assert(false, std::format("unknown rectype {} with sz {}", _record_type, PayloadSize)); | |
} | |
}; | |
struct MyEnergiRecord | |
{ | |
u8 PayloadSize [[color("0000FF")]]; | |
u32 start = $; | |
u32 record_end = start + PayloadSize; | |
u8 NetId [[color("00ffff")]]; | |
u16 _PacketType; | |
u8 DevId; | |
bool debug = true || (_PacketType == 14128); | |
u8 _wtf1; // 0xff => good known device, 0x1 => some shit, 0x4 => last record, | |
std::assert(_wtf1 == 0xFF || _wtf1 == 4 || _wtf1 == 1, ""); | |
if (debug) { | |
std::print("tpe {}, NetId {}, DevId {}, sz {}", | |
_PacketType, NetId , DevId, PayloadSize); | |
if (_PacketType == 13584 || _PacketType == 31097 || _PacketType == 14128) { | |
MyDevice DevSerial [[color("880000")]]; | |
std::print(" DevSerial {}", DevSerial); | |
if (_PacketType == 14128) { | |
HarviRecord<PayloadSize, record_end> HarviRecord; | |
} else if(_PacketType == 13584) { | |
SharedRecord<PayloadSize, record_end> SharedRecord; | |
} else if (_PacketType == 31097) { | |
ZappiRecord<PayloadSize, record_end> ZappiRecord; | |
} | |
} else { | |
std::print(" UKNOWN PACKET TYPE: {}", _PacketType); | |
} | |
} | |
u8 _unknown_end[PayloadSize - ($ - addressof(NetId))] [[color("aaaaaa")]] ; | |
if (debug) std::print(" {} {}", sizeof(_unknown_end), _unknown_end); | |
}; | |
struct MyenergiPkt | |
{ | |
u32 MAGIC; | |
std::assert(std::mem::read_unsigned($, 4) == 0x00000000, "myenergi message starts with 0x0 in second octet"); | |
padding [4]; | |
u32 Serial; | |
u32 _wtf1; | |
u32 Timestamp ; | |
u32 _wtf2; | |
u16 _FirmwareVersion; | |
u8 _NetworkId; | |
u8 _wtf3; | |
u16 PacketSize [[color("0000FF")]]; | |
u16 _wtf4; | |
MyEnergiRecord Records[while (!std::mem::eof())]; | |
}; | |
MyenergiPkt pkt @ 0x0; |
Local communication is done via multicast, some initial research is done here: https://github.com/PeterTM/mqtt-energi
All myenergi devices must be in the same network.
sudo tcpdump -i $MEIF -n -p multicast
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Stream processing: