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 auto from(const uint8_t* arr)
48 {
49 ActiveIndexTy index = {arr[0], arr[1], arr[2], arr[3]};
50 return index;
51 }
52};
53
55template<typename Config> struct RampHandlerTy
56{
57 using ConfigTy = Config;
58 static constexpr uint32_t startPeriod = Config::rampStartPeriodMs;
59
60 // \in stepSpeed how long to wait before incrementing (ms)
61 // \in rampSaturates does the ramp saturates, or else wrap around?
62 RampHandlerTy(uint32_t stepSpeed, bool rampSaturates = Config::defaultRampSaturates) :
63 stepSpeed {stepSpeed},
65 lastTimeMeasured {1000},
66 isForward {true},
67 animEffect {Config::defaultCustomRampAnimEffect},
68 animChoice {Config::defaultCustomRampAnimChoice}
69 {
70 }
71
72 void LMBD_INLINE reset()
73 {
74 isForward = true;
75 stepSpeed = Config::defaultCustomRampStepSpeedMs;
76 rampSaturates = Config::defaultRampSaturates;
77 animEffect = Config::defaultCustomRampAnimEffect;
78 animChoice = Config::defaultCustomRampAnimChoice;
79 }
80
81 void LMBD_INLINE update_ramp(uint8_t rampValue, uint32_t holdTime, auto callback)
82 {
83 // restart the rampage
84 if (holdTime < lastTimeMeasured)
85 {
86 lastTimeMeasured = holdTime;
87
88 if (holdTime < startPeriod)
89 {
90 // toggle forward / backward direction
91 isForward = !isForward;
92 if (rampSaturates)
93 {
94 if (rampValue == 0)
95 isForward = true;
96 if (rampValue == 255)
97 isForward = false;
98 }
99 }
100 return;
101 }
102
103 // count how many step we advanced
104 uint32_t lastCounter = lastTimeMeasured / stepSpeed;
105 uint32_t nextCounter = holdTime / stepSpeed;
106 if (nextCounter <= lastCounter)
107 return;
108
109 // apply steps
110 for (uint32_t I = 0; I < nextCounter - lastCounter; ++I)
111 {
112 // increment iff possible
113 if (isForward && (!rampSaturates || rampValue < 255))
114 rampValue += 1;
115
116 // decrement iff possible
117 if (!isForward && (!rampSaturates || rampValue > 0))
118 rampValue -= 1;
119
120 // forward value to callback
121 callback(rampValue);
122 }
123
124 lastTimeMeasured = holdTime;
125 }
126
127 uint32_t stepSpeed;
128 bool rampSaturates;
129 bool animEffect;
130 bool animChoice;
131 uint32_t lastTimeMeasured;
132 bool isForward;
133};
134
138template<typename Config, typename AllGroups, uint8_t hiddenGroupsCount> struct ModeManagerTy
139{
140 using SelfTy = ModeManagerTy<Config, AllGroups, hiddenGroupsCount>;
141 using ConfigTy = Config;
142 using AllGroupsTy = AllGroups;
143 using AllStatesTy = details::StateTyFrom<AllGroups>;
144
146 static constexpr uint8_t nbGroupsTotal {std::tuple_size_v<AllGroupsTy>};
147 static constexpr uint8_t hiddenGroupCount = hiddenGroupsCount;
148
149 static_assert(nbGroupsTotal >= hiddenGroupsCount, "Manager cannot operate without accessible groups");
150
152 static constexpr uint8_t nbAccessibleGroups = nbGroupsTotal - hiddenGroupsCount;
153
154 // last group index must not collide with modes::store::noGroupIndex
155 static_assert(nbGroupsTotal <= 15, "Maximum of 15 groups has been exceeded.");
156
157 template<uint8_t Idx> using GroupAt = std::tuple_element_t<Idx, AllGroupsTy>;
158
159 // required to support manager-level context
160 using HasAnyGroup = details::anyOf<AllGroupsTy>;
161 static constexpr bool hasSunsetAnimation = HasAnyGroup::hasSunsetAnimation;
162 static constexpr bool hasBrightCallback = HasAnyGroup::hasBrightCallback;
163 static constexpr bool requireUserThread = HasAnyGroup::requireUserThread;
164 static constexpr bool hasCustomRamp = HasAnyGroup::hasCustomRamp;
165 static constexpr bool hasSystemCallbacks = hasCustomRamp || HasAnyGroup::hasSystemCallbacks;
166 static constexpr bool hasButtonCustomUI = HasAnyGroup::hasButtonCustomUI;
167
168 // useful for runtime tests of mode properties
169 using EveryModeBool = details::asTableFor<AllGroupsTy>;
170 static constexpr auto everySunsetCallback = EveryModeBool::everySunsetCallback;
171 static constexpr auto everyBrightCallback = EveryModeBool::everyBrightCallback;
172 static constexpr auto everyRequireUserThread = EveryModeBool::everyRequireUserThread;
173 static constexpr auto everyCustomRamp = EveryModeBool::everyCustomRamp;
174 static constexpr auto everySystemCallbacks = EveryModeBool::everySystemCallbacks;
175 static constexpr auto everyButtonCustomUI = EveryModeBool::everyButtonCustomUI;
176
177 // constructors
178 ModeManagerTy(hardware::LampTy& lamp) : activeIndex {ActiveIndexTy::from(Config::initialActiveIndex)}, lamp {lamp} {}
179
180 ModeManagerTy() = delete;
181 ModeManagerTy(const ModeManagerTy&) = delete;
182 ModeManagerTy& operator=(const ModeManagerTy&) = delete;
183
185 auto get_context() { return ContextTy<SelfTy, SelfTy>(*this); }
186
188 template<typename CallBack> static void LMBD_INLINE dispatch_group(auto& ctx, CallBack&& cb)
189 {
190 uint8_t groupId = ctx.get_active_group(nbGroupsTotal);
191
192 details::unroll<nbGroupsTotal>([&](auto Idx) LMBD_INLINE {
193 if (Idx == groupId)
194 {
195 cb(context_as<GroupAt<Idx>>(ctx));
196 }
197 });
198 }
199
201 template<bool systemCallbacksOnly, typename CallBack> static void LMBD_INLINE foreach_group(auto& ctx, CallBack&& cb)
202 {
203 if constexpr (systemCallbacksOnly)
204 {
205 details::unroll<nbGroupsTotal>([&](auto Idx) LMBD_INLINE {
206 using GroupHere = GroupAt<Idx>;
207 constexpr bool hasCallbacks = GroupHere::hasSystemCallbacks;
208
209 if constexpr (hasCallbacks)
210 {
211 cb(context_as<GroupAt<Idx>>(ctx));
212 }
213 });
214 }
215 else
216 {
217 details::unroll<nbGroupsTotal>([&](auto Idx) LMBD_INLINE {
218 cb(context_as<GroupAt<Idx>>(ctx));
219 });
220 }
221 }
222
223 //
224 // store
225 //
226
227 // persistent values
228 enum class Store : uint16_t
229 {
230 lastActive,
231 modeMemory,
232 usedFavoriteCount,
233 favoriteModes,
234 lastUsedFavorite,
235 isInFavoriteGroup
236 };
237
238 static constexpr uint32_t storeId = modes::store::derivateStoreId<modes::store::hash("ManagerStoreId"), AllGroupsTy>;
239
240 //
241 // state
242 //
243
244 struct StateTy
245 {
247 AllStatesTy groupStates;
248
250 std::array<uint8_t, nbGroupsTotal> lastModeMemory = {};
251
253 static constexpr uint8_t maxFavoriteCount = 8;
255 std::array<ActiveIndexTy, maxFavoriteCount> favorites = {};
256 uint8_t usedFavoriteCount = 0;
257
258 static_assert(maxFavoriteCount < 16, "Maximum of 15 favorite as been exceeded");
259
260 // (variables for pending favorite state machine)
261 uint8_t isFavoritePending = 0;
263 bool isInDeleteFavorite = false;
265 uint8_t lastFavoriteStep = 0;
270
272 uint32_t lastScrollStopped = 0;
273
275 RampHandlerTy<Config> rampHandler = {Config::defaultCustomRampStepSpeedMs};
277 RampHandlerTy<Config> scrollHandler = {Config::scrollRampStepSpeedMs};
279 bool clearStripOnModeChange = Config::defaultClearStripOnModeChange;
280
281 // special effects
283
284 // inside lamp.config
285 // - skipFirstLedsForEffect = 0; // should the loop skip some lower LEDs?
286 // - skipFirstLedsForAmount = 0; // how many pixels to shave from the top?
287
289 static void LMBD_INLINE before_enter_mode(auto& ctx)
290 {
291 auto& self = ctx.state;
292 self.rampHandler.reset();
293 self.clearStripOnModeChange = Config::defaultClearStripOnModeChange;
294 }
295
297 static void LMBD_INLINE after_enter_mode(auto& ctx)
298 {
299 auto& self = ctx.state;
300 if (self.clearStripOnModeChange)
301 {
302 ctx.lamp.clear();
303 }
304 }
305 };
306
308 template<typename Group> StateTyOf<Group>* LMBD_INLINE getStateGroupOf()
309 {
310 using StateTy = StateTyOf<Group>;
311 using OptionalTy = std::optional<StateTy>;
312
313 StateTy* substate = nullptr;
314 details::unroll<nbGroupsTotal>([&](auto Idx) LMBD_INLINE {
315 using GroupHere = GroupAt<Idx>;
316 constexpr bool IsHere = std::is_same_v<GroupHere, Group>;
317
318 if constexpr (IsHere)
319 {
320 OptionalTy& opt = std::get<OptionalTy>(state.groupStates);
321 if (!opt.has_value())
322 {
323 opt.emplace();
324 }
325
326 StateTy& stateHere = *opt;
327 substate = &stateHere;
328 }
329 });
330 assert(substate != nullptr && "this should not have compiled at all!");
331
332 static_assert(details::ModeBelongsTo<Group, AllGroups>);
333 return substate;
334 }
335
337 template<typename Mode> StateTyOf<Mode>& LMBD_INLINE getStateOf()
338 {
339 using TargetStateTy = StateTyOf<Mode>;
340
341 // Mode is unknown / as no state, return placeholder
342 if constexpr (std::is_same_v<TargetStateTy, NoState>)
343 {
344 return placeholder;
345
346 // Mode is ManagerTy, return our own state
347 }
348 else if constexpr (std::is_same_v<TargetStateTy, StateTy>)
349 {
350 return state;
351
352 // Mode is GroupTy, return the state of the group
353 }
354 else if constexpr (details::GroupBelongsTo<Mode, AllGroups>)
355 {
356 TargetStateTy* substate = getStateGroupOf<Mode>();
357
358 if (substate == nullptr)
359 {
360 substate = (TargetStateTy*)&placeholder;
361 assert(false && "this code should be unreachable, but is it?");
362 }
363
364 return *substate;
365 }
366 else
367 {
368 static_assert(details::ModeExists<Mode, AllGroups>);
369
370 // Mode is somewhere in a group, search for it, return its state
371 TargetStateTy* substate = nullptr;
372
373 details::unroll<nbGroupsTotal>([&](auto Idx) LMBD_INLINE {
374 using Group = GroupAt<Idx>;
375 using AllModes = typename Group::AllModesTy;
376 if constexpr (details::ModeBelongsTo<Mode, AllModes>)
377 {
378 substate = Group::template getStateOf<Mode>(*this);
379 }
380 });
381 assert(substate != nullptr && "this should not have compiled at all!");
382
383 if (substate == nullptr)
384 {
385 substate = (TargetStateTy*)&placeholder;
386 assert(false && "this code should be unreachable, but is it?");
387 }
388
389 return *substate;
390 }
391 }
392
393 //
394 // navigation
395 //
396
397 static constexpr bool isGroupManager = true;
398 static constexpr bool isModeManager = true;
399
401 static void next_group(auto& ctx)
402 {
403 // change current group: Limited to accessible groups
404 uint8_t groupIdBefore = ctx.get_active_group(nbAccessibleGroups);
405 ctx.set_active_group(groupIdBefore + 1, nbAccessibleGroups);
406 }
407
409 static void next_mode(auto& ctx)
410 {
411 dispatch_group(ctx, [](auto group) {
412 group.next_mode();
413 });
414 }
415
417 static void jump_to_new_active_index(auto& ctx, const ActiveIndexTy& newActiveIndex)
418 {
419 // not limited to accessible groups
420 ctx.set_active_group(newActiveIndex.groupIndex, nbGroupsTotal);
421 ctx.set_active_mode(newActiveIndex.modeIndex);
422 // just copy the other values
423 ctx.modeManager.activeIndex.customIndex = newActiveIndex.customIndex;
424 ctx.modeManager.activeIndex.rampIndex = newActiveIndex.rampIndex;
425
426 // if ramps loaded in enter_mode != ones saved as favorite, emulate update
427 if (ctx.modeManager.activeIndex.rawIndex != newActiveIndex.rawIndex)
428 {
429 ctx.modeManager.activeIndex = newActiveIndex;
430 custom_ramp_update(ctx, ctx.get_active_custom_ramp());
431 }
432 }
433
441 static bool jump_to_favorite(auto& ctx, uint8_t which_one, bool shouldSaveLastActiveIndex)
442 {
443 // sanity check
444 if (ctx.state.usedFavoriteCount <= 0)
445 return false;
446
447 // wrap back to max number of favorites
448 which_one = (which_one % ctx.state.usedFavoriteCount);
449 ctx.state.lastFavoriteStep = which_one;
450
451 // store last active index before jump
452 if (shouldSaveLastActiveIndex)
453 {
454 ctx.state.beforeFavoriteGroupIndex = ctx.modeManager.activeIndex.groupIndex;
455 ctx.state.beforeFavoriteModeIndex = ctx.modeManager.activeIndex.modeIndex;
456 }
457
458 if (which_one >= ctx.state.maxFavoriteCount)
459 return false;
460
461 const auto targetFavorite = ctx.state.favorites[which_one];
462 // reset once with the right mode
463 jump_to_new_active_index(ctx, targetFavorite);
464
465 // show which favorite is currently set
466 overlay.clear();
467 display_favorite_number_ramp(ctx, which_one, ctx.state.usedFavoriteCount, true, 1000);
468
469 return true;
470 }
471
478 static bool set_favorite_now(auto& ctx, uint8_t which_one = 0)
479 {
480 // new favorite added
481 if (which_one != ctx.state.maxFavoriteCount && which_one == ctx.state.usedFavoriteCount)
482 {
483 // augment favorite count until we reach the max
484 ctx.state.usedFavoriteCount = std::min<uint8_t>(ctx.state.usedFavoriteCount + 1, ctx.state.maxFavoriteCount);
485 }
486
487 if (which_one < ctx.state.maxFavoriteCount)
488 {
489 ctx.state.favorites[which_one] = ctx.modeManager.activeIndex;
490 return true;
491 }
492 return false;
493 }
494
500 static bool delete_favorite_now(auto& ctx)
501 {
502 // delete current
503 const auto which_one = ctx.state.lastFavoriteStep;
504 if (ctx.state.usedFavoriteCount > 0 and which_one < ctx.state.maxFavoriteCount)
505 {
506 ctx.state.usedFavoriteCount -= 1;
507
508 for (uint8_t i = which_one; i < ctx.state.maxFavoriteCount - 1; ++i)
509 {
510 // make them all go down one spot
511 ctx.state.favorites[i] = ctx.state.favorites[i + 1];
512 }
513 // changed favorite index, jump
514 if (ctx.state.usedFavoriteCount > 0)
515 {
516 jump_to_new_active_index(ctx, ctx.state.favorites[which_one % ctx.state.usedFavoriteCount]);
517 }
518 else
519 {
520 // no more favorite, restore last used
521
522 // reset favorite indicator
523 ctx.state.isInFavoriteMockGroup = false;
524 // return to previous state
525 ctx.set_active_group(ctx.state.beforeFavoriteGroupIndex);
526 ctx.set_active_mode(ctx.state.beforeFavoriteModeIndex);
527
528 // blip to indicate favorite mode exit
529 ctx.blip(250);
530 }
531 return true;
532 }
533 return false;
534 }
535
541 static void handle_scroll_modes(auto& ctx, uint32_t holdDuration)
542 {
543 auto& scrollHandler = ctx.state.scrollHandler;
544 scrollHandler.isForward = false; // (always scroll modes backward)
545
546 static constexpr uint32_t scrollActivationTiming = 750;
547 if (holdDuration <= scrollActivationTiming)
548 {
549 // display the ramp and do nothing else
550 overlay_animate_ramp(
551 ctx, holdDuration, scrollActivationTiming, colors::PaletteGradient<colors::White, colors::Cyan>);
552 return;
553 }
554
555 scrollHandler.update_ramp(128, holdDuration, [&](uint8_t rampValue) {
556 uint8_t modeIndex = ctx.get_active_mode();
557 uint8_t groupIndex = ctx.get_active_group();
558 uint8_t modeCount = ctx.get_modes_count();
559
560 ctx.state.isLastScrollAGroupChange = false;
561
562 // we are going backward
563 //
564 if (rampValue < 128)
565 {
566 // if modeIndex is not the first, just decrement it
567 if (modeIndex > 0)
568 {
569 ctx.set_active_mode(modeIndex - 1, modeCount);
570
571 // or else decrement group, then set mode to last one
572 }
573 else
574 {
575 ctx.state.isLastScrollAGroupChange = true;
576 // if groupIndex is not the first, just decrement it
577 if (groupIndex > 0)
578 {
579 // can only access visible modes
580 ctx.set_active_group(groupIndex - 1, nbAccessibleGroups);
581
582 // else wrap to last group
583 }
584 else
585 {
586 // can only access visible modes
587 ctx.set_active_group(nbAccessibleGroups - 1, nbAccessibleGroups);
588 }
589
590 // backward scroll: set mode to last one on group change
591 modeCount = ctx.get_modes_count();
592 ctx.set_active_mode(modeCount - 1, modeCount);
593 }
594
595 // we are going forward
596 //
597 }
598 else
599 {
600 // if modeIndex is not the last, just increment it
601 if (modeIndex + 1 < modeCount)
602 {
603 ctx.next_mode();
604
605 // or else increment group
606 }
607 else
608 {
609 ctx.state.isLastScrollAGroupChange = true;
610 // if groupIndex is not the last, just increment it
611 if (groupIndex + 1 < nbAccessibleGroups)
612 {
613 ctx.next_group();
614
615 // else wrap to first group
616 }
617 else
618 {
619 ctx.set_active_group(0, nbAccessibleGroups);
620 }
621
622 // forward scroll: set mode to first one on group change
623 ctx.set_active_mode(0, modeCount);
624 }
625 }
626 });
627 }
628
634 static void enter_hidden_group(auto& ctx, uint8_t index)
635 {
636 assert(index < hiddenGroupsCount);
637 ctx.set_active_group(nbAccessibleGroups + index, nbGroupsTotal);
638 }
639
641 static bool overlay_animate_ramp(
642 auto& ctx, float holdDuration, float stepSize, const colors::PaletteTy& palette, const uint32_t timeout = 0)
643 {
644 // where we are: 0-255 rampColorRing
645 const uint32_t stepProgress = floor((holdDuration * 256.0) / stepSize);
646 return overlay_animate_ramp(ctx, stepProgress % 256, palette);
647 }
648 static bool overlay_animate_ramp(auto& ctx,
649 uint8_t progress,
650 const colors::PaletteTy& palette,
651 const uint32_t timeout = 0)
652 {
653 // only display on indexable
654 if constexpr (ctx.lamp.flavor == hardware::LampTypes::indexable)
655 {
656 // if first display failed, add a new ramp and try again
657 if (not overlay.update_type(ctx, draw::overlay::ElementType::RAMP, 0, progress, palette))
658 {
659 // add new ramp element
660 overlay.add_ui_element(ctx, draw::overlay::ElementType::RAMP, palette, 0, 0, progress);
661 }
662
663 // if timeout is requested, update it
664 if (timeout > 0)
665 overlay.update_type_timeout(ctx, draw::overlay::ElementType::RAMP, 0, timeout);
666 }
667 return (progress >= 250);
668 }
669
671 static void overlay_animate_dot(auto& ctx,
672 uint16_t index,
673 uint16_t positionX,
674 uint8_t progress,
675 const auto& palette,
676 const uint32_t timeout = 0)
677 {
678 // only display on indexable
679 if constexpr (ctx.lamp.flavor == hardware::LampTypes::indexable)
680 {
681 // if first display failed, add a new ramp and try again
682 if (not overlay.update_type(ctx, draw::overlay::ElementType::DOT, index, progress, palette))
683 {
684 // add new element
685 overlay.add_ui_element(ctx, draw::overlay::ElementType::DOT, palette, positionX, 0, progress);
686 }
687
688 // if timeout is requested, update it
689 if (timeout > 0)
690 overlay.update_type_timeout(ctx, draw::overlay::ElementType::DOT, index, timeout);
691 }
692 }
693
695 static uint8_t get_modes_count(auto& ctx)
696 {
697 uint8_t value = 0;
698 dispatch_group(ctx, [&](auto group) {
699 value = decltype(group)::LocalModeTy::nbModes;
700 });
701 return value;
702 }
703
705 static void enter_group(auto& ctx, const uint8_t value)
706 {
707 // prevent value overflow
708 assert(value < nbGroupsTotal);
709
710 auto manager = ctx.modeManager.get_context();
711
712 // signal that we are quitting the mode
713 ctx.modeManager.quit_mode(manager);
714
715 // switch group (after quit mode)
716 ctx.modeManager.activeIndex.groupIndex = value;
717 // switch mode (restore last stored id)
718 ctx.modeManager.activeIndex.modeIndex = ctx.state.lastModeMemory[value];
719
720 // signal that we entered a new mode
721 ctx.modeManager.enter_mode(manager);
722 }
723
725 static void quit_group(auto& ctx)
726 {
727 //
728 uint8_t modeIdBefore = ctx.get_active_mode();
729
730 // changes to lastModeMemory made in this function will be persistent
731 auto keyModeMemory = ctx.template storageFor<Store::modeMemory>(ctx.state.lastModeMemory);
732
733 // save last mode used in group, before switching
734 uint8_t groupIdBefore = ctx.get_active_group(nbGroupsTotal);
735 ctx.state.lastModeMemory[groupIdBefore] = modeIdBefore;
736 }
737
739 static void enter_mode(auto& ctx)
740 {
741 ctx.state.before_enter_mode(ctx);
742
743 // enter mode
744 dispatch_group(ctx, [](auto group) {
745 group.enter_mode();
746 });
747
748 ctx.state.after_enter_mode(ctx);
749 }
750
752 static void quit_mode(auto& ctx)
753 {
754 dispatch_group(ctx, [](auto group) {
755 group.quit_mode();
756 });
757 }
758
759 //
760 // all the callbacks
761 //
762
764 static void loop(auto& ctx)
765 {
766 // handle pending favorite
767 if (ctx.state.isFavoritePending > 0)
768 {
769 ctx.state.isFavoritePending -= 1;
770
771 if (ctx.state.isFavoritePending == 0 && ctx.state.whichFavoritePending <= ctx.state.usedFavoriteCount)
772 {
773 if (ctx.set_favorite_now(ctx.state.whichFavoritePending))
774 {
775 logic::alerts::manager.raise(logic::alerts::Type::FAVORITE_SET);
776 }
777 }
778 }
779
780 // handle favorite delete
781 if (ctx.state.isFavoriteDeletePending > 0)
782 {
783 ctx.state.isFavoriteDeletePending -= 1;
784
785 // delete favorite
786 if (ctx.state.isFavoriteDeletePending == 0)
787 {
788 ctx.delete_favorite_now();
789 }
790 }
791
792 // handle the sunset timer update
793 if (ctx.state.isSunsetTimingPending > 0)
794 {
795 ctx.state.isSunsetTimingPending -= 1;
796 if (ctx.state.isSunsetTimingPending == 0)
797 {
798 // set and update sunset timer
800 // blip AFTER the update
801 ctx.blip(50);
802 }
803 }
804
805 if (ctx.lamp.config.skipFirstLedsForEffect > 0)
806 {
807 ctx.lamp.config.skipFirstLedsForEffect -= 1;
808 }
809
810 if (ctx.state.skipNextFrameEffect > 0)
811 {
812 ctx.state.skipNextFrameEffect -= 1;
813
814 // reached last skip frame, restore mode
815 if (ctx.state.skipNextFrameEffect == 0)
816 {
817 ctx.lamp.restoreBrightness();
818 }
819 return;
820 }
821
822 ctx.lamp.refresh_tick_value();
823
824 // udpate modes and groups
825 dispatch_group(ctx, [](auto group) {
826 group.loop();
827 });
828
829 // display the overlay after the group update
830 overlay.display_update(ctx);
831 }
832
838 static void sunset_update(auto& ctx, float progress)
839 {
840 dispatch_group(ctx, [&](auto group) {
841 group.sunset_update(progress);
842 });
843 }
844
850 static void brightness_update(auto& ctx, brightness_t brightness)
851 {
852 dispatch_group(ctx, [&](auto group) {
853 group.brightness_update(brightness);
854 });
855 }
856
858 static void power_on_sequence(auto& ctx)
859 {
860 foreach_group<true>(ctx, [](auto group) {
861 group.power_on_sequence();
862 });
863
864 // activate last used favorite, in the favorite group
865 if (ctx.state.isInFavoriteMockGroup && jump_to_favorite(ctx, ctx.state.lastFavoriteStep, false))
866 {
867 // success jump to favorite
868 }
869 else
870 {
871 // activate current mode
872 uint8_t groupIdBefore = ctx.get_active_group(nbGroupsTotal);
873 ctx.set_active_group(groupIdBefore);
874 }
875 }
876
878 static void power_off_sequence(auto& ctx)
879 {
880 foreach_group<true>(ctx, [](auto group) {
881 group.power_off_sequence();
882 });
883 }
884
886 static void write_parameters(auto& ctx)
887 {
888 // clear the stored parameters, before storing ours.
890
891 // this scope is the only one where parameters will be kept
892 ctx.template storageSaveOnly<Store::lastActive>(ctx.modeManager.activeIndex);
893
894 // save the maxFavoriteCount possible favorites
895 ctx.template storageSaveOnly<Store::usedFavoriteCount>(ctx.modeManager.state.usedFavoriteCount);
896 ctx.template storageSaveOnly<Store::favoriteModes>(ctx.modeManager.state.favorites);
897 ctx.template storageSaveOnly<Store::lastUsedFavorite>(ctx.modeManager.state.lastFavoriteStep);
898 ctx.template storageSaveOnly<Store::isInFavoriteGroup>(ctx.state.isInFavoriteMockGroup);
899 ctx.template storageSaveOnly<Store::modeMemory>(ctx.state.lastModeMemory);
900
901 foreach_group<not hasCustomRamp>(ctx, [&ctx](auto group) {
902 if constexpr (group.hasCustomRamp)
903 {
904 using StoreHere = typename decltype(group)::StoreEnum;
905 group.template storageSaveOnly<StoreHere::rampMemory>(group.state.customRampMemory);
906 group.template storageSaveOnly<StoreHere::indexMemory>(group.state.customIndexMemory);
907 }
908
909 group.write_parameters();
910 });
911 }
912
914 static void read_parameters(auto& ctx)
915 {
916 // remove old filesystem data if we detect obsolete "storeId" serial
917 using LocalStore = details::LocalStoreOf<decltype(ctx)>;
918 LocalStore::template migrateStoreIfNeeded<storeId>();
919
920 // load last active mode
921 ctx.template storageLoadOnly<Store::lastActive>(ctx.modeManager.activeIndex);
922
923 // load the maxFavoriteCount possible favorites
924 ctx.template storageLoadOnly<Store::usedFavoriteCount>(ctx.modeManager.state.usedFavoriteCount);
925 ctx.template storageLoadOnly<Store::favoriteModes>(ctx.state.favorites);
926 ctx.template storageLoadOnly<Store::lastUsedFavorite>(ctx.modeManager.state.lastFavoriteStep);
927 ctx.template storageLoadOnly<Store::isInFavoriteGroup>(ctx.state.isInFavoriteMockGroup);
928 ctx.template storageLoadOnly<Store::modeMemory>(ctx.state.lastModeMemory);
929
930 // for each group, migrate & handle custom ramp memory
931 foreach_group<not hasCustomRamp>(ctx, [&ctx](auto group) {
932 using LocalStore = details::LocalStoreOf<decltype(group)>;
933 LocalStore::template migrateStoreIfNeeded<storeId>();
934
935 if constexpr (group.hasCustomRamp)
936 {
937 using StoreHere = typename LocalStore::EnumTy;
938 group.template storageLoadOnly<StoreHere::rampMemory>(group.state.customRampMemory);
939 group.template storageLoadOnly<StoreHere::indexMemory>(group.state.customIndexMemory);
940 }
941
942 group.read_parameters();
943 });
944 }
945
947 static void user_thread(auto& ctx)
948 {
949 dispatch_group(ctx, [](auto group) {
950 group.user_thread();
951 });
952 }
953
960 static void custom_ramp_update(auto& ctx, uint8_t rampValue, uint32_t timeout = 0)
961 {
962 uint8_t groupId = ctx.get_active_group();
963 uint8_t modeId = ctx.get_active_mode();
964
965 if (ctx.everyCustomRamp[groupId][modeId] && ctx.state.rampHandler.animEffect)
966 {
967 switch (ctx.state.rampHandler.animChoice)
968 {
969 case 0:
970 overlay_animate_ramp(ctx, rampValue, modes::colors::PaletteBlackBodyColors, timeout);
971 break;
972
973 case 1:
974 overlay_animate_ramp(ctx, rampValue, modes::colors::PaletteRainbowColors, timeout);
975 break;
976 }
977 }
978
979 dispatch_group(ctx, [&](auto group) {
980 group.custom_ramp_update(rampValue);
981 });
982 }
983
985 static bool custom_click(auto& ctx, uint8_t nbClick)
986 {
987 bool retVal = false;
988 dispatch_group(ctx, [&](auto group) {
989 retVal = group.custom_click(nbClick);
990 });
991 return retVal;
992 }
993
995 static bool custom_hold(auto& ctx, uint8_t nbClickAndHold, bool isEndOfHoldEvent, uint32_t holdDuration)
996 {
997 bool retVal = false;
998 dispatch_group(ctx, [&](auto group) {
999 retVal = group.custom_hold(nbClickAndHold, isEndOfHoldEvent, holdDuration);
1000 });
1001 return retVal;
1002 }
1003
1004 //
1005 // Private action functions
1006 //
1007
1016 static void display_favorite_number_ramp(auto& ctx,
1017 const uint8_t favoriteIndex,
1018 const uint8_t maxFavoriteIndex,
1019 const bool display = false,
1020 const uint32_t timeout = 0)
1021 {
1022 const uint8_t maxPixelDisplay = std::min<uint8_t>(ctx.state.maxFavoriteCount, maxFavoriteIndex);
1023 for (uint8_t i = 0; i < maxPixelDisplay; ++i)
1024 {
1025 const bool shouldDisplay = (display and i <= favoriteIndex);
1026 overlay_animate_dot(
1027 ctx, i, i, shouldDisplay ? 255 : 0, colors::PaletteGradient<colors::Black, colors::Cyan>, timeout);
1028 }
1029 }
1030
1037 template<bool displayFavoriteNumber = true>
1038 static void animate_favorite_pick(auto& ctx, float holdDuration, float stepSize)
1039 {
1040 // user as a number of favorite set
1041 // occasional +1 if not all favorite are set (allow a new favorite)
1042 const uint8_t numberOfFavoriteSet =
1043 ctx.state.usedFavoriteCount + ((ctx.state.usedFavoriteCount < ctx.state.maxFavoriteCount) ? 1 : 0);
1044
1045 // up to maxFavoriteCount step state: "which_one" is [0, 1, 2, 3, ...] and "do not set" is the max index + 1
1046 uint32_t stepCount = numberOfFavoriteSet + floor(holdDuration / stepSize);
1047 stepCount = stepCount % (numberOfFavoriteSet + 1);
1048
1049 // display ramp to show where user is standing
1050 if (stepCount >= numberOfFavoriteSet)
1051 {
1052 // cancel action
1053 ctx.state.isFavoritePending = 0;
1054
1055 // display ramp for the first time to allow the user to cancel the action
1056 if (holdDuration <= 2 * stepSize)
1057 {
1058 overlay_animate_ramp(ctx, holdDuration, stepSize, colors::PaletteGradient<colors::White, colors::Cyan>);
1059 }
1060 }
1061 else
1062 {
1063 // green ramp : favorites
1064 overlay_animate_ramp(ctx, holdDuration, stepSize, colors::PaletteGradient<colors::Green, colors::White>);
1065
1066 // extra display on the first pixels (count pixels to know fav no)
1067 if constexpr (displayFavoriteNumber)
1068 {
1069 // display the set favorite ramp
1070 display_favorite_number_ramp(ctx, stepCount, numberOfFavoriteSet, stepCount < numberOfFavoriteSet);
1071 }
1072
1073 // set this, after a while upon no longer holding button, favorite is set
1074 ctx.state.isFavoritePending = 10;
1075 ctx.state.whichFavoritePending = stepCount;
1076 }
1077 }
1078
1085 template<bool displayFavoriteNumber = true>
1086 static void animate_favorite_delete(auto& ctx, float holdDuration, float stepSize)
1087 {
1088 // no favorite deletion if no favorites
1089 if (ctx.state.usedFavoriteCount <= 0)
1090 return;
1091
1092 ctx.state.isInDeleteFavorite = true;
1093
1094 uint32_t stepCount = floor(holdDuration / stepSize);
1095 stepCount = stepCount % 2;
1096
1097 if (stepCount == 0)
1098 {
1099 if (overlay_animate_ramp(ctx, holdDuration, stepSize, colors::PaletteGradient<colors::Orange, colors::Red>))
1100 {
1101 // set this on ramp saturation. The favorite will be removed in 2 frames
1102 ctx.state.isFavoriteDeletePending = 2;
1103 }
1104 else
1105 {
1106 // no deletion if release on ramp
1107 ctx.state.isFavoriteDeletePending = 0;
1108 }
1109
1110 // extra display on the first pixels (count pixels to know fav no)
1111 if constexpr (displayFavoriteNumber)
1112 {
1113 // display ramp
1114 display_favorite_number_ramp(ctx, ctx.state.lastFavoriteStep, ctx.state.usedFavoriteCount, true);
1115 }
1116 }
1117 // else: do nothing
1118 }
1119
1120 //
1121 // members with direct access
1122 //
1123
1125 ActiveIndexTy activeIndex;
1126
1128 hardware::LampTy& lamp;
1129
1131 inline static draw::overlay::Manager<> overlay;
1132
1133 //
1134 // private members
1135 //
1136
1137private:
1139 NoState placeholder;
1141 StateTy state;
1142};
1143
1148template<typename ManagerConfig, typename... Groups> using ManagerForConfig =
1149 ModeManagerTy<ManagerConfig, std::tuple<Groups...>, 0>;
1150
1159template<typename... Groups> using ManagerFor = ModeManagerTy<DefaultManagerConfig, std::tuple<Groups...>, 0>;
1160
1170template<uint8_t hiddenGroupCnt, typename... Groups> using ManagerForHiddenGroups =
1171 ModeManagerTy<DefaultManagerConfig, std::tuple<Groups...>, hiddenGroupCnt>;
1172
1177template<uint8_t hiddenGroupCnt, typename ManagerConfig, typename... Groups> using ManagerFoHiddenConfig =
1178 ModeManagerTy<ManagerConfig, std::tuple<Groups...>, hiddenGroupCnt>;
1179
1180} // namespace lampda::modes
1181
1182#endif
Handle the main alerts behavior.
Define assertions helpers.
void raise(const Type type)
Raise an alert.
Definition: alerts.cpp:858
ContextTy and associated definitions.
Filesystem interaction tools.
Define the main led strip interaction object.
AlertManager_t manager
Instanciation of the AlertManager.
Definition: alerts.cpp:27
void add_time_minutes(const uint8_t time_minutes)
add some time to the sunset timer. Limited to 10 minutes
Definition: sunset_timer.cpp:124
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.
static void clear_stored()
Force clear the stored parameters.
Definition: keystore.hpp:74
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:1178
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:1171
ModeManagerTy< ManagerConfig, std::tuple< Groups... >, 0 > ManagerForConfig
Same as modes::ManagerFor but with custom defaults.
Definition: manager_type.hpp:1149
@ 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:1159
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:56
void user_thread()
Called at each tick of the secondary thread.
Definition: default_behavior.hpp:159
void read_parameters()
Called when system wants to read parameters from filesystem.
Definition: default_behavior.hpp:93
void power_on_sequence()
Called when the system powers on (must be non blocking function!)
Definition: default_behavior.hpp:31
void write_parameters()
Called when system wants to write parameters to filesystem.
Definition: default_behavior.hpp:87
void power_off_sequence()
Called when the system powers off (must be non blocking function!)
Definition: default_behavior.hpp:42
void loop()
Called at each tick of the main loop.
Definition: default_behavior.hpp:139
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:245
RampHandlerTy< Config > scrollHandler
Ramp handlers: mode scroll ramp.
Definition: manager_type.hpp:277
bool clearStripOnModeChange
Should clear the strip before switching mode.
Definition: manager_type.hpp:279
RampHandlerTy< Config > rampHandler
Ramp handlers: custom ramp (or "color ramp")
Definition: manager_type.hpp:275
bool isInFavoriteMockGroup
Indicates that we are in the fake favorite page.
Definition: manager_type.hpp:266
uint8_t beforeFavoriteModeIndex
store the mode index we need to go to when quitting the favorite page
Definition: manager_type.hpp:268
uint8_t lastFavoriteStep
last used favorite index
Definition: manager_type.hpp:265
AllStatesTy groupStates
All group states, containing all modes individual states.
Definition: manager_type.hpp:247
uint8_t isFavoriteDeletePending
indicate that the deletion of a favorite in in process
Definition: manager_type.hpp:264
uint8_t beforeFavoriteGroupIndex
store the group index we need to go to when quitting the favorite page
Definition: manager_type.hpp:267
uint8_t usedFavoriteCount
number of favorite set by user [0, maxFavoriteCount]
Definition: manager_type.hpp:256
bool isLastScrollAGroupChange
last mode change in scroll changed group
Definition: manager_type.hpp:271
uint8_t whichFavoritePending
indicates the favorite currently selected
Definition: manager_type.hpp:262
uint32_t lastScrollStopped
keep track of the last scrool release time
Definition: manager_type.hpp:272
static void LMBD_INLINE before_enter_mode(auto &ctx)
configuration-related actions done before entering mode
Definition: manager_type.hpp:289
uint8_t isSunsetTimingPending
Indicates that a sunset timer ramp is active.
Definition: manager_type.hpp:269
std::array< ActiveIndexTy, maxFavoriteCount > favorites
Store the active index of every favorite.
Definition: manager_type.hpp:255
uint8_t skipNextFrameEffect
should the next .loop() mode be skipped?
Definition: manager_type.hpp:282
std::array< uint8_t, nbGroupsTotal > lastModeMemory
When switching group, remember which mode was on last time we visited it.
Definition: manager_type.hpp:250
static constexpr uint8_t maxFavoriteCount
Maximum allowed favorite count.
Definition: manager_type.hpp:253
static void LMBD_INLINE after_enter_mode(auto &ctx)
configuration-related actions done after mode entering
Definition: manager_type.hpp:297
uint8_t isFavoritePending
indicate that the addition of a favorite in in process
Definition: manager_type.hpp:261
bool isInDeleteFavorite
indicates that we are in a favorite deletion process
Definition: manager_type.hpp:263
Logic of the sunset time, eg the system auto stops after a set delay.
Define templated tools to analyze the manager objects.