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 |