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:
cjkas 2026-10-22 21:19:29 +02:00
parent a36888f8a1
commit 482e495678
24 changed files with 1839 additions and 5 deletions

32
src/ProtoParse.h Normal file
View file

@ -0,0 +1,32 @@
#ifndef PROTO_PARSE_H
#define PROTO_PARSE_H
#include <stddef.h>
#include <stdint.h>
#include <ArduinoJson.h>
#include "ProtoSchema.h"
// Reading the frames a socket protocol 2 client sends (docs/PROTOCOL.md).
// Kept free of the web server so it can be unit tested on the PC
// (pio test -e native).
// The largest frame a client may send; a longer one is refused unread.
#define PROTO_MAX_CLIENT_FRAME 4096
struct proto_request_t {
uint8_t type = 0;
uint32_t id = 0; // 0 when the frame had none
};
// Reads one frame into doc and out. none when it is a message a client may
// send; otherwise the error to reply with. out.id is set whenever the frame
// had one, so the reply can name it. The payload is doc["d"].
proto_error_t parseClientFrame(const char *text, size_t len, JsonDocument &doc, proto_request_t &out);
// An op (16-127) as opposed to a control message.
inline bool protoIsOp(uint8_t type) { return type >= 16 && type <= 127; }
// A field of a payload, or its default when it was left out.
int32_t protoInt(JsonObjectConst d, pf field);
bool protoBool(JsonObjectConst d, pf field);
// nullptr when it was left out, as a string's default is "".
const char *protoString(JsonObjectConst d, pf field);
bool protoHas(JsonObjectConst d, pf field);
#endif