ModelEditor.h
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #ifndef MODEL_EDITOR_H | ||
| 2 | #define MODEL_EDITOR_H | ||
| 3 | #include <stddef.h> | ||
| 4 | #include <stdint.h> | ||
| 5 | #include "SomfyFrame.h" | ||
| 6 | #include "ShadeTypes.h" | ||
| 7 | |||
| 8 | // The rules ModelEditor applies when the shade model is edited: which id, sort | ||
| 9 | // order and remote address a new room, shade or group gets, how ids go into | ||
| 10 | // and out of a list, and which GPIO pins a shade may not use. Kept free of the | ||
| 11 | // shade model so it can be unit tested on the PC (pio test -e native). The | ||
| 12 | // edits themselves are applied on loop() (SomfyJson.cpp), since only loop() | ||
| 13 | // changes the shade model (docs/adr/0002-loop-task-is-the-only-writer-of-the-shade-model.md). | ||
| 14 | |||
| 15 | // The edits, one per edit route. | ||
| 16 | enum class model_edit_type_t : uint8_t { | ||
| 17 | add_room, add_shade, add_group, | ||
| 18 | save_room, save_shade, save_group, | ||
| 19 | delete_room, delete_shade, delete_group, | ||
| 20 | link_remote, unlink_remote, | ||
| 21 | link_to_group, unlink_from_group, | ||
| 22 | link_repeater, unlink_repeater, | ||
| 23 | set_paired, | ||
| 24 | room_order, shade_order, group_order | ||
| 25 | }; | ||
| 26 | // What an edit did, sent in the edit op's reply. id is the room, | ||
| 27 | // shade or group it added or changed, 0 when none. shadeId is the shade a | ||
| 28 | // group gained or lost. code is the validation error of a shade that wasn't | ||
| 29 | // saved, 0 otherwise. notFound: it was refused for an id that doesn't exist. | ||
| 30 | struct edit_result_t { | ||
| 31 | bool ok = true; | ||
| 32 | bool notFound = false; | ||
| 33 | uint8_t id = 0; | ||
| 34 | uint8_t shadeId = 0; | ||
| 35 | int8_t code = 0; | ||
| 36 | const char *desc = ""; | ||
| 37 | 2 | static edit_result_t error(const char *desc, int8_t code = 0) { | |
| 38 | 2 | edit_result_t r; | |
| 39 | 2 | r.ok = false; | |
| 40 | 2 | r.desc = desc; | |
| 41 | 2 | r.code = code; | |
| 42 | 2 | return r; | |
| 43 | } | ||
| 44 | 1 | static edit_result_t missing(const char *desc) { | |
| 45 | 1 | edit_result_t r = error(desc); | |
| 46 | 1 | r.notFound = true; | |
| 47 | 1 | return r; | |
| 48 | } | ||
| 49 | }; | ||
| 50 | // Why an add, or the read that offers one, is refused when every slot of its | ||
| 51 | // kind is taken. | ||
| 52 | #define EDIT_ROOMS_FULL "Maximum number of rooms exceeded." | ||
| 53 | #define EDIT_SHADES_FULL "Maximum number of shades exceeded." | ||
| 54 | #define EDIT_GROUPS_FULL "Maximum number of groups exceeded." | ||
| 55 | |||
| 56 | // The lists an edit can put in a new order. | ||
| 57 | enum class sort_list_t : uint8_t { none, rooms, shades, groups }; | ||
| 58 | // What protocol 2 clients are sent once an edit has gone through: the whole | ||
| 59 | // items it changed (0 for none), a list's new order, the repeaters. Deletes | ||
| 60 | // send their removals as they happen, so they need nothing here. | ||
| 61 | struct edit_changes_t { | ||
| 62 | uint8_t shadeId = 0; | ||
| 63 | uint8_t groupId = 0; | ||
| 64 | uint8_t roomId = 0; | ||
| 65 | sort_list_t order = sort_list_t::none; | ||
| 66 | bool repeaters = false; | ||
| 67 | // A delete: the id of what was removed. | ||
| 68 | uint8_t removedShadeId = 0; | ||
| 69 | uint8_t removedGroupId = 0; | ||
| 70 | uint8_t removedRoomId = 0; | ||
| 71 | }; | ||
| 72 | edit_changes_t editChanges(model_edit_type_t type, const edit_result_t &result); | ||
| 73 | |||
| 74 | // The lowest id from 1 to lastId that isn't in usedIds, or 0 when every one | ||
| 75 | // is taken. | ||
| 76 | uint8_t firstFreeId(const uint8_t *usedIds, size_t count, uint8_t lastId); | ||
| 77 | // The sort order that puts a new item after every existing one. | ||
| 78 | int8_t nextSortOrder(const int8_t *orders, size_t count); | ||
| 79 | // candidate, or the next address after it that isn't in used. No two shades or | ||
| 80 | // groups may share a remote address. | ||
| 81 | uint32_t freeRemoteAddress(uint32_t candidate, const uint32_t *used, size_t count); | ||
| 82 | // The remote address a new shade or group with that id is offered: the | ||
| 83 | // starting address plus its id, or the next free one after it. | ||
| 84 | uint32_t offeredRemoteAddress(uint32_t startingAddress, uint8_t id, const uint32_t *used, size_t count); | ||
| 85 | |||
| 86 | // A list of ids or addresses with 0 for an empty slot, kept packed at the front. | ||
| 87 | // Adding one that is already there changes nothing and succeeds; false when the | ||
| 88 | // list is full. | ||
| 89 | bool addToList(uint8_t *list, size_t size, uint8_t item); | ||
| 90 | bool addToList(uint32_t *list, size_t size, uint32_t item); | ||
| 91 | // Removes every copy and packs the rest to the front. False when it wasn't there. | ||
| 92 | bool removeFromList(uint8_t *list, size_t size, uint8_t item); | ||
| 93 | bool removeFromList(uint32_t *list, size_t size, uint32_t item); | ||
| 94 | // Packs the items to the front, keeping their order. | ||
| 95 | void packList(uint8_t *list, size_t size); | ||
| 96 | |||
| 97 | // Who else drives a GPIO pin. | ||
| 98 | struct pin_users_t { | ||
| 99 | bool (*uses)(uint8_t pin, void *ctx) = nullptr; | ||
| 100 | void *ctx = nullptr; | ||
| 101 | 55 | bool usesPin(uint8_t pin) const { return this->uses && this->uses(pin, this->ctx); } | |
| 102 | }; | ||
| 103 | #define PIN_CONFLICT_RADIO -10 | ||
| 104 | #define PIN_CONFLICT_ETHERNET -11 | ||
| 105 | #define PIN_CONFLICT_SHADE -12 | ||
| 106 | // Whether a shade of type driven over proto may use these pins (255 for none): | ||
| 107 | // 0, or which of the radio, the Ethernet PHY or another shade already uses one. | ||
| 108 | // Only the pins the shade type and protocol actually drive are checked. | ||
| 109 | int8_t gpioPinConflict(shade_types type, radio_proto proto, uint8_t upPin, uint8_t downPin, uint8_t myPin, | ||
| 110 | const pin_users_t &radio, const pin_users_t ðernet, const pin_users_t &otherShades); | ||
| 111 | #endif | ||
| 112 |