1
// channel-modes.c: Multi-channel functions for Anduril.
2
// Copyright (C) 2017-2023 Selene ToyKeeper
3
// SPDX-License-Identifier: GPL-3.0-or-later
7
#include "channel-modes.h"
9
uint8_t channel_mode_state(Event event, uint16_t arg) {
10
#ifdef USE_CHANNEL_MODE_ARGS
11
static int8_t tint_ramp_direction = 1;
12
static uint8_t prev_tint = 0;
13
// don't activate auto-tint modes unless the user hits the edge
14
// and keeps pressing for a while
15
static uint8_t past_edge_counter = 0;
16
// bugfix: click-click-hold from off to strobes would invoke tint ramping
17
// in addition to changing state... so ignore any tint-ramp events which
18
// don't look like they were meant to be here
19
static uint8_t active = 0;
20
uint8_t tint = cfg.channel_mode_args[channel_mode];
23
// it's possible that a light may need 3H but not 3C,
24
// so try to detect if 3C is needed
25
#if NUM_CHANNEL_MODES > 1
26
// 3 clicks: next channel mode
27
if (event == EV_3clicks) {
28
uint8_t next = channel_mode;
29
// go to next channel mode until we find one which is enabled
30
// (and don't do any infinite loops if the user disabled them all)
34
next = (next + 1) % NUM_CHANNEL_MODES;
35
} while ((! channel_mode_enabled(next)) && count < NUM_CHANNEL_MODES);
36
//} while ((! channel_modes_enabled[next]) && count < NUM_CHANNEL_MODES);
38
// undo change if infinite loop detected (redundant?)
39
//if (NUM_CHANNEL_MODES == count) next = channel_mode;
41
// if mode hasn't changed, abort
42
if (channel_mode == next)
43
return EVENT_NOT_HANDLED;
45
set_channel_mode(next);
47
// remember after battery changes
48
cfg.channel_mode = channel_mode;
52
#endif // if NUM_CHANNEL_MODES > 1
54
#ifdef USE_CUSTOM_CHANNEL_3H_MODES
55
// defer to mode-specific function if defined
56
if (channel_3H_modes[channel_mode]) {
57
StatePtr tint_func = channel_3H_modes[channel_mode];
58
uint8_t err = tint_func(event, arg);
59
if (EVENT_HANDLED == err) return EVENT_HANDLED;
60
// else let the default handler run
63
#ifdef USE_CHANNEL_MODE_ARGS
64
#ifndef DONT_USE_DEFAULT_CHANNEL_ARG_MODE
65
// click, click, hold: change the current channel's arg (like tint)
66
if (event == EV_click3_hold) {
67
///// adjust value from 0 to 255
68
// reset at beginning of movement
70
active = 1; // first frame means this is for us
71
past_edge_counter = 0; // doesn't start until user hits the edge
73
// ignore event if we weren't the ones who handled the first frame
74
if (! active) return EVENT_NOT_HANDLED;
76
#ifdef USE_STEPPED_TINT_RAMPING
77
if ((tint_ramp_direction > 0 && tint < 255) ||
78
(tint_ramp_direction < 0 && tint > 0)) {
79
// ramp slower in stepped mode
80
if (cfg.tint_ramp_style && (arg % HOLD_TIMEOUT != 0))
83
const uint8_t step_size = (cfg.tint_ramp_style < 2)
84
? 1 : 254 / (cfg.tint_ramp_style-1);
85
tint = nearest_tint_value(
86
tint + ((int16_t)step_size * tint_ramp_direction)
89
#else // smooth tint ramping only
90
if ((tint_ramp_direction > 0) && (tint < 255)) { tint ++; }
92
if ((tint_ramp_direction < 0) && (tint > 0)) { tint --; }
93
#endif // ifdef USE_STEPPED_TINT_RAMPING
95
// if tint change stalled, let user know we hit the edge
96
else if (prev_tint == tint) {
97
if (past_edge_counter == 0) blip();
98
past_edge_counter = 1;
101
cfg.channel_mode_args[channel_mode] = tint;
102
set_level(actual_level);
103
return EVENT_HANDLED;
106
// click, click, hold, release: reverse direction for next ramp
107
else if (event == EV_click3_hold_release) {
108
active = 0; // ignore next hold if it wasn't meant for us
110
tint_ramp_direction = -tint_ramp_direction;
111
if (0 == tint) tint_ramp_direction = 1;
112
else if (255 == tint) tint_ramp_direction = -1;
113
// remember tint after battery change
114
cfg.channel_mode_args[channel_mode] = tint;
116
// bug?: for some reason, brightness can seemingly change
117
// from 1/150 to 2/150 without this next line... not sure why
118
set_level(actual_level);
119
return EVENT_HANDLED;
121
#endif // ifndef DONT_USE_DEFAULT_CHANNEL_ARG_MODE
122
#endif // ifdef USE_CHANNEL_MODE_ARGS
124
#if defined(USE_SIMPLE_UI)
125
// remaining mappings aren't "simple", so stop here
126
if (cfg.simple_ui_active) {
127
return EVENT_NOT_HANDLED;
131
#if NUM_CHANNEL_MODES > 1
132
// channel toggle menu on ... 9H?
133
else if (event == EV_click9_hold) {
134
push_state(channel_mode_config_state, 0);
135
return EVENT_HANDLED;
139
return EVENT_NOT_HANDLED;
143
#if NUM_CHANNEL_MODES > 1
144
void channel_mode_config_save(uint8_t step, uint8_t value) {
145
// 1 menu item per channel mode, to enable or disable that mode
146
step --; // step is 1-based, channel modes are 0-based
147
if (value) channel_mode_enable(step);
148
else channel_mode_disable(step);
151
uint8_t channel_mode_config_state(Event event, uint16_t arg) {
153
// make config steps match channel modes
154
config_color_per_step = true;
155
// 1 menu item per channel mode, to enable or disable that mode
156
ret = config_state_base(
159
channel_mode_config_save
161
// no other menu needs this
162
config_color_per_step = false;
168
#if defined(USE_CHANNEL_MODE_ARGS) && defined(USE_STEPPED_TINT_RAMPING)
169
uint8_t nearest_tint_value(const int16_t target) {
170
// const symbols for more readable code, will be removed by the compiler
171
const uint8_t tint_min = 0;
172
const uint8_t tint_max = 255;
173
const uint8_t tint_range = tint_max - tint_min;
175
// only equal mix of both channels
176
if (1 == cfg.tint_ramp_style) return (tint_min + tint_max) >> 1;
178
if (target < tint_min) return tint_min;
179
if (target > tint_max) return tint_max;
180
if (0 == cfg.tint_ramp_style) return target; // smooth ramping
182
const uint8_t step_size = tint_range / (cfg.tint_ramp_style-1);
184
uint8_t tint_result = tint_min;
185
for (uint8_t i=0; i<cfg.tint_ramp_style; i++) {
186
tint_result = tint_min
187
+ (i * (uint16_t)tint_range / (cfg.tint_ramp_style-1));
188
int16_t diff = target - tint_result;
189
if (diff <= (step_size>>1)) return tint_result;
195
#ifdef USE_CIRCULAR_TINT_3H
196
uint8_t circular_tint_3h(Event event, uint16_t arg) {
197
static int8_t tint_ramp_direction = 1;
198
// bugfix: click-click-hold from off to strobes would invoke tint ramping
199
// in addition to changing state... so ignore any tint-ramp events which
200
// don't look like they were meant to be here
201
static uint8_t active = 0;
202
uint8_t tint = cfg.channel_mode_args[channel_mode];
204
// click, click, hold: change the current channel's arg (like tint)
205
if (event == EV_click3_hold) {
206
///// adjust value from 0 to 255 in a circle
207
// reset at beginning of movement
209
active = 1; // first frame means this is for us
211
// ignore event if we weren't the ones who handled the first frame
212
if (! active) return EVENT_NOT_HANDLED;
214
// smooth tint ramping only
215
tint += tint_ramp_direction;
217
cfg.channel_mode_args[channel_mode] = tint;
218
set_level(actual_level);
219
return EVENT_HANDLED;
222
// click, click, hold, release: reverse direction for next ramp
223
else if (event == EV_click3_hold_release) {
224
active = 0; // ignore next hold if it wasn't meant for us
226
tint_ramp_direction = -tint_ramp_direction;
227
// remember tint after battery change
229
// bug?: for some reason, brightness can seemingly change
230
// from 1/150 to 2/150 without this next line... not sure why
231
set_level(actual_level);
232
return EVENT_HANDLED;
235
return EVENT_NOT_HANDLED;