src/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
8 / 9
88.9%
Branches:
-
-%
Functions:
8 / 9
88.9%

ShadeBehaviour.h
Line Branch Exec Source
1 #ifndef SHADE_BEHAVIOUR_H
2 #define SHADE_BEHAVIOUR_H
3 #include <Arduino.h>
4 #include "SomfyFrame.h"
5 #include "ShadeSettings.h"
6 #include "ShadeMotion.h"
7 #include "ShadeSensors.h"
8 #include "ShadeChanges.h"
9
10 // What a shade does with frames, commands and radio events, and where that
11 // leaves it. Kept free of the shade model, radio and network so it can be
12 // unit tested on the PC (pio test -e native). Only loop() calls it.
13
14 // What sending a command did. ticket is the radio job carrying it, 0 when the
15 // command went out over GPIO instead.
16 struct send_result_t {
17 bool sent = false;
18 uint32_t ticket = 0;
19 12 explicit operator bool() const { return this->sent; }
20 };
21
22 // How a shade's commands reach its motor, over the radio or GPIO. When it
23 // sends, the frame as sent is in the shade's lastFrame and every other shade
24 // has heard it; the shade hears its own frame itself.
25 class MotorLink {
26 public:
27 // sent is false when the radio could not take the command.
28 virtual send_result_t send(somfy_commands cmd, uint8_t repeat, uint8_t stepSize, uint16_t delayMs, bool priority) = 0;
29 };
30
31 // What a shade's behaviour tells the rest of the firmware, as it happens.
32 class ShadeEvents {
33 public:
34 // SHADE_CHANGED_* bits for the change fan-out.
35 virtual void onChanged(uint16_t bits) = 0;
36 // A command reached the shade. source is "remote", "internal" and so on.
37 virtual void onCommand(somfy_commands cmd, const char *source, uint32_t sourceAddress) = 0;
38 // A position, the favorite or the sun flag changed and is worth saving.
39 virtual void onSaveWanted() = 0;
40 // A sensor, sun flag or flag frame arrived, so the groups work their
41 // flags out again.
42 virtual void onSensorFlags() = 0;
43 };
44
45 class ShadeBehaviour {
46 public:
47 // Where the shade is and where it is going. Positions run 0-100, the
48 // favorite is -1 when there is none.
49 struct State {
50 float currentPos = 0.0f;
51 float currentTiltPos = 0.0f;
52 float target = 0.0f;
53 float tiltTarget = 0.0f;
54 int8_t direction = 0; // 0 = stopped, 1 = down, -1 = up.
55 int8_t tiltDirection = 0; // 0 = stopped, 1 = clockwise, -1 = counter clockwise.
56 int8_t lastMovement = 0;
57 float myPos = -1.0f;
58 float myTiltPos = -1.0f;
59 // The flags bits that are state: sun flag, sunny, windy, demo mode and
60 // the unused lighted. The setting bits are in ShadeSettings.
61 uint8_t stateFlags = 0;
62 };
63 // lastFrame is the shade's last frame, shared with the send path that
64 // builds the next one from it. shadeId and name are for the log.
65 ShadeBehaviour(const ShadeSettings &settings, const RemoteSettings &remote, somfy_frame_t &lastFrame,
66 MotorLink &link, ShadeEvents &events, const uint8_t &shadeId, const char *name);
67 ShadeBehaviour(const ShadeBehaviour &) = delete;
68 ShadeBehaviour &operator=(const ShadeBehaviour &) = delete;
69 147 const State &state() const { return this->s; }
70 bool isIdle() const;
71 bool isAtTarget() const;
72
73 // A frame from a remote, sensor or linked remote, or one the shade or
74 // another shade sent (internal).
75 void onFrame(somfy_frame_t &frame, bool internal, uint32_t now);
76 // A remote button may still be held: acts on lastFrame once it is clear
77 // whether the press was short or long.
78 void onWaitingFrame(uint32_t now);
79 // A group the shade belongs to sent cmd.
80 void onGroupCommand(somfy_commands cmd, uint8_t repeat, uint32_t now);
81 // Every loop() pass: moves the simulated position, stops at the target and
82 // stores the favorite.
83 void tick(uint32_t now);
84 // A command that may set the shade moving was queued as radio job ticket.
85 void onTxQueued(uint32_t ticket, uint32_t now);
86 // A radio job started, finished, was aborted or dropped.
87 void onTxEvent(const somfy_tx_event_t &evt);
88
89 // Commands from the web, MQTT and Home Assistant. False when the radio
90 // refused a command they sent.
91 bool sendCommand(somfy_commands cmd, uint8_t repeat, uint8_t stepSize, uint32_t now);
92 bool sendTiltCommand(somfy_commands cmd, uint32_t now);
93 bool moveToTarget(float pos, float tilt, uint32_t now);
94 bool moveToTiltTarget(float target, uint32_t now);
95 bool setMyPosition(int8_t pos, int8_t tilt, uint32_t now);
96 bool moveToMyPosition(uint32_t now);
97
98 // Puts the shade at pos and tilt (-1 leaves one alone) without moving it.
99 void setPosition(float pos, float tilt);
100 // Where the config file says the shade was. The targets are where it is.
101 void restore(float pos, float tiltPos, float myPos, float myTiltPos);
102 // Forgets the tilt of a shade that has none.
103 void clearTilt();
104 void setStateFlags(uint8_t flags) { this->s.stateFlags = flags; }
105 // Back to a stopped shade at 0 with no favorite, for an empty slot.
106 void clear();
107
108 private:
109 const ShadeSettings &settings;
110 const RemoteSettings &remote;
111 somfy_frame_t &lastFrame;
112 MotorLink &link;
113 ShadeEvents &events;
114 const uint8_t &shadeId;
115 const char *name;
116 State s;
117 uint32_t moveStart = 0;
118 uint32_t tiltStart = 0;
119 ShadeSensors sensors;
120 float startPos = 0.0f;
121 float startTiltPos = 0.0f;
122 bool settingMyPos = false;
123 bool settingPos = false;
124 bool settingTiltPos = false;
125 uint32_t awaitMy = 0;
126 // Radio jobs whose outcome decides what the motor really did.
127 ShadeTxState tx;
128
129 // Sends through the link, then hears the frame as every other shade has
130 // and waits for it to go on air if it sets the shade moving.
131 send_result_t transmit(somfy_commands cmd, uint8_t repeat, uint8_t stepSize, uint16_t delayMs, bool priority, uint32_t now);
132 bool sendStop(int8_t dir, bool tilt, uint32_t now);
133 bool sendSetMy(uint16_t delayMs, float oldPos, float oldTiltPos, uint32_t now);
134 // Makes pos/tiltPos the favorite and tells the motor to store it.
135 bool storeMyPosition(float pos, float tiltPos, uint32_t now);
136 // Repeats for a tilt command, more for a separate tilt motor.
137 uint8_t tiltRepeats() const;
138 // A tilt-only shade has no lift to move: it stays all the way down.
139 void pinTiltOnlyLift();
140 void runToLimit(int8_t dir, bool tilt, uint32_t since);
141 // Sets the targets the sun or wind sends the shade to.
142 void applySensorMove(SensorMove move);
143 // Sets the targets for a step in dir. False when the step is dropped.
144 bool applyStep(int8_t dir, uint8_t frameStepSize);
145 void setTiltMovement(int8_t dir, uint32_t now);
146 void setMovement(int8_t dir, uint32_t now);
147 71 bool isToggle() const { return isToggleShade(this->settings.shadeType); }
148 somfy_commands transformCommand(somfy_commands cmd) const;
149 212 int8_t transformPosition(float fpos) const { return displayPosition(fpos, this->settings.flipPosition); }
150 1536 void emitState() { this->events.onChanged(SHADE_CHANGED_STATE); }
151 59 void emitCommand(somfy_commands cmd, const char *source, uint32_t sourceAddress) { this->events.onCommand(cmd, source, sourceAddress); }
152 37 void commitShadePosition() { this->events.onSaveWanted(); }
153 ✗ void commitTiltPosition() { this->events.onSaveWanted(); }
154 2 void commitMyPosition() { this->events.onSaveWanted(); }
155 // State setters. Each marks what clients see as changed when it does change.
156 float p_currentPos(float pos);
157 float p_currentTiltPos(float pos);
158 int8_t p_direction(int8_t dir);
159 int8_t p_tiltDirection(int8_t dir);
160 float p_target(float target);
161 float p_tiltTarget(float target);
162 float p_myPos(float pos);
163 float p_myTiltPos(float pos);
164 bool p_flag(somfy_flags_t flag, bool val);
165 bool p_sunFlag(bool val);
166 bool p_sunny(bool val);
167 bool p_windy(bool val);
168 };
169 #endif
170