mirror of
https://github.com/rstrouse/ESPSomfy-RTS.git
synced 2026-11-01 14:22:12 +01:00
feat: socket protocol 2 core on ws://host/ws
The /ws endpoint runs next to the old socket on port 8080. The handshake checks the token under security, the device sends hello, then a sync of the schema (unless the client has it), header, rooms, shades, groups, repeaters and status, paced by the client's next. Events go out with the old ones: shadeState or the whole shade on a config change, groups, rooms, removals, orders, repeaters, shadeCommand, memory, wifi, ethernet, and the frames and frequencyScan feeds. The table of types, fields, errors and feeds is defined once and is sent as the schema. The writer, item writers, sync and client frame parser are pure and natively tested. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
a36888f8a1
commit
482e495678
24 changed files with 1839 additions and 5 deletions
106
src/ProtoSchema.cpp
Normal file
106
src/ProtoSchema.cpp
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "ProtoSchema.h"
|
||||
|
||||
#define PROTO_FIELD_ROW(name, key, kind, def) {#name, key, kind, def},
|
||||
static const proto_field_info_t FIELDS[] = { PROTO_FIELDS(PROTO_FIELD_ROW) };
|
||||
#undef PROTO_FIELD_ROW
|
||||
// Errors and feeds have no sender, so it is left empty and starts as 0.
|
||||
#define PROTO_NAMED_ROW(name, number, ...) {number, #name, PROTO_SENDER(__VA_ARGS__, 0)},
|
||||
#define PROTO_SENDER(sender, ...) sender
|
||||
static const proto_named_t TYPES[] = { PROTO_TYPES(PROTO_NAMED_ROW) };
|
||||
static const proto_named_t ERRORS[] = { PROTO_ERRORS(PROTO_NAMED_ROW) };
|
||||
static const proto_named_t FEEDS[] = { PROTO_FEEDS(PROTO_NAMED_ROW) };
|
||||
#undef PROTO_SENDER
|
||||
#undef PROTO_NAMED_ROW
|
||||
static_assert(sizeof(FIELDS) / sizeof(FIELDS[0]) == static_cast<size_t>(pf::count), "a field without a row");
|
||||
|
||||
const proto_field_info_t &protoField(pf field) { return FIELDS[static_cast<uint8_t>(field)]; }
|
||||
const proto_field_info_t *protoFieldByKey(const char *key) {
|
||||
for(const proto_field_info_t &f : FIELDS)
|
||||
if(strcmp(f.key, key) == 0) return &f;
|
||||
return nullptr;
|
||||
}
|
||||
uint8_t protoTypeCount() { return sizeof(TYPES) / sizeof(TYPES[0]); }
|
||||
const proto_named_t &protoTypeAt(uint8_t index) { return TYPES[index]; }
|
||||
uint8_t protoErrorCount() { return sizeof(ERRORS) / sizeof(ERRORS[0]); }
|
||||
const proto_named_t &protoErrorAt(uint8_t index) { return ERRORS[index]; }
|
||||
uint8_t protoFeedCount() { return sizeof(FEEDS) / sizeof(FEEDS[0]); }
|
||||
const proto_named_t &protoFeedAt(uint8_t index) { return FEEDS[index]; }
|
||||
const char *protoTypeName(uint8_t number) {
|
||||
for(const proto_named_t &t : TYPES)
|
||||
if(t.number == number) return t.name;
|
||||
return nullptr;
|
||||
}
|
||||
bool protoClientMaySend(uint8_t type) {
|
||||
for(const proto_named_t &t : TYPES)
|
||||
if(t.number == type) return t.sender == 'c' || t.sender == 'b';
|
||||
return false;
|
||||
}
|
||||
|
||||
static uint32_t fnv(uint32_t hash, const char *text) {
|
||||
for(; *text; text++) {
|
||||
hash ^= (uint8_t)*text;
|
||||
hash *= 16777619u;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
static uint32_t fnvNamed(uint32_t hash, const proto_named_t &row) {
|
||||
char num[12];
|
||||
snprintf(num, sizeof(num), "%u:", row.number);
|
||||
hash = fnv(hash, num);
|
||||
hash = fnv(hash, row.name);
|
||||
return fnv(hash, ";");
|
||||
}
|
||||
const char *protoSchemaHash() {
|
||||
static char hex[9] = {0};
|
||||
if(hex[0]) return hex;
|
||||
uint32_t hash = 2166136261u;
|
||||
for(const proto_named_t &t : TYPES) hash = fnvNamed(hash, t);
|
||||
for(const proto_field_info_t &f : FIELDS) {
|
||||
char row[64];
|
||||
snprintf(row, sizeof(row), "%s:%s:%c:%ld;", f.name, f.key, f.kind, (long)f.def);
|
||||
hash = fnv(hash, row);
|
||||
}
|
||||
for(const proto_named_t &e : ERRORS) hash = fnvNamed(hash, e);
|
||||
for(const proto_named_t &q : FEEDS) hash = fnvNamed(hash, q);
|
||||
snprintf(hex, sizeof(hex), "%08lx", (unsigned long)hash);
|
||||
return hex;
|
||||
}
|
||||
|
||||
void ProtoWriter::addInt(const proto_field_info_t &f, int64_t value) {
|
||||
if(value == f.def) return;
|
||||
if(value >= INT32_MIN && value <= INT32_MAX) this->json.addElem(f.key, (int32_t)value);
|
||||
else this->json.addElem(f.key, (uint32_t)value);
|
||||
}
|
||||
void ProtoWriter::item(int64_t value) {
|
||||
if(value >= INT32_MIN && value <= INT32_MAX) this->json.addElem((int32_t)value);
|
||||
else this->json.addElem((uint32_t)value);
|
||||
}
|
||||
|
||||
void beginFrame(JsonFormatter &json, proto_type_t type, uint32_t id) {
|
||||
json.beginObject();
|
||||
json.addElem("v", (uint8_t)PROTO_VERSION);
|
||||
json.addElem("t", static_cast<uint8_t>(type));
|
||||
if(id) json.addElem("id", id);
|
||||
json.beginObject("d");
|
||||
}
|
||||
void endFrame(JsonFormatter &json) {
|
||||
json.endObject();
|
||||
json.endObject();
|
||||
}
|
||||
void writeErrorReply(JsonFormatter &json, uint32_t id, proto_error_t code, const char *desc) {
|
||||
beginFrame(json, proto_type_t::reply, id);
|
||||
ProtoWriter w(json);
|
||||
w.add(pf::ok, false);
|
||||
w.add(pf::code, static_cast<uint8_t>(code));
|
||||
w.add(pf::desc, desc);
|
||||
endFrame(json);
|
||||
}
|
||||
void writeHello(JsonFormatter &json, const char *fw) {
|
||||
beginFrame(json, proto_type_t::hello);
|
||||
ProtoWriter w(json);
|
||||
w.add(pf::fw, fw);
|
||||
w.add(pf::schemaHash, protoSchemaHash());
|
||||
endFrame(json);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue