mirror of
https://github.com/rstrouse/ESPSomfy-RTS.git
synced 2026-10-31 22:02:17 +01:00
Merge pull request #43 from cjkas/refactor/change-fanout
refactor: send shade and group changes once per loop() pass
This commit is contained in:
commit
e516a4138c
12 changed files with 268 additions and 51 deletions
|
|
@ -1,6 +1,6 @@
|
|||
# Plan: domain modules behind the LoopQueue
|
||||
|
||||
**Status:** steps 0–3 built (PRs #39, #40, #41 and #42); steps 4 and 5 planned.
|
||||
**Status:** steps 0–4 built (PRs #39 to #43); step 5 planned.
|
||||
**Written:** 2026-10-15, against commit `e624a41` on `refactor/entry-points-outputs`. Terms are defined in [GLOSSARY.md](../GLOSSARY.md); the threading rule is [ADR-0002](adr/0002-loop-task-is-the-only-writer-of-the-shade-model.md).
|
||||
|
||||
## Why
|
||||
|
|
@ -158,6 +158,16 @@ Flash: `esp32devdbg` 1,547,381 → 1,547,821 bytes, `esp32dev` 1,398,337 → 1,4
|
|||
|
||||
Flash: `esp32devdbg` 1,547,821 → 1,536,337 bytes, `esp32dev` 1,400,401 → 1,393,221 bytes. The 19 handler bodies became one path.
|
||||
|
||||
### Step 4: what changed in behaviour
|
||||
- **MQTT gets the shade's real `position` and `target`.** Both were a constant 50 since `c94819e`, and `direction` was a constant 0 when MQTT connected.
|
||||
- **Each pass sends a changed shade or group once.** The `p_*` setters and `emitState()` only mark the shade (`SHADE_CHANGED_*` bits in `ShadeChanges.h`). At the end of each `loop()` pass, `SomfyShadeController::sendChanges()` publishes the changed MQTT topics and sends one `shadeState` or `groupState`.
|
||||
- A group whose flags change now sends one `groupState`, not two.
|
||||
- `emitState(evt)` with an event name (`shadeAdded`, ...) and `emitState(num)` for one client still go out at once.
|
||||
- **`position` and `tiltPosition` are retained by the broker** when they change, as they already were when MQTT connected. A client that subscribes later gets the current position.
|
||||
- **Topic values come from one tested place.** `publishShadeTopics()` writes the topics from a shade snapshot, and `publishState()` (all topics, on connect) uses it too.
|
||||
|
||||
8 new native tests, 327 in all. Flash: `esp32devdbg` 1,536,337 → 1,536,533 bytes, `esp32dev` 1,393,221 → 1,393,449 bytes.
|
||||
|
||||
### Step 0 on a device
|
||||
NVS stays at `0x9000`, so the rolling codes survive. The file system partition shrinks from `0xE0000` to `0xD0000`, and the existing LittleFS won't mount, because `LittleFS.begin()` doesn't format. So:
|
||||
1. Take a backup through `/backup`.
|
||||
|
|
@ -174,7 +184,6 @@ NVS stays at `0x9000`, so the rolling codes survive. The file system partition s
|
|||
## Found during the review, not part of this plan
|
||||
|
||||
Still open, and each can be fixed on its own:
|
||||
- MQTT `position`/`target` published as a constant 50 ([SomfyShade.cpp:305](../src/SomfyShade.cpp#L305), [SomfyMqtt.cpp:36-38](../src/outputs/SomfyMqtt.cpp#L36-L38)). It's a one-line fix that needn't wait for step 4.
|
||||
- A restore without shades skips the shade and group records but not the room records, so it reads the rest of the file from the wrong place ([ConfigFile.cpp:578-580](../src/ConfigFile.cpp#L578-L580)).
|
||||
- `settingPos` is never cleared when the lift arrives, while `settingTiltPos` is. A later arrival may send an extra stop. Not confirmed on a device.
|
||||
- The Home Assistant integration sends no `apikey` header on shade, group and tilt commands.
|
||||
|
|
@ -187,3 +196,7 @@ Fixed by step 3:
|
|||
- `/linkRemote` and `/unlinkRemote` sent a 500 and then a second response.
|
||||
- The web UI's `procRoomRemoved` kept the removed room and dropped the others, and `deleteShade` never closed its prompt (`prompt.remove;`).
|
||||
- The web UI called `/repeaters`, which didn't exist.
|
||||
|
||||
Fixed by step 4:
|
||||
- MQTT `position`/`target` were published as a constant 50, and `direction` as 0 on connect.
|
||||
- A group whose flags changed sent `groupState` twice.
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ lib_deps =
|
|||
platform = native
|
||||
test_framework = unity
|
||||
test_build_src = yes
|
||||
build_src_filter = -<*> +<Utils.cpp> +<SomfyFrame.cpp> +<JsonFormatter.cpp> +<ShadeMotion.cpp> +<ShadeSensors.cpp> +<PulseDecoder.cpp> +<TxBuffer.cpp> +<RadioScheduler.cpp> +<ShadeRequests.cpp> +<entryPoints/MqttCommands.cpp> +<ModelSnapshot.cpp> +<ModelEditor.cpp>
|
||||
build_src_filter = -<*> +<Utils.cpp> +<SomfyFrame.cpp> +<JsonFormatter.cpp> +<ShadeMotion.cpp> +<ShadeSensors.cpp> +<PulseDecoder.cpp> +<TxBuffer.cpp> +<RadioScheduler.cpp> +<ShadeRequests.cpp> +<entryPoints/MqttCommands.cpp> +<ModelSnapshot.cpp> +<ModelEditor.cpp> +<ShadeChanges.cpp>
|
||||
build_flags =
|
||||
-std=gnu++17
|
||||
-Isrc
|
||||
|
|
|
|||
28
src/ShadeChanges.cpp
Normal file
28
src/ShadeChanges.cpp
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#include "ShadeChanges.h"
|
||||
|
||||
static bool flag(const shade_snapshot_t &shade, somfy_flags_t f) { return (shade.flags & static_cast<uint8_t>(f)) != 0; }
|
||||
|
||||
void publishShadeTopics(uint16_t changed, const shade_snapshot_t &shade, topic_sink_t sink, void *ctx) {
|
||||
// Where the shade is and is going are kept by the broker so a client that
|
||||
// connects later gets them; the rest are passing news.
|
||||
if(changed & SHADE_CHANGED_POSITION) sink("position", shade.position, true, ctx);
|
||||
if(changed & SHADE_CHANGED_DIRECTION) sink("direction", shade.direction, true, ctx);
|
||||
if(changed & SHADE_CHANGED_TARGET) sink("target", shade.target, true, ctx);
|
||||
if(changed & SHADE_CHANGED_ROLLING_CODE) sink("lastRollingCode", shade.lastRollingCode, false, ctx);
|
||||
if(changed & SHADE_CHANGED_MY_POS) sink("mypos", shade.myPos, true, ctx);
|
||||
if(changed & SHADE_CHANGED_MY_TILT_POS) sink("myTiltPos", shade.myTiltPos, true, ctx);
|
||||
if(changed & SHADE_CHANGED_TILT_DIRECTION) sink("tiltDirection", shade.tiltDirection, true, ctx);
|
||||
if(changed & SHADE_CHANGED_TILT_POSITION) sink("tiltPosition", shade.tiltPosition, true, ctx);
|
||||
if(changed & SHADE_CHANGED_TILT_TARGET) sink("tiltTarget", shade.tiltTarget, true, ctx);
|
||||
if(changed & SHADE_CHANGED_SUN_FLAG) sink("sunFlag", flag(shade, somfy_flags_t::SunFlag), false, ctx);
|
||||
if(changed & SHADE_CHANGED_SUNNY) sink("sunny", flag(shade, somfy_flags_t::Sunny), false, ctx);
|
||||
if(changed & SHADE_CHANGED_WINDY) sink("windy", flag(shade, somfy_flags_t::Windy), false, ctx);
|
||||
}
|
||||
uint16_t shadeStateTopics(const shade_snapshot_t &shade) {
|
||||
uint16_t topics = SHADE_CHANGED_POSITION | SHADE_CHANGED_DIRECTION | SHADE_CHANGED_TARGET | SHADE_CHANGED_ROLLING_CODE |
|
||||
SHADE_CHANGED_MY_POS | SHADE_CHANGED_MY_TILT_POS | SHADE_CHANGED_WINDY;
|
||||
if(shade.tiltType != static_cast<uint8_t>(tilt_types::none))
|
||||
topics |= SHADE_CHANGED_TILT_DIRECTION | SHADE_CHANGED_TILT_POSITION | SHADE_CHANGED_TILT_TARGET;
|
||||
if(shade.sunSensor) topics |= SHADE_CHANGED_SUN_FLAG | SHADE_CHANGED_SUNNY;
|
||||
return topics;
|
||||
}
|
||||
44
src/ShadeChanges.h
Normal file
44
src/ShadeChanges.h
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
#ifndef SHADE_CHANGES_H
|
||||
#define SHADE_CHANGES_H
|
||||
#include <stdint.h>
|
||||
#include "ModelSnapshot.h"
|
||||
|
||||
// What changed about a shade since the change fan-out last sent it out, as
|
||||
// bits. The shade's setters and emitState() set them; once per loop() pass the
|
||||
// fan-out sends one shadeState for the shade, publishes the MQTT topics whose
|
||||
// bits are set, and clears them. Kept free of the shade model and MQTT so it
|
||||
// can be unit tested on the PC (pio test -e native).
|
||||
enum : uint16_t {
|
||||
SHADE_CHANGED_POSITION = 0x0001,
|
||||
SHADE_CHANGED_TILT_POSITION = 0x0002,
|
||||
SHADE_CHANGED_DIRECTION = 0x0004,
|
||||
SHADE_CHANGED_TILT_DIRECTION = 0x0008,
|
||||
SHADE_CHANGED_TARGET = 0x0010,
|
||||
SHADE_CHANGED_TILT_TARGET = 0x0020,
|
||||
SHADE_CHANGED_MY_POS = 0x0040,
|
||||
SHADE_CHANGED_MY_TILT_POS = 0x0080,
|
||||
SHADE_CHANGED_ROLLING_CODE = 0x0100,
|
||||
SHADE_CHANGED_SUN_FLAG = 0x0200,
|
||||
SHADE_CHANGED_SUNNY = 0x0400,
|
||||
SHADE_CHANGED_WINDY = 0x0800,
|
||||
// Something only the socket's shadeState carries changed.
|
||||
SHADE_CHANGED_STATE = 0x1000
|
||||
};
|
||||
// The bits that have an MQTT topic.
|
||||
#define SHADE_CHANGED_TOPICS 0x0FFF
|
||||
// The same for a group: its direction topic, and groupState for the socket.
|
||||
enum : uint8_t {
|
||||
GROUP_CHANGED_DIRECTION = 0x01,
|
||||
GROUP_CHANGED_STATE = 0x02
|
||||
};
|
||||
|
||||
// Publishes one MQTT topic: its name under shades/<id>/, its value and whether
|
||||
// the broker keeps it.
|
||||
typedef void (*topic_sink_t)(const char *topic, int32_t value, bool retain, void *ctx);
|
||||
// Publishes the topics of the changed bits with the shade's values, positions
|
||||
// as clients see them.
|
||||
void publishShadeTopics(uint16_t changed, const shade_snapshot_t &shade, topic_sink_t sink, void *ctx);
|
||||
// The topics a shade publishes in full when MQTT connects: the tilt ones only
|
||||
// with a tilt, the sun ones only with a sun sensor.
|
||||
uint16_t shadeStateTopics(const shade_snapshot_t &shade);
|
||||
#endif
|
||||
|
|
@ -35,6 +35,17 @@ enum class shade_types : byte {
|
|||
cgate1 = 0x0F,
|
||||
rgate1 = 0x10
|
||||
};
|
||||
// The bits of a shade's or group's flags.
|
||||
enum class somfy_flags_t : byte {
|
||||
SunFlag = 0x01,
|
||||
SunSensor = 0x02,
|
||||
DemoMode = 0x04,
|
||||
Light = 0x08,
|
||||
Windy = 0x10,
|
||||
Sunny = 0x20,
|
||||
Lighted = 0x40,
|
||||
SimMy = 0x80
|
||||
};
|
||||
enum class tilt_types : byte {
|
||||
none = 0x00,
|
||||
tiltmotor = 0x01,
|
||||
|
|
|
|||
32
src/Somfy.h
32
src/Somfy.h
|
|
@ -10,18 +10,9 @@
|
|||
#include "ShadeTypes.h"
|
||||
#include "ShadeMotion.h"
|
||||
#include "ShadeSensors.h"
|
||||
#include "ShadeChanges.h"
|
||||
#include "Transceiver.h"
|
||||
|
||||
enum class somfy_flags_t : byte {
|
||||
SunFlag = 0x01,
|
||||
SunSensor = 0x02,
|
||||
DemoMode = 0x04,
|
||||
Light = 0x08,
|
||||
Windy = 0x10,
|
||||
Sunny = 0x20,
|
||||
Lighted = 0x40,
|
||||
SimMy = 0x80
|
||||
};
|
||||
enum class gpio_flags_t : byte {
|
||||
LowLevelTrigger = 0x01
|
||||
};
|
||||
|
|
@ -180,8 +171,14 @@ class SomfyShade : public SomfyRemote {
|
|||
bool sendCommand(somfy_commands cmd, uint8_t repeat, uint8_t stepSize = 0);
|
||||
bool linkRemote(uint32_t remoteAddress, uint16_t rollingCode = 0);
|
||||
bool unlinkRemote(uint32_t remoteAddress);
|
||||
void emitState(const char *evt = "shadeState");
|
||||
// Marks the shade as changed; the change fan-out sends shadeState once per
|
||||
// loop() pass.
|
||||
void emitState();
|
||||
// Sends evt about the shade to every client now.
|
||||
void emitState(const char *evt);
|
||||
void emitState(uint8_t num, const char *evt = "shadeState");
|
||||
// What changed since the fan-out last sent the shade (SHADE_CHANGED_* bits).
|
||||
uint16_t changed = 0;
|
||||
void emitCommand(somfy_commands cmd, const char *source, uint32_t sourceAddress, const char *evt = "shadeCommand");
|
||||
void emitCommand(uint8_t num, somfy_commands cmd, const char *source, uint32_t sourceAddress, const char *evt = "shadeCommand");
|
||||
// False when the radio refused a command it sent.
|
||||
|
|
@ -193,6 +190,8 @@ class SomfyShade : public SomfyRemote {
|
|||
static void unpublish(uint8_t id);
|
||||
static void unpublish(uint8_t id, const char *topic);
|
||||
void publishState();
|
||||
// Publishes the MQTT topics of the SHADE_CHANGED_* bits in changed.
|
||||
void publishTopics(uint16_t changed);
|
||||
void commit();
|
||||
void commitShadePosition();
|
||||
void commitTiltPosition();
|
||||
|
|
@ -250,8 +249,14 @@ class SomfyGroup : public SomfyRemote {
|
|||
static void unpublish(uint8_t id, const char *topic);
|
||||
void publishState();
|
||||
void updateFlags();
|
||||
void emitState(const char *evt = "groupState");
|
||||
// Marks the group as changed; the change fan-out sends groupState once per
|
||||
// loop() pass.
|
||||
void emitState();
|
||||
// Sends evt about the group to every client now.
|
||||
void emitState(const char *evt);
|
||||
void emitState(uint8_t num, const char *evt = "groupState");
|
||||
// What changed since the fan-out last sent the group (GROUP_CHANGED_* bits).
|
||||
uint8_t changed = 0;
|
||||
bool sendCommand(somfy_commands cmd);
|
||||
bool sendCommand(somfy_commands cmd, uint8_t repeat, uint8_t stepSize = 0);
|
||||
int8_t p_direction(int8_t dir);
|
||||
|
|
@ -307,6 +312,9 @@ class SomfyShadeController {
|
|||
void markTxPending(uint32_t remoteAddress, uint32_t ticket);
|
||||
void processTxEvent(const somfy_tx_event_t &evt);
|
||||
void emitState(uint8_t num = 255);
|
||||
// The change fan-out: sends every shade and group that changed since the
|
||||
// last pass, its changed MQTT topics and one state event. loop() only.
|
||||
void sendChanges();
|
||||
void publish();
|
||||
void processWaitingFrame();
|
||||
void commit();
|
||||
|
|
|
|||
|
|
@ -158,6 +158,10 @@ void loop() {
|
|||
loopQueue.drain();
|
||||
logIfSlow(TAG, "Loop queue", timing.lap(), LOOP_SLOW_MS);
|
||||
esp_task_wdt_reset();
|
||||
// What changed in this pass goes out once, to MQTT and the sockets.
|
||||
somfy.sendChanges();
|
||||
logIfSlow(TAG, "Loop changes", timing.lap(), LOOP_SLOW_MS);
|
||||
esp_task_wdt_reset();
|
||||
if(net.connected() || net.softAPOpened) {
|
||||
if(!rebootDelay.reboot && net.connected() && !net.softAPOpened) {
|
||||
git.loop();
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ int8_t SomfyGroup::p_direction(int8_t dir) {
|
|||
int8_t old = this->direction;
|
||||
if(old != dir) {
|
||||
this->direction = dir;
|
||||
this->publish("direction", this->direction);
|
||||
this->changed |= GROUP_CHANGED_DIRECTION;
|
||||
}
|
||||
return old;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -298,22 +298,23 @@ void SomfyShade::checkMovement() {
|
|||
}
|
||||
|
||||
|
||||
// State Setters
|
||||
// State Setters. Each marks what clients see as changed when it does change;
|
||||
// the change fan-out publishes it once per loop() pass.
|
||||
float SomfyShade::p_currentPos(float pos) {
|
||||
float old = this->currentPos;
|
||||
this->currentPos = pos;
|
||||
if(floor(old) != floor(pos)) this->publish("position", (uint8_t)50);
|
||||
if(floor(old) != floor(pos)) this->changed |= SHADE_CHANGED_POSITION;
|
||||
return old;
|
||||
}
|
||||
float SomfyShade::p_currentTiltPos(float pos) {
|
||||
float old = this->currentTiltPos;
|
||||
this->currentTiltPos = pos;
|
||||
if(floor(old) != floor(pos)) this->publish("tiltPosition", this->transformPosition(static_cast<uint8_t>(floor(this->currentTiltPos))));
|
||||
if(floor(old) != floor(pos)) this->changed |= SHADE_CHANGED_TILT_POSITION;
|
||||
return old;
|
||||
}
|
||||
uint16_t SomfyShade::p_lastRollingCode(uint16_t code) {
|
||||
uint16_t old = SomfyRemote::p_lastRollingCode(code);
|
||||
if(old != code) this->publish("lastRollingCode", code);
|
||||
if(old != code) this->changed |= SHADE_CHANGED_ROLLING_CODE;
|
||||
return old;
|
||||
}
|
||||
bool SomfyShade::p_flag(somfy_flags_t flag, bool val) {
|
||||
|
|
@ -326,24 +327,24 @@ bool SomfyShade::p_flag(somfy_flags_t flag, bool val) {
|
|||
}
|
||||
bool SomfyShade::p_sunFlag(bool val) {
|
||||
bool old = this->p_flag(somfy_flags_t::SunFlag, val);
|
||||
if(old != val) this->publish("sunFlag", static_cast<uint8_t>(val));
|
||||
if(old != val) this->changed |= SHADE_CHANGED_SUN_FLAG;
|
||||
return old;
|
||||
}
|
||||
bool SomfyShade::p_windy(bool val) {
|
||||
bool old = this->p_flag(somfy_flags_t::Windy, val);
|
||||
if(old != val) this->publish("windy", static_cast<uint8_t>(val));
|
||||
if(old != val) this->changed |= SHADE_CHANGED_WINDY;
|
||||
return old;
|
||||
}
|
||||
bool SomfyShade::p_sunny(bool val) {
|
||||
bool old = this->p_flag(somfy_flags_t::Sunny, val);
|
||||
if(old != val) this->publish("sunny", static_cast<uint8_t>(val));
|
||||
if(old != val) this->changed |= SHADE_CHANGED_SUNNY;
|
||||
return old;
|
||||
}
|
||||
int8_t SomfyShade::p_direction(int8_t dir) {
|
||||
int8_t old = this->direction;
|
||||
if(old != dir) {
|
||||
this->direction = dir;
|
||||
this->publish("direction", this->direction, true);
|
||||
this->changed |= SHADE_CHANGED_DIRECTION;
|
||||
}
|
||||
return old;
|
||||
}
|
||||
|
|
@ -351,7 +352,7 @@ int8_t SomfyShade::p_tiltDirection(int8_t dir) {
|
|||
int8_t old = this->tiltDirection;
|
||||
if(old != dir) {
|
||||
this->tiltDirection = dir;
|
||||
this->publish("tiltDirection", this->tiltDirection, true);
|
||||
this->changed |= SHADE_CHANGED_TILT_DIRECTION;
|
||||
}
|
||||
return old;
|
||||
}
|
||||
|
|
@ -360,7 +361,7 @@ float SomfyShade::p_target(float target) {
|
|||
if(old != target) {
|
||||
this->target = target;
|
||||
if(this->transformPosition(old) != this->transformPosition(target))
|
||||
this->publish("target", this->transformPosition(this->target), true);
|
||||
this->changed |= SHADE_CHANGED_TARGET;
|
||||
}
|
||||
return old;
|
||||
}
|
||||
|
|
@ -369,17 +370,16 @@ float SomfyShade::p_tiltTarget(float target) {
|
|||
if(old != target) {
|
||||
this->tiltTarget = target;
|
||||
if(this->transformPosition(old) != this->transformPosition(target))
|
||||
this->publish("tiltTarget", this->transformPosition(this->tiltTarget), true);
|
||||
this->changed |= SHADE_CHANGED_TILT_TARGET;
|
||||
}
|
||||
return old;
|
||||
}
|
||||
float SomfyShade::p_myPos(float pos) {
|
||||
float old = this->myPos;
|
||||
if(old != pos) {
|
||||
//if(this->transformPosition(pos) == 0) Serial.println("MyPos = %.2f", pos);
|
||||
this->myPos = pos;
|
||||
if(this->transformPosition(old) != this->transformPosition(pos))
|
||||
this->publish("mypos", this->transformPosition(this->myPos), true);
|
||||
this->changed |= SHADE_CHANGED_MY_POS;
|
||||
}
|
||||
return old;
|
||||
}
|
||||
|
|
@ -388,7 +388,7 @@ float SomfyShade::p_myTiltPos(float pos) {
|
|||
if(old != pos) {
|
||||
this->myTiltPos = pos;
|
||||
if(this->transformPosition(old) != this->transformPosition(pos))
|
||||
this->publish("myTiltPos", this->transformPosition(this->myTiltPos), true);
|
||||
this->changed |= SHADE_CHANGED_MY_TILT_POS;
|
||||
}
|
||||
return old;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ extern SomfyShadeController somfy;
|
|||
extern SocketEmitter sockEmit;
|
||||
extern MQTTClass mqtt;
|
||||
|
||||
void SomfyShade::emitState() { this->changed |= SHADE_CHANGED_STATE; }
|
||||
void SomfyShade::emitState(const char *evt) { this->emitState(255, evt); }
|
||||
void SomfyShade::emitState(uint8_t num, const char *evt) {
|
||||
JsonSockEvent *json = sockEmit.beginEmit(evt);
|
||||
|
|
@ -65,6 +66,7 @@ void SomfyRoom::emitState(uint8_t num, const char *evt) {
|
|||
sockEmit.endEmit(num);
|
||||
this->publish();
|
||||
}
|
||||
void SomfyGroup::emitState() { this->changed |= GROUP_CHANGED_STATE; }
|
||||
void SomfyGroup::emitState(const char *evt) { this->emitState(255, evt); }
|
||||
void SomfyGroup::emitState(uint8_t num, const char *evt) {
|
||||
uint8_t flags = 0;
|
||||
|
|
@ -102,6 +104,26 @@ void emitRequestResult(uint32_t requestId, const edit_result_t &result) {
|
|||
json->endObject();
|
||||
sockEmit.endEmit();
|
||||
}
|
||||
// The change fan-out. Everything that changed about a shade or group in this
|
||||
// loop() pass goes out once: its MQTT topics, then one state event.
|
||||
void SomfyShadeController::sendChanges() {
|
||||
for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) {
|
||||
SomfyShade &shade = this->shades[i];
|
||||
const uint16_t changed = shade.changed;
|
||||
shade.changed = 0;
|
||||
if(!changed || shade.getShadeId() == 255) continue;
|
||||
if(changed & SHADE_CHANGED_TOPICS) shade.publishTopics(changed);
|
||||
shade.emitState(255, "shadeState");
|
||||
}
|
||||
for(uint8_t i = 0; i < SOMFY_MAX_GROUPS; i++) {
|
||||
SomfyGroup &group = this->groups[i];
|
||||
const uint8_t changed = group.changed;
|
||||
group.changed = 0;
|
||||
if(!changed || group.getGroupId() == 255) continue;
|
||||
if(changed & GROUP_CHANGED_DIRECTION) group.publish("direction", group.direction);
|
||||
group.emitState(255, "groupState");
|
||||
}
|
||||
}
|
||||
void SomfyShadeController::emitState(uint8_t num) {
|
||||
for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) {
|
||||
SomfyShade *shade = &this->shades[i];
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include "Utils.h"
|
||||
#include "ConfigSettings.h"
|
||||
#include "Somfy.h"
|
||||
#include "SomfySnapshot.h"
|
||||
#include "Sockets.h"
|
||||
#include "entryPoints/MQTT.h"
|
||||
|
||||
|
|
@ -31,28 +32,23 @@ void SomfyRoom::unpublish() {
|
|||
mqtt.unpublish(topic);
|
||||
}
|
||||
}
|
||||
// Publishes one topic of a shade: shades/<id>/<topic>.
|
||||
static void publishShadeTopic(const char *topic, int32_t value, bool retain, void *ctx) {
|
||||
SomfyShade *shade = static_cast<SomfyShade *>(ctx);
|
||||
if(value < 0) shade->publish(topic, static_cast<int8_t>(value), retain);
|
||||
else shade->publish(topic, static_cast<uint32_t>(value), retain);
|
||||
}
|
||||
void SomfyShade::publishTopics(uint16_t changed) {
|
||||
if(!mqtt.connected()) return;
|
||||
shade_snapshot_t s;
|
||||
snapshotShade(*this, s);
|
||||
publishShadeTopics(changed, s, publishShadeTopic, this);
|
||||
}
|
||||
void SomfyShade::publishState() {
|
||||
if(mqtt.connected()) {
|
||||
this->publish("position", (uint8_t)50, true);
|
||||
this->publish("direction", (int8_t)0, true);
|
||||
this->publish("target", (uint8_t)50, true);
|
||||
this->publish("lastRollingCode", this->lastRollingCode);
|
||||
this->publish("mypos", this->transformPosition(this->myPos), true);
|
||||
this->publish("myTiltPos", this->transformPosition(this->myTiltPos), true);
|
||||
if(this->tiltType != tilt_types::none) {
|
||||
this->publish("tiltDirection", this->tiltDirection, true);
|
||||
this->publish("tiltPosition", this->transformPosition(this->currentTiltPos), true);
|
||||
this->publish("tiltTarget", this->transformPosition(this->tiltTarget), true);
|
||||
}
|
||||
const uint8_t sunFlag = !!(this->flags & static_cast<uint8_t>(somfy_flags_t::SunFlag));
|
||||
const uint8_t isSunny = !!(this->flags & static_cast<uint8_t>(somfy_flags_t::Sunny));
|
||||
const uint8_t isWindy = !!(this->flags & static_cast<uint8_t>(somfy_flags_t::Windy));
|
||||
if(this->hasSunSensor()) {
|
||||
this->publish("sunFlag", sunFlag);
|
||||
this->publish("sunny", isSunny);
|
||||
}
|
||||
this->publish("windy", isWindy);
|
||||
}
|
||||
if(!mqtt.connected()) return;
|
||||
shade_snapshot_t s;
|
||||
snapshotShade(*this, s);
|
||||
publishShadeTopics(shadeStateTopics(s), s, publishShadeTopic, this);
|
||||
}
|
||||
void SomfyShade::publishDisco() {
|
||||
if(!mqtt.connected() || !settings.MQTT.pubDisco) return;
|
||||
|
|
|
|||
91
test/test_shadechanges/test_shadechanges.cpp
Normal file
91
test/test_shadechanges/test_shadechanges.cpp
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
#include <unity.h>
|
||||
#include <string>
|
||||
#include "ShadeChanges.h"
|
||||
|
||||
static std::string published;
|
||||
static void record(const char *topic, int32_t value, bool retain, void *ctx) {
|
||||
(void)ctx;
|
||||
if(!published.empty()) published += " ";
|
||||
published += std::string(topic) + "=" + std::to_string(value) + (retain ? "R" : "");
|
||||
}
|
||||
static std::string topics(uint16_t changed, const shade_snapshot_t &shade) {
|
||||
published.clear();
|
||||
publishShadeTopics(changed, shade, record, nullptr);
|
||||
return published;
|
||||
}
|
||||
// A shade as the snapshot shows it to clients.
|
||||
static shade_snapshot_t shade() {
|
||||
shade_snapshot_t s{};
|
||||
s.shadeId = 4;
|
||||
s.position = 30;
|
||||
s.target = 70;
|
||||
s.direction = 1;
|
||||
s.myPos = -1;
|
||||
s.myTiltPos = -1;
|
||||
s.tiltType = static_cast<uint8_t>(tilt_types::none);
|
||||
s.tiltPosition = 40;
|
||||
s.tiltTarget = 45;
|
||||
s.tiltDirection = -1;
|
||||
s.lastRollingCode = 1234;
|
||||
return s;
|
||||
}
|
||||
|
||||
void setUp() {}
|
||||
void tearDown() {}
|
||||
|
||||
static void test_nothing_changed_publishes_nothing() {
|
||||
TEST_ASSERT_EQUAL_STRING("", topics(0, shade()).c_str());
|
||||
TEST_ASSERT_EQUAL_STRING("", topics(SHADE_CHANGED_STATE, shade()).c_str());
|
||||
}
|
||||
static void test_position_and_target_are_the_shades_own() {
|
||||
// Both used to go out as a constant 50.
|
||||
TEST_ASSERT_EQUAL_STRING("position=30R", topics(SHADE_CHANGED_POSITION, shade()).c_str());
|
||||
TEST_ASSERT_EQUAL_STRING("target=70R", topics(SHADE_CHANGED_TARGET, shade()).c_str());
|
||||
}
|
||||
static void test_only_the_changed_topics_go_out() {
|
||||
TEST_ASSERT_EQUAL_STRING("direction=1R lastRollingCode=1234",
|
||||
topics(SHADE_CHANGED_DIRECTION | SHADE_CHANGED_ROLLING_CODE, shade()).c_str());
|
||||
}
|
||||
static void test_an_unset_favorite_is_minus_1() {
|
||||
TEST_ASSERT_EQUAL_STRING("mypos=-1R myTiltPos=-1R", topics(SHADE_CHANGED_MY_POS | SHADE_CHANGED_MY_TILT_POS, shade()).c_str());
|
||||
}
|
||||
static void test_tilt_topics() {
|
||||
TEST_ASSERT_EQUAL_STRING("tiltDirection=-1R tiltPosition=40R tiltTarget=45R",
|
||||
topics(SHADE_CHANGED_TILT_DIRECTION | SHADE_CHANGED_TILT_POSITION | SHADE_CHANGED_TILT_TARGET, shade()).c_str());
|
||||
}
|
||||
static void test_sun_and_wind_are_0_or_1() {
|
||||
shade_snapshot_t s = shade();
|
||||
s.flags = static_cast<uint8_t>(somfy_flags_t::SunFlag) | static_cast<uint8_t>(somfy_flags_t::Windy);
|
||||
TEST_ASSERT_EQUAL_STRING("sunFlag=1 sunny=0 windy=1",
|
||||
topics(SHADE_CHANGED_SUN_FLAG | SHADE_CHANGED_SUNNY | SHADE_CHANGED_WINDY, s).c_str());
|
||||
}
|
||||
static void test_full_state_of_a_plain_shade() {
|
||||
// Direction used to go out as a constant 0 when MQTT connected.
|
||||
TEST_ASSERT_EQUAL_STRING("position=30R direction=1R target=70R lastRollingCode=1234 mypos=-1R myTiltPos=-1R windy=0",
|
||||
topics(shadeStateTopics(shade()), shade()).c_str());
|
||||
}
|
||||
static void test_full_state_adds_tilt_and_sun_when_the_shade_has_them() {
|
||||
shade_snapshot_t s = shade();
|
||||
s.tiltType = static_cast<uint8_t>(tilt_types::integrated);
|
||||
s.sunSensor = true;
|
||||
const uint16_t state = shadeStateTopics(s);
|
||||
TEST_ASSERT_TRUE(state & SHADE_CHANGED_TILT_POSITION);
|
||||
TEST_ASSERT_TRUE(state & SHADE_CHANGED_SUNNY);
|
||||
TEST_ASSERT_FALSE(state & SHADE_CHANGED_STATE);
|
||||
TEST_ASSERT_EQUAL_UINT16(SHADE_CHANGED_TOPICS, state);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(test_nothing_changed_publishes_nothing);
|
||||
RUN_TEST(test_position_and_target_are_the_shades_own);
|
||||
RUN_TEST(test_only_the_changed_topics_go_out);
|
||||
RUN_TEST(test_an_unset_favorite_is_minus_1);
|
||||
RUN_TEST(test_tilt_topics);
|
||||
RUN_TEST(test_sun_and_wind_are_0_or_1);
|
||||
RUN_TEST(test_full_state_of_a_plain_shade);
|
||||
RUN_TEST(test_full_state_adds_tilt_and_sun_when_the_shade_has_them);
|
||||
return UNITY_END();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue