Lamp-Da 0.1
A compact lantern project
Loading...
Searching...
No Matches
manager_type.hpp
Go to the documentation of this file.
1#ifndef MANAGER_TYPE_H
2#define MANAGER_TYPE_H
3
9#include <cstdint>
10#include <utility>
11#include <tuple>
12#include <array>
13
16
18
20
23#include "src/modes/include/default_config.hpp"
26
27namespace lampda::modes {
28
30union ActiveIndexTy
31{
32 struct
33 {
35 uint8_t groupIndex; // group id (as ordered in the manager)
36 uint8_t modeIndex; // mode id (as ordered in its group)
38
39 uint8_t rampIndex; // ramp value (as set by the user)
40 uint8_t customIndex; // custom (as set by the mode)
41 };
42
43 uint32_t rawIndex;
44
45 // static constructors
46 //
47 static constexpr auto from(const uint8_t* arr)
48 {
49 ActiveIndexTy index = {arr[0], arr[1], arr[2], arr[3]};
50 return index;
51 }
52 static constexpr auto from(const std::array<uint8_t, 4>& arr) { return ActiveIndexTy::from(arr.data()); }
53};
54
56template<typename Config> struct RampHandlerTy
57{
58 using ConfigTy = Config;
59 static constexpr uint32_t startPeriod = Config::rampStartPeriodMs;
60
61 // \in stepSpeed how long to wait before incrementing (ms)
62 // \in rampSaturates does the ramp saturates, or else wrap around?
63 RampHandlerTy(uint32_t stepSpeed, bool rampSaturates = Config::defaultRampSaturates) :
64 stepSpeed {stepSpeed},
66 lastTimeMeasured {1000},
67 isForward {true},
68 animEffect {Config::defaultCustomRampAnimEffect},
69 animChoice {Config::defaultCustomRampAnimChoice}
70 {
71 }
72
73 void LMBD_INLINE reset()
74 {
75 isForward = true;
76 stepSpeed = Config::defaultCustomRampStepSpeedMs;
77 rampSaturates = Config::defaultRampSaturates;
78 animEffect = Config::defaultCustomRampAnimEffect;
79 animChoice = Config::defaultCustomRampAnimChoice;
80 }
81
82 void LMBD_INLINE update_ramp(uint8_t rampValue, uint32_t holdTime, auto callback)
83 {
84 // restart the rampage
85 if (holdTime < lastTimeMeasured)
86 {
87 lastTimeMeasured = holdTime;
88
89 if (holdTime < startPeriod)
90 {
91 // toggle forward / backward direction
92 isForward = !isForward;
93 if (rampSaturates)
94 {
95 if (rampValue == 0)
96 isForward = true;
97 if (rampValue == 255)
98 isForward = false;
99 }
100 }
101 return;
102 }
103
104 // count how many step we advanced
105 uint32_t lastCounter = lastTimeMeasured / stepSpeed;
106 uint32_t nextCounter = holdTime / stepSpeed;
107 if (nextCounter <= lastCounter)
108 return;
109
110 // apply steps
111 for (uint32_t I = 0; I < nextCounter - lastCounter; ++I)
112 {
113 // increment iff possible
114 if (isForward && (!rampSaturates || rampValue < 255))
115 rampValue += 1;
116
117 // decrement iff possible
118 if (!isForward && (!rampSaturates || rampValue > 0))
119 rampValue -= 1;
120
121 // forward value to callback
122 callback(rampValue);
123 }
124
125 lastTimeMeasured = holdTime;
126 }
127
128 uint32_t stepSpeed;
129 bool rampSaturates;
130 bool animEffect;
131 bool animChoice;
132 uint32_t lastTimeMeasured;
133 bool isForward;
134};
135
139template<typename Config, typename AllGroups, uint8_t hiddenGroupsCount> struct ModeManagerTy
140{
141 using SelfTy = ModeManagerTy<Config, AllGroups, hiddenGroupsCount>;
142 using ConfigTy = Config;
143 using AllGroupsTy = AllGroups;
144 using AllStatesTy = details::StateTyFrom<AllGroups>;
145
147 static constexpr uint8_t nbGroupsTotal {std::tuple_size_v<AllGroupsTy>};
148 static constexpr uint8_t hiddenGroupCount = hiddenGroupsCount;
149
150 static_assert(nbGroupsTotal >= hiddenGroupsCount, "Manager cannot operate without accessible groups");
151
153 static constexpr uint8_t nbAccessibleGroups = nbGroupsTotal - hiddenGroupsCount;
154
155 // last group index must not collide with modes::store::noGroupIndex
156 static_assert(nbGroupsTotal <= 15, "Maximum of 15 groups has been exceeded.");
157
158 template<uint8_t Idx> using GroupAt = std::tuple_element_t<Idx, AllGroupsTy>;
159
160 // required to support manager-level context
161 using HasAnyGroup = details::anyOf<AllGroupsTy>;
162 static constexpr bool hasSunsetAnimation = HasAnyGroup::hasSunsetAnimation;
163 static constexpr bool hasBrightCallback = HasAnyGroup::hasBrightCallback;
164 static constexpr bool requireUserThread = HasAnyGroup::requireUserThread;
165 static constexpr bool hasCustomRamp = HasAnyGroup::hasCustomRamp;
166 static constexpr bool hasSystemCallbacks = hasCustomRamp || HasAnyGroup::hasSystemCallbacks;
167 static constexpr bool hasButtonCustomUI = HasAnyGroup::hasButtonCustomUI;
168
169 // useful for runtime tests of mode properties
170 using EveryModeBool = details::asTableFor<AllGroupsTy>;
171 static constexpr auto everySunsetCallback = EveryModeBool::everySunsetCallback;
172 static constexpr auto everyBrightCallback = EveryModeBool::everyBrightCallback;
173 static constexpr auto everyRequireUserThread = EveryModeBool::everyRequireUserThread;
174 static constexpr auto everyCustomRamp = EveryModeBool::everyCustomRamp;
175 static constexpr auto everySystemCallbacks = EveryModeBool::everySystemCallbacks;
176 static constexpr auto everyButtonCustomUI = EveryModeBool::everyButtonCustomUI;
177
178 // constructors
179 ModeManagerTy(hardware::LampTy& lamp) : activeIndex {ActiveIndexTy::from(Config::initialActiveIndex)}, lamp {lamp} {}
180
181 ModeManagerTy() = delete;
182 ModeManagerTy(const ModeManagerTy&) = delete;
183 ModeManagerTy& operator=(const ModeManagerTy&) = delete;
184
186 auto get_context() { return ContextTy<SelfTy, SelfTy>(*this); }
187
189 template<typename CallBack> static void LMBD_INLINE dispatch_group(auto& ctx, CallBack&& cb)
190 {
191 uint8_t groupId = ctx.get_active_group(nbGroupsTotal);
192
193 details::unroll<nbGroupsTotal>([&](auto Idx) LMBD_INLINE {
194 if (Idx == groupId)
195 {
196 cb(context_as<GroupAt<Idx>>(ctx));
197 }
198 });
199 }
200
202 template<bool systemCallbacksOnly, typename CallBack> static void LMBD_INLINE foreach_group(auto& ctx, CallBack&& cb)
203 {
204 if constexpr (systemCallbacksOnly)
205 {
206 details::unroll<nbGroupsTotal>([&](auto Idx) LMBD_INLINE {
207 using GroupHere = GroupAt<Idx>;
208 constexpr bool hasCallbacks = GroupHere::hasSystemCallbacks;
209
210 if constexpr (hasCallbacks)
211 {
212 cb(context_as<GroupAt<Idx>>(ctx));
213 }
214 });
215 }
216 else
217 {
218 details::unroll<nbGroupsTotal>([&](auto Idx) LMBD_INLINE {
219 cb(context_as<GroupAt<Idx>>(ctx));
220 });
221 }
222 }
223
224 //
225 // store
226 //
227
228 // persistent values
229 enum class Store : uint16_t
230 {
231 lastActive,
232 modeMemory,
233 usedFavoriteCount,
234 favoriteModes,
235 lastUsedFavorite,
236 isInFavoriteGroup
237 };
238
239 static constexpr uint32_t storeId = modes::store::derivateStoreId<modes::store::hash("ManagerStoreId"), AllGroupsTy>;
240
241 //
242 // state
243 //
244
245 struct StateTy
246 {
248 AllStatesTy groupStates;
249
251 std::array<uint8_t, nbGroupsTotal> lastModeMemory = {};
252
254 static constexpr uint8_t maxFavoriteCount = 8;
256 std::array<ActiveIndexTy, maxFavoriteCount> favorites = {};
257 uint8_t usedFavoriteCount = 0;
258
260 static constexpr uint8_t defaultFavoriteCount_indexable = 6;
262 static constexpr std::array<ActiveIndexTy, maxFavoriteCount> defaultFavorites_indexable = {
264 ActiveIndexTy::from({0, 2, 80, 0}),
266 ActiveIndexTy::from({0, 2, 165, 0}),
268 ActiveIndexTy::from({0, 2, 0, 0}),
270 ActiveIndexTy::from({0, 0, 0, 0}),
272 ActiveIndexTy::from({0, 0, 128, 0}),
274 ActiveIndexTy::from({0, 0, 255, 0}),
275 };
276
278 static constexpr uint8_t defaultFavoriteCount_simple = 0;
280 static constexpr std::array<ActiveIndexTy, maxFavoriteCount> defaultFavorites_simple = {};
281
282 static_assert(maxFavoriteCount < 16, "Maximum of 15 favorite as been exceeded");
283
284 // (variables for pending favorite state machine)
285 uint8_t isFavoritePending = 0;
287 bool isInDeleteFavorite = false;
289 uint8_t lastFavoriteStep = 0;
293
295 uint32_t lastScrollStopped = 0;
296
298 RampHandlerTy<Config> rampHandler = {Config::defaultCustomRampStepSpeedMs};
300 RampHandlerTy<Config> scrollHandler = {Config::scrollRampStepSpeedMs};
302 bool clearStripOnModeChange = Config::defaultClearStripOnModeChange;
303
304 // special effects
306
308 void reset()
309 {
310 lastModeMemory = {};
311
312 favorites = {};
314
317 isInDeleteFavorite = false;
320 isInFavoriteMockGroup = false;
321 beforeFavoriteActiveIndex = ActiveIndexTy();
323
326
328 }
329
330 // inside lamp.config
331 // - skipFirstLedsForEffect = 0; // should the loop skip some lower LEDs?
332 // - skipFirstLedsForAmount = 0; // how many pixels to shave from the top?
333
335 static void LMBD_INLINE before_enter_mode(auto& ctx)
336 {
337 auto& self = ctx.state;
338 self.rampHandler.reset();
339 self.clearStripOnModeChange = Config::defaultClearStripOnModeChange;
340 }
341
343 static void LMBD_INLINE after_enter_mode(auto& ctx)
344 {
345 auto& self = ctx.state;
346 if (self.clearStripOnModeChange)
347 {
348 ctx.lamp.clear();
349 }
350 }
351 };
352
354 template<typename Group> StateTyOf<Group>* LMBD_INLINE getStateGroupOf()
355 {
356 using StateTy = StateTyOf<Group>;
357 using OptionalTy = std::optional<StateTy>;
358
359 StateTy* substate = nullptr;
360 details::unroll<nbGroupsTotal>([&](auto Idx) LMBD_INLINE {
361 using GroupHere = GroupAt<Idx>;
362 constexpr bool IsHere = std::is_same_v<GroupHere, Group>;
363
364 if constexpr (IsHere)
365 {
366 OptionalTy& opt = std::get<OptionalTy>(state.groupStates);
367 if (!opt.has_value())
368 {
369 opt.emplace();
370 }
371
372 StateTy& stateHere = *opt;
373 substate = &stateHere;
374 }
375 });
376 assert(substate != nullptr && "this should not have compiled at all!");
377
378 static_assert(details::ModeBelongsTo<Group, AllGroups>);
379 return substate;
380 }
381
383 template<typename Mode> StateTyOf<Mode>& LMBD_INLINE getStateOf()
384 {
385 using TargetStateTy = StateTyOf<Mode>;
386
387 // Mode is unknown / as no state, return placeholder
388 if constexpr (std::is_same_v<TargetStateTy, NoState>)
389 {
390 return placeholder;
391
392 // Mode is ManagerTy, return our own state
393 }
394 else if constexpr (std::is_same_v<TargetStateTy, StateTy>)
395 {
396 return state;
397
398 // Mode is GroupTy, return the state of the group
399 }
400 else if constexpr (details::GroupBelongsTo<Mode, AllGroups>)
401 {
402 TargetStateTy* substate = getStateGroupOf<Mode>();
403
404 if (substate == nullptr)
405 {
406 substate = (TargetStateTy*)&placeholder;
407 assert(false && "this code should be unreachable, but is it?");
408 }
409
410 return *substate;
411 }
412 else
413 {
414 static_assert(details::ModeExists<Mode, AllGroups>);
415
416 // Mode is somewhere in a group, search for it, return its state
417 TargetStateTy* substate = nullptr;
418
419 details::unroll<nbGroupsTotal>([&](auto Idx) LMBD_INLINE {
420 using Group = GroupAt<Idx>;
421 using AllModes = typename Group::AllModesTy;
422 if constexpr (details::ModeBelongsTo<Mode, AllModes>)
423 {
424 substate = Group::template getStateOf<Mode>(*this);
425 }
426 });
427 assert(substate != nullptr && "this should not have compiled at all!");
428
429 if (substate == nullptr)
430 {
431 substate = (TargetStateTy*)&placeholder;
432 assert(false && "this code should be unreachable, but is it?");
433 }
434
435 return *substate;
436 }
437 }
438
439 //
440 // navigation
441 //
442
443 static constexpr bool isGroupManager = true;
444 static constexpr bool isModeManager = true;
445
447 static void next_group(auto& ctx)
448 {
449 // change current group: Limited to accessible groups
450 uint8_t groupIdBefore = ctx.get_active_group(nbAccessibleGroups);
451 ctx.set_active_group(groupIdBefore + 1, nbAccessibleGroups);
452 }
453
455 static void next_mode(auto& ctx)
456 {
457 dispatch_group(ctx, [](auto group) {
458 group.next_mode();
459 });
460 }
461
463 static void jump_to_new_active_index(auto& ctx, const ActiveIndexTy& newActiveIndex)
464 {
465 // not limited to accessible groups
466 ctx.set_active_group(newActiveIndex.groupIndex, nbGroupsTotal);
467 ctx.set_active_mode(newActiveIndex.modeIndex);
468 // just copy the other values
469 ctx.modeManager.activeIndex.customIndex = newActiveIndex.customIndex;
470 ctx.modeManager.activeIndex.rampIndex = newActiveIndex.rampIndex;
471
472 // if ramps loaded in enter_mode != ones saved as favorite, emulate update
473 if (ctx.modeManager.activeIndex.rawIndex != newActiveIndex.rawIndex)
474 {
475 ctx.modeManager.activeIndex = newActiveIndex;
476 custom_ramp_update(ctx, ctx.get_active_custom_ramp());
477 }
478 }
479
487 static bool jump_to_favorite(auto& ctx, uint8_t which_one, bool shouldSaveLastActiveIndex)
488 {
489 // sanity check
490 if (ctx.state.usedFavoriteCount <= 0)
491 return false;
492
493 // wrap back to max number of favorites
494 which_one = (which_one % ctx.state.usedFavoriteCount);
495 ctx.state.lastFavoriteStep = which_one;
496
497 // store last active index before jump
498 if (shouldSaveLastActiveIndex)
499 {
500 ctx.state.beforeFavoriteActiveIndex = ctx.modeManager.activeIndex;
501 }
502
503 if (which_one >= ctx.state.maxFavoriteCount)
504 return false;
505
506 const auto targetFavorite = ctx.state.favorites[which_one];
507 // reset once with the right mode
508 jump_to_new_active_index(ctx, targetFavorite);
509
510 // show which favorite is currently set
511 overlay.clear();
512 display_favorite_number_ramp(ctx, which_one, ctx.state.usedFavoriteCount, true, 1000);
513
514 // indicate favorite mode entry
515 ctx.blip(250);
516
517 // indicate that we are now in a favorite group
518 ctx.state.isInFavoriteMockGroup = true;
519
520 return true;
521 }
522
526 static bool exit_favorite_group(auto& ctx, ActiveIndexTy newActiveIndex)
527 {
528 if (not ctx.state.isInFavoriteMockGroup)
529 return false;
530
531#ifdef LMBD_SIMULATION
532 fprintf(stderr, "Exit fake favorite group\n");
533#endif
534 // reset favorite indicator
535 ctx.state.isInFavoriteMockGroup = false;
536 // return to previous state
537 jump_to_new_active_index(ctx, newActiveIndex);
538
539 // blip to indicate favorite mode exit
540 ctx.blip(250);
541
542 return true;
543 }
544
551 static bool set_favorite_now(auto& ctx, uint8_t which_one = 0)
552 {
553 // new favorite added
554 if (which_one != ctx.state.maxFavoriteCount && which_one == ctx.state.usedFavoriteCount)
555 {
556 // augment favorite count until we reach the max
557 ctx.state.usedFavoriteCount = std::min<uint8_t>(ctx.state.usedFavoriteCount + 1, ctx.state.maxFavoriteCount);
558 }
559
560 if (which_one < ctx.state.maxFavoriteCount)
561 {
562 ctx.state.favorites[which_one] = ctx.modeManager.activeIndex;
563 return true;
564 }
565 return false;
566 }
567
573 static bool delete_favorite_now(auto& ctx)
574 {
575 // delete current
576 const auto which_one = ctx.state.lastFavoriteStep;
577 if (ctx.state.usedFavoriteCount > 0 and which_one < ctx.state.maxFavoriteCount)
578 {
579 ctx.state.usedFavoriteCount -= 1;
580
581 for (uint8_t i = which_one; i < ctx.state.maxFavoriteCount - 1; ++i)
582 {
583 // make them all go down one spot
584 ctx.state.favorites[i] = ctx.state.favorites[i + 1];
585 }
586 // changed favorite index, jump
587 if (ctx.state.usedFavoriteCount > 0)
588 {
589 jump_to_favorite(ctx, which_one, false);
590 }
591 else
592 {
593 // no more favorite, restore last used mode
594 exit_favorite_group(ctx, ctx.state.beforeFavoriteActiveIndex);
595 }
596 return true;
597 }
598 return false;
599 }
600
606 static void handle_scroll_modes(auto& ctx, uint32_t holdDuration)
607 {
608 auto& scrollHandler = ctx.state.scrollHandler;
609 scrollHandler.isForward = false; // (always scroll modes backward)
610
611 static constexpr uint32_t scrollActivationTiming = 750;
612 if (holdDuration <= scrollActivationTiming)
613 {
614 // display the ramp and do nothing else
615 overlay_animate_ramp(
616 ctx, holdDuration, scrollActivationTiming, colors::PaletteGradient<colors::White, colors::Cyan>);
617 return;
618 }
619
620 scrollHandler.update_ramp(128, holdDuration, [&](uint8_t rampValue) {
621 uint8_t modeIndex = ctx.get_active_mode();
622 uint8_t groupIndex = ctx.get_active_group();
623 uint8_t modeCount = ctx.get_modes_count();
624
625 ctx.state.isLastScrollAGroupChange = false;
626
627 // we are going backward
628 //
629 if (rampValue < 128)
630 {
631 // if modeIndex is not the first, just decrement it
632 if (modeIndex > 0)
633 {
634 ctx.set_active_mode(modeIndex - 1, modeCount);
635
636 // or else decrement group, then set mode to last one
637 }
638 else
639 {
640 ctx.state.isLastScrollAGroupChange = true;
641 // if groupIndex is not the first, just decrement it
642 if (groupIndex > 0)
643 {
644 // can only access visible modes
645 ctx.set_active_group(groupIndex - 1, nbAccessibleGroups);
646
647 // else wrap to last group
648 }
649 else
650 {
651 // can only access visible modes
652 ctx.set_active_group(nbAccessibleGroups - 1, nbAccessibleGroups);
653 }
654
655 // backward scroll: set mode to last one on group change
656 modeCount = ctx.get_modes_count();
657 ctx.set_active_mode(modeCount - 1, modeCount);
658 }
659
660 // we are going forward
661 //
662 }
663 else
664 {
665 // if modeIndex is not the last, just increment it
666 if (modeIndex + 1 < modeCount)
667 {
668 ctx.next_mode();
669
670 // or else increment group
671 }
672 else
673 {
674 ctx.state.isLastScrollAGroupChange = true;
675 // if groupIndex is not the last, just increment it
676 if (groupIndex + 1 < nbAccessibleGroups)
677 {
678 ctx.next_group();
679
680 // else wrap to first group
681 }
682 else
683 {
684 ctx.set_active_group(0, nbAccessibleGroups);
685 }
686
687 // forward scroll: set mode to first one on group change
688 ctx.set_active_mode(0, modeCount);
689 }
690 }
691 });
692 }
693
699 static void enter_hidden_group(auto& ctx, uint8_t index)
700 {
701 assert(index < hiddenGroupsCount);
702 ctx.set_active_group(nbAccessibleGroups + index, nbGroupsTotal);
703 }
704
706 static bool overlay_animate_ramp(
707 auto& ctx, float holdDuration, float stepSize, const colors::PaletteTy& palette, const uint32_t timeout = 0)
708 {
709 // where we are: 0-255 rampColorRing
710 const uint32_t stepProgress = floor((holdDuration * 256.0) / stepSize);
711 return overlay_animate_ramp(ctx, stepProgress % 256, palette);
712 }
713 static bool overlay_animate_ramp(auto& ctx,
714 uint8_t progress,
715 const colors::PaletteTy& palette,
716 const uint32_t timeout = 0)
717 {
718 // only display on indexable
719 if constexpr (ctx.lamp.flavor == hardware::LampTypes::indexable)
720 {
721 // if first display failed, add a new ramp and try again
722 if (not overlay.update_type(ctx, draw::overlay::ElementType::RAMP, 0, progress, palette))
723 {
724 // add new ramp element
725 overlay.add_ui_element(ctx, draw::overlay::ElementType::RAMP, palette, 0, 0, progress);
726 }
727
728 // if timeout is requested, update it
729 if (timeout > 0)
730 overlay.update_type_timeout(ctx, draw::overlay::ElementType::RAMP, 0, timeout);
731 }
732 else if constexpr (ctx.lamp.flavor == hardware::LampTypes::simple)
733 {
734 // blip at the ramp end
735 if (progress >= 250)
736 ctx.blip(100);
737 }
738 return (progress >= 250);
739 }
740
742 static void overlay_animate_dot(auto& ctx,
743 uint16_t index,
744 uint16_t positionX,
745 uint8_t progress,
746 const auto& palette,
747 const uint32_t timeout = 0)
748 {
749 // only display on indexable
750 if constexpr (ctx.lamp.flavor == hardware::LampTypes::indexable)
751 {
752 // if first display failed, add a new ramp and try again
753 if (not overlay.update_type(ctx, draw::overlay::ElementType::DOT, index, progress, palette))
754 {
755 // add new element
756 overlay.add_ui_element(ctx, draw::overlay::ElementType::DOT, palette, positionX, 0, progress);
757 }
758
759 // if timeout is requested, update it
760 if (timeout > 0)
761 overlay.update_type_timeout(ctx, draw::overlay::ElementType::DOT, index, timeout);
762 }
763 }
764
766 static uint8_t get_modes_count(auto& ctx)
767 {
768 uint8_t value = 0;
769 dispatch_group(ctx, [&](auto group) {
770 value = decltype(group)::LocalModeTy::nbModes;
771 });
772 return value;
773 }
774
776 static void enter_group(auto& ctx, const uint8_t value)
777 {
778 // prevent value overflow
779 assert(value < nbGroupsTotal);
780
781 auto manager = ctx.modeManager.get_context();
782
783 // signal that we are quitting the mode
784 ctx.modeManager.quit_mode(manager);
785
786 // switch group (after quit mode)
787 ctx.modeManager.activeIndex.groupIndex = value;
788 // switch mode (restore last stored id)
789 ctx.modeManager.activeIndex.modeIndex = ctx.state.lastModeMemory[value];
790
791 // signal that we entered a new mode
792 ctx.modeManager.enter_mode(manager);
793 }
794
796 static void quit_group(auto& ctx)
797 {
798 //
799 uint8_t modeIdBefore = ctx.get_active_mode();
800
801 // changes to lastModeMemory made in this function will be persistent
802 auto keyModeMemory = ctx.template storageFor<Store::modeMemory>(ctx.state.lastModeMemory);
803
804 // save last mode used in group, before switching
805 uint8_t groupIdBefore = ctx.get_active_group(nbGroupsTotal);
806 ctx.state.lastModeMemory[groupIdBefore] = modeIdBefore;
807 }
808
810 static void enter_mode(auto& ctx)
811 {
812 ctx.state.before_enter_mode(ctx);
813
814 // enter mode
815 dispatch_group(ctx, [](auto group) {
816 group.enter_mode();
817 });
818
819 ctx.state.after_enter_mode(ctx);
820 }
821
823 static void quit_mode(auto& ctx)
824 {
825 dispatch_group(ctx, [](auto group) {
826 group.quit_mode();
827 });
828 }
829
830 //
831 // all the callbacks
832 //
833
835 static void loop(auto& ctx)
836 {
837 // handle pending favorite
838 if (ctx.state.isFavoritePending > 0)
839 {
840 ctx.state.isFavoritePending -= 1;
841
842 if (ctx.state.isFavoritePending == 0 && ctx.state.whichFavoritePending <= ctx.state.usedFavoriteCount)
843 {
844 if (ctx.set_favorite_now(ctx.state.whichFavoritePending))
845 {
846 logic::alerts::manager.raise(logic::alerts::Type::FAVORITE_SET);
847 }
848 }
849 }
850
851 // handle favorite delete
852 if (ctx.state.isFavoriteDeletePending > 0)
853 {
854 ctx.state.isFavoriteDeletePending -= 1;
855
856 // delete favorite
857 if (ctx.state.isFavoriteDeletePending == 0)
858 {
859 ctx.delete_favorite_now();
860 }
861 }
862
863 // handle the sunset timer update
864 if (ctx.state.isSunsetTimingPending > 0)
865 {
866 ctx.state.isSunsetTimingPending -= 1;
867 if (ctx.state.isSunsetTimingPending == 0)
868 {
869 // set and update sunset timer
871 // blip AFTER the update
872 ctx.blip(50);
873 }
874 }
875
876 if (ctx.lamp.config.skipFirstLedsForEffect > 0)
877 {
878 ctx.lamp.config.skipFirstLedsForEffect -= 1;
879 }
880
881 if (ctx.state.skipNextFrameEffect > 0)
882 {
883 ctx.state.skipNextFrameEffect -= 1;
884
885 // reached last skip frame, restore mode
886 if (ctx.state.skipNextFrameEffect == 0)
887 {
888 ctx.lamp.restoreBrightness();
889 }
890 return;
891 }
892
893 ctx.lamp.refresh_tick_value();
894
895 // udpate modes and groups
896 dispatch_group(ctx, [](auto group) {
897 group.loop();
898 });
899
900 // display the overlay after the group update
901 overlay.display_update(ctx);
902 }
903
909 static void sunset_update(auto& ctx, float progress)
910 {
911 dispatch_group(ctx, [&](auto group) {
912 group.sunset_update(progress);
913 });
914 }
915
921 static void brightness_update(auto& ctx, brightness_t brightness)
922 {
923 dispatch_group(ctx, [&](auto group) {
924 group.brightness_update(brightness);
925 });
926 }
927
929 static void power_on_sequence(auto& ctx)
930 {
931 // start with tick value
932 ctx.lamp.refresh_tick_value();
933
934 foreach_group<true>(ctx, [](auto group) {
935 group.power_on_sequence();
936 });
937
938 // activate last used favorite, in the favorite group
939 if (ctx.state.isInFavoriteMockGroup and jump_to_favorite(ctx, ctx.state.lastFavoriteStep, false))
940 {
941 // success jump to favorite
942 }
943 else
944 {
945 // activate current mode
946 uint8_t groupIdBefore = ctx.get_active_group(nbGroupsTotal);
947 ctx.set_active_group(groupIdBefore);
948 }
949 }
950
952 static void power_off_sequence(auto& ctx)
953 {
954 foreach_group<true>(ctx, [](auto group) {
955 group.power_off_sequence();
956 });
957 }
958
960 static void write_parameters(auto& ctx)
961 {
962 // clear the stored parameters, before storing ours.
964
965 // this scope is the only one where parameters will be kept
966 ctx.template storageSaveOnly<Store::lastActive>(ctx.modeManager.activeIndex);
967
968 // save the maxFavoriteCount possible favorites
969 ctx.template storageSaveOnly<Store::usedFavoriteCount>(ctx.modeManager.state.usedFavoriteCount);
970 ctx.template storageSaveOnly<Store::favoriteModes>(ctx.modeManager.state.favorites);
971 ctx.template storageSaveOnly<Store::lastUsedFavorite>(ctx.modeManager.state.lastFavoriteStep);
972 ctx.template storageSaveOnly<Store::isInFavoriteGroup>(ctx.state.isInFavoriteMockGroup);
973 ctx.template storageSaveOnly<Store::modeMemory>(ctx.state.lastModeMemory);
974
975 foreach_group<not hasCustomRamp>(ctx, [&ctx](auto group) {
976 if constexpr (group.hasCustomRamp)
977 {
978 using StoreHere = typename decltype(group)::StoreEnum;
979 group.template storageSaveOnly<StoreHere::rampMemory>(group.state.customRampMemory);
980 group.template storageSaveOnly<StoreHere::indexMemory>(group.state.customIndexMemory);
981 }
982
983 group.write_parameters();
984 });
985 }
986
988 static void read_parameters(auto& ctx)
989 {
990 // Reset the states before reading the parameters
991 ctx.state.reset();
992
993 // remove old filesystem data if we detect obsolete "storeId" serial
994 using LocalStore = details::LocalStoreOf<decltype(ctx)>;
995 LocalStore::template migrateStoreIfNeeded<storeId>();
996
997 // load last active mode and active ramp, or default index
998 ctx.template storageLoadOnly<Store::lastActive>(ctx.modeManager.activeIndex,
999 ActiveIndexTy::from(Config::initialActiveIndex));
1000
1001 if constexpr (ctx.lamp.flavor == hardware::LampTypes::indexable)
1002 {
1003 // load the maxFavoriteCount possible favorites, or default favorites, for indexable
1004 ctx.template storageLoadOnly<Store::usedFavoriteCount>(ctx.modeManager.state.usedFavoriteCount,
1005 ctx.modeManager.state.defaultFavoriteCount_indexable);
1006 ctx.template storageLoadOnly<Store::favoriteModes>(ctx.state.favorites,
1007 ctx.modeManager.state.defaultFavorites_indexable);
1008 }
1009 else if constexpr (ctx.lamp.flavor == hardware::LampTypes::simple)
1010 {
1011 // load the maxFavoriteCount possible favorites, or default favorites, for simple
1012 ctx.template storageLoadOnly<Store::usedFavoriteCount>(ctx.modeManager.state.usedFavoriteCount,
1013 ctx.modeManager.state.defaultFavoriteCount_simple);
1014 ctx.template storageLoadOnly<Store::favoriteModes>(ctx.state.favorites,
1015 ctx.modeManager.state.defaultFavorites_simple);
1016 }
1017
1018 ctx.template storageLoadOnly<Store::lastUsedFavorite>(ctx.modeManager.state.lastFavoriteStep);
1019 ctx.template storageLoadOnly<Store::isInFavoriteGroup>(ctx.state.isInFavoriteMockGroup);
1020 ctx.template storageLoadOnly<Store::modeMemory>(ctx.state.lastModeMemory);
1021
1022 // for each group, migrate & handle custom ramp memory
1023 foreach_group<not hasCustomRamp>(ctx, [&ctx](auto group) {
1024 using LocalStore = details::LocalStoreOf<decltype(group)>;
1025 LocalStore::template migrateStoreIfNeeded<storeId>();
1026
1027 if constexpr (group.hasCustomRamp)
1028 {
1029 using StoreHere = typename LocalStore::EnumTy;
1030 // Get saved ramp values, or default values pulled from modes
1031 group.template storageLoadOnly<StoreHere::rampMemory>(group.state.customRampMemory,
1032 group.template get_custom_ramp_default_value());
1033 group.template storageLoadOnly<StoreHere::indexMemory>(group.state.customIndexMemory);
1034 }
1035
1036 group.read_parameters();
1037 });
1038 }
1039
1041 static void user_thread(auto& ctx)
1042 {
1043 dispatch_group(ctx, [](auto group) {
1044 group.user_thread();
1045 });
1046 }
1047
1054 static void custom_ramp_update(auto& ctx, uint8_t rampValue, uint32_t timeout = 0)
1055 {
1056 uint8_t groupId = ctx.get_active_group();
1057 uint8_t modeId = ctx.get_active_mode();
1058
1059 if (ctx.everyCustomRamp[groupId][modeId] && ctx.state.rampHandler.animEffect)
1060 {
1061 switch (ctx.state.rampHandler.animChoice)
1062 {
1063 case 0:
1064 overlay_animate_ramp(ctx, rampValue, modes::colors::PaletteBlackBodyColors, timeout);
1065 break;
1066
1067 case 1:
1068 overlay_animate_ramp(ctx, rampValue, modes::colors::PaletteRainbowColors, timeout);
1069 break;
1070 }
1071 }
1072
1073 dispatch_group(ctx, [&](auto group) {
1074 group.custom_ramp_update(rampValue);
1075 });
1076 }
1077
1079 static bool custom_click(auto& ctx, uint8_t nbClick)
1080 {
1081 bool retVal = false;
1082 dispatch_group(ctx, [&](auto group) {
1083 retVal = group.custom_click(nbClick);
1084 });
1085 return retVal;
1086 }
1087
1089 static bool custom_hold(auto& ctx, uint8_t nbClickAndHold, bool isEndOfHoldEvent, uint32_t holdDuration)
1090 {
1091 bool retVal = false;
1092 dispatch_group(ctx, [&](auto group) {
1093 retVal = group.custom_hold(nbClickAndHold, isEndOfHoldEvent, holdDuration);
1094 });
1095 return retVal;
1096 }
1097
1098 //
1099 // Private action functions
1100 //
1101
1110 static void display_favorite_number_ramp(auto& ctx,
1111 const uint8_t favoriteIndex,
1112 const uint8_t maxFavoriteIndex,
1113 const bool display = false,
1114 const uint32_t timeout = 0)
1115 {
1116 const uint8_t maxPixelDisplay = std::min<uint8_t>(ctx.state.maxFavoriteCount, maxFavoriteIndex);
1117 for (uint8_t i = 0; i < maxPixelDisplay; ++i)
1118 {
1119 const bool shouldDisplay = (display and i <= favoriteIndex);
1120 overlay_animate_dot(
1121 ctx, i, i, shouldDisplay ? 255 : 0, colors::PaletteGradient<colors::Black, colors::Cyan>, timeout);
1122 }
1123 }
1124
1125 static uint8_t get_number_of_allowed_favorites(auto& ctx)
1126 {
1127 // user as a number of favorite set
1128 // occasional +1 if not all favorite are set (allow a new favorite)
1129 return ctx.state.usedFavoriteCount + ((ctx.state.usedFavoriteCount < ctx.state.maxFavoriteCount) ? 1 : 0);
1130 }
1131
1136 template<bool displayFavoriteNumber = true>
1137 static void set_current_mode_as_favorite(auto& ctx, uint8_t favoriteIndex, uint32_t displayTimeout_s = 0)
1138 {
1139 const uint8_t numberOfFavoriteSet = get_number_of_allowed_favorites(ctx);
1140 if (favoriteIndex >= numberOfFavoriteSet)
1141 {
1143 "Cannot set a favorite at index %d, max index must be less than %d", favoriteIndex, numberOfFavoriteSet);
1144 return;
1145 }
1146
1147 // extra display on the first pixels (count pixels to know fav no)
1148 if constexpr (displayFavoriteNumber)
1149 {
1150 // display the set favorite ramp
1151 display_favorite_number_ramp(
1152 ctx, favoriteIndex, numberOfFavoriteSet, favoriteIndex < numberOfFavoriteSet, displayTimeout_s);
1153 }
1154
1155 // set this, after a while upon no longer holding button, favorite is set
1156 ctx.state.isFavoritePending = 10;
1157 ctx.state.whichFavoritePending = favoriteIndex;
1158 }
1159
1166 template<bool displayFavoriteNumber = true>
1167 static void animate_favorite_pick(auto& ctx, float holdDuration, float stepSize)
1168 {
1169 const uint8_t numberOfFavoriteSet = get_number_of_allowed_favorites(ctx);
1170
1171 // up to maxFavoriteCount step state: "which_one" is [0, 1, 2, 3, ...] and "do not set" is the max index + 1
1172 uint32_t stepCount = numberOfFavoriteSet + floor(holdDuration / stepSize);
1173 stepCount = stepCount % (numberOfFavoriteSet + 1);
1174
1175 // display ramp to show where user is standing
1176 if (stepCount >= numberOfFavoriteSet)
1177 {
1178 // cancel action
1179 ctx.state.isFavoritePending = 0;
1180
1181 // display ramp for the first time to allow the user to cancel the action
1182 if (holdDuration <= 2 * stepSize)
1183 {
1184 overlay_animate_ramp(ctx, holdDuration, stepSize, colors::PaletteGradient<colors::White, colors::Cyan>);
1185 }
1186 }
1187 else
1188 {
1189 // green ramp : favorites
1190 overlay_animate_ramp(ctx, holdDuration, stepSize, colors::PaletteGradient<colors::Green, colors::White>);
1191
1192 // Set the favorite: this actually do not set it immediatly but waits until the function is not called anymores
1193 set_current_mode_as_favorite<displayFavoriteNumber>(ctx, stepCount);
1194 }
1195 }
1196
1203 template<bool displayFavoriteNumber = true>
1204 static void animate_favorite_delete(auto& ctx, float holdDuration, float stepSize)
1205 {
1206 // no favorite deletion if no favorites
1207 if (ctx.state.usedFavoriteCount <= 0)
1208 return;
1209
1210 ctx.state.isInDeleteFavorite = true;
1211
1212 uint32_t stepCount = floor(holdDuration / stepSize);
1213 stepCount = stepCount % 2;
1214
1215 if (stepCount == 0)
1216 {
1217 if (overlay_animate_ramp(ctx, holdDuration, stepSize, colors::PaletteGradient<colors::Orange, colors::Red>))
1218 {
1219 // set this on ramp saturation. The favorite will be removed in 2 frames
1220 ctx.state.isFavoriteDeletePending = 2;
1221 }
1222 else
1223 {
1224 // no deletion if release on ramp
1225 ctx.state.isFavoriteDeletePending = 0;
1226 }
1227
1228 // extra display on the first pixels (count pixels to know fav no)
1229 if constexpr (displayFavoriteNumber)
1230 {
1231 // display ramp
1232 display_favorite_number_ramp(ctx, ctx.state.lastFavoriteStep, ctx.state.usedFavoriteCount, true);
1233 }
1234 }
1235 // else: do nothing
1236 }
1237
1238 //
1239 // members with direct access
1240 //
1241
1243 ActiveIndexTy activeIndex;
1244
1246 hardware::LampTy& lamp;
1247
1249 inline static draw::overlay::Manager<> overlay;
1250
1251 //
1252 // private members
1253 //
1254
1255private:
1257 NoState placeholder;
1259 StateTy state;
1260};
1261
1266template<typename ManagerConfig, typename... Groups> using ManagerForConfig =
1267 ModeManagerTy<ManagerConfig, std::tuple<Groups...>, 0>;
1268
1277template<typename... Groups> using ManagerFor = ModeManagerTy<DefaultManagerConfig, std::tuple<Groups...>, 0>;
1278
1288template<uint8_t hiddenGroupCnt, typename... Groups> using ManagerForHiddenGroups =
1289 ModeManagerTy<DefaultManagerConfig, std::tuple<Groups...>, hiddenGroupCnt>;
1290
1295template<uint8_t hiddenGroupCnt, typename ManagerConfig, typename... Groups> using ManagerFoHiddenConfig =
1296 ModeManagerTy<ManagerConfig, std::tuple<Groups...>, hiddenGroupCnt>;
1297
1298} // namespace lampda::modes
1299
1300#endif
Handle the main alerts behavior.
Define assertions helpers.
void raise(const Type type)
Raise an alert.
Definition: alerts.cpp:878
ContextTy and associated definitions.
Filesystem interaction tools.
Define the main led strip interaction object.
void lampda_print(const char *format,...)
C linkage to print functions.
Definition: text_out.cpp:227
AlertManager_t manager
Instanciation of the AlertManager.
Definition: alerts.cpp:29
void add_time_minutes(const uint8_t time_minutes)
add some time to the sunset timer. Limited in the range [1; 10] minutes. If the timer is not started ...
Definition: sunset_timer.cpp:170
static constexpr PaletteTy PaletteBlackBodyColors
Black body radiation, with the high end changed to be nicer.
Definition: palettes.hpp:396
std::array< uint32_t, 16 > PaletteTy
Palette types.
Definition: palettes.hpp:18
static constexpr PaletteTy PaletteRainbowColors
HSV Rainbow.
Definition: palettes.hpp:360
@ indexable
Equivalent to LMBD_LAMP_TYPE__INDEXABLE.
@ simple
Equivalent to LMBD_LAMP_TYPE__SIMPLE.
static void clear_stored()
Force clear the stored parameters.
Definition: keystore.hpp:75
Contains basic interface types to implement custom user modes.
Definition: control_fixed_modes.hpp:12
ModeManagerTy< ManagerConfig, std::tuple< Groups... >, hiddenGroupCnt > ManagerFoHiddenConfig
Same as modes::ManagerFor but with custom defaults, and additional hidden groups.
Definition: manager_type.hpp:1296
ModeManagerTy< DefaultManagerConfig, std::tuple< Groups... >, hiddenGroupCnt > ManagerForHiddenGroups
Group together several mode groups defined through modes::GroupFor. Will use the last hiddenGroupCnt ...
Definition: manager_type.hpp:1289
ModeManagerTy< ManagerConfig, std::tuple< Groups... >, 0 > ManagerForConfig
Same as modes::ManagerFor but with custom defaults.
Definition: manager_type.hpp:1267
@ rampSaturates
(bool) Mode saturates on custom ramp, or else wrap?
ModeManagerTy< DefaultManagerConfig, std::tuple< Groups... >, 0 > ManagerFor
Group together several mode groups defined through modes::GroupFor.
Definition: manager_type.hpp:1277
static auto context_as(auto &ctx)
Bind provided context to another modes::BasicMode.
Definition: context_type.hpp:25
void brightness_update(const brightness_t brightness)
Called when the system changes the LED strip brightness.
Definition: default_behavior.hpp:59
void user_thread()
Called at each tick of the secondary thread.
Definition: default_behavior.hpp:206
void read_parameters()
Called when system wants to read parameters from filesystem.
Definition: default_behavior.hpp:96
void power_on_sequence()
Called when the system powers on (must be non blocking function!)
Definition: default_behavior.hpp:34
void write_parameters()
Called when system wants to write parameters to filesystem.
Definition: default_behavior.hpp:90
void power_off_sequence()
Called when the system powers off (must be non blocking function!)
Definition: default_behavior.hpp:45
void loop()
Called at each tick of the main loop.
Definition: default_behavior.hpp:186
uint16_t brightness_t
Define the type of the brightness parameters.
Definition: constants.h:147
GlobalSimStateTy state
Store the global simulation state.
Definition: simulator_state.cpp:7
Lamp overlay manager.
Default manager configuration, enables you to customize defaults.
Definition: default_config.hpp:42
Definition: manager_type.hpp:246
RampHandlerTy< Config > scrollHandler
Ramp handlers: mode scroll ramp.
Definition: manager_type.hpp:300
static constexpr uint8_t defaultFavoriteCount_simple
By default, the system will have X favorites.
Definition: manager_type.hpp:278
static constexpr std::array< ActiveIndexTy, maxFavoriteCount > defaultFavorites_indexable
The default favorites are defined below :
Definition: manager_type.hpp:262
bool clearStripOnModeChange
Should clear the strip before switching mode.
Definition: manager_type.hpp:302
RampHandlerTy< Config > rampHandler
Ramp handlers: custom ramp (or "color ramp")
Definition: manager_type.hpp:298
bool isInFavoriteMockGroup
Indicates that we are in the fake favorite page.
Definition: manager_type.hpp:290
static constexpr uint8_t defaultFavoriteCount_indexable
By default, the system will have X favorites.
Definition: manager_type.hpp:260
uint8_t lastFavoriteStep
last used favorite index
Definition: manager_type.hpp:289
AllStatesTy groupStates
All group states, containing all modes individual states.
Definition: manager_type.hpp:248
uint8_t isFavoriteDeletePending
indicate that the deletion of a favorite in in process
Definition: manager_type.hpp:288
ActiveIndexTy beforeFavoriteActiveIndex
store the index we need to go to when quitting the favorite page
Definition: manager_type.hpp:291
static constexpr std::array< ActiveIndexTy, maxFavoriteCount > defaultFavorites_simple
The default favorites are defined below :
Definition: manager_type.hpp:280
uint8_t usedFavoriteCount
number of favorite set by user [0, maxFavoriteCount]
Definition: manager_type.hpp:257
bool isLastScrollAGroupChange
last mode change in scroll changed group
Definition: manager_type.hpp:294
uint8_t whichFavoritePending
indicates the favorite currently selected
Definition: manager_type.hpp:286
uint32_t lastScrollStopped
keep track of the last scrool release time
Definition: manager_type.hpp:295
static void LMBD_INLINE before_enter_mode(auto &ctx)
configuration-related actions done before entering mode
Definition: manager_type.hpp:335
uint8_t isSunsetTimingPending
Indicates that a sunset timer ramp is active.
Definition: manager_type.hpp:292
void reset()
Reset the stored variable states.
Definition: manager_type.hpp:308
std::array< ActiveIndexTy, maxFavoriteCount > favorites
Store the active index of every favorite.
Definition: manager_type.hpp:256
uint8_t skipNextFrameEffect
should the next .loop() mode be skipped?
Definition: manager_type.hpp:305
std::array< uint8_t, nbGroupsTotal > lastModeMemory
When switching group, remember which mode was on last time we visited it.
Definition: manager_type.hpp:251
static constexpr uint8_t maxFavoriteCount
Maximum allowed favorite count.
Definition: manager_type.hpp:254
static void LMBD_INLINE after_enter_mode(auto &ctx)
configuration-related actions done after mode entering
Definition: manager_type.hpp:343
uint8_t isFavoritePending
indicate that the addition of a favorite in in process
Definition: manager_type.hpp:285
bool isInDeleteFavorite
indicates that we are in a favorite deletion process
Definition: manager_type.hpp:287
Logic of the sunset time, eg the system auto stops after a set delay.
Define templated tools to analyze the manager objects.