aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--spaghetti-monster/anduril/BRANDS9
-rw-r--r--spaghetti-monster/anduril/MODELS39
-rw-r--r--spaghetti-monster/anduril/Makefile15
-rw-r--r--spaghetti-monster/anduril/anduril-manual.txt646
-rw-r--r--spaghetti-monster/anduril/anduril.c2872
-rw-r--r--spaghetti-monster/anduril/aux-leds.c178
-rw-r--r--spaghetti-monster/anduril/aux-leds.h85
-rw-r--r--spaghetti-monster/anduril/battcheck-mode-fsm.h26
-rw-r--r--spaghetti-monster/anduril/battcheck-mode.c82
-rw-r--r--spaghetti-monster/anduril/battcheck-mode.h31
-rw-r--r--spaghetti-monster/anduril/beacon-mode.c73
-rw-r--r--spaghetti-monster/anduril/beacon-mode.h31
-rw-r--r--spaghetti-monster/anduril/candle-mode.c156
-rw-r--r--spaghetti-monster/anduril/candle-mode.h32
-rw-r--r--spaghetti-monster/anduril/cfg-blf-gt-mini.h2
-rw-r--r--spaghetti-monster/anduril/cfg-blf-gt.h12
-rw-r--r--spaghetti-monster/anduril/cfg-blf-lantern.h19
-rw-r--r--spaghetti-monster/anduril/cfg-blf-q8.h13
-rw-r--r--spaghetti-monster/anduril/cfg-emisar-d1.h6
-rw-r--r--spaghetti-monster/anduril/cfg-emisar-d18.h34
-rw-r--r--spaghetti-monster/anduril/cfg-emisar-d1s.h6
-rw-r--r--spaghetti-monster/anduril/cfg-emisar-d1v2.h6
-rw-r--r--spaghetti-monster/anduril/cfg-emisar-d4-219c.h2
-rw-r--r--spaghetti-monster/anduril/cfg-emisar-d4.h10
-rw-r--r--spaghetti-monster/anduril/cfg-emisar-d4s-219c.h2
-rw-r--r--spaghetti-monster/anduril/cfg-emisar-d4s.h19
-rw-r--r--spaghetti-monster/anduril/cfg-emisar-d4sv2-219.h2
-rw-r--r--spaghetti-monster/anduril/cfg-emisar-d4sv2.h20
-rw-r--r--spaghetti-monster/anduril/cfg-emisar-d4v2-219.h2
-rw-r--r--spaghetti-monster/anduril/cfg-emisar-d4v2-nofet.h10
-rw-r--r--spaghetti-monster/anduril/cfg-emisar-d4v2.h13
-rw-r--r--spaghetti-monster/anduril/cfg-ff-e01.h14
-rw-r--r--spaghetti-monster/anduril/cfg-ff-pl47-219.h2
-rw-r--r--spaghetti-monster/anduril/cfg-ff-pl47.h19
-rw-r--r--spaghetti-monster/anduril/cfg-ff-pl47g2.h13
-rw-r--r--spaghetti-monster/anduril/cfg-ff-rot66-219.h2
-rw-r--r--spaghetti-monster/anduril/cfg-ff-rot66.h9
-rw-r--r--spaghetti-monster/anduril/cfg-ff-rot66g2.h11
-rw-r--r--spaghetti-monster/anduril/cfg-fw3a-219.h2
-rw-r--r--spaghetti-monster/anduril/cfg-fw3a-nofet.h11
-rw-r--r--spaghetti-monster/anduril/cfg-fw3a.h9
-rw-r--r--spaghetti-monster/anduril/cfg-mateminco-mf01-mini.h14
-rw-r--r--spaghetti-monster/anduril/cfg-mateminco-mf01s.h14
-rw-r--r--spaghetti-monster/anduril/cfg-noctigon-k1-12v.h16
-rw-r--r--spaghetti-monster/anduril/cfg-noctigon-k1-sbt90.h20
-rw-r--r--spaghetti-monster/anduril/cfg-noctigon-k1.h16
-rw-r--r--spaghetti-monster/anduril/cfg-noctigon-kr4-219.h2
-rw-r--r--spaghetti-monster/anduril/cfg-noctigon-kr4-nofet.h16
-rw-r--r--spaghetti-monster/anduril/cfg-noctigon-kr4.h17
-rw-r--r--spaghetti-monster/anduril/cfg-sofirn-sp36.h6
-rw-r--r--spaghetti-monster/anduril/config-default.h160
-rw-r--r--spaghetti-monster/anduril/config-mode.c164
-rw-r--r--spaghetti-monster/anduril/config-mode.h32
-rw-r--r--spaghetti-monster/anduril/factory-reset-fsm.h28
-rw-r--r--spaghetti-monster/anduril/factory-reset.c71
-rw-r--r--spaghetti-monster/anduril/factory-reset.h26
-rw-r--r--spaghetti-monster/anduril/ff-strobe-modes.c81
-rw-r--r--spaghetti-monster/anduril/ff-strobe-modes.h33
-rw-r--r--spaghetti-monster/anduril/load-save-config-fsm.h89
-rw-r--r--spaghetti-monster/anduril/load-save-config.c152
-rw-r--r--spaghetti-monster/anduril/load-save-config.h31
-rw-r--r--spaghetti-monster/anduril/lockout-mode-fsm.h29
-rw-r--r--spaghetti-monster/anduril/lockout-mode.c198
-rw-r--r--spaghetti-monster/anduril/lockout-mode.h32
-rw-r--r--spaghetti-monster/anduril/misc.c61
-rw-r--r--spaghetti-monster/anduril/misc.h28
-rw-r--r--spaghetti-monster/anduril/momentary-mode.c86
-rw-r--r--spaghetti-monster/anduril/momentary-mode.h29
-rw-r--r--spaghetti-monster/anduril/off-mode.c306
-rw-r--r--spaghetti-monster/anduril/off-mode.h27
-rw-r--r--spaghetti-monster/anduril/ramp-mode-fsm.h45
-rw-r--r--spaghetti-monster/anduril/ramp-mode.c525
-rw-r--r--spaghetti-monster/anduril/ramp-mode.h190
-rw-r--r--spaghetti-monster/anduril/sos-mode.c75
-rw-r--r--spaghetti-monster/anduril/sos-mode.h29
-rw-r--r--spaghetti-monster/anduril/strobe-modes-fsm.h44
-rw-r--r--spaghetti-monster/anduril/strobe-modes.c297
-rw-r--r--spaghetti-monster/anduril/strobe-modes.h98
-rw-r--r--spaghetti-monster/anduril/sunset-timer.c78
-rw-r--r--spaghetti-monster/anduril/sunset-timer.h35
-rw-r--r--spaghetti-monster/anduril/tempcheck-mode.c75
-rw-r--r--spaghetti-monster/anduril/tempcheck-mode.h30
-rw-r--r--spaghetti-monster/anduril/tint-ramping.c87
-rw-r--r--spaghetti-monster/anduril/tint-ramping.h27
-rw-r--r--spaghetti-monster/anduril/version-check-mode.c47
-rw-r--r--spaghetti-monster/anduril/version-check-mode.h37
-rw-r--r--spaghetti-monster/fsm-adc.c14
-rw-r--r--spaghetti-monster/fsm-adc.h9
-rw-r--r--spaghetti-monster/fsm-events.c118
-rw-r--r--spaghetti-monster/fsm-events.h75
-rw-r--r--spaghetti-monster/fsm-main.c14
-rw-r--r--spaghetti-monster/fsm-misc.c34
-rw-r--r--spaghetti-monster/fsm-pcint.c21
-rw-r--r--spaghetti-monster/fsm-ramping.c3
-rw-r--r--spaghetti-monster/fsm-ramping.h4
-rw-r--r--spaghetti-monster/fsm-standby.h34
-rw-r--r--spaghetti-monster/fsm-states.c8
-rw-r--r--spaghetti-monster/fsm-states.h7
-rw-r--r--spaghetti-monster/fsm-wdt.c4
99 files changed, 5301 insertions, 3092 deletions
diff --git a/spaghetti-monster/anduril/BRANDS b/spaghetti-monster/anduril/BRANDS
new file mode 100644
index 0000000..b74ddab
--- /dev/null
+++ b/spaghetti-monster/anduril/BRANDS
@@ -0,0 +1,9 @@
+Vendor / Model IDs for version check function
+
+Undefined 0000
+Emisar 0100 - 0199
+Noctigon 0200 - 0199
+Lumintop 0300 - 0399
+Fireflies 0400 - 0499
+Mateminco 0500 - 0599
+Sofirn 0600 - 0699
diff --git a/spaghetti-monster/anduril/MODELS b/spaghetti-monster/anduril/MODELS
new file mode 100644
index 0000000..6697e52
--- /dev/null
+++ b/spaghetti-monster/anduril/MODELS
@@ -0,0 +1,39 @@
+Model numbers:
+0111 emisar-d4
+0112 emisar-d4-219c
+0113 emisar-d4v2
+0114 emisar-d4v2-219
+0115 emisar-d4v2-nofet
+0121 emisar-d1
+0122 emisar-d1s
+0123 emisar-d1v2
+0131 emisar-d4s
+0132 emisar-d4s-219c
+0133 emisar-d4sv2
+0134 emisar-d4sv2-219
+0141 emisar-d18
+0211 noctigon-kr4
+0212 noctigon-kr4-nofet
+0213 noctigon-kr4-219
+0251 noctigon-k1
+0252 noctigon-k1-sbt90
+0253 noctigon-k1-12v
+0311 fw3a
+0312 fw3a-219
+0313 fw3a-nofet
+0321 blf-gt
+0322 blf-gt-mini
+0411 ff-rot66
+0412 ff-rot66-219
+0413 ff-rot66g2
+0421 ff-pl47
+0422 ff-pl47-219
+0423 ff-pl47g2
+0441 ff-e01
+0511 mateminco-mf01s
+0521 mateminco-mf01-mini
+0611 blf-q8
+0612 sofirn-sp36
+0621 blf-lantern
+Duplicates:
+Missing:
diff --git a/spaghetti-monster/anduril/Makefile b/spaghetti-monster/anduril/Makefile
index 25c56fa..d1e6b46 100644
--- a/spaghetti-monster/anduril/Makefile
+++ b/spaghetti-monster/anduril/Makefile
@@ -4,4 +4,17 @@ all:
clean:
rm -f *.hex *~ *.elf *.o
-.phony: clean
+todo:
+ @egrep 'TODO:|FIXME:' *.[ch]
+
+models:
+ @echo -n > MODELS
+ @echo 'Model numbers:' >> MODELS
+ @grep '^#define MODEL_NUMBER' cfg-*.h | perl -ne '/cfg-(.*)\.h:#define MODEL_NUMBER "(.*)"/ && print "$$2\t$$1\n";' | sort -n >> MODELS
+ @echo 'Duplicates:' >> MODELS
+ @cat cfg-*.h | grep '^#define MODEL_NUMBER' | sort | uniq -c | grep -v ' 1 ' || true >> MODELS
+ @echo 'Missing:' >> MODELS
+ @for f in cfg-*.h ; do grep --silent '^#define MODEL_NUMBER' $$f ; if [ "$$?" = "1" ] ; then echo " $$f" ; fi ; done >> MODELS
+ @cat MODELS
+
+.phony: clean todo
diff --git a/spaghetti-monster/anduril/anduril-manual.txt b/spaghetti-monster/anduril/anduril-manual.txt
index 97e6589..b3a1cef 100644
--- a/spaghetti-monster/anduril/anduril-manual.txt
+++ b/spaghetti-monster/anduril/anduril-manual.txt
@@ -23,46 +23,208 @@ That is all the user needs to know for basic use, but there are many
more modes and features available for people who want more.
Before reading the rest of this manual, it is recommended that users
-look at the Anduril UI diagram, which should be provided along with the
+look at the Anduril UI diagram(s), which should be provided along with the
flashlight.
+Button presses
+--------------
+
+Button presses are abbreviated using a simple notation:
+
+ - 1C: One click. Press and then quickly release the button.
+ - 1H: Hold. Press the button, but keep holding it.
+ - 2C: Two clicks. Press and release quickly, twice.
+ - 2H: Click, hold. Click two times, but hold the second press.
+ - 3C: Three clicks. Press and release quickly, three times.
+ - 3H: Click, click, hold. Click three times, but hold the final press.
+
+The same pattern is used with higher numbers too. For example, 10C
+means ten clicks... and 10H means ten clicks but hold the final press.
+
+
+Simple UI
+---------
+
+By default, the light uses a simple UI. This is useful if you lend the
+light to someone else, or if you just don't want to bother with any
+crazy disco modes.
+
+Simple UI has all the basic functions needed to work as a flashlight,
+but the minimum and maximum brightness are limited to make it safer, and
+any complex or advanced functions are blocked.
+
+Functions available in Simple UI include:
+
+ - 1C: On / off
+ - 1H: Ramp up (or down, if button was released less than a second ago)
+ - 2H: If light is on : ramp down
+ If light is off: momentary high mode
+ - 2C: Double click to go to / from highest safe level
+ - 4C: Lockout mode.
+
+Some other modes and functions are available too. When the light is
+off, these are the options:
+
+ - 3C: Battery check mode. (displays voltage once, then turns off)
+ - 4C: Lockout mode.
+ - 10H: Switch to Advanced UI.
+ - 15C or more: Version check.
+
+In Lockout mode with Simple UI, there are a few functions:
+
+ - 1H: Momentary moon
+ - 2H: Momentary low
+ - 4C: Unlock and turn on
+ - 4H: Unlock and turn on at low level
+ - 5C: Unlock and turn on at high level
+
+To change between Simple UI and Advanced UI, turn the light off and then
+do one of these:
+
+ In Simple UI:
+ - 10H: Go to Advanced UI.
+
+ In Advanced UI:
+ - 10C: Go to Simple UI.
+ - 10H: Configure Simple UI.
+
+If you get lost, or if you want to auto-calibrate the temperature
+sensor, do a factory reset. The process for this is:
+
+ - Loosen tailcap
+ - Hold button
+ - Tighten tailcap
+ - Keep holding button for about 4s
+
+The light should flicker while getting brighter, then briefly burst to
+full power. Hold until it reaches full power to do a reset, or let go
+of the button early to abort.
+
+On some lights, like products where the tailcap method is impossible,
+use 13H from Off to do a factory reset. If this is difficult, try
+counting it like music to make it easier:
+
+ 1 2 3 4
+ 1 2 3 4
+ 1 2 3 4
+ HOLD
+
+Simple UI is enabled after each factory reset.
+
+
+Advanced UI
+-----------
+
+Most of the information below this is for the Advanced UI. Anything not
+already noted above is blocked in the Simple UI.
+
+
Ramping / Stepped Ramping Modes
-------------------------------
-Anduril's normal operation mode uses a smooth ramp or a stepped ramp,
-depending on which style the user prefers.
+Anduril's ramping mode uses a smooth ramp or a stepped ramp, depending
+on which style the user prefers.
Each ramp has its own settings -- floor (lowest level), ceiling (highest
level), and the stepped ramp can also have a configurable number of
steps.
-There are four ways to access this mode when the light is off:
+Additionally, Simple UI has its own ramp settings for floor, ceiling,
+and number of steps. The smooth/stepped style is inherited from the
+Advanced UI's ramp.
- - Click: Turn on at the memorized brightness.
- - Hold: Turn on at the floor level. The light should give a very
- subtle dark blink when the user can let go of the button to stay at
- the floor, or keep holding the button to ramp up.
- - 2 clicks: Turn on at the ceiling level.
- - Click, hold: Turn on at the ceiling level, then ramp down.
+There are four ways to access ramping mode when the light is off:
-While the light is on, a few basic actions are available:
+ - 1C: Turn on at the memorized brightness.
+ (see below for details about what "memorized" means)
- - Click: Turn off.
- - 2 clicks: Go to or from turbo (full power).
- - Hold: Change brightness (up). If the button was released less than
- a second ago, or if it's already at the ceiling, it goes down
- instead.
- - Click, hold: Change brightness (down).
- - 3 clicks: Switch to the other ramp.
- - 4 clicks: Go to ramp config mode.
- - 5 clicks: Activate manual memory and save the current brightness.
- - 5 clicks, but hold the last click: Go back to automatic memory.
-
-The "automatic" vs "manual" memory modes change the level the light goes
-to with 1 click from off. In automatic mode, it uses the last
-brightness the user ramped to. In manual mode, it uses the brightness
-the user explicitly saved with 5 clicks.
+ - 1H: Turn on at the floor level. Let go after the light turns on to
+ stay at the floor level, or keep holding to ramp up.
+
+ - 2C: Turn on at the ceiling level.
+
+ - 2H: Turn on at full power, turn off when released. (momentary turbo)
+ (in Simple UI, this uses the ceiling level instead of turbo)
+
+While the light is on, a few actions are available:
+
+ - 1C: Turn off.
+ - 2C: Go to or from the ceiling level.
+ (or if already at ceiling, and not in Simple UI, go to/from turbo)
+ - 1H: Change brightness (up). If the button was released less than a
+ second ago, or if it's already at the ceiling, it goes down instead.
+ - 2H: Change brightness (down).
+
+ - 3C: Switch to the other ramp style. (smooth / stepped)
+ - 3H: Tint ramping (on lights which have it).
+ - 3H: Momentary turbo (on lights with no tint ramping).
+
+ - 4C: Go to lockout mode.
+
+ - 5C: Go to momentary mode.
+ - 5H: Start a sunset timer. Details are below in the Sunset Timer section.
+
+ - 7H: Ramp config menu.
+ - Item 1: Floor level.
+ - Item 2: Ceiling level.
+ - Item 3: Number of steps (except for smooth ramp).
+
+ - 10C: Activate manual memory and save the current brightness.
+ - 10H: Manual memory config menu.
+ - Item 1: Disable manual memory and go back to automatic memory.
+ (doesn't matter what value the user enters at the prompt)
+ - Item 2: Configure the manual memory timer.
+ Sets the timer to N minutes, where N is the number of
+ clicks. A value of 0 (no clicks) turns the timer off.
+
+Memory determines which brightness level the light goes to with 1 click
+from off. There are three types of brightness memory to choose from:
+
+ - Automatic: Always uses the last-ramped brightness.
+
+ - Manual: Always uses the user's saved brightness.
+
+ - Hybrid: Uses the automatic memory brightness if the light was only
+ off for a short time, or resets to the manual memory level if it was
+ off for a longer time.
+ The timer for this is configurable from 0 to 255 minutes.
+
+Another way to think of it is: There are three styles of memory for the
+last-ramped brightness level...
+
+ - Always remember (automatic)
+ - Remember for N minutes (hybrid)
+ - Never remember (manual)
+
+To choose a memory style, set the configuration accordingly:
+
+ mem type manual mem manual mem timer
+ -------- ---------- ----------------
+ automatic off any
+ manual on zero
+ hybrid on non-zero
+
+
+Sunset Timer
+------------
+
+In the ramp mode or candle mode, it's possible to make the light turn
+itself off after a while.
+
+To activate the timer, go to the brightness you want and then use a 5H
+action. Keep holding the button, and the light should blink once per
+second. Each blink adds 5 minutes to the timer.
+
+In ramp mode, it slowly dims until it's at the lowest level, then shuts
+off. In candle mode, it stays at the same brightness until the final
+minute, at which point it dims and dies out.
+
+The user can change the brightness while the timer is active. If this
+happens during the final few minutes, it also "bumps" the timer up to a
+minimum of 3 minutes. So if it's getting really dim and you need a
+little more time, you could do a 5H to add 5 minutes, or simply ramp up
+to the desired brightness.
Other Modes
@@ -71,11 +233,57 @@ Other Modes
Anduril has several other modes too. To access these, press the button
more than 2 times when the light is off:
- - 3 clicks: Access the blinky / utility modes.
- - Click, click, hold: Access the strobe modes.
- - 4 clicks: Lockout mode.
- - 5 clicks: Momentary mode.
- - 6 clicks: Muggle mode.
+ - 3C: Blinky / utility modes, starting with battery check.
+ - 3H: Strobe modes, starting with the most recently used strobe.
+ - 4C: Lockout mode.
+ - 5C: Momentary mode.
+ - 7C / 7H: Aux LED configuration.
+ - 10H: Simple UI configuration menu.
+ - 13H: Factory reset (on some lights).
+ - 15C or more: Version check.
+
+
+Lockout Mode
+------------
+
+Click 4 times from Off to enter Lockout mode. Or 4 times from Ramp.
+This makes the light safe to carry in a pocket or a bag or anywhere else
+the button might be pressed by accident.
+
+To exit lockout mode, click 4 times. The light should blink briefly and
+then turn on at the memorized level. Or hold the final press to turn on
+at the floor level instead:
+
+ - 4C: Go to ramp mode (memorized level).
+ (uses manual mem level if there is one)
+
+ - 4H: Go to ramp mode (floor level).
+
+ - 5C: Go to ramp mode (ceiling level).
+
+Lockout mode also doubles as a momentary moon mode, so the user can do
+quick tasks without having to unlock the light. The brightness in
+lockout mode has two levels:
+
+ - 1H: Light up at the lowest floor level.
+
+ - 2H: Light up at the highest floor level.
+ (or the manual mem level, if there is one)
+
+It is also possible to make the light lock itself automatically after
+being turned off. To enable this, go to lockout mode and use a 10H
+action to activate the auto-lock config menu. Release the button after
+the first blink. Then at the prompt, click N times to set the auto-lock
+timeout to N minutes.
+
+ - 10H: Auto-lock config menu. Click N times to set timeout to N minutes.
+ A value of zero disables the auto-lock feature.
+ So, to turn off auto-lock, don't click at all.
+
+And on lights which have aux LEDs, there may be additional functions:
+
+ - 7C / 7H: Change Lockout Mode's aux LED pattern. More details on
+ this below, in a separate section.
Blinky / Utility Modes
@@ -83,25 +291,25 @@ Blinky / Utility Modes
Click 3 times from Off to access Anduril's blinky / utility modes. This
always starts at battery check and the user can proceed to other blinky
-modes from there. The sequence is:
+modes if Advanced UI is enabled. The sequence is:
- 1. Battery check.
- 2. Sunset mode.
- 3. Beacon mode.
- 4. Temperature check.
+ - Battery check.
+ - Temperature check (if light has a temperature sensor).
+ - Beacon mode.
+ - SOS mode (if enabled).
In all of these modes, some basic actions are available:
- Click: Turn off.
- 2 clicks: Next blinky mode.
-Additionally, in beacon and temperature check modes:
+Additionally, in battery check and temperature check modes:
- - 4 clicks: Go to the beacon config mode or thermal config mode.
+ - 7H: Go to the voltage config menu or thermal config menu.
In more detail, this is what each blinky / utility mode does:
- 1. Battery check.
+ Battery check:
Blinks out the battery voltage per cell. Full is 4.2V, empty is
about 3.0V. The light blinks the whole-number digit first, pauses,
@@ -111,33 +319,35 @@ In more detail, this is what each blinky / utility mode does:
A "zero" digit is represented by a very quick blink.
- 2. Sunset mode.
+ The voltage config menu has one setting:
- This starts at a low level, then dims gradually for an hour, and
- then shuts off. It is intended for use when going to bed.
+ 1. Voltage correction factor. This adjusts the battery
+ measurement sensor, allowing the user to add or subtract up to
+ 0.30V in 0.05V steps. Click N times to enter a value:
- 3. Beacon mode.
+ 1C: -0.30V
+ 2C: -0.25V
+ 3C: -0.20V
+ 4C: -0.15V
+ 5C: -0.10V
+ 6C: -0.05V
+ 7C: default, 0V
+ 8C: +0.05V
+ 9C: +0.10V
+ 10C: +0.15V
+ 11C: +0.20V
+ 12C: +0.25V
+ 13C: +0.30V
- Blinks at a slow speed. The light stays on for half a second, and
- then stays off until the next blink. The brightness and the number
- of seconds between pulses are configurable:
-
- - Brightness is the user's last-ramped level, so set this in
- ramping mode before starting beacon mode.
-
- - Speed is configured in beacon config mode. Click 4 times to
- enter beacon config mode, wait for the light to stutter, then
- click to enter the number of seconds per blink. For example,
- to do a 10-second alpine beacon, click 10 times.
-
- 4. Temperature check.
+ Temperature check:
Blinks out the current temperature in degrees C. This number
should be pretty close to what a real thermometer says. If not, it
- would be a good idea to click 4 times to enter thermal config mode,
- and calibrate the sensor.
+ would be a good idea to enter the thermal config menu and calibrate
+ the sensor. Or let the light settle to room temperature, then use
+ factory reset to auto-calibrate the sensor.
- Thermal config mode has two settings:
+ The thermal config menu has two settings:
- Current temperature. Click once per degree C to calibrate the
sensor. For example, if the ambient temperature is 21 C, then
@@ -149,6 +359,30 @@ In more detail, this is what each blinky / utility mode does:
example, to set the limit to 50 C, click 20 times. The default
is 45 C, and the highest value it will allow is 70 C.
+ Beacon mode:
+
+ Blinks at a slow speed. The light stays on for 100ms, and then
+ stays off until the next blink. The brightness and the number of
+ seconds between pulses are configurable:
+
+ - Brightness is the user's last-ramped level, so set this in
+ ramping mode before starting beacon mode.
+
+ - Speed is configured by holding the button. The light should
+ blink once per second while holding the button. Release it
+ after the desired amount of time has passed, to set a new
+ beacon speed.
+ For example, to do a 10-second alpine beacon, hold the button
+ for 10 seconds.
+
+ SOS mode:
+
+ Blinks out a distress signal. Three short, three long, three short.
+ Repeats until light is turned off or until battery is low.
+
+ The last-ramped brightness in Ramping Mode determines the brightness
+ of SOS Mode.
+
Strobe / Mood Modes
-------------------
@@ -167,14 +401,16 @@ so it will return to whichever one you used last.
In all of these modes, a few actions are available:
- - Click: Turn off.
- - 2 Clicks: Next strobe / mood mode.
- - Hold: Increase brightness, or strobe faster. (except lightning)
- - Click, hold: Decrease brightness, or strobe slower. (except lightning)
+ - 1C: Turn off.
+ - 2C: Next strobe / mood mode.
+ - 1H: Increase brightness, or strobe faster. (except lightning)
+ - 2H: Decrease brightness, or strobe slower. (except lightning)
+ - 5C: Go to momentary mode, for a momentary strobe.
+ (this is useful for light painting)
Additionally, candle mode has one more action:
- - 3 clicks: Add 30 minutes to the timer.
+ - 5H: Activate Sunset Timer, and/or add 5 minutes to the timer.
In more detail, here is what each mode does:
@@ -202,6 +438,8 @@ In more detail, here is what each mode does:
Disorienting strobe light. Can be used to irritate people. Speed
is configurable, and the duty cycle is always 33%.
+ Be careful about heat in this mode, if using it for a long time.
+
- Lightning storm mode
Flashes at random brightness and random speed to simulate lightning
@@ -210,40 +448,21 @@ In more detail, here is what each mode does:
full power without warning.
-Lockout Mode
-------------
-
-Click 4 times from Off to enter Lockout mode. This makes the light safe
-to carry in a pocket or a bag or anywhere else the button might be
-pressed by accident.
-
-To exit lockout mode, click 4 times. The light should blink briefly and
-then return to the regular "off" mode.
-
-Lockout mode also doubles as a momentary moon mode, so the user can do
-quick tasks without having to unlock the light. The brightness in
-lockout mode has two levels:
-
- - Hold: light up at the floor level of the current ramp.
- - Click, Hold: light up at the floor level of the other ramp.
-
-
Momentary Mode
--------------
-Click 5 times from Off to enter Momentary mode. This locks the
-flashlight into a single-mode interface where the LEDs are only on when
-the button is held down. It is intended for Morse code, light painting,
-and other tasks where the light should be on only for a short time and
-probably in a pattern.
+Click 5 times from Off to enter Momentary mode. Or 5 times from Ramp,
+or 5 times from a strobe mode.
-Momentary mode does either a steady brightness level or a strobe. To
-select which one, go to the mode you want to use, adjust the brightness
-and speed and other settings, then turn the light off. Then click 5
-times to enter momentary mode.
+This locks the flashlight into a single-mode interface where the LEDs
+are only on when the button is held down. It is intended for Morse
+code, light painting, and other tasks where the light should be on only
+for a short time and probably in a pattern.
-Supported momentary modes are steady (normal ramping mode) and strobes
-(everything in the "strobe" mode group).
+Momentary mode does either a steady brightness level or a strobe,
+depending on which was active before going to momentary mode. To select
+which one, go to the mode you want to use, adjust the brightness and
+speed and other settings, then click 5 times to enter momentary mode.
In steady mode, brightness is the last-ramped level, so adjust that
before entering momentary mode.
@@ -255,59 +474,37 @@ To exit this mode, physically disconnect power by unscrewing the tailcap
or battery tube.
-Muggle Mode
------------
-
-Click 6 times from Off to enter Muggle mode. This is a simpler and
-less-bright interface which makes the light relatively safe to lend to
-children or other people who could use the light unsafely.
-
-In Muggle mode, there are only a few available actions:
-
- - Click: Turn the light on or off.
- - Hold: Change brightness.
- - 6 clicks: Exit Muggle mode.
-
-The brightness in this mode usually goes from about 10 lm to 300 lm.
-
-Muggle mode is remembered even after changing the battery. This helps
-prevent children from exiting the mode by unscrewing the tailcap.
-However, be sure to supervise children whenever they are using any
-powerful tools, including a bright flashlight.
-
-Note: If the light steps down in muggle mode, it probably needs to have
-the temperature sensor calibrated. Use thermal config mode to do this.
-
-
-Configuration Modes
+Configuration Menus
-------------------
-Every config mode has the same interface. The menu has one or more
-options the user can configure, and it will go through them in order.
-For each menu item, the light will follow the same pattern:
+Every config menu has the same interface. It has one or more options
+the user can configure, and it will go through them in order. For each
+menu item, the light follows the same pattern:
- - Blink one or more times, corresponding to the item number.
+ - Blink once, then go to a lower brightness. The user can keep
+ holding the button to skip this menu item, or release the button to
+ dive in and enter a new value.
- - Stutter or "buzz" quickly between two brightness levels for a few
- seconds. This indicates that the user can click one or more times
- to enter a number. It will keep buzzing until the user stops
- clicking, so there is no need to hurry.
+ - If the user released the button:
- - Pause, and then go to the next option.
+ - Stutter or "buzz" quickly between two brightness levels for a few
+ seconds. This indicates that the user can click one or more times
+ to enter a number. It will keep buzzing until the user stops
+ clicking, so there is no need to hurry.
-After the light has gone through all of the menu options, it should
-return to whatever mode the light was in before entering the config
-mode.
+After entering a number, or after skipping every menu item, it waits
+until the button is released then exits the menu. It should return to
+whatever mode the light was in before entering the config menu.
-If the user doesn't press a button during a menu item's "buzz" phase,
-that item remains unchanged from its previous value.
-
-Ramp Config Mode
+Ramp Config Menu
----------------
-While the light is on in a ramping mode, click 4 times to enter the
-config mode for the current ramp.
+While the light is on in a ramping mode, click 7 times (but hold the
+final click) to access the config menu for the current ramp.
+
+Or, to access the ramp config for Simple UI, make sure the Simple UI is
+not active, then do a 10H action from Off.
For smooth ramping mode, there are two menu options:
@@ -320,6 +517,16 @@ For the stepped ramping mode, there are three menu options:
2. Ceiling. (default = 120/150)
3. Number of steps. (default = 7)
+For the Simple UI mode, there are three menu options. They are the same
+as stepped ramping mode.
+
+ 1. Floor. (default = 20/150)
+ 2. Ceiling. (default = 120/150)
+ 3. Number of steps. (default = 5)
+
+Default values are different for each model of flashlight. The numbers
+above are only examples.
+
To configure the floor level, click the button equal to the number of
ramp levels (out of 150) at which the floor should be. To set the
lowest possible level, click once.
@@ -329,8 +536,32 @@ click sets the highest possible level, 2 clicks is the 2nd-highest, 3
clicks is the 3rd-highest level, etc. To set the default of 120/150,
click 31 times.
-When configuring the number of steps, the value can be anything from 2
-to 150.
+When configuring the number of steps, the value can be anything from 1
+to 150. A value of 1 is a special case. It places the step halfway
+between the floor and ceiling levels.
+
+
+Version Check Mode
+------------------
+
+This allows people to see which version of the firmware is installed on
+their light. The format for this is (usually) 12 digits -- a date
+followed by a model number. YYYYMMDDBBPP
+
+ - YYYY: Year
+ - MM: Month
+ - DD: Day
+ - BB: Brand ID
+ - PP: Product ID
+
+The date is when the firmware was compiled. If the vendor did not set
+this value, it defaults to 1969-07-20, the date of first human contact
+with the moon. However, it might not be a date at all; some vendors may
+specify a completely different type of value here.
+
+The brand/product values are also known as the model number. These are
+hard-coded in the source code for each light's build target, and can be
+looked up in the "MODELS" file or by using the "make models" command.
Protection Features
@@ -366,25 +597,148 @@ Aux LED modes typically include:
- Blinking
To configure the aux LEDs, go to the mode you want to configure and then
-click the button a few times:
-
- - Off mode: 7 clicks.
- - Lockout mode: 3 clicks.
+click the button 7 times. This should change the aux LEDs to the next
+mode supported on this light.
-This should change the aux LEDs to the next mode supported on this
-light.
+ - 7C: Next aux LED mode.
If the aux LEDs can change color, there are additional actions to change
the color. It is the same as above, but hold the button on the last
click and then let go when the desired color is reached.
- - Off mode: 7 clicks, but hold the last click.
- - Lockout mode: 3 clicks, but hold the last click.
+ - 7H: Next aux LED color.
+
+On most lights, the colors follow this sequence:
+
+ - Red
+ - Yellow (Red+Green)
+ - Green
+ - Cyan (Green+Blue)
+ - Blue
+ - Purple (Blue+Red)
+ - White (Red+Green+Blue)
+ - Disco (fast random colors)
+ - Rainbow (cycles through all colors in order)
+ - Voltage (uses color to display battery charge)
+
+In voltage mode, the colors follow the same sequence, in the same order
+as a rainbow... with red indicating a low battery and purple indicating
+a full battery.
For lights with a button LED, the button LED typically stays on while
the main emitters are on. Its brightness level is set in a way which
mirrors the main LED -- off, low, or high.
+For lights with a RGB button LED, the button LED indicates battery
+charge during use in the same manner as the aux LED voltage mode.
+
For lights with front-facing aux LEDs, the aux LEDs typically stay off
when the main emitters are on, and when the light is otherwise awake.
The aux LEDs on most lights only turn on when the light is asleep.
+
+
+Tint Ramping
+------------
+
+Some lights have more than one set of LEDs which can be adjusted to
+change the color temperature of the light. On these models, there is a
+global button mapping which works at all times unless it's overridden by
+the mode the light is in:
+
+ - 3H: Tint ramping
+
+So, at almost any time, click 3 times but hold the final press, and it
+can change the color of the light. This is best done with the light at
+a moderately high level, because it makes the changes smooth. Low
+levels have more coarse resolution and will typically change in very
+visible steps.
+
+At both ends of the tint ramp, there is an "auto tint" mode. This
+automatically chooses a tint based on the brightness level... so it can
+be warm white while dim, or cool white while bright. Or vice-versa. To
+access this, ramp to the end of the tint range, then keep holding until
+the light blinks a second time.
+
+
+UI Reference Table
+------------------
+
+This is a table of all button mappings in Anduril, in one place:
+
+Mode UI Button Action
+---- -- ------ ------
+Off Any 1C On (ramp mode, memorized level)
+Off Any 1H On (ramp mode, floor level)
+Off Any 2C On (ramp mode, ceiling level)
+Off Simple 2H On (momentary ceiling level)
+Off Full 2H On (momentary turbo)
+Off Any 3C Battcheck mode
+Off Full 3H Strobe mode (whichever was used last)
+Off Any 4C Lockout mode
+Off Full 5C Momentary mode
+Off Full 7C Aux LEDs: Next pattern
+Off Full 7H Aux LEDs: Next color
+Off Full 10C Enable Simple UI
+Off Simple 10H Disable Simple UI
+Off Full 10H Simple UI ramp config menu (1: floor, 2: ceiling, [3: steps])
+Off Any 13H Factory reset (on some lights)
+Off Any 15+C Version check
+
+Ramp Any 1C Off
+Ramp Any 1H Ramp (up, with reversing)
+Ramp Any 2H Ramp (down)
+Ramp Simple 2C Go to/from ceiling
+Ramp Full 2C Go to/from ceiling (or turbo if at ceil already)
+Ramp Full 3C Change ramp style (smooth / stepped)
+Ramp Any 3H Tint ramping (on some lights)
+Ramp Any 3H Momentary turbo (on lights without tint ramping)
+Ramp Any 4C Lockout mode
+Ramp Full 5C Momentary mode
+Ramp Full 5H Sunset timer on, and add 5 minutes
+Ramp Full 7H Ramp config menu (1: floor, 2: ceiling, [3: steps])
+Ramp Full 10C Turn on manual memory and save current brightness
+Ramp Full 10H Manual memory config menu (1: off, 2: set timeout)
+
+Lockout Any 1C/1H Momentary moon (lowest floor)
+Lockout Any 2C/2H Momentary moon (highest floor, or manual mem level)
+Lockout Any 4C On (ramp mode, memorized level)
+Lockout Any 4H On (ramp mode, floor level)
+Lockout Any 5C On (ramp mode, ceiling level)
+Lockout Full 7C Aux LEDs: Next pattern
+Lockout Full 7H Aux LEDs: Next color
+Lockout Full 10H Auto-lock config menu (1: set timeout)
+
+Strobe (any) Full 1C Off
+Strobe (any) Full 2C Next strobe mode
+Strobe (any) Full 3H Tint ramping (on some lights)
+Strobe (any) Full 5C Momentary mode (using current strobe)
+
+Candle Full 1H/2H Brighter / dimmer
+Candle Full 5H Sunset timer on, add 5 minutes
+Party strobe Full 1H/2H Faster / slower
+Tactical strobe Full 1H/2H Faster / slower
+Biking Full 1H/2H Brighter / dimmer
+Lightning Full 1H Interrupt current flash or start new one
+
+Batt check Any 1C Off
+Batt check Full 2C Next blinky mode (Temp check, Beacon, SOS)
+Batt check Full 7H Voltage config menu
+
+Temp check Full 1C Off
+Temp check Full 2C Next blinky mode (Beacon, SOS, Batt check)
+Temp check Full 7H Thermal config menu
+
+Beacon Full 1C Off
+Beacon Full 1H Configure beacon timing
+Beacon Full 2C Next blinky mode (SOS, Batt check, Temp check)
+
+SOS Full 1C Off
+SOS Full 2C Next blinky mode (Batt check, Temp check, Beacon)
+
+Momentary Full Any On (until button is released)
+Momentary Full Disconnect power Exit Momentary mode
+
+Config menus Full Hold Skip current item with no changes
+Config menus Full Release Configure current item
+
+Number entry Full Click Add 1 to value for current item
diff --git a/spaghetti-monster/anduril/anduril.c b/spaghetti-monster/anduril/anduril.c
index bbc10c0..f632229 100644
--- a/spaghetti-monster/anduril/anduril.c
+++ b/spaghetti-monster/anduril/anduril.c
@@ -2,7 +2,7 @@
* Anduril: Narsil-inspired UI for SpaghettiMonster.
* (Anduril is Aragorn's sword, the blade Narsil reforged)
*
- * Copyright (C) 2017-2019 Selene ToyKeeper
+ * Copyright (C) 2017-2020 Selene ToyKeeper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -18,2632 +18,326 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+/*
+ * Usually a program would be structured like this...
+ * - Library headers
+ * - App headers
+ * - App code
+ *
+ * ... in each source file.
+ * ... and each library and part of the program would be linked together.
+ *
+ * But this doesn't follow that pattern, because it's using the
+ * -fwhole-program
+ * flag to reduce the compiled size. It lets us fit more features
+ * in a tiny MCU chip's ROM.
+ *
+ * So the structure is like this instead...
+ * - App-level configuration headers
+ * - Default config
+ * - Per build target config
+ * - Library-level configuration headers
+ * - Library code (FSM itself)
+ * - App headers
+ * - App code (all of it, inline)
+ *
+ * Don't do this in regular programs. It's weird and kind of gross.
+ * But in this case it gives us a bunch of much-needed space, so... woot.
+ *
+ * Also, there are a ton of compile-time options because it needs to build
+ * a bunch of different versions and each one needs to be trimmed as small
+ * as possible. These are mostly "USE" flags.
+ */
+
/********* User-configurable options *********/
+#include "config-default.h"
+
+/********* specific settings for known driver types *********/
// Anduril config file name (set it here or define it at the gcc command line)
//#define CONFIGFILE cfg-blf-q8.h
-#define USE_LVP
-
-// parameters for this defined below or per-driver
-#define USE_THERMAL_REGULATION
-#define DEFAULT_THERM_CEIL 45 // try not to get hotter than this
-
-#define USE_FACTORY_RESET
-//#define USE_SOFT_FACTORY_RESET // only needed on models which can't use hold-button-at-boot
-
-// dual-switch support (second switch is a tail clicky)
-// (currently incompatible with factory reset)
-//#define START_AT_MEMORIZED_LEVEL
-
-// include a function to blink out the firmware version
-#define USE_VERSION_CHECK
-
-// short blip when crossing from "click" to "hold" from off
-// (helps the user hit moon mode exactly, instead of holding too long
-// or too short)
-#define MOON_TIMING_HINT // only applies if B_TIMING_ON == B_PRESS_T
-// short blips while ramping
-#define BLINK_AT_RAMP_MIDDLE
-//#define BLINK_AT_RAMP_FLOOR
-#define BLINK_AT_RAMP_CEILING
-//#define BLINK_AT_STEPS // whenever a discrete ramp mode is passed in smooth mode
-
-// ramp down via regular button hold if a ramp-up ended <1s ago
-// ("hold, release, hold" ramps down instead of up)
-#define USE_REVERSING
-
-// add a runtime option to switch between automatic memory (default)
-// and manual memory (only available if compiled in)
-// (manual memory makes 1-click-from-off start at the same level each time)
-// (the level can be set explicitly with 5 clicks from on,
-// or the user can go back to automatic with click-click-click-click-hold)
-#define USE_MANUAL_MEMORY
-
-// battery readout style (pick one)
-#define BATTCHECK_VpT
-//#define BATTCHECK_8bars // FIXME: breaks build
-//#define BATTCHECK_4bars // FIXME: breaks build
-
-// enable/disable various strobe modes
-#define USE_BIKE_FLASHER_MODE
-#define USE_PARTY_STROBE_MODE
-#define USE_TACTICAL_STROBE_MODE
-#define USE_LIGHTNING_MODE
-#define USE_CANDLE_MODE
-
-// enable sunset (goodnight) mode
-#define USE_GOODNIGHT_MODE
-#define GOODNIGHT_TIME 60 // minutes (approximately)
-#define GOODNIGHT_LEVEL 24 // ~11 lm
-
-// enable beacon mode
-#define USE_BEACON_MODE
-
-// enable momentary mode
-#define USE_MOMENTARY_MODE
-
-//Muggle mode for easy UI
-#define USE_MUGGLE_MODE
-
-// make the ramps configurable by the user
-#define USE_RAMP_CONFIG
-
-// boring strobes nobody really likes, but sometimes flashlight companies want
-// (these replace the fun strobe group,
-// so don't enable them at the same time as any of the above strobes)
-//#define USE_POLICE_STROBE_MODE
-//#define USE_SOS_MODE
-//#define USE_SOS_MODE_IN_FF_GROUP // put SOS in the "boring strobes" mode
-//#define USE_SOS_MODE_IN_BLINKY_GROUP // put SOS in the blinkies mode group
-
-// cut clock speed at very low modes for better efficiency
-// (defined here so config files can override it)
-#define USE_DYNAMIC_UNDERCLOCKING
-
-/***** specific settings for known driver types *****/
#include "tk.h"
#include incfile(CONFIGFILE)
-// brightness to use when no memory is set
-#ifndef DEFAULT_LEVEL
-#define DEFAULT_LEVEL MAX_1x7135
-#endif
+/********* Include headers which need to be before FSM *********/
-// thermal properties, if not defined per-driver
-#ifndef MIN_THERM_STEPDOWN
-#define MIN_THERM_STEPDOWN MAX_1x7135 // lowest value it'll step down to
-#endif
-#ifndef THERM_FASTER_LEVEL
- #ifdef MAX_Nx7135
- #define THERM_FASTER_LEVEL MAX_Nx7135 // throttle back faster when high
- #else
- #define THERM_FASTER_LEVEL (RAMP_SIZE*4/5) // throttle back faster when high
- #endif
-#endif
-#ifdef USE_THERMAL_REGULATION
-#define USE_SET_LEVEL_GRADUALLY // isn't used except for thermal adjustments
-#endif
-
-
-/********* Configure SpaghettiMonster *********/
-#define USE_DELAY_ZERO
-#define USE_RAMPING
-#ifndef RAMP_LENGTH
-#define RAMP_LENGTH 150 // default, if not overridden in a driver cfg file
-#endif
-#define MAX_BIKING_LEVEL 120 // should be 127 or less
-#define USE_BATTCHECK
-
-#if defined(USE_MUGGLE_MODE)
-#ifndef MUGGLE_FLOOR
-#define MUGGLE_FLOOR 22
-#endif
-#ifndef MUGGLE_CEILING
-#define MUGGLE_CEILING (MAX_1x7135+20)
-#endif
-#endif
-#define USE_IDLE_MODE // reduce power use while awake and no tasks are pending
+// enable FSM features needed by basic ramping functions
+#include "ramp-mode-fsm.h"
-// full FET strobe can be a bit much... use max regulated level instead,
-// if there's a bright enough regulated level
-#ifndef STROBE_BRIGHTNESS
-#ifdef MAX_Nx7135
-#define STROBE_BRIGHTNESS MAX_Nx7135
-#else
-#define STROBE_BRIGHTNESS MAX_LEVEL
-#endif
+#ifdef USE_FACTORY_RESET
+#include "factory-reset-fsm.h"
#endif
-#if defined(USE_CANDLE_MODE) || defined(USE_BIKE_FLASHER_MODE) || defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE) || defined(USE_LIGHTNING_MODE)
-#define USE_STROBE_STATE
+#ifdef USE_BATTCHECK_MODE
+#include "battcheck-mode-fsm.h"
#endif
-#if defined(USE_POLICE_STROBE_MODE) || defined(USE_SOS_MODE_IN_FF_GROUP)
-#define USE_BORING_STROBE_STATE
+#ifdef USE_LOCKOUT_MODE
+#include "lockout-mode-fsm.h"
#endif
-// auto-detect how many eeprom bytes
-#define USE_EEPROM
-typedef enum {
- ramp_style_e,
- #ifdef USE_RAMP_CONFIG
- ramp_smooth_floor_e,
- ramp_smooth_ceil_e,
- ramp_discrete_floor_e,
- ramp_discrete_ceil_e,
- ramp_discrete_steps_e,
- #endif
- #ifdef USE_MANUAL_MEMORY
- manual_memory_e,
- #endif
- #ifdef USE_TINT_RAMPING
- tint_e,
- #endif
- #ifdef USE_STROBE_STATE
- strobe_type_e,
- #endif
- #if defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE)
- strobe_delays_0_e,
- strobe_delays_1_e,
- #endif
- #ifdef USE_BIKE_FLASHER_MODE
- bike_flasher_brightness_e,
- #endif
- #ifdef USE_BEACON_MODE
- beacon_seconds_e,
- #endif
- #ifdef USE_MUGGLE_MODE
- muggle_mode_active_e,
- #endif
- #ifdef USE_THERMAL_REGULATION
- therm_ceil_e,
- therm_cal_offset_e,
- #endif
- #ifdef USE_INDICATOR_LED
- indicator_led_mode_e,
- #endif
- #ifdef USE_AUX_RGB_LEDS
- rgb_led_off_mode_e,
- rgb_led_lockout_mode_e,
- #endif
- eeprom_indexes_e_END
-} eeprom_indexes_e;
-#define EEPROM_BYTES eeprom_indexes_e_END
+// enable FSM features needed by strobe modes
+#include "strobe-modes-fsm.h"
-#ifdef START_AT_MEMORIZED_LEVEL
-#define USE_EEPROM_WL
-#define EEPROM_WL_BYTES 1
-#endif
-
-// auto-configure other stuff...
-#if defined(USE_LIGHTNING_MODE) || defined(USE_CANDLE_MODE)
-#define USE_PSEUDO_RAND
-#endif
+// figure out how many bytes of eeprom are needed,
+// based on which UI features are enabled
+// (include this one last)
+#include "load-save-config-fsm.h"
-#if defined(USE_CANDLE_MODE)
-#ifndef USE_TRIANGLE_WAVE
-#define USE_TRIANGLE_WAVE
-#endif
-#endif
-#ifdef USE_SOFT_FACTORY_RESET
-#define USE_REBOOT
-#endif
+/********* bring in FSM / SpaghettiMonster *********/
+#define USE_IDLE_MODE // reduce power use while awake and no tasks are pending
#include "spaghetti-monster.h"
-// configure the timing of turning on/off in regular ramp mode
-// press: react as soon as the button is pressed
-#define B_PRESS_T 0
-// release: react as soon as the button is released
-#define B_RELEASE_T 1
-// timeout: react as soon as we're sure the user isn't doing a double-click
-#define B_TIMEOUT_T 2
-// defaults are release on, timeout off
-#ifndef B_TIMING_ON
-//#define B_TIMING_ON B_PRESS_T
-#define B_TIMING_ON B_RELEASE_T
-#endif
-#ifndef B_TIMING_OFF
-//#define B_TIMING_OFF B_RELEASE_T
-#define B_TIMING_OFF B_TIMEOUT_T
-#endif
+/********* Include all the regular app headers *********/
+#include "off-mode.h"
+#include "ramp-mode.h"
+#include "load-save-config.h"
+#include "config-mode.h"
+#include "aux-leds.h"
+#include "misc.h"
-// FSM states
-uint8_t off_state(Event event, uint16_t arg);
-// simple numeric entry config menu
-uint8_t config_state_base(Event event, uint16_t arg,
- uint8_t num_config_steps,
- void (*savefunc)());
-#define MAX_CONFIG_VALUES 3
-uint8_t config_state_values[MAX_CONFIG_VALUES];
-// ramping mode and its related config mode
-uint8_t steady_state(Event event, uint16_t arg);
-#ifdef USE_RAMP_CONFIG
-uint8_t ramp_config_state(Event event, uint16_t arg);
-#endif
-#ifdef USE_TINT_RAMPING
-// not actually a mode, more of a fallback under other modes
-uint8_t tint_ramping_state(Event event, uint16_t arg);
-#endif
-// party and tactical strobes
-#ifdef USE_STROBE_STATE
-uint8_t strobe_state(Event event, uint16_t arg);
-#endif
-#ifdef USE_BORING_STROBE_STATE
-uint8_t boring_strobe_state(Event event, uint16_t arg);
-volatile uint8_t boring_strobe_type = 0;
-void sos_blink(uint8_t num, uint8_t dah);
-#define NUM_BORING_STROBES 2
-#endif
-#ifdef USE_BATTCHECK
-uint8_t battcheck_state(Event event, uint16_t arg);
-#endif
-#ifdef USE_THERMAL_REGULATION
-#define USE_BLINK_NUM
-uint8_t tempcheck_state(Event event, uint16_t arg);
-uint8_t thermal_config_state(Event event, uint16_t arg);
-#endif
-#ifdef USE_GOODNIGHT_MODE
-// 1-hour ramp down from low, then automatic off
-uint8_t goodnight_state(Event event, uint16_t arg);
-#endif
-#ifdef USE_BEACON_MODE
-// beacon mode and its related config mode
-uint8_t beacon_state(Event event, uint16_t arg);
-uint8_t beacon_config_state(Event event, uint16_t arg);
-#endif
-#ifdef USE_SOS_MODE_IN_BLINKY_GROUP
-// automatic SOS emergency signal
-uint8_t sos_state(Event event, uint16_t arg);
-#endif
-// soft lockout
-#define MOON_DURING_LOCKOUT_MODE
-// if enabled, 2nd lockout click goes to the other ramp's floor level
-#define LOCKOUT_MOON_FANCY
-uint8_t lockout_state(Event event, uint16_t arg);
-#ifdef USE_MOMENTARY_MODE
-// momentary / signalling mode
-uint8_t momentary_state(Event event, uint16_t arg);
-uint8_t momentary_mode = 0; // 0 = ramping, 1 = strobe
-uint8_t momentary_active = 0; // boolean, true if active *right now*
-#endif
-#ifdef USE_MUGGLE_MODE
-// muggle mode, super-simple, hard to exit
-uint8_t muggle_state(Event event, uint16_t arg);
-uint8_t muggle_mode_active = 0;
+#ifdef USE_SUNSET_TIMER
+#include "sunset-timer.h"
#endif
-// general helper function for config modes
-uint8_t number_entry_state(Event event, uint16_t arg);
-// return value from number_entry_state()
-volatile uint8_t number_entry_value;
-
-void blink_confirm(uint8_t num);
-void blip();
-#if defined(USE_INDICATOR_LED) && defined(TICK_DURING_STANDBY)
-void indicator_blink(uint8_t arg);
-#endif
-#if defined(USE_AUX_RGB_LEDS) && defined(TICK_DURING_STANDBY)
-uint8_t setting_rgb_mode_now = 0;
-void rgb_led_update(uint8_t mode, uint8_t arg);
-void rgb_led_voltage_readout(uint8_t bright);
-/*
- * 0: R
- * 1: RG
- * 2: G
- * 3: GB
- * 4: B
- * 5: R B
- * 6: RGB
- * 7: rainbow
- * 8: voltage
- */
-const PROGMEM uint8_t rgb_led_colors[] = {
- 0b00000001, // 0: red
- 0b00000101, // 1: yellow
- 0b00000100, // 2: green
- 0b00010100, // 3: cyan
- 0b00010000, // 4: blue
- 0b00010001, // 5: purple
- 0b00010101, // 6: white
-};
-#define RGB_LED_NUM_COLORS 10
-#define RGB_LED_NUM_PATTERNS 4
-#ifndef RGB_LED_OFF_DEFAULT
-//#define RGB_LED_OFF_DEFAULT 0x18 // low, voltage
-#define RGB_LED_OFF_DEFAULT 0x17 // low, rainbow
-#endif
-#ifndef RGB_LED_LOCKOUT_DEFAULT
-#define RGB_LED_LOCKOUT_DEFAULT 0x37 // blinking, rainbow
-#endif
-#ifndef RGB_RAINBOW_SPEED
-#define RGB_RAINBOW_SPEED 0x0f // change color every 16 frames
-#endif
-uint8_t rgb_led_off_mode = RGB_LED_OFF_DEFAULT;
-uint8_t rgb_led_lockout_mode = RGB_LED_LOCKOUT_DEFAULT;
+#ifdef USE_VERSION_CHECK
+#include "version-check-mode.h"
#endif
-#ifdef USE_FACTORY_RESET
-void factory_reset();
+#ifdef USE_BATTCHECK_MODE
+#include "battcheck-mode.h"
#endif
-// remember stuff even after battery was changed
-void load_config();
-void save_config();
-#ifdef START_AT_MEMORIZED_LEVEL
-void save_config_wl();
+#ifdef USE_BEACON_MODE
+#include "beacon-mode.h"
#endif
-// default ramp options if not overridden earlier per-driver
-#ifndef RAMP_STYLE
-#define RAMP_STYLE 0 // smooth default
-#endif
-#ifndef RAMP_SMOOTH_FLOOR
- #define RAMP_SMOOTH_FLOOR 1
-#endif
-#ifndef RAMP_SMOOTH_CEIL
- #if PWM_CHANNELS == 3
- #define RAMP_SMOOTH_CEIL MAX_Nx7135
- #else
- #define RAMP_SMOOTH_CEIL MAX_LEVEL - 30
- #endif
-#endif
-#ifndef RAMP_DISCRETE_FLOOR
- #define RAMP_DISCRETE_FLOOR 20
-#endif
-#ifndef RAMP_DISCRETE_CEIL
- #define RAMP_DISCRETE_CEIL RAMP_SMOOTH_CEIL
+#ifdef USE_THERMAL_REGULATION
+#include "tempcheck-mode.h"
#endif
-#ifndef RAMP_DISCRETE_STEPS
- #define RAMP_DISCRETE_STEPS 7
+
+#ifdef USE_LOCKOUT_MODE
+#include "lockout-mode.h"
#endif
-// mile marker(s) partway up the ramp
-// default: blink only at border between regulated and FET
-#ifdef BLINK_AT_RAMP_MIDDLE
- #if PWM_CHANNELS >= 3
- #ifndef BLINK_AT_RAMP_MIDDLE_1
- #define BLINK_AT_RAMP_MIDDLE_1 MAX_Nx7135
- #ifndef BLINK_AT_RAMP_MIDDLE_2
- #define BLINK_AT_RAMP_MIDDLE_2 MAX_1x7135
- #endif
- #endif
- #else
- #ifndef BLINK_AT_RAMP_MIDDLE_1
- #define BLINK_AT_RAMP_MIDDLE_1 MAX_1x7135
- #endif
- #endif
+#ifdef USE_MOMENTARY_MODE
+#include "momentary-mode.h"
#endif
-// brightness control
-uint8_t memorized_level = DEFAULT_LEVEL;
-#ifdef USE_MANUAL_MEMORY
-uint8_t manual_memory = 0;
+#ifdef USE_TINT_RAMPING
+#include "tint-ramping.h"
#endif
-// smooth vs discrete ramping
-volatile uint8_t ramp_style = RAMP_STYLE; // 0 = smooth, 1 = discrete
-volatile uint8_t ramp_smooth_floor = RAMP_SMOOTH_FLOOR;
-volatile uint8_t ramp_smooth_ceil = RAMP_SMOOTH_CEIL;
-volatile uint8_t ramp_discrete_floor = RAMP_DISCRETE_FLOOR;
-volatile uint8_t ramp_discrete_ceil = RAMP_DISCRETE_CEIL;
-volatile uint8_t ramp_discrete_steps = RAMP_DISCRETE_STEPS;
-uint8_t ramp_discrete_step_size; // don't set this
-
-#ifdef USE_INDICATOR_LED
- // bits 2-3 control lockout mode
- // bits 0-1 control "off" mode
- // modes are: 0=off, 1=low, 2=high, 3=blinking (if TICK_DURING_STANDBY enabled)
- #ifdef INDICATOR_LED_DEFAULT_MODE
- uint8_t indicator_led_mode = INDICATOR_LED_DEFAULT_MODE;
- #else
- #ifdef USE_INDICATOR_LED_WHILE_RAMPING
- //uint8_t indicator_led_mode = (1<<2) + 2;
- uint8_t indicator_led_mode = (2<<2) + 1;
- #else
- uint8_t indicator_led_mode = (3<<2) + 1;
- #endif
- #endif
+
+#ifdef USE_FACTORY_RESET
+#include "factory-reset.h"
#endif
-// calculate the nearest ramp level which would be valid at the moment
-// (is a no-op for smooth ramp, but limits discrete ramp to only the
-// correct levels for the user's config)
-uint8_t nearest_level(int16_t target);
+// this one detects its own enable/disable settings
+#include "strobe-modes.h"
-#ifdef USE_THERMAL_REGULATION
-// brightness before thermal step-down
-uint8_t target_level = 0;
-void set_level_and_therm_target(uint8_t level);
-#else
-#define set_level_and_therm_target(level) set_level(level)
+#ifdef USE_SOS_MODE
+#include "sos-mode.h"
#endif
-// internal numbering for strobe modes
-#ifdef USE_STROBE_STATE
-typedef enum {
- #ifdef USE_PARTY_STROBE_MODE
- party_strobe_e,
- #endif
- #ifdef USE_TACTICAL_STROBE_MODE
- tactical_strobe_e,
- #endif
- #ifdef USE_LIGHTNING_MODE
- lightning_storm_e,
- #endif
- #ifdef USE_CANDLE_MODE
- candle_mode_e,
- #endif
- #ifdef USE_BIKE_FLASHER_MODE
- bike_flasher_e,
- #endif
- strobe_mode_END
-} strobe_mode_te;
-const int NUM_STROBES = strobe_mode_END;
+/********* Include all the app logic source files *********/
+// (is a bit weird to do things this way,
+// but it saves a lot of space by letting us use the -fwhole-program flag)
-// which strobe mode is active?
-#ifdef USE_CANDLE_MODE
-volatile strobe_mode_te strobe_type = candle_mode_e;
-#else
-volatile strobe_mode_te strobe_type = 0;
-#endif
-#endif
+#include "off-mode.c"
+#include "ramp-mode.c"
+#include "load-save-config.c"
+#include "config-mode.c"
+#include "aux-leds.c"
+#include "misc.c"
-#if defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE)
-// party / tactical strobe timing
-volatile uint8_t strobe_delays[] = { 41, 67 }; // party strobe 24 Hz, tactical strobe 10 Hz
+#ifdef USE_SUNSET_TIMER
+#include "sunset-timer.c"
#endif
-// bike mode config options
-#ifdef USE_BIKE_FLASHER_MODE
-volatile uint8_t bike_flasher_brightness = MAX_1x7135;
+#ifdef USE_VERSION_CHECK
+#include "version-check-mode.c"
#endif
-#ifdef USE_CANDLE_MODE
-uint8_t candle_mode_state(Event event, uint16_t arg);
-uint8_t triangle_wave(uint8_t phase);
-#ifndef CANDLE_AMPLITUDE
-#define CANDLE_AMPLITUDE 25
-#endif
+#ifdef USE_BATTCHECK_MODE
+#include "battcheck-mode.c"
#endif
#ifdef USE_BEACON_MODE
-// beacon timing
-volatile uint8_t beacon_seconds = 2;
+#include "beacon-mode.c"
#endif
-#ifdef USE_VERSION_CHECK
-#define USE_BLINK_DIGIT
-#include "version.h"
-const PROGMEM uint8_t version_number[] = VERSION_NUMBER;
-uint8_t version_check_state(Event event, uint16_t arg);
+#ifdef USE_THERMAL_REGULATION
+#include "tempcheck-mode.c"
#endif
-uint8_t off_state(Event event, uint16_t arg) {
- // turn emitter off when entering state
- if (event == EV_enter_state) {
- set_level(0);
- #ifdef USE_INDICATOR_LED
- indicator_led(indicator_led_mode & 0x03);
- #elif defined(USE_AUX_RGB_LEDS)
- rgb_led_update(rgb_led_off_mode, 0);
- #endif
- // sleep while off (lower power use)
- // (unless delay requested; give the ADC some time to catch up)
- if (! arg) { go_to_standby = 1; }
- return MISCHIEF_MANAGED;
- }
- // go back to sleep eventually if we got bumped but didn't leave "off" state
- else if (event == EV_tick) {
- if (arg > HOLD_TIMEOUT) {
- go_to_standby = 1;
- #ifdef USE_INDICATOR_LED
- indicator_led(indicator_led_mode & 0x03);
- #elif defined(USE_AUX_RGB_LEDS)
- rgb_led_update(rgb_led_off_mode, arg);
- #endif
- }
- return MISCHIEF_MANAGED;
- }
- #if defined(TICK_DURING_STANDBY) && (defined(USE_INDICATOR_LED) || defined(USE_AUX_RGB_LEDS))
- // blink the indicator LED, maybe
- else if (event == EV_sleep_tick) {
- #ifdef USE_INDICATOR_LED
- if ((indicator_led_mode & 0b00000011) == 0b00000011) {
- indicator_blink(arg);
- }
- #elif defined(USE_AUX_RGB_LEDS)
- rgb_led_update(rgb_led_off_mode, arg);
- #endif
- return MISCHIEF_MANAGED;
- }
- #endif
- #if (B_TIMING_ON == B_PRESS_T)
- // hold (initially): go to lowest level (floor), but allow abort for regular click
- else if (event == EV_click1_press) {
- set_level(nearest_level(1));
- return MISCHIEF_MANAGED;
- }
- #endif // B_TIMING_ON == B_PRESS_T
- // hold: go to lowest level
- else if (event == EV_click1_hold) {
- #if (B_TIMING_ON == B_PRESS_T)
- #ifdef MOON_TIMING_HINT
- if (arg == 0) {
- // let the user know they can let go now to stay at moon
- blip();
- } else
- #endif
- #else // B_RELEASE_T or B_TIMEOUT_T
- set_level(nearest_level(1));
- #endif
- // don't start ramping immediately;
- // give the user time to release at moon level
- //if (arg >= HOLD_TIMEOUT) { // smaller
- if (arg >= (!ramp_style) * HOLD_TIMEOUT) { // more consistent
- set_state(steady_state, 1);
- }
- return MISCHIEF_MANAGED;
- }
- // hold, release quickly: go to lowest level (floor)
- else if (event == EV_click1_hold_release) {
- set_state(steady_state, 1);
- return MISCHIEF_MANAGED;
- }
- #if (B_TIMING_ON != B_TIMEOUT_T)
- // 1 click (before timeout): go to memorized level, but allow abort for double click
- else if (event == EV_click1_release) {
- #ifdef USE_MANUAL_MEMORY
- if (manual_memory)
- set_level(nearest_level(manual_memory));
- else
- #endif
- set_level(nearest_level(memorized_level));
- return MISCHIEF_MANAGED;
- }
- #endif // if (B_TIMING_ON != B_TIMEOUT_T)
- // 1 click: regular mode
- else if (event == EV_1click) {
- #ifdef USE_MANUAL_MEMORY
- if (manual_memory)
- set_state(steady_state, manual_memory);
- else
- #endif
- set_state(steady_state, memorized_level);
- return MISCHIEF_MANAGED;
- }
- // click, hold: go to highest level (ceiling) (for ramping down)
- else if (event == EV_click2_hold) {
- set_state(steady_state, MAX_LEVEL);
- return MISCHIEF_MANAGED;
- }
- // 2 clicks: highest mode (ceiling)
- else if (event == EV_2clicks) {
- set_state(steady_state, MAX_LEVEL);
- return MISCHIEF_MANAGED;
- }
- // 3 clicks (initial press): off, to prep for later events
- else if (event == EV_click3_press) {
- set_level(0);
- return MISCHIEF_MANAGED;
- }
- #ifdef USE_BATTCHECK
- // 3 clicks: battcheck mode / blinky mode group 1
- else if (event == EV_3clicks) {
- set_state(battcheck_state, 0);
- return MISCHIEF_MANAGED;
- }
- #endif
- // click, click, long-click: strobe mode
- #ifdef USE_STROBE_STATE
- else if (event == EV_click3_hold) {
- set_state(strobe_state, 0);
- return MISCHIEF_MANAGED;
- }
- #elif defined(USE_BORING_STROBE_STATE)
- else if (event == EV_click3_hold) {
- set_state(boring_strobe_state, 0);
- return MISCHIEF_MANAGED;
- }
- #endif
- // 4 clicks: soft lockout
- else if (event == EV_4clicks) {
- blink_confirm(2);
- set_state(lockout_state, 0);
- return MISCHIEF_MANAGED;
- }
- #ifdef USE_MOMENTARY_MODE
- // 5 clicks: momentary mode
- else if (event == EV_5clicks) {
- blink_confirm(1);
- set_state(momentary_state, 0);
- return MISCHIEF_MANAGED;
- }
- #endif
- #ifdef USE_MUGGLE_MODE
- // 6 clicks: muggle mode
- else if (event == EV_6clicks) {
- blink_confirm(1);
- set_state(muggle_state, 0);
- return MISCHIEF_MANAGED;
- }
- #endif
- #ifdef USE_INDICATOR_LED
- // 7 clicks: change indicator LED mode
- else if (event == EV_7clicks) {
- uint8_t mode = (indicator_led_mode & 3) + 1;
- #ifdef TICK_DURING_STANDBY
- mode = mode & 3;
- #else
- mode = mode % 3;
- #endif
- #ifdef INDICATOR_LED_SKIP_LOW
- if (mode == 1) { mode ++; }
- #endif
- indicator_led_mode = (indicator_led_mode & 0b11111100) | mode;
- indicator_led(mode);
- save_config();
- return MISCHIEF_MANAGED;
- }
- #elif defined(USE_AUX_RGB_LEDS)
- // 7 clicks: change RGB aux LED pattern
- else if (event == EV_7clicks) {
- uint8_t mode = (rgb_led_off_mode >> 4) + 1;
- mode = mode % RGB_LED_NUM_PATTERNS;
- rgb_led_off_mode = (mode << 4) | (rgb_led_off_mode & 0x0f);
- rgb_led_update(rgb_led_off_mode, 0);
- save_config();
- blink_confirm(1);
- return MISCHIEF_MANAGED;
- }
- // 7 clicks (hold last): change RGB aux LED color
- else if (event == EV_click7_hold) {
- setting_rgb_mode_now = 1;
- if (0 == (arg & 0x3f)) {
- uint8_t mode = (rgb_led_off_mode & 0x0f) + 1;
- mode = mode % RGB_LED_NUM_COLORS;
- rgb_led_off_mode = mode | (rgb_led_off_mode & 0xf0);
- //save_config();
- }
- rgb_led_update(rgb_led_off_mode, arg);
- return MISCHIEF_MANAGED;
- }
- else if (event == EV_click7_hold_release) {
- setting_rgb_mode_now = 0;
- save_config();
- return MISCHIEF_MANAGED;
- }
- #endif // end 7 clicks
- #if defined(USE_TENCLICK_THERMAL_CONFIG) && defined(USE_THERMAL_REGULATION)
- // 10 clicks: thermal config mode
- else if (event == EV_10clicks) {
- push_state(thermal_config_state, 0);
- return MISCHIEF_MANAGED;
- }
- #endif
- #ifdef USE_VERSION_CHECK
- // 15+ clicks: show the version number
- else if (event == EV_15clicks) {
- set_state(version_check_state, 0);
- return MISCHIEF_MANAGED;
- }
- #endif
- #if defined(USE_FACTORY_RESET) && defined(USE_SOFT_FACTORY_RESET)
- // 13 clicks and hold the last click: invoke factory reset (reboot)
- else if (event == EV_click13_hold) {
- reboot();
- return MISCHIEF_MANAGED;
- }
- #endif
- return EVENT_NOT_HANDLED;
-}
-
-
-uint8_t steady_state(Event event, uint16_t arg) {
- uint8_t mode_min = ramp_smooth_floor;
- uint8_t mode_max = ramp_smooth_ceil;
- uint8_t ramp_step_size = 1;
- #ifdef USE_REVERSING
- static int8_t ramp_direction = 1;
- #endif
- #if (B_TIMING_OFF == B_RELEASE_T)
- // if the user double clicks, we need to abort turning off,
- // and this stores the level to return to
- static uint8_t level_before_off = 0;
- #endif
- if (ramp_style) {
- mode_min = ramp_discrete_floor;
- mode_max = ramp_discrete_ceil;
- ramp_step_size = ramp_discrete_step_size;
- }
-
- // turn LED on when we first enter the mode
- if ((event == EV_enter_state) || (event == EV_reenter_state)) {
- #if defined(USE_MOMENTARY_MODE) && defined(USE_STROBE_STATE)
- momentary_mode = 0; // 0 = ramping, 1 = strobes
- #endif
- // if we just got back from config mode, go back to memorized level
- if (event == EV_reenter_state) {
- arg = memorized_level;
- }
- // remember this level, unless it's moon or turbo
- if ((arg > mode_min) && (arg < mode_max))
- memorized_level = arg;
- // use the requested level even if not memorized
- arg = nearest_level(arg);
- set_level_and_therm_target(arg);
- #ifdef USE_REVERSING
- ramp_direction = 1;
- #endif
- return MISCHIEF_MANAGED;
- }
- #if (B_TIMING_OFF == B_RELEASE_T)
- // 1 click (early): off, if configured for early response
- else if (event == EV_click1_release) {
- level_before_off = actual_level;
- set_level_and_therm_target(0);
- return MISCHIEF_MANAGED;
- }
- // 2 clicks (early): abort turning off, if configured for early response
- else if (event == EV_click2_press) {
- set_level_and_therm_target(level_before_off);
- return MISCHIEF_MANAGED;
- }
- #endif // if (B_TIMING_OFF == B_RELEASE_T)
- // 1 click: off
- else if (event == EV_1click) {
- set_state(off_state, 0);
- return MISCHIEF_MANAGED;
- }
- // 2 clicks: go to/from highest level
- else if (event == EV_2clicks) {
- if (actual_level < MAX_LEVEL) {
- // true turbo, not the mode-specific ceiling
- set_level_and_therm_target(MAX_LEVEL);
- }
- else {
- set_level_and_therm_target(memorized_level);
- }
- return MISCHIEF_MANAGED;
- }
- // 3 clicks: toggle smooth vs discrete ramping
- else if (event == EV_3clicks) {
- ramp_style = !ramp_style;
- save_config();
- #ifdef START_AT_MEMORIZED_LEVEL
- save_config_wl();
- #endif
- blip();
- memorized_level = nearest_level(actual_level);
- set_level_and_therm_target(memorized_level);
- return MISCHIEF_MANAGED;
- }
- #ifdef USE_RAMP_CONFIG
- // 4 clicks: configure this ramp mode
- else if (event == EV_4clicks) {
- push_state(ramp_config_state, 0);
- return MISCHIEF_MANAGED;
- }
- #endif
- // hold: change brightness (brighter)
- else if (event == EV_click1_hold) {
- // ramp slower in discrete mode
- if (ramp_style && (arg % HOLD_TIMEOUT != 0)) {
- return MISCHIEF_MANAGED;
- }
- #ifdef USE_REVERSING
- // fix ramp direction on first frame if necessary
- if (!arg) {
- // make it ramp down instead, if already at max
- if (actual_level >= mode_max) { ramp_direction = -1; }
- // make it ramp up if already at min
- // (off->hold->stepped_min->release causes this state)
- else if (actual_level <= mode_min) { ramp_direction = 1; }
- }
- // if the button is stuck, err on the side of safety and ramp down
- else if ((arg > TICKS_PER_SECOND * 5) && (actual_level >= mode_max)) {
- ramp_direction = -1;
- }
- // if the button is still stuck, lock the light
- else if ((arg > TICKS_PER_SECOND * 10) && (actual_level <= mode_min)) {
- blip();
- set_state(lockout_state, 0);
- }
- memorized_level = nearest_level((int16_t)actual_level \
- + (ramp_step_size * ramp_direction));
- #else
- memorized_level = nearest_level((int16_t)actual_level + ramp_step_size);
- #endif
- #if defined(BLINK_AT_RAMP_CEILING) || defined(BLINK_AT_RAMP_MIDDLE)
- // only blink once for each threshold
- if ((memorized_level != actual_level) && (
- 0 // for easier syntax below
- #ifdef BLINK_AT_RAMP_MIDDLE_1
- || (memorized_level == BLINK_AT_RAMP_MIDDLE_1)
- #endif
- #ifdef BLINK_AT_RAMP_MIDDLE_2
- || (memorized_level == BLINK_AT_RAMP_MIDDLE_2)
- #endif
- #ifdef BLINK_AT_RAMP_CEILING
- || (memorized_level == mode_max)
- #endif
- #if defined(USE_REVERSING) && defined(BLINK_AT_RAMP_FLOOR)
- || (memorized_level == mode_min)
- #endif
- )) {
- blip();
- }
- #endif
- #if defined(BLINK_AT_STEPS)
- uint8_t foo = ramp_style;
- ramp_style = 1;
- uint8_t nearest = nearest_level((int16_t)actual_level);
- ramp_style = foo;
- // only blink once for each threshold
- if ((memorized_level != actual_level) &&
- (ramp_style == 0) &&
- (memorized_level == nearest)
- )
- {
- blip();
- }
- #endif
- set_level_and_therm_target(memorized_level);
- return MISCHIEF_MANAGED;
- }
- #if defined(USE_REVERSING) || defined(START_AT_MEMORIZED_LEVEL)
- // reverse ramp direction on hold release
- else if (event == EV_click1_hold_release) {
- #ifdef USE_REVERSING
- ramp_direction = -ramp_direction;
- #endif
- #ifdef START_AT_MEMORIZED_LEVEL
- save_config_wl();
- #endif
- return MISCHIEF_MANAGED;
- }
- #endif
- // click, hold: change brightness (dimmer)
- else if (event == EV_click2_hold) {
- #ifdef USE_REVERSING
- ramp_direction = 1;
- #endif
- // ramp slower in discrete mode
- if (ramp_style && (arg % HOLD_TIMEOUT != 0)) {
- return MISCHIEF_MANAGED;
- }
- // TODO? make it ramp up instead, if already at min?
- memorized_level = nearest_level((int16_t)actual_level - ramp_step_size);
- #if defined(BLINK_AT_RAMP_FLOOR) || defined(BLINK_AT_RAMP_MIDDLE)
- // only blink once for each threshold
- if ((memorized_level != actual_level) && (
- 0 // for easier syntax below
- #ifdef BLINK_AT_RAMP_MIDDLE_1
- || (memorized_level == BLINK_AT_RAMP_MIDDLE_1)
- #endif
- #ifdef BLINK_AT_RAMP_MIDDLE_2
- || (memorized_level == BLINK_AT_RAMP_MIDDLE_2)
- #endif
- #ifdef BLINK_AT_RAMP_FLOOR
- || (memorized_level == mode_min)
- #endif
- )) {
- blip();
- }
- #endif
- #if defined(BLINK_AT_STEPS)
- uint8_t foo = ramp_style;
- ramp_style = 1;
- uint8_t nearest = nearest_level((int16_t)actual_level);
- ramp_style = foo;
- // only blink once for each threshold
- if ((memorized_level != actual_level) &&
- (ramp_style == 0) &&
- (memorized_level == nearest)
- )
- {
- blip();
- }
- #endif
- set_level_and_therm_target(memorized_level);
- return MISCHIEF_MANAGED;
- }
- #ifdef START_AT_MEMORIZED_LEVEL
- // click, release, hold, release: save new ramp level (if necessary)
- else if (event == EV_click2_hold_release) {
- save_config_wl();
- return MISCHIEF_MANAGED;
- }
- #endif
- #ifdef USE_MANUAL_MEMORY
- else if (event == EV_5clicks) {
- manual_memory = actual_level;
- save_config();
- blip();
- }
- else if (event == EV_click5_hold) {
- if (0 == arg) {
- manual_memory = 0;
- save_config();
- blip();
- }
- }
- #endif
- #if defined(USE_SET_LEVEL_GRADUALLY) || defined(USE_REVERSING)
- else if (event == EV_tick) {
- #ifdef USE_REVERSING
- // un-reverse after 1 second
- if (arg == TICKS_PER_SECOND) ramp_direction = 1;
- #endif
- #ifdef USE_SET_LEVEL_GRADUALLY
- int16_t diff = gradual_target - actual_level;
- static uint16_t ticks_since_adjust = 0;
- ticks_since_adjust++;
- if (diff) {
- uint16_t ticks_per_adjust = 256;
- if (diff < 0) {
- //diff = -diff;
- if (actual_level > THERM_FASTER_LEVEL) {
- #ifdef THERM_HARD_TURBO_DROP
- ticks_per_adjust >>= 2;
- #endif
- ticks_per_adjust >>= 2;
- }
- } else {
- // rise at half speed
- ticks_per_adjust <<= 1;
- }
- while (diff) {
- ticks_per_adjust >>= 1;
- //diff >>= 1;
- diff /= 2; // because shifting produces weird behavior
- }
- if (ticks_since_adjust > ticks_per_adjust)
- {
- gradual_tick();
- ticks_since_adjust = 0;
- }
- }
- #endif // ifdef USE_SET_LEVEL_GRADUALLY
- return MISCHIEF_MANAGED;
- }
- #endif
- #ifdef USE_THERMAL_REGULATION
- // overheating: drop by an amount proportional to how far we are above the ceiling
- else if (event == EV_temperature_high) {
- #if 0
- blip();
- #endif
- #ifdef THERM_HARD_TURBO_DROP
- //if (actual_level > THERM_FASTER_LEVEL) {
- if (actual_level == MAX_LEVEL) {
- #ifdef USE_SET_LEVEL_GRADUALLY
- set_level_gradually(THERM_FASTER_LEVEL);
- target_level = THERM_FASTER_LEVEL;
- #else
- set_level_and_therm_target(THERM_FASTER_LEVEL);
- #endif
- } else
- #endif
- if (actual_level > MIN_THERM_STEPDOWN) {
- int16_t stepdown = actual_level - arg;
- if (stepdown < MIN_THERM_STEPDOWN) stepdown = MIN_THERM_STEPDOWN;
- else if (stepdown > MAX_LEVEL) stepdown = MAX_LEVEL;
- #ifdef USE_SET_LEVEL_GRADUALLY
- set_level_gradually(stepdown);
- #else
- set_level(stepdown);
- #endif
- }
- return MISCHIEF_MANAGED;
- }
- // underheating: increase slowly if we're lower than the target
- // (proportional to how low we are)
- else if (event == EV_temperature_low) {
- #if 0
- blip();
- #endif
- if (actual_level < target_level) {
- //int16_t stepup = actual_level + (arg>>1);
- int16_t stepup = actual_level + arg;
- if (stepup > target_level) stepup = target_level;
- else if (stepup < MIN_THERM_STEPDOWN) stepup = MIN_THERM_STEPDOWN;
- #ifdef USE_SET_LEVEL_GRADUALLY
- set_level_gradually(stepup);
- #else
- set_level(stepup);
- #endif
- }
- return MISCHIEF_MANAGED;
- }
- #ifdef USE_SET_LEVEL_GRADUALLY
- // temperature is within target window
- // (so stop trying to adjust output)
- else if (event == EV_temperature_okay) {
- // if we're still adjusting output... stop after the current step
- if (gradual_target > actual_level)
- gradual_target = actual_level + 1;
- else if (gradual_target < actual_level)
- gradual_target = actual_level - 1;
- return MISCHIEF_MANAGED;
- }
- #endif // ifdef USE_SET_LEVEL_GRADUALLY
- #endif // ifdef USE_THERMAL_REGULATION
- return EVENT_NOT_HANDLED;
-}
+#ifdef USE_LOCKOUT_MODE
+#include "lockout-mode.c"
+#endif
+#ifdef USE_MOMENTARY_MODE
+#include "momentary-mode.c"
+#endif
#ifdef USE_TINT_RAMPING
-uint8_t tint_ramping_state(Event event, uint16_t arg) {
- static int8_t tint_ramp_direction = 1;
- static uint8_t prev_tint = 0;
- // don't activate auto-tint modes unless the user hits the edge
- // and keeps pressing for a while
- static uint8_t past_edge_counter = 0;
- // bugfix: click-click-hold from off to strobes would invoke tint ramping
- // in addition to changing state... so ignore any tint-ramp events which
- // don't look like they were meant to be here
- static uint8_t active = 0;
-
- // click, click, hold: change the tint
- if (event == EV_click3_hold) {
- // reset at beginning of movement
- if (! arg) {
- active = 1; // first frame means this is for us
- past_edge_counter = 0; // doesn't start until user hits the edge
- }
- // ignore event if we weren't the ones who handled the first frame
- if (! active) return EVENT_HANDLED;
-
- // change normal tints
- if ((tint_ramp_direction > 0) && (tint < 254)) {
- tint += 1;
- }
- else if ((tint_ramp_direction < 0) && (tint > 1)) {
- tint -= 1;
- }
- // if the user kept pressing long enough, go the final step
- if (past_edge_counter == 64) {
- past_edge_counter ++;
- tint ^= 1; // 0 -> 1, 254 -> 255
- blip();
- }
- // if tint change stalled, let user know we hit the edge
- else if (prev_tint == tint) {
- if (past_edge_counter == 0) blip();
- // count up but don't wrap back to zero
- if (past_edge_counter < 255) past_edge_counter ++;
- }
- prev_tint = tint;
- set_level(actual_level);
- return EVENT_HANDLED;
- }
-
- // click, click, hold, release: reverse direction for next ramp
- else if (event == EV_click3_hold_release) {
- active = 0; // ignore next hold if it wasn't meant for us
- // reverse
- tint_ramp_direction = -tint_ramp_direction;
- if (tint == 0) tint_ramp_direction = 1;
- else if (tint == 255) tint_ramp_direction = -1;
- // remember tint after battery change
- save_config();
- return EVENT_HANDLED;
- }
-
- return EVENT_NOT_HANDLED;
-}
-#endif // ifdef USE_TINT_RAMPING
+#include "tint-ramping.c"
+#endif
+#ifdef USE_FACTORY_RESET
+#include "factory-reset.c"
+#endif
#ifdef USE_STROBE_STATE
-uint8_t strobe_state(Event event, uint16_t arg) {
- static int8_t ramp_direction = 1;
+#include "strobe-modes.c"
+#endif
- // 'st' reduces ROM size by avoiding access to a volatile var
- // (maybe I should just make it nonvolatile?)
- strobe_mode_te st = strobe_type;
+#ifdef USE_SOS_MODE
+#include "sos-mode.c"
+#endif
- #ifdef USE_MOMENTARY_MODE
- momentary_mode = 1; // 0 = ramping, 1 = strobes
- #endif
- #ifdef USE_CANDLE_MODE
- // pass all events to candle mode, when it's active
- // (the code is in its own pseudo-state to keep things cleaner)
- if (st == candle_mode_e) {
- candle_mode_state(event, arg);
- }
- #endif
+// runs one time at boot, when power is connected
+void setup() {
- if (0) {} // placeholder
- // init anything which needs to be initialized
- else if (event == EV_enter_state) {
- ramp_direction = 1;
- return MISCHIEF_MANAGED;
- }
- // 1 click: off
- else if (event == EV_1click) {
- set_state(off_state, 0);
- return MISCHIEF_MANAGED;
- }
- // 2 clicks: rotate through strobe/flasher modes
- else if (event == EV_2clicks) {
- strobe_type = (st + 1) % NUM_STROBES;
- save_config();
- return MISCHIEF_MANAGED;
- }
- // hold: change speed (go faster)
- // or change brightness (brighter)
- else if (event == EV_click1_hold) {
- if (0) {} // placeholder
-
- // party / tactical strobe faster
- #if defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE)
- #ifdef USE_TACTICAL_STROBE_MODE
- else if (st <= tactical_strobe_e) {
- #else
- else if (st == party_strobe_e) {
- #endif
- if ((arg & 1) == 0) {
- uint8_t d = strobe_delays[st];
- d -= ramp_direction;
- if (d < 8) d = 8;
- else if (d > 254) d = 254;
- strobe_delays[st] = d;
- }
- }
- #endif
+ #ifndef START_AT_MEMORIZED_LEVEL
- // lightning has no adjustments
- //else if (st == lightning_storm_e) {}
+ // regular e-switch light, no hard clicky power button
- // biking mode brighter
- #ifdef USE_BIKE_FLASHER_MODE
- else if (st == bike_flasher_e) {
- bike_flasher_brightness += ramp_direction;
- if (bike_flasher_brightness < 2) bike_flasher_brightness = 2;
- else if (bike_flasher_brightness > MAX_BIKING_LEVEL) bike_flasher_brightness = MAX_BIKING_LEVEL;
- set_level(bike_flasher_brightness);
- }
- #endif
+ // blink at power-on to let user know power is connected
+ blink_once();
- return MISCHIEF_MANAGED;
- }
- // reverse ramp direction on hold release
- // ... and save new strobe settings
- else if (event == EV_click1_hold_release) {
- ramp_direction = -ramp_direction;
- save_config();
- return MISCHIEF_MANAGED;
- }
- // click, hold: change speed (go slower)
- // or change brightness (dimmer)
- else if (event == EV_click2_hold) {
- ramp_direction = 1;
-
- if (0) {} // placeholder
-
- // party / tactical strobe slower
- #if defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE)
- #ifdef USE_TACTICAL_STROBE_MODE
- else if (st <= tactical_strobe_e) {
- #else
- else if (st == party_strobe_e) {
- #endif
- if ((arg & 1) == 0) {
- if (strobe_delays[st] < 255) strobe_delays[st] ++;
- }
- }
+ #ifdef USE_FACTORY_RESET
+ if (button_is_pressed())
+ factory_reset();
#endif
- // lightning has no adjustments
- //else if (st == lightning_storm_e) {}
+ load_config();
- // biking mode dimmer
- #ifdef USE_BIKE_FLASHER_MODE
- else if (st == bike_flasher_e) {
- if (bike_flasher_brightness > 2)
- bike_flasher_brightness --;
- set_level(bike_flasher_brightness);
- }
+ #if defined(USE_MANUAL_MEMORY) && defined(USE_MANUAL_MEMORY_TIMER)
+ // without this, initial boot-up brightness is wrong
+ // when manual mem is enabled with a non-zero timer
+ if (manual_memory) memorized_level = manual_memory;
#endif
- return MISCHIEF_MANAGED;
- }
- // release hold: save new strobe settings
- else if (event == EV_click2_hold_release) {
- save_config();
- return MISCHIEF_MANAGED;
- }
- #if defined(USE_LIGHTNING_MODE) || defined(USE_CANDLE_MODE)
- // clock tick: bump the random seed
- else if (event == EV_tick) {
- // un-reverse after 1 second
- if (arg == TICKS_PER_SECOND) ramp_direction = 1;
-
- pseudo_rand_seed += arg;
- return MISCHIEF_MANAGED;
- }
- #endif
- return EVENT_NOT_HANDLED;
-}
-
-#if defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE)
-inline void party_tactical_strobe_mode_iter(uint8_t st) {
- // one iteration of main loop()
- uint8_t del = strobe_delays[st];
- // TODO: make tac strobe brightness configurable?
- set_level(STROBE_BRIGHTNESS);
- if (0) {} // placeholde0
- #ifdef USE_PARTY_STROBE_MODE
- else if (st == party_strobe_e) { // party strobe
- #ifdef PARTY_STROBE_ONTIME
- nice_delay_ms(PARTY_STROBE_ONTIME);
- #else
- if (del < 42) delay_zero();
- else nice_delay_ms(1);
- #endif
- }
- #endif
- #ifdef USE_TACTICAL_STROBE_MODE
- else { //tactical strobe
- nice_delay_ms(del >> 1);
- }
- #endif
- set_level(0);
- nice_delay_ms(del); // no return check necessary on final delay
-}
-#endif
-
-#ifdef USE_LIGHTNING_MODE
-inline void lightning_storm_iter() {
- // one iteration of main loop()
- int16_t brightness;
- uint16_t rand_time;
-
- // turn the emitter on at a random level,
- // for a random amount of time between 1ms and 32ms
- //rand_time = 1 << (pseudo_rand() % 7);
- rand_time = pseudo_rand() & 63;
- brightness = 1 << (pseudo_rand() % 7); // 1, 2, 4, 8, 16, 32, 64
- brightness += 1 << (pseudo_rand() % 5); // 2 to 80 now
- brightness += pseudo_rand() % brightness; // 2 to 159 now (w/ low bias)
- if (brightness > MAX_LEVEL) brightness = MAX_LEVEL;
- set_level(brightness);
- nice_delay_ms(rand_time);
-
- // decrease the brightness somewhat more gradually, like lightning
- uint8_t stepdown = brightness >> 3;
- if (stepdown < 1) stepdown = 1;
- while(brightness > 1) {
- nice_delay_ms(rand_time);
- brightness -= stepdown;
- if (brightness < 0) brightness = 0;
- set_level(brightness);
- /*
- if ((brightness < MAX_LEVEL/2) && (! (pseudo_rand() & 15))) {
- brightness <<= 1;
- set_level(brightness);
- }
- */
- if (! (pseudo_rand() & 3)) {
- nice_delay_ms(rand_time);
- set_level(brightness>>1);
- }
- }
-
- // turn the emitter off,
- // for a random amount of time between 1ms and 8192ms
- // (with a low bias)
- rand_time = 1 << (pseudo_rand() % 13);
- rand_time += pseudo_rand() % rand_time;
- set_level(0);
- nice_delay_ms(rand_time); // no return check necessary on final delay
-}
-#endif
+ #ifdef USE_TINT_RAMPING
+ // add tint ramping underneath every other state
+ push_state(tint_ramping_state, 0);
+ #endif // ifdef USE_TINT_RAMPING
-#ifdef USE_BIKE_FLASHER_MODE
-inline void bike_flasher_iter() {
- // one iteration of main loop()
- uint8_t burst = bike_flasher_brightness << 1;
- if (burst > MAX_LEVEL) burst = MAX_LEVEL;
- for(uint8_t i=0; i<4; i++) {
- set_level(burst);
- nice_delay_ms(5);
- set_level(bike_flasher_brightness);
- nice_delay_ms(65);
- }
- nice_delay_ms(720); // no return check necessary on final delay
-}
-#endif
+ push_state(off_state, 1);
-#endif // ifdef USE_STROBE_STATE
-
-#ifdef USE_CANDLE_MODE
-uint8_t candle_mode_state(Event event, uint16_t arg) {
- static int8_t ramp_direction = 1;
- #define MAX_CANDLE_LEVEL (RAMP_LENGTH-CANDLE_AMPLITUDE-15)
- static uint8_t candle_wave1 = 0;
- static uint8_t candle_wave2 = 0;
- static uint8_t candle_wave3 = 0;
- static uint8_t candle_wave2_speed = 0;
- // these should add up to 100
- #define CANDLE_WAVE1_MAXDEPTH 30
- #define CANDLE_WAVE2_MAXDEPTH 45
- #define CANDLE_WAVE3_MAXDEPTH 25
- static const uint8_t candle_wave1_depth = CANDLE_WAVE1_MAXDEPTH * CANDLE_AMPLITUDE / 100;
- static uint8_t candle_wave2_depth = CANDLE_WAVE2_MAXDEPTH * CANDLE_AMPLITUDE / 100;
- static uint8_t candle_wave3_depth = CANDLE_WAVE3_MAXDEPTH * CANDLE_AMPLITUDE / 100;
- static uint8_t candle_mode_brightness = 24;
- static uint8_t candle_mode_timer = 0;
- #define TICKS_PER_CANDLE_MINUTE 4096 // about 65 seconds
- #define MINUTES_PER_CANDLE_HALFHOUR 27 // ish
-
- if (event == EV_enter_state) {
- candle_mode_timer = 0; // in case any time was left over from earlier
- ramp_direction = 1;
- return MISCHIEF_MANAGED;
- }
- // 2 clicks: cancel timer
- else if (event == EV_2clicks) {
- // parent state just rotated through strobe/flasher modes,
- // so cancel timer... in case any time was left over from earlier
- candle_mode_timer = 0;
- return MISCHIEF_MANAGED;
- }
- // hold: change brightness (brighter)
- else if (event == EV_click1_hold) {
- // ramp away from extremes
- if (! arg) {
- if (candle_mode_brightness >= MAX_CANDLE_LEVEL) { ramp_direction = -1; }
- else if (candle_mode_brightness <= 1) { ramp_direction = 1; }
- }
- // change brightness, but not too far
- candle_mode_brightness += ramp_direction;
- if (candle_mode_brightness < 1) candle_mode_brightness = 1;
- else if (candle_mode_brightness > MAX_CANDLE_LEVEL) candle_mode_brightness = MAX_CANDLE_LEVEL;
- return MISCHIEF_MANAGED;
- }
- // reverse ramp direction on hold release
- else if (event == EV_click1_hold_release) {
- ramp_direction = -ramp_direction;
- return MISCHIEF_MANAGED;
- }
- // click, hold: change brightness (dimmer)
- else if (event == EV_click2_hold) {
- ramp_direction = 1;
- if (candle_mode_brightness > 1)
- candle_mode_brightness --;
- return MISCHIEF_MANAGED;
- }
- // 3 clicks: add 30m to candle timer
- else if (event == EV_3clicks) {
- if (candle_mode_timer < (255 - MINUTES_PER_CANDLE_HALFHOUR)) {
- // add 30m to the timer
- candle_mode_timer += MINUTES_PER_CANDLE_HALFHOUR;
- // blink to confirm
- set_level(actual_level + 32);
- delay_4ms(2);
- }
- return MISCHIEF_MANAGED;
- }
- // clock tick: animate candle brightness
- else if (event == EV_tick) {
- // un-reverse after 1 second
- if (arg == TICKS_PER_SECOND) ramp_direction = 1;
-
- // self-timer dims the light during the final minute
- uint8_t subtract = 0;
- if (candle_mode_timer == 1) {
- subtract = ((candle_mode_brightness+CANDLE_AMPLITUDE)
- * ((arg & (TICKS_PER_CANDLE_MINUTE-1)) >> 4))
- >> 8;
- }
- // we passed a minute mark, decrease timer if it's running
- if ((arg & (TICKS_PER_CANDLE_MINUTE-1)) == (TICKS_PER_CANDLE_MINUTE - 1)) {
- if (candle_mode_timer > 0) {
- candle_mode_timer --;
- //set_level(0); delay_4ms(2);
- // if the timer ran out, shut off
- if (! candle_mode_timer) {
- set_state(off_state, 0);
- }
- }
- }
- // 3-oscillator synth for a relatively organic pattern
- uint8_t add;
- add = ((triangle_wave(candle_wave1) * candle_wave1_depth) >> 8)
- + ((triangle_wave(candle_wave2) * candle_wave2_depth) >> 8)
- + ((triangle_wave(candle_wave3) * candle_wave3_depth) >> 8);
- int8_t brightness = candle_mode_brightness + add - subtract;
- if (brightness < 0) { brightness = 0; }
- set_level(brightness);
-
- // wave1: slow random LFO
- // TODO: make wave slower and more erratic?
- if ((arg & 1) == 0) candle_wave1 += pseudo_rand() & 1;
- // wave2: medium-speed erratic LFO
- candle_wave2 += candle_wave2_speed;
- // wave3: erratic fast wave
- candle_wave3 += pseudo_rand() % 37;
- // S&H on wave2 frequency to make it more erratic
- if ((pseudo_rand() & 0b00111111) == 0)
- candle_wave2_speed = pseudo_rand() % 13;
- // downward sawtooth on wave2 depth to simulate stabilizing
- if ((candle_wave2_depth > 0) && ((pseudo_rand() & 0b00111111) == 0))
- candle_wave2_depth --;
- // random sawtooth retrigger
- if (pseudo_rand() == 0) {
- // random amplitude
- //candle_wave2_depth = 2 + (pseudo_rand() % ((CANDLE_WAVE2_MAXDEPTH * CANDLE_AMPLITUDE / 100) - 2));
- candle_wave2_depth = pseudo_rand() % (CANDLE_WAVE2_MAXDEPTH * CANDLE_AMPLITUDE / 100);
- //candle_wave3_depth = 5;
- candle_wave2 = 0;
- }
- // downward sawtooth on wave3 depth to simulate stabilizing
- if ((candle_wave3_depth > 2) && ((pseudo_rand() & 0b00011111) == 0))
- candle_wave3_depth --;
- if ((pseudo_rand() & 0b01111111) == 0)
- // random amplitude
- //candle_wave3_depth = 2 + (pseudo_rand() % ((CANDLE_WAVE3_MAXDEPTH * CANDLE_AMPLITUDE / 100) - 2));
- candle_wave3_depth = pseudo_rand() % (CANDLE_WAVE3_MAXDEPTH * CANDLE_AMPLITUDE / 100);
- return MISCHIEF_MANAGED;
- }
- return EVENT_NOT_HANDLED;
-}
-#endif // #ifdef USE_CANDLE_MODE
+ #else // if START_AT_MEMORIZED_LEVEL
+ // dual switch: e-switch + power clicky
+ // power clicky acts as a momentary mode
+ load_config();
-#ifdef USE_BORING_STROBE_STATE
-uint8_t boring_strobe_state(Event event, uint16_t arg) {
- // police strobe and SOS, meh
- // 'st' reduces ROM size by avoiding access to a volatile var
- // (maybe I should just make it nonvolatile?)
- uint8_t st = boring_strobe_type;
+ #ifdef USE_TINT_RAMPING
+ // add tint ramping underneath every other state
+ push_state(tint_ramping_state, 0);
+ #endif // ifdef USE_TINT_RAMPING
- if (event == EV_enter_state) {
- return MISCHIEF_MANAGED;
- }
- // 1 click: off
- else if (event == EV_1click) {
- // reset to police strobe for next time
- boring_strobe_type = 0;
- set_state(off_state, 0);
- return MISCHIEF_MANAGED;
- }
- // 2 clicks: rotate through strobe/flasher modes
- else if (event == EV_2clicks) {
- boring_strobe_type = (st + 1) % NUM_BORING_STROBES;
- return MISCHIEF_MANAGED;
- }
- return EVENT_NOT_HANDLED;
-}
+ if (button_is_pressed())
+ // hold button to go to moon
+ push_state(steady_state, 1);
+ else
+ // otherwise use memory
+ push_state(steady_state, memorized_level);
-#ifdef USE_POLICE_STROBE_MODE
-inline void police_strobe_iter() {
- // one iteration of main loop()
- // flash at 16 Hz then 8 Hz, 8 times each
- for (uint8_t del=41; del<100; del+=41) {
- for (uint8_t f=0; f<8; f++) {
- set_level(STROBE_BRIGHTNESS);
- nice_delay_ms(del >> 1);
- set_level(0);
- nice_delay_ms(del);
- }
- }
-}
-#endif
-#endif // #ifdef USE_BORING_STROBE_STATE
+ #endif // ifdef START_AT_MEMORIZED_LEVEL
-#ifdef USE_SOS_MODE
-#ifdef USE_SOS_MODE_IN_BLINKY_GROUP
-uint8_t sos_state(Event event, uint16_t arg) {
- // 1 click: off
- if (event == EV_1click) {
- set_state(off_state, 0);
- return MISCHIEF_MANAGED;
- }
- // 2 clicks: next mode
- else if (event == EV_2clicks) {
- #ifdef USE_THERMAL_REGULATION
- set_state(tempcheck_state, 0);
- #else
- set_state(battcheck_state, 0);
- #endif
- return MISCHIEF_MANAGED;
- }
- return EVENT_NOT_HANDLED;
}
-#endif
-void sos_blink(uint8_t num, uint8_t dah) {
- #define DIT_LENGTH 200
- for (; num > 0; num--) {
- set_level(memorized_level);
- nice_delay_ms(DIT_LENGTH);
- if (dah) { // dah is 3X as long as a dit
- nice_delay_ms(DIT_LENGTH*2);
- }
- set_level(0);
- // one "off" dit between blinks
- nice_delay_ms(DIT_LENGTH);
- }
- // three "off" dits (or one "dah") between letters
- // (except for SOS, which is collectively treated as a single "letter")
- //nice_delay_ms(DIT_LENGTH*2);
-}
-inline void sos_mode_iter() {
- // one iteration of main loop()
- //nice_delay_ms(1000);
- sos_blink(3, 0); // S
- sos_blink(3, 1); // O
- sos_blink(3, 0); // S
- nice_delay_ms(2000);
-}
-#endif // #ifdef USE_SOS_MODE
+// runs repeatedly whenever light is "on" (not in standby)
+void loop() {
+ // "current_state" is volatile, so cache it to reduce code size
+ StatePtr state = current_state;
-#ifdef USE_BATTCHECK
-uint8_t battcheck_state(Event event, uint16_t arg) {
- // 1 click: off
- if (event == EV_1click) {
- set_state(off_state, 0);
- return MISCHIEF_MANAGED;
- }
- #ifdef USE_GOODNIGHT_MODE
- // 2 clicks: goodnight mode
- else if (event == EV_2clicks) {
- set_state(goodnight_state, 0);
- return MISCHIEF_MANAGED;
- }
- #elif defined(USE_BEACON_MODE)
- // 2 clicks: beacon mode
- else if (event == EV_2clicks) {
- set_state(beacon_state, 0);
- return MISCHIEF_MANAGED;
- }
- #elif defined(USE_THERMAL_REGULATION)
- // 2 clicks: tempcheck mode
- else if (event == EV_2clicks) {
- set_state(tempcheck_state, 0);
- return MISCHIEF_MANAGED;
- }
+ #ifdef USE_AUX_RGB_LEDS_WHILE_ON
+ // display battery charge on RGB button during use
+ if (! setting_rgb_mode_now) rgb_led_voltage_readout(1);
#endif
- return EVENT_NOT_HANDLED;
-}
-#endif
+ if (0) {} // placeholder
-#ifdef USE_THERMAL_REGULATION
-uint8_t tempcheck_state(Event event, uint16_t arg) {
- // 1 click: off
- if (event == EV_1click) {
- set_state(off_state, 0);
- return MISCHIEF_MANAGED;
- }
- #ifdef USE_BATTCHECK
- // 2 clicks: battcheck mode
- else if (event == EV_2clicks) {
- set_state(battcheck_state, 0);
- return MISCHIEF_MANAGED;
+ #ifdef USE_VERSION_CHECK
+ else if (state == version_check_state) {
+ version_check_iter();
}
#endif
- // 4 clicks: thermal config mode
- else if (event == EV_4clicks) {
- push_state(thermal_config_state, 0);
- return MISCHIEF_MANAGED;
- }
- return EVENT_NOT_HANDLED;
-}
-#endif
-
-#ifdef USE_BEACON_MODE
-uint8_t beacon_state(Event event, uint16_t arg) {
- // 1 click: off
- if (event == EV_1click) {
- set_state(off_state, 0);
- return MISCHIEF_MANAGED;
- }
- // TODO: use sleep ticks to measure time between pulses,
- // to save power
- // 2 clicks: next mode
- else if (event == EV_2clicks) {
- #ifdef USE_SOS_MODE_IN_BLINKY_GROUP
- set_state(sos_state, 0);
- #elif defined(USE_THERMAL_REGULATION)
- set_state(tempcheck_state, 0);
- #elif defined(USE_BATTCHECK)
- set_state(battcheck_state, 0);
- #endif
- return MISCHIEF_MANAGED;
- }
- // 4 clicks: beacon config mode
- else if (event == EV_4clicks) {
- push_state(beacon_config_state, 0);
- return MISCHIEF_MANAGED;
- }
- return EVENT_NOT_HANDLED;
-}
-#endif // #ifdef USE_BEACON_MODE
-
-
-#ifdef USE_GOODNIGHT_MODE
-#define GOODNIGHT_TICKS_PER_STEPDOWN (GOODNIGHT_TIME*TICKS_PER_SECOND*60L/GOODNIGHT_LEVEL)
-uint8_t goodnight_state(Event event, uint16_t arg) {
- static uint16_t ticks_since_stepdown = 0;
- // blink on start
- if (event == EV_enter_state) {
- ticks_since_stepdown = 0;
- blink_confirm(2);
- set_level(GOODNIGHT_LEVEL);
- return MISCHIEF_MANAGED;
- }
- // 1 click: off
- else if (event == EV_1click) {
- set_state(off_state, 0);
- return MISCHIEF_MANAGED;
- }
- // 2 clicks: next mode
- else if (event == EV_2clicks) {
- #ifdef USE_BEACON_MODE
- set_state(beacon_state, 0);
- #elif defined(USE_SOS_MODE_IN_BLINKY_GROUP)
- set_state(sos_state, 0);
- #elif defined(USE_THERMAL_REGULATION)
- set_state(tempcheck_state, 0);
- #endif
- return MISCHIEF_MANAGED;
- }
- // tick: step down (maybe) or off (maybe)
- else if (event == EV_tick) {
- if (++ticks_since_stepdown > GOODNIGHT_TICKS_PER_STEPDOWN) {
- ticks_since_stepdown = 0;
- set_level(actual_level-1);
- if (! actual_level) {
- #if 0 // test blink, to help measure timing
- set_level(MAX_LEVEL>>2);
- delay_4ms(8/2);
- set_level(0);
- #endif
- set_state(off_state, 0);
- }
- }
- return MISCHIEF_MANAGED;
+ #ifdef USE_STROBE_STATE
+ else if ((state == strobe_state)
+ #ifdef USE_MOMENTARY_MODE
+ // also handle momentary strobes
+ || ((state == momentary_state) && (momentary_mode == 1) && (momentary_active))
+ #endif
+ ) {
+ strobe_state_iter();
}
- return EVENT_NOT_HANDLED;
-}
-#endif
-
+ #endif // #ifdef USE_STROBE_STATE
-uint8_t lockout_state(Event event, uint16_t arg) {
- #ifdef MOON_DURING_LOCKOUT_MODE
- // momentary(ish) moon mode during lockout
- // button is being held
- #ifdef USE_AUX_RGB_LEDS
- // don't turn on during RGB aux LED configuration
- if (event == EV_click3_hold) { set_level(0); } else
- #endif
- if ((event & (B_CLICK | B_PRESS)) == (B_CLICK | B_PRESS)) {
- #ifdef LOCKOUT_MOON_LOWEST
- // Use lowest moon configured
- uint8_t lvl = ramp_smooth_floor;
- if (ramp_discrete_floor < lvl) lvl = ramp_discrete_floor;
- set_level(lvl);
- #elif defined(LOCKOUT_MOON_FANCY)
- uint8_t levels[] = { ramp_smooth_floor, ramp_discrete_floor };
- if ((event & 0x0f) == 2) {
- set_level(levels[ramp_style^1]);
- } else {
- set_level(levels[ramp_style]);
- }
- #else
- // Use moon from current ramp
- set_level(nearest_level(1));
- #endif
- }
- // button was released
- else if ((event & (B_CLICK | B_PRESS)) == (B_CLICK)) {
- set_level(0);
+ #ifdef USE_BORING_STROBE_STATE
+ else if (state == boring_strobe_state) {
+ boring_strobe_state_iter();
}
#endif
- // regular event handling
- // conserve power while locked out
- // (allow staying awake long enough to exit, but otherwise
- // be persistent about going back to sleep every few seconds
- // even if the user keeps pressing the button)
- #ifdef USE_INDICATOR_LED
- if (event == EV_enter_state) {
- indicator_led(indicator_led_mode >> 2);
- } else
- #elif defined(USE_AUX_RGB_LEDS)
- if (event == EV_enter_state) {
- rgb_led_update(rgb_led_lockout_mode, 0);
- } else
- #endif
- if (event == EV_tick) {
- if (arg > HOLD_TIMEOUT) {
- go_to_standby = 1;
- #ifdef USE_INDICATOR_LED
- indicator_led(indicator_led_mode >> 2);
- #elif defined(USE_AUX_RGB_LEDS)
- rgb_led_update(rgb_led_lockout_mode, arg);
- #endif
- }
- return MISCHIEF_MANAGED;
- }
- #if defined(TICK_DURING_STANDBY) && (defined(USE_INDICATOR_LED) || defined(USE_AUX_RGB_LEDS))
- else if (event == EV_sleep_tick) {
- #if defined(USE_INDICATOR_LED)
- if ((indicator_led_mode & 0b00001100) == 0b00001100) {
- indicator_blink(arg);
- }
- #elif defined(USE_AUX_RGB_LEDS)
- rgb_led_update(rgb_led_lockout_mode, arg);
- #endif
- return MISCHIEF_MANAGED;
- }
- #endif
- #if defined(USE_INDICATOR_LED)
- // 3 clicks: rotate through indicator LED modes (lockout mode)
- else if (event == EV_3clicks) {
- #if defined(USE_INDICATOR_LED)
- uint8_t mode = indicator_led_mode >> 2;
- #ifdef TICK_DURING_STANDBY
- mode = (mode + 1) & 3;
- #else
- mode = (mode + 1) % 3;
- #endif
- #ifdef INDICATOR_LED_SKIP_LOW
- if (mode == 1) { mode ++; }
- #endif
- indicator_led_mode = (mode << 2) + (indicator_led_mode & 0x03);
- indicator_led(mode);
- #elif defined(USE_AUX_RGB_LEDS)
+ #ifdef USE_BATTCHECK
+ else if (state == battcheck_state) {
+ battcheck();
+ #ifdef USE_SIMPLE_UI
+ // in simple mode, turn off after one readout
+ // FIXME: can eat the next button press
+ // (state changes in loop() act weird)
+ if (simple_ui_active) set_state_deferred(off_state, 0);
+ else nice_delay_ms(1000);
#endif
- save_config();
- return MISCHIEF_MANAGED;
- }
- #elif defined(USE_AUX_RGB_LEDS)
- // 3 clicks: change RGB aux LED pattern
- else if (event == EV_3clicks) {
- uint8_t mode = (rgb_led_lockout_mode >> 4) + 1;
- mode = mode % RGB_LED_NUM_PATTERNS;
- rgb_led_lockout_mode = (mode << 4) | (rgb_led_lockout_mode & 0x0f);
- rgb_led_update(rgb_led_lockout_mode, 0);
- save_config();
- blink_confirm(1);
- return MISCHIEF_MANAGED;
- }
- // click, click, hold: change RGB aux LED color
- else if (event == EV_click3_hold) {
- setting_rgb_mode_now = 1;
- if (0 == (arg & 0x3f)) {
- uint8_t mode = (rgb_led_lockout_mode & 0x0f) + 1;
- mode = mode % RGB_LED_NUM_COLORS;
- rgb_led_lockout_mode = mode | (rgb_led_lockout_mode & 0xf0);
- //save_config();
- }
- rgb_led_update(rgb_led_lockout_mode, arg);
- return MISCHIEF_MANAGED;
- }
- // click, click, hold, release: save new color
- else if (event == EV_click3_hold_release) {
- setting_rgb_mode_now = 0;
- save_config();
- return MISCHIEF_MANAGED;
}
#endif
- // 4 clicks: exit
- else if (event == EV_4clicks) {
- blink_confirm(1);
- set_state(off_state, 0);
- return MISCHIEF_MANAGED;
- }
-
- return EVENT_NOT_HANDLED;
-}
-
-
-#ifdef USE_MOMENTARY_MODE
-uint8_t momentary_state(Event event, uint16_t arg) {
- // TODO: momentary strobe here? (for light painting)
-
- // init strobe mode, if relevant
- #ifdef USE_STROBE_STATE
- if ((event == EV_enter_state) && (momentary_mode == 1)) {
- strobe_state(event, arg);
- }
- #endif
-
- // light up when the button is pressed; go dark otherwise
- // button is being held
- if ((event & (B_CLICK | B_PRESS)) == (B_CLICK | B_PRESS)) {
- momentary_active = 1;
- // 0 = ramping, 1 = strobes
- if (momentary_mode == 0) {
- set_level(memorized_level);
- }
- return MISCHIEF_MANAGED;
- }
- // button was released
- else if ((event & (B_CLICK | B_PRESS)) == (B_CLICK)) {
- momentary_active = 0;
- set_level(0);
- //go_to_standby = 1; // sleep while light is off
- return MISCHIEF_MANAGED;
- }
-
- // Sleep, dammit! (but wait a few seconds first)
- // (because standby mode uses such little power that it can interfere
- // with exiting via tailcap loosen+tighten unless you leave power
- // disconnected for several seconds, so we want to be awake when that
- // happens to speed up the process)
- else if (event == EV_tick) {
- #ifdef USE_STROBE_STATE
- if (momentary_active) {
- // 0 = ramping, 1 = strobes
- if (momentary_mode == 1) {
- return strobe_state(event, arg);
- }
- }
- else {
- #endif
- if (arg > TICKS_PER_SECOND*5) { // sleep after 5 seconds
- go_to_standby = 1; // sleep while light is off
- // turn off lighted button
- #ifdef USE_INDICATOR_LED
- indicator_led(0);
- #elif defined(USE_AUX_RGB_LEDS)
- rgb_led_update(0, 0);
- #endif
- }
- #ifdef USE_STROBE_STATE
- }
- #endif
- return MISCHIEF_MANAGED;
- }
-
- return EVENT_NOT_HANDLED;
-}
-#endif
-
-#ifdef USE_MUGGLE_MODE
-uint8_t muggle_state(Event event, uint16_t arg) {
- static int8_t ramp_direction;
- static int8_t muggle_off_mode;
-
- // turn LED off when we first enter the mode
- if (event == EV_enter_state) {
- ramp_direction = 1;
-
- #ifdef START_AT_MEMORIZED_LEVEL
- memorized_level = arg;
- muggle_off_mode = 0;
- set_level(memorized_level);
-
- if (! muggle_mode_active) { // don't write eeprom at every boot
- muggle_mode_active = 1;
- save_config();
- }
- #else
- muggle_mode_active = 1;
- save_config();
-
- muggle_off_mode = 1;
- //memorized_level = MAX_1x7135;
- memorized_level = (MUGGLE_FLOOR + MUGGLE_CEILING) / 2;
- #endif
- return MISCHIEF_MANAGED;
- }
- // initial press: moon hint
- else if (event == EV_click1_press) {
- if (muggle_off_mode)
- set_level(MUGGLE_FLOOR);
- }
- // initial release: direct to memorized level
- else if (event == EV_click1_release) {
- if (muggle_off_mode)
- set_level(memorized_level);
- }
- // if the user keeps pressing, turn off
- else if (event == EV_click2_press) {
- muggle_off_mode = 1;
- set_level(0);
- }
- // 1 click: on/off
- else if (event == EV_1click) {
- muggle_off_mode ^= 1;
- if (muggle_off_mode) {
- set_level(0);
- }
- /*
- else {
- set_level(memorized_level);
- }
- */
- return MISCHIEF_MANAGED;
- }
- // hold: change brightness
- else if (event == EV_click1_hold) {
- // ramp at half speed
- if (arg & 1) return MISCHIEF_MANAGED;
-
- // if off, start at bottom
- if (muggle_off_mode) {
- muggle_off_mode = 0;
- ramp_direction = 1;
- set_level(MUGGLE_FLOOR);
- }
- else {
- uint8_t m;
- m = actual_level;
- // ramp down if already at ceiling
- if ((arg <= 1) && (m >= MUGGLE_CEILING)) ramp_direction = -1;
- // ramp
- m += ramp_direction;
- if (m < MUGGLE_FLOOR)
- m = MUGGLE_FLOOR;
- if (m > MUGGLE_CEILING)
- m = MUGGLE_CEILING;
- memorized_level = m;
- set_level(m);
- }
- return MISCHIEF_MANAGED;
- }
- // reverse ramp direction on hold release
- else if (event == EV_click1_hold_release) {
- ramp_direction = -ramp_direction;
- #ifdef START_AT_MEMORIZED_LEVEL
- save_config_wl(); // momentary use should retain brightness level
- #endif
- return MISCHIEF_MANAGED;
- }
- /*
- // click, hold: change brightness (dimmer)
- else if (event == EV_click2_hold) {
- ramp_direction = 1;
- if (memorized_level > MUGGLE_FLOOR)
- memorized_level = actual_level - 1;
- set_level(memorized_level);
- return MISCHIEF_MANAGED;
- }
- */
- // 6 clicks: exit muggle mode
- else if (event == EV_6clicks) {
- blink_confirm(1);
- muggle_mode_active = 0;
- save_config();
- set_state(off_state, 0);
- return MISCHIEF_MANAGED;
- }
- // tick: housekeeping
- else if (event == EV_tick) {
- // un-reverse after 1 second
- if (arg == TICKS_PER_SECOND) ramp_direction = 1;
-
- // turn off, but don't go to the main "off" state
- if (muggle_off_mode) {
- if (arg > TICKS_PER_SECOND*1) { // sleep after 1 second
- #ifdef USE_AUX_RGB_LEDS_WHILE_ON
- rgb_led_set(0);
- #endif
- go_to_standby = 1; // sleep while light is off
- }
- }
- return MISCHIEF_MANAGED;
- }
#ifdef USE_THERMAL_REGULATION
- // overheating is handled specially in muggle mode
- else if(event == EV_temperature_high) {
- #if 0
- blip();
- #endif
- // ignore warnings while off
- if (! muggle_off_mode) {
- // step down proportional to the amount of overheating
- int16_t new = actual_level - arg;
- if (new < MUGGLE_FLOOR) { new = MUGGLE_FLOOR; }
- set_level(new);
- }
- return MISCHIEF_MANAGED;
- }
- #endif
- #ifdef USE_LVP
- // low voltage is handled specially in muggle mode
- else if(event == EV_voltage_low) {
- uint8_t lvl = (actual_level >> 1) + (actual_level >> 2);
- if (lvl >= MUGGLE_FLOOR) {
- set_level(lvl);
- } else {
- muggle_off_mode = 1;
- }
- return MISCHIEF_MANAGED;
+ // TODO: blink out therm_ceil during thermal_config_state?
+ else if (state == tempcheck_state) {
+ blink_num(temperature);
+ nice_delay_ms(1000);
}
#endif
- return EVENT_NOT_HANDLED;
-}
-#endif
-
-
-#ifdef USE_VERSION_CHECK
-uint8_t version_check_state(Event event, uint16_t arg) {
- return EVENT_NOT_HANDLED;
-}
-#endif
-
-
-// ask the user for a sequence of numbers, then save them and return to caller
-uint8_t config_state_base(Event event, uint16_t arg,
- uint8_t num_config_steps,
- void (*savefunc)()) {
- static uint8_t config_step;
- if (event == EV_enter_state) {
- config_step = 0;
- set_level(0);
- return MISCHIEF_MANAGED;
- }
- // advance forward through config steps
- else if (event == EV_tick) {
- if (config_step < num_config_steps) {
- push_state(number_entry_state, config_step + 1);
- }
- else {
- // TODO: blink out some sort of success pattern
- savefunc();
- save_config();
- //set_state(retstate, retval);
- pop_state();
- }
- return MISCHIEF_MANAGED;
- }
- // an option was set (return from number_entry_state)
- else if (event == EV_reenter_state) {
- config_state_values[config_step] = number_entry_value;
- config_step ++;
- return MISCHIEF_MANAGED;
- }
- //return EVENT_NOT_HANDLED;
- // eat all other events; don't pass any through to parent
- return EVENT_HANDLED;
-}
-
-#ifdef USE_RAMP_CONFIG
-void ramp_config_save() {
- // parse values
- uint8_t val;
- if (ramp_style) { // discrete / stepped ramp
-
- val = config_state_values[0];
- if (val) { ramp_discrete_floor = val; }
-
- val = config_state_values[1];
- if (val) { ramp_discrete_ceil = MAX_LEVEL + 1 - val; }
-
- val = config_state_values[2];
- if (val) ramp_discrete_steps = val;
-
- } else { // smooth ramp
-
- val = config_state_values[0];
- if (val) { ramp_smooth_floor = val; }
-
- val = config_state_values[1];
- if (val) { ramp_smooth_ceil = MAX_LEVEL + 1 - val; }
-
- }
-}
-
-uint8_t ramp_config_state(Event event, uint16_t arg) {
- uint8_t num_config_steps;
- num_config_steps = 2 + ramp_style;
- return config_state_base(event, arg,
- num_config_steps, ramp_config_save);
-}
-#endif // #ifdef USE_RAMP_CONFIG
-
-
-#ifdef USE_THERMAL_REGULATION
-void thermal_config_save() {
- // parse values
- uint8_t val;
-
- // calibrate room temperature
- val = config_state_values[0];
- if (val) {
- int8_t rawtemp = temperature - therm_cal_offset;
- therm_cal_offset = val - rawtemp;
- adc_reset = 2; // invalidate all recent temperature data
- }
-
- val = config_state_values[1];
- if (val) {
- // set maximum heat limit
- therm_ceil = 30 + val - 1;
- }
- if (therm_ceil > MAX_THERM_CEIL) therm_ceil = MAX_THERM_CEIL;
-}
-
-uint8_t thermal_config_state(Event event, uint16_t arg) {
- return config_state_base(event, arg,
- 2, thermal_config_save);
-}
-#endif // #ifdef USE_THERMAL_REGULATION
-
-
-#ifdef USE_BEACON_MODE
-void beacon_config_save() {
- // parse values
- uint8_t val = config_state_values[0];
- if (val) {
- beacon_seconds = val;
- }
-}
-
-uint8_t beacon_config_state(Event event, uint16_t arg) {
- return config_state_base(event, arg,
- 1, beacon_config_save);
-}
-
-inline void beacon_mode_iter() {
- // one iteration of main loop()
- set_level(memorized_level);
- nice_delay_ms(100);
- set_level(0);
- nice_delay_ms(((beacon_seconds) * 1000) - 100);
-}
-#endif // #ifdef USE_BEACON_MODE
-
-
-uint8_t number_entry_state(Event event, uint16_t arg) {
- static uint8_t value;
- static uint8_t blinks_left;
- static uint8_t entry_step;
- static uint16_t wait_ticks;
- if (event == EV_enter_state) {
- value = 0;
- blinks_left = arg;
- entry_step = 0;
- wait_ticks = 0;
- return MISCHIEF_MANAGED;
- }
- // advance through the process:
- // 0: wait a moment
- // 1: blink out the 'arg' value
- // 2: wait a moment
- // 3: "buzz" while counting clicks
- // 4: save and exit
- else if (event == EV_tick) {
- // wait a moment
- if ((entry_step == 0) || (entry_step == 2)) {
- if (wait_ticks < TICKS_PER_SECOND/2)
- wait_ticks ++;
- else {
- entry_step ++;
- wait_ticks = 0;
- }
- }
- // blink out the option number
- else if (entry_step == 1) {
- if (blinks_left) {
- if ((wait_ticks & 31) == 10) {
- set_level(RAMP_SIZE/4);
- }
- else if ((wait_ticks & 31) == 20) {
- set_level(0);
- }
- else if ((wait_ticks & 31) == 31) {
- blinks_left --;
- }
- wait_ticks ++;
- }
- else {
- entry_step ++;
- wait_ticks = 0;
- }
- }
- else if (entry_step == 3) { // buzz while waiting for a number to be entered
- wait_ticks ++;
- // buzz for N seconds after last event
- if ((wait_ticks & 3) == 0) {
- set_level(RAMP_SIZE/6);
- }
- else if ((wait_ticks & 3) == 2) {
- set_level(RAMP_SIZE/8);
- }
- // time out after 3 seconds
- if (wait_ticks > TICKS_PER_SECOND*3) {
- //number_entry_value = value;
- set_level(0);
- entry_step ++;
- }
- }
- else if (entry_step == 4) {
- number_entry_value = value;
- pop_state();
- }
- return MISCHIEF_MANAGED;
- }
- // count clicks
- else if (event == EV_click1_release) {
- empty_event_sequence();
- if (entry_step == 3) { // only count during the "buzz"
- value ++;
- wait_ticks = 0;
- // flash briefly
- set_level(RAMP_SIZE/2);
- delay_4ms(8/2);
- set_level(0);
- }
- return MISCHIEF_MANAGED;
- }
- return EVENT_NOT_HANDLED;
-}
-
-
-// find the ramp level closest to the target,
-// using only the levels which are allowed in the current state
-uint8_t nearest_level(int16_t target) {
- // bounds check
- // using int16_t here saves us a bunch of logic elsewhere,
- // by allowing us to correct for numbers < 0 or > 255 in one central place
- uint8_t mode_min = ramp_smooth_floor;
- uint8_t mode_max = ramp_smooth_ceil;
- if (ramp_style) {
- mode_min = ramp_discrete_floor;
- mode_max = ramp_discrete_ceil;
- }
- if (target < mode_min) return mode_min;
- if (target > mode_max) return mode_max;
- // the rest isn't relevant for smooth ramping
- if (! ramp_style) return target;
-
- uint8_t ramp_range = ramp_discrete_ceil - ramp_discrete_floor;
- ramp_discrete_step_size = ramp_range / (ramp_discrete_steps-1);
- uint8_t this_level = ramp_discrete_floor;
-
- for(uint8_t i=0; i<ramp_discrete_steps; i++) {
- this_level = ramp_discrete_floor + (i * (uint16_t)ramp_range / (ramp_discrete_steps-1));
- int16_t diff = target - this_level;
- if (diff < 0) diff = -diff;
- if (diff <= (ramp_discrete_step_size>>1))
- return this_level;
- }
- return this_level;
-}
-
-#ifdef USE_THERMAL_REGULATION
-void set_level_and_therm_target(uint8_t level) {
- target_level = level;
- set_level(level);
-}
-#endif
-
-void blink_confirm(uint8_t num) {
- for (; num>0; num--) {
- set_level(MAX_LEVEL/4);
- delay_4ms(10/4);
- set_level(0);
- delay_4ms(100/4);
- }
-}
-
-// Just go dark for a moment to indicate to user that something happened
-void blip() {
- uint8_t temp = actual_level;
- set_level(0);
- delay_4ms(3);
- set_level(temp);
-}
-
-
-#if defined(USE_INDICATOR_LED) && defined(TICK_DURING_STANDBY)
-// beacon-like mode for the indicator LED
-void indicator_blink(uint8_t arg) {
- // turn off aux LEDs when battery is empty
- if (voltage < VOLTAGE_LOW) { indicator_led(0); return; }
-
- #ifdef USE_FANCIER_BLINKING_INDICATOR
-
- // fancy blink, set off/low/high levels here:
- uint8_t seq[] = {0, 1, 2, 1, 0, 0, 0, 0,
- 0, 0, 1, 0, 0, 0, 0, 0};
- indicator_led(seq[arg & 15]);
-
- #else // basic blink, 1/8th duty cycle
-
- if (! (arg & 7)) {
- indicator_led(2);
- }
- else {
- indicator_led(0);
+ #ifdef USE_BEACON_MODE
+ else if (state == beacon_state) {
+ beacon_mode_iter();
}
-
#endif
-}
-#endif
-
-#if defined(USE_AUX_RGB_LEDS) && defined(TICK_DURING_STANDBY)
-uint8_t voltage_to_rgb() {
- uint8_t levels[] = {
- // voltage, color
- 0, 0, // 0, R
- 33, 1, // 1, R+G
- 35, 2, // 2, G
- 37, 3, // 3, G+B
- 39, 4, // 4, B
- 41, 5, // 5, R + B
- 44, 6, // 6, R+G+B // skip; looks too similar to G+B
- 255, 6, // 7, R+G+B
- };
- uint8_t volts = voltage;
- if (volts < 29) return 0;
-
- uint8_t i;
- for (i = 0; volts >= levels[i]; i += 2) {}
- uint8_t color_num = levels[(i - 2) + 1];
- return pgm_read_byte(rgb_led_colors + color_num);
-}
-
-// do fancy stuff with the RGB aux LEDs
-// mode: 0bPPPPCCCC where PPPP is the pattern and CCCC is the color
-// arg: time slice number
-void rgb_led_update(uint8_t mode, uint8_t arg) {
- static uint8_t rainbow = 0; // track state of rainbow mode
- static uint8_t frame = 0; // track state of animation mode
-
- // turn off aux LEDs when battery is empty
- // (but if voltage==0, that means we just booted and don't know yet)
- uint8_t volts = voltage; // save a few bytes by caching volatile value
- if ((volts) && (volts < VOLTAGE_LOW)) {
- rgb_led_set(0);
- #ifdef USE_BUTTON_LED
- button_led_set(0);
- #endif
- return;
- }
-
- uint8_t pattern = (mode>>4); // off, low, high, blinking, ... more?
- uint8_t color = mode & 0x0f;
- // preview in blinking mode is awkward... use high instead
- if ((! go_to_standby) && (pattern > 2)) { pattern = 2; }
-
-
- const uint8_t *colors = rgb_led_colors;
- uint8_t actual_color = 0;
- if (color < 7) { // normal color
- actual_color = pgm_read_byte(colors + color);
- }
- else if (color == 7) { // rainbow
- uint8_t speed = 0x03; // awake speed
- if (go_to_standby) speed = RGB_RAINBOW_SPEED; // asleep speed
- if (0 == (arg & speed)) {
- rainbow = (rainbow + 1) % 6;
- }
- actual_color = pgm_read_byte(colors + rainbow);
- }
- else { // voltage
- // show actual voltage while asleep...
- if (go_to_standby) {
- actual_color = voltage_to_rgb();
- // choose a color based on battery voltage
- //if (volts >= 38) actual_color = pgm_read_byte(colors + 4);
- //else if (volts >= 33) actual_color = pgm_read_byte(colors + 2);
- //else actual_color = pgm_read_byte(colors + 0);
- }
- // ... but during preview, cycle colors quickly
- else {
- actual_color = pgm_read_byte(colors + (((arg>>1) % 3) << 1));
- }
- }
-
- // pick a brightness from the animation sequence
- if (pattern == 3) {
- // uses an odd length to avoid lining up with rainbow loop
- uint8_t animation[] = {2, 1, 0, 0, 0, 0, 0, 0, 0,
- 1, 0, 0, 0, 0, 0, 0, 0, 0, 1};
- frame = (frame + 1) % sizeof(animation);
- pattern = animation[frame];
- }
- uint8_t result;
- #ifdef USE_BUTTON_LED
- uint8_t button_led_result;
- #endif
- switch (pattern) {
- case 0: // off
- result = 0;
- #ifdef USE_BUTTON_LED
- button_led_result = 0;
- #endif
- break;
- case 1: // low
- result = actual_color;
- #ifdef USE_BUTTON_LED
- button_led_result = 1;
- #endif
- break;
- default: // high
- result = (actual_color << 1);
- #ifdef USE_BUTTON_LED
- button_led_result = 2;
- #endif
- break;
+ #if defined(USE_SOS_MODE) && defined(USE_SOS_MODE_IN_BLINKY_GROUP)
+ else if (state == sos_state) {
+ sos_mode_iter();
}
- rgb_led_set(result);
- #ifdef USE_BUTTON_LED
- button_led_set(button_led_result);
#endif
-}
-
-void rgb_led_voltage_readout(uint8_t bright) {
- uint8_t color = voltage_to_rgb();
- if (bright) color = color << 1;
- rgb_led_set(color);
-}
-#endif
-
-#ifdef USE_FACTORY_RESET
-void factory_reset() {
- // display a warning for a few seconds before doing the actual reset,
- // so the user has time to abort if they want
- #define SPLODEY_TIME 2500
- #define SPLODEY_STEPS 64
- #define SPLODEY_TIME_PER_STEP (SPLODEY_TIME/SPLODEY_STEPS)
- uint8_t bright;
- uint8_t reset = 1;
- // wind up to an explosion
- for (bright=0; bright<SPLODEY_STEPS; bright++) {
- set_level(bright);
- nice_delay_ms(SPLODEY_TIME_PER_STEP/2);
- set_level(bright>>1);
- nice_delay_ms(SPLODEY_TIME_PER_STEP/2);
- if (! button_is_pressed()) {
- reset = 0;
- break;
- }
- }
- // explode, if button pressed long enough
- if (reset) {
- #ifdef USE_THERMAL_REGULATION
- // auto-calibrate temperature... assume current temperature is 21 C
- config_state_values[0] = 21;
- config_state_values[1] = 0;
- thermal_config_save();
- #endif
- // save all settings to eeprom
- // (assuming they're all at default because we haven't loaded them yet)
- save_config();
-
- bright = MAX_LEVEL;
- for (; bright > 0; bright--) {
- set_level(bright);
- nice_delay_ms(SPLODEY_TIME_PER_STEP/8);
- }
- }
- // explosion cancelled, fade away
+ #ifdef USE_IDLE_MODE
else {
- for (; bright > 0; bright--) {
- set_level(bright);
- nice_delay_ms(SPLODEY_TIME_PER_STEP/3);
- }
- }
-}
-#endif
-
-
-void load_config() {
- if (load_eeprom()) {
- ramp_style = eeprom[ramp_style_e];
- #ifdef USE_RAMP_CONFIG
- ramp_smooth_floor = eeprom[ramp_smooth_floor_e];
- ramp_smooth_ceil = eeprom[ramp_smooth_ceil_e];
- ramp_discrete_floor = eeprom[ramp_discrete_floor_e];
- ramp_discrete_ceil = eeprom[ramp_discrete_ceil_e];
- ramp_discrete_steps = eeprom[ramp_discrete_steps_e];
- #endif
- #ifdef USE_MANUAL_MEMORY
- manual_memory = eeprom[manual_memory_e];
- #endif
- #ifdef USE_TINT_RAMPING
- tint = eeprom[tint_e];
- #endif
- #if defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE)
- strobe_type = eeprom[strobe_type_e]; // TODO: move this to eeprom_wl?
- strobe_delays[0] = eeprom[strobe_delays_0_e];
- strobe_delays[1] = eeprom[strobe_delays_1_e];
- #endif
- #ifdef USE_BIKE_FLASHER_MODE
- bike_flasher_brightness = eeprom[bike_flasher_brightness_e];
- #endif
- #ifdef USE_BEACON_MODE
- beacon_seconds = eeprom[beacon_seconds_e];
- #endif
- #ifdef USE_MUGGLE_MODE
- muggle_mode_active = eeprom[muggle_mode_active_e];
- #endif
- #ifdef USE_THERMAL_REGULATION
- therm_ceil = eeprom[therm_ceil_e];
- therm_cal_offset = eeprom[therm_cal_offset_e];
- #endif
- #ifdef USE_INDICATOR_LED
- indicator_led_mode = eeprom[indicator_led_mode_e];
- #endif
- #ifdef USE_AUX_RGB_LEDS
- rgb_led_off_mode = eeprom[rgb_led_off_mode_e];
- rgb_led_lockout_mode = eeprom[rgb_led_lockout_mode_e];
- #endif
- }
- #ifdef START_AT_MEMORIZED_LEVEL
- if (load_eeprom_wl()) {
- memorized_level = eeprom_wl[0];
+ // doze until next clock tick
+ idle_mode();
}
#endif
-}
-
-void save_config() {
- eeprom[ramp_style_e] = ramp_style;
- #ifdef USE_RAMP_CONFIG
- eeprom[ramp_smooth_floor_e] = ramp_smooth_floor;
- eeprom[ramp_smooth_ceil_e] = ramp_smooth_ceil;
- eeprom[ramp_discrete_floor_e] = ramp_discrete_floor;
- eeprom[ramp_discrete_ceil_e] = ramp_discrete_ceil;
- eeprom[ramp_discrete_steps_e] = ramp_discrete_steps;
- #endif
- #ifdef USE_MANUAL_MEMORY
- eeprom[manual_memory_e] = manual_memory;
- #endif
- #ifdef USE_TINT_RAMPING
- eeprom[tint_e] = tint;
- #endif
- #if defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE)
- eeprom[strobe_type_e] = strobe_type; // TODO: move this to eeprom_wl?
- eeprom[strobe_delays_0_e] = strobe_delays[0];
- eeprom[strobe_delays_1_e] = strobe_delays[1];
- #endif
- #ifdef USE_BIKE_FLASHER_MODE
- eeprom[bike_flasher_brightness_e] = bike_flasher_brightness;
- #endif
- #ifdef USE_BEACON_MODE
- eeprom[beacon_seconds_e] = beacon_seconds;
- #endif
- #ifdef USE_MUGGLE_MODE
- eeprom[muggle_mode_active_e] = muggle_mode_active;
- #endif
- #ifdef USE_THERMAL_REGULATION
- eeprom[therm_ceil_e] = therm_ceil;
- eeprom[therm_cal_offset_e] = therm_cal_offset;
- #endif
- #ifdef USE_INDICATOR_LED
- eeprom[indicator_led_mode_e] = indicator_led_mode;
- #endif
- #ifdef USE_AUX_RGB_LEDS
- eeprom[rgb_led_off_mode_e] = rgb_led_off_mode;
- eeprom[rgb_led_lockout_mode_e] = rgb_led_lockout_mode;
- #endif
-
- save_eeprom();
-}
-#ifdef START_AT_MEMORIZED_LEVEL
-void save_config_wl() {
- eeprom_wl[0] = memorized_level;
- save_eeprom_wl();
}
-#endif
+// instead of handling EV_low_voltage in each mode,
+// it's handled globally here to make the code smaller and simpler
void low_voltage() {
+
+ // "current_state" is volatile, so cache it to reduce code size
StatePtr state = current_state;
// TODO: turn off aux LED(s) when power is really low
@@ -2657,8 +351,7 @@ void low_voltage() {
}
#endif
- // in normal or muggle mode, step down or turn off
- //else if ((state == steady_state) || (state == muggle_state)) {
+ // in normal mode, step down or turn off
else if (state == steady_state) {
if (actual_level > 1) {
uint8_t lvl = (actual_level >> 1) + (actual_level >> 2);
@@ -2672,169 +365,6 @@ void low_voltage() {
else {
set_state(off_state, 0);
}
-}
-
-
-void setup() {
- #ifdef START_AT_MEMORIZED_LEVEL
- // dual switch: e-switch + power clicky
- // power clicky acts as a momentary mode
- load_config();
-
- #ifdef USE_MUGGLE_MODE
- if (muggle_mode_active)
- push_state(muggle_state, memorized_level);
- else
- #endif
- if (button_is_pressed())
- // hold button to go to moon
- push_state(steady_state, 1);
- else
- // otherwise use memory
- push_state(steady_state, memorized_level);
-
- #else // if not START_AT_MEMORIZED_LEVEL
-
- // blink at power-on to let user know power is connected
- set_level(RAMP_SIZE/8);
- delay_4ms(3);
- set_level(0);
-
- #ifdef USE_FACTORY_RESET
- if (button_is_pressed())
- factory_reset();
- #endif
- load_config();
-
- #ifdef USE_TINT_RAMPING
- // add tint ramping underneath every other state
- push_state(tint_ramping_state, 0);
- #endif // ifdef USE_TINT_RAMPING
-
- #ifdef USE_MUGGLE_MODE
- if (muggle_mode_active)
- push_state(muggle_state, (MUGGLE_FLOOR+MUGGLE_CEILING)/2);
- else
- #endif
- push_state(off_state, 1);
-
- #endif // ifdef START_AT_MEMORIZED_LEVEL
}
-
-void loop() {
-
- StatePtr state = current_state;
-
- #ifdef USE_AUX_RGB_LEDS_WHILE_ON
- if (! setting_rgb_mode_now) rgb_led_voltage_readout(1);
- #endif
-
- if (0) {}
-
- #ifdef USE_VERSION_CHECK
- else if (state == version_check_state) {
- for (uint8_t i=0; i<sizeof(version_number)-1; i++) {
- blink_digit(pgm_read_byte(version_number + i) - '0');
- nice_delay_ms(300);
- }
- // FIXME: when user interrupts with button, "off" takes an extra click
- // before it'll turn back on, because the click to cancel gets sent
- // to the "off" state instead of version_check_state
- //while (button_is_pressed()) {}
- //empty_event_sequence();
-
- set_state(off_state, 0);
- }
- #endif // #ifdef USE_VERSION_CHECK
-
- #ifdef USE_STROBE_STATE
- else if ((state == strobe_state)
- #ifdef USE_MOMENTARY_MODE
- // also handle momentary strobes
- || ((state == momentary_state) && (momentary_mode == 1) && (momentary_active))
- #endif
- ) {
- uint8_t st = strobe_type;
-
- switch(st) {
- #if defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE)
- #ifdef USE_PARTY_STROBE_MODE
- case party_strobe_e:
- #endif
- #ifdef USE_TACTICAL_STROBE_MODE
- case tactical_strobe_e:
- #endif
- party_tactical_strobe_mode_iter(st);
- break;
- #endif
-
- #ifdef USE_LIGHTNING_MODE
- case lightning_storm_e:
- lightning_storm_iter();
- break;
- #endif
-
- #ifdef USE_BIKE_FLASHER_MODE
- case bike_flasher_e:
- bike_flasher_iter();
- break;
- #endif
- }
-
- }
- #endif // #ifdef USE_STROBE_STATE
-
- #ifdef USE_BORING_STROBE_STATE
- else if (state == boring_strobe_state) {
- switch(boring_strobe_type) {
- #ifdef USE_POLICE_STROBE_MODE
- case 0: // police strobe
- police_strobe_iter();
- break;
- #endif
-
- #ifdef USE_SOS_MODE_IN_FF_GROUP
- default: // SOS
- sos_mode_iter();
- break;
- #endif
- }
- }
- #endif // #ifdef USE_BORING_STROBE_STATE
-
- #ifdef USE_BATTCHECK
- else if (state == battcheck_state) {
- battcheck();
- }
- #endif
-
- #ifdef USE_BEACON_MODE
- else if (state == beacon_state) {
- beacon_mode_iter();
- }
- #endif
-
- #ifdef USE_SOS_MODE_IN_BLINKY_GROUP
- else if (state == sos_state) {
- sos_mode_iter();
- }
- #endif
-
- #ifdef USE_THERMAL_REGULATION
- // TODO: blink out therm_ceil during thermal_config_state?
- else if (state == tempcheck_state) {
- blink_num(temperature);
- nice_delay_ms(1000);
- }
- #endif
-
- #ifdef USE_IDLE_MODE
- else {
- // doze until next clock tick
- idle_mode();
- }
- #endif
-
-}
diff --git a/spaghetti-monster/anduril/aux-leds.c b/spaghetti-monster/anduril/aux-leds.c
new file mode 100644
index 0000000..c819bd7
--- /dev/null
+++ b/spaghetti-monster/anduril/aux-leds.c
@@ -0,0 +1,178 @@
+/*
+ * aux-leds.c: Aux LED functions for Anduril.
+ *
+ * Copyright (C) 2017 Selene ToyKeeper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef AUX_LEDS_C
+#define AUX_LEDS_C
+
+#include "aux-leds.h"
+
+
+#if defined(USE_INDICATOR_LED) && defined(TICK_DURING_STANDBY)
+// beacon-like mode for the indicator LED
+void indicator_blink(uint8_t arg) {
+ // turn off aux LEDs when battery is empty
+ if (voltage < VOLTAGE_LOW) { indicator_led(0); return; }
+
+ #ifdef USE_FANCIER_BLINKING_INDICATOR
+
+ // fancy blink, set off/low/high levels here:
+ static const uint8_t seq[] = {0, 1, 2, 1, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0, 0, 0};
+ indicator_led(seq[arg & 15]);
+
+ #else // basic blink, 1/8th duty cycle
+
+ if (! (arg & 7)) {
+ indicator_led(2);
+ }
+ else {
+ indicator_led(0);
+ }
+
+ #endif
+}
+#endif
+
+#if defined(USE_AUX_RGB_LEDS) && defined(TICK_DURING_STANDBY)
+uint8_t voltage_to_rgb() {
+ static const uint8_t levels[] = {
+ // voltage, color
+ 0, 0, // 0, R
+ 33, 1, // 1, R+G
+ 35, 2, // 2, G
+ 37, 3, // 3, G+B
+ 39, 4, // 4, B
+ 41, 5, // 5, R + B
+ 44, 6, // 6, R+G+B // skip; looks too similar to G+B
+ 255, 6, // 7, R+G+B
+ };
+ uint8_t volts = voltage;
+ if (volts < 29) return 0;
+
+ uint8_t i;
+ for (i = 0; volts >= levels[i]; i += 2) {}
+ uint8_t color_num = levels[(i - 2) + 1];
+ return pgm_read_byte(rgb_led_colors + color_num);
+}
+
+// do fancy stuff with the RGB aux LEDs
+// mode: 0bPPPPCCCC where PPPP is the pattern and CCCC is the color
+// arg: time slice number
+void rgb_led_update(uint8_t mode, uint8_t arg) {
+ static uint8_t rainbow = 0; // track state of rainbow mode
+ static uint8_t frame = 0; // track state of animation mode
+
+ // turn off aux LEDs when battery is empty
+ // (but if voltage==0, that means we just booted and don't know yet)
+ uint8_t volts = voltage; // save a few bytes by caching volatile value
+ if ((volts) && (volts < VOLTAGE_LOW)) {
+ rgb_led_set(0);
+ #ifdef USE_BUTTON_LED
+ button_led_set(0);
+ #endif
+ return;
+ }
+
+ uint8_t pattern = (mode>>4); // off, low, high, blinking, ... more?
+ uint8_t color = mode & 0x0f;
+
+ // preview in blinking mode is awkward... use high instead
+ if ((! go_to_standby) && (pattern > 2)) { pattern = 2; }
+
+
+ const uint8_t *colors = rgb_led_colors;
+ uint8_t actual_color = 0;
+ if (color < 7) { // normal color
+ actual_color = pgm_read_byte(colors + color);
+ }
+ else if (color == 7) { // disco
+ rainbow = (rainbow + 1 + pseudo_rand() % 5) % 6;
+ actual_color = pgm_read_byte(colors + rainbow);
+ }
+ else if (color == 8) { // rainbow
+ uint8_t speed = 0x03; // awake speed
+ if (go_to_standby) speed = RGB_RAINBOW_SPEED; // asleep speed
+ if (0 == (arg & speed)) {
+ rainbow = (rainbow + 1) % 6;
+ }
+ actual_color = pgm_read_byte(colors + rainbow);
+ }
+ else { // voltage
+ // show actual voltage while asleep...
+ if (go_to_standby) {
+ actual_color = voltage_to_rgb();
+ // choose a color based on battery voltage
+ //if (volts >= 38) actual_color = pgm_read_byte(colors + 4);
+ //else if (volts >= 33) actual_color = pgm_read_byte(colors + 2);
+ //else actual_color = pgm_read_byte(colors + 0);
+ }
+ // ... but during preview, cycle colors quickly
+ else {
+ actual_color = pgm_read_byte(colors + (((arg>>1) % 3) << 1));
+ }
+ }
+
+ // pick a brightness from the animation sequence
+ if (pattern == 3) {
+ // uses an odd length to avoid lining up with rainbow loop
+ static const uint8_t animation[] = {2, 1, 0, 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 1};
+ frame = (frame + 1) % sizeof(animation);
+ pattern = animation[frame];
+ }
+ uint8_t result;
+ #ifdef USE_BUTTON_LED
+ uint8_t button_led_result;
+ #endif
+ switch (pattern) {
+ case 0: // off
+ result = 0;
+ #ifdef USE_BUTTON_LED
+ button_led_result = 0;
+ #endif
+ break;
+ case 1: // low
+ result = actual_color;
+ #ifdef USE_BUTTON_LED
+ button_led_result = 1;
+ #endif
+ break;
+ default: // high
+ result = (actual_color << 1);
+ #ifdef USE_BUTTON_LED
+ button_led_result = 2;
+ #endif
+ break;
+ }
+ rgb_led_set(result);
+ #ifdef USE_BUTTON_LED
+ button_led_set(button_led_result);
+ #endif
+}
+
+void rgb_led_voltage_readout(uint8_t bright) {
+ uint8_t color = voltage_to_rgb();
+ if (bright) color = color << 1;
+ rgb_led_set(color);
+}
+#endif
+
+
+#endif
+
diff --git a/spaghetti-monster/anduril/aux-leds.h b/spaghetti-monster/anduril/aux-leds.h
new file mode 100644
index 0000000..94dc3c0
--- /dev/null
+++ b/spaghetti-monster/anduril/aux-leds.h
@@ -0,0 +1,85 @@
+/*
+ * aux-leds.h: Aux LED functions for Anduril.
+ *
+ * Copyright (C) 2017 Selene ToyKeeper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef AUX_LEDS_H
+#define AUX_LEDS_H
+
+#if defined(USE_INDICATOR_LED) && defined(TICK_DURING_STANDBY)
+void indicator_blink(uint8_t arg);
+#endif
+#if defined(USE_AUX_RGB_LEDS) && defined(TICK_DURING_STANDBY)
+uint8_t setting_rgb_mode_now = 0;
+void rgb_led_update(uint8_t mode, uint8_t arg);
+void rgb_led_voltage_readout(uint8_t bright);
+/*
+ * 0: R
+ * 1: RG
+ * 2: G
+ * 3: GB
+ * 4: B
+ * 5: R B
+ * 6: RGB
+ * 7: rainbow
+ * 8: voltage
+ */
+const PROGMEM uint8_t rgb_led_colors[] = {
+ 0b00000001, // 0: red
+ 0b00000101, // 1: yellow
+ 0b00000100, // 2: green
+ 0b00010100, // 3: cyan
+ 0b00010000, // 4: blue
+ 0b00010001, // 5: purple
+ 0b00010101, // 6: white
+};
+// intentionally 1 higher than total modes, to make "voltage" easier to reach
+// (at Hank's request)
+#define RGB_LED_NUM_COLORS 11
+#define RGB_LED_NUM_PATTERNS 4
+#ifndef RGB_LED_OFF_DEFAULT
+#define RGB_LED_OFF_DEFAULT 0x19 // low, voltage
+//#define RGB_LED_OFF_DEFAULT 0x18 // low, rainbow
+#endif
+#ifndef RGB_LED_LOCKOUT_DEFAULT
+#define RGB_LED_LOCKOUT_DEFAULT 0x37 // blinking, disco
+#endif
+#ifndef RGB_RAINBOW_SPEED
+#define RGB_RAINBOW_SPEED 0x0f // change color every 16 frames
+#endif
+uint8_t rgb_led_off_mode = RGB_LED_OFF_DEFAULT;
+uint8_t rgb_led_lockout_mode = RGB_LED_LOCKOUT_DEFAULT;
+#endif
+
+#ifdef USE_INDICATOR_LED
+ // bits 2-3 control lockout mode
+ // bits 0-1 control "off" mode
+ // modes are: 0=off, 1=low, 2=high, 3=blinking (if TICK_DURING_STANDBY enabled)
+ #ifdef INDICATOR_LED_DEFAULT_MODE
+ uint8_t indicator_led_mode = INDICATOR_LED_DEFAULT_MODE;
+ #else
+ #ifdef USE_INDICATOR_LED_WHILE_RAMPING
+ //uint8_t indicator_led_mode = (1<<2) + 2;
+ uint8_t indicator_led_mode = (2<<2) + 1;
+ #else
+ uint8_t indicator_led_mode = (3<<2) + 1;
+ #endif
+ #endif
+#endif
+
+
+#endif
diff --git a/spaghetti-monster/anduril/battcheck-mode-fsm.h b/spaghetti-monster/anduril/battcheck-mode-fsm.h
new file mode 100644
index 0000000..8f19e12
--- /dev/null
+++ b/spaghetti-monster/anduril/battcheck-mode-fsm.h
@@ -0,0 +1,26 @@
+/*
+ * battcheck-mode-fsm.h: FSM config for battery check mode in Anduril.
+ *
+ * Copyright (C) 2017 Selene ToyKeeper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef BATTCHECK_MODE_FSM_H
+#define BATTCHECK_MODE_FSM_H
+
+#define USE_BATTCHECK
+
+
+#endif
diff --git a/spaghetti-monster/anduril/battcheck-mode.c b/spaghetti-monster/anduril/battcheck-mode.c
new file mode 100644
index 0000000..d34559b
--- /dev/null
+++ b/spaghetti-monster/anduril/battcheck-mode.c
@@ -0,0 +1,82 @@
+/*
+ * battcheck-mode.c: Battery check mode for Anduril.
+ *
+ * Copyright (C) 2017 Selene ToyKeeper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef BATTCHECK_MODE_C
+#define BATTCHECK_MODE_C
+
+#include "battcheck-mode.h"
+
+uint8_t battcheck_state(Event event, uint16_t arg) {
+ ////////// Every action below here is blocked in the simple UI //////////
+ #ifdef USE_SIMPLE_UI
+ if (simple_ui_active) {
+ return EVENT_NOT_HANDLED;
+ }
+ #endif
+
+ // 1 click: off
+ if (event == EV_1click) {
+ set_state(off_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+
+ // 2 clicks: next blinky mode
+ else if (event == EV_2clicks) {
+ #if defined(USE_THERMAL_REGULATION)
+ set_state(tempcheck_state, 0);
+ #elif defined(USE_BEACON_MODE)
+ set_state(beacon_state, 0);
+ #elif defined(USE_SOS_MODE) && defined(USE_SOS_MODE_IN_BLINKY_GROUP)
+ set_state(sos_state, 0);
+ #endif
+ return MISCHIEF_MANAGED;
+ }
+
+ #ifdef USE_VOLTAGE_CORRECTION
+ // 7H: voltage config mode
+ else if (event == EV_click7_hold) {
+ push_state(voltage_config_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ #endif
+
+ return EVENT_NOT_HANDLED;
+}
+
+#ifdef USE_VOLTAGE_CORRECTION
+// the user can adjust the battery measurements... on a scale of 1 to 13
+// 1 = subtract 0.30V
+// 2 = subtract 0.25V
+// ...
+// 7 = no effect (add 0V)
+// 8 = add 0.05V
+// ...
+// 13 = add 0.30V
+void voltage_config_save(uint8_t step, uint8_t value) {
+ if (value) voltage_correction = value;
+}
+
+uint8_t voltage_config_state(Event event, uint16_t arg) {
+ return config_state_base(event, arg, 1, voltage_config_save);
+}
+#endif // #ifdef USE_VOLTAGE_CORRECTION
+
+
+#endif
+
diff --git a/spaghetti-monster/anduril/battcheck-mode.h b/spaghetti-monster/anduril/battcheck-mode.h
new file mode 100644
index 0000000..965ffd9
--- /dev/null
+++ b/spaghetti-monster/anduril/battcheck-mode.h
@@ -0,0 +1,31 @@
+/*
+ * battcheck-mode.h: Battery check mode for Anduril.
+ *
+ * Copyright (C) 2017 Selene ToyKeeper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef BATTCHECK_MODE_H
+#define BATTCHECK_MODE_H
+
+uint8_t battcheck_state(Event event, uint16_t arg);
+
+#ifdef USE_VOLTAGE_CORRECTION
+void voltage_config_save(uint8_t step, uint8_t value);
+uint8_t voltage_config_state(Event event, uint16_t arg);
+#endif
+
+
+#endif
diff --git a/spaghetti-monster/anduril/beacon-mode.c b/spaghetti-monster/anduril/beacon-mode.c
new file mode 100644
index 0000000..e8e5004
--- /dev/null
+++ b/spaghetti-monster/anduril/beacon-mode.c
@@ -0,0 +1,73 @@
+/*
+ * beacon-mode.c: Beacon mode for Anduril.
+ *
+ * Copyright (C) 2017 Selene ToyKeeper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef BEACON_MODE_C
+#define BEACON_MODE_C
+
+#include "beacon-mode.h"
+
+inline void beacon_mode_iter() {
+ // one iteration of main loop()
+ if (! button_last_state) {
+ set_level(memorized_level);
+ nice_delay_ms(100);
+ set_level(0);
+ nice_delay_ms(((beacon_seconds) * 1000) - 100);
+ }
+}
+
+uint8_t beacon_state(Event event, uint16_t arg) {
+ // 1 click: off
+ if (event == EV_1click) {
+ set_state(off_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ // TODO: use sleep ticks to measure time between pulses,
+ // to save power
+
+ // 2 clicks: next blinky mode
+ else if (event == EV_2clicks) {
+ #if defined(USE_SOS_MODE) && defined(USE_SOS_MODE_IN_BLINKY_GROUP)
+ set_state(sos_state, 0);
+ #elif defined(USE_BATTCHECK)
+ set_state(battcheck_state, 0);
+ #elif defined(USE_THERMAL_REGULATION)
+ set_state(tempcheck_state, 0);
+ #endif
+ return MISCHIEF_MANAGED;
+ }
+ // hold: configure beacon timing
+ else if (event == EV_click1_hold) {
+ if (0 == (arg % TICKS_PER_SECOND)) {
+ blink_once();
+ }
+ return MISCHIEF_MANAGED;
+ }
+ // release hold: save new timing
+ else if (event == EV_click1_hold_release) {
+ beacon_seconds = 1 + (arg / TICKS_PER_SECOND);
+ save_config();
+ return MISCHIEF_MANAGED;
+ }
+ return EVENT_NOT_HANDLED;
+}
+
+
+#endif
+
diff --git a/spaghetti-monster/anduril/beacon-mode.h b/spaghetti-monster/anduril/beacon-mode.h
new file mode 100644
index 0000000..752918e
--- /dev/null
+++ b/spaghetti-monster/anduril/beacon-mode.h
@@ -0,0 +1,31 @@
+/*
+ * beacon-mode.h: Beacon mode for Anduril.
+ *
+ * Copyright (C) 2017 Selene ToyKeeper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef BEACON_MODE_H
+#define BEACON_MODE_H
+
+// beacon timing
+uint8_t beacon_seconds = 2;
+
+// beacon mode
+uint8_t beacon_state(Event event, uint16_t arg);
+inline void beacon_mode_iter();
+
+
+#endif
diff --git a/spaghetti-monster/anduril/candle-mode.c b/spaghetti-monster/anduril/candle-mode.c
new file mode 100644
index 0000000..3704ee6
--- /dev/null
+++ b/spaghetti-monster/anduril/candle-mode.c
@@ -0,0 +1,156 @@
+/*
+ * candle-mode.c: Candle mode for Anduril.
+ *
+ * Copyright (C) 2017 Selene ToyKeeper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef CANDLE_MODE_C
+#define CANDLE_MODE_C
+
+#include "candle-mode.h"
+
+#ifdef USE_SUNSET_TIMER
+#include "sunset-timer.h"
+#endif
+
+uint8_t candle_mode_state(Event event, uint16_t arg) {
+ static int8_t ramp_direction = 1;
+ #define MAX_CANDLE_LEVEL (RAMP_LENGTH-CANDLE_AMPLITUDE-15)
+ static uint8_t candle_wave1 = 0;
+ static uint8_t candle_wave2 = 0;
+ static uint8_t candle_wave3 = 0;
+ static uint8_t candle_wave2_speed = 0;
+ // these should add up to 100
+ #define CANDLE_WAVE1_MAXDEPTH 30
+ #define CANDLE_WAVE2_MAXDEPTH 45
+ #define CANDLE_WAVE3_MAXDEPTH 25
+ static const uint8_t candle_wave1_depth = CANDLE_WAVE1_MAXDEPTH * CANDLE_AMPLITUDE / 100;
+ static uint8_t candle_wave2_depth = CANDLE_WAVE2_MAXDEPTH * CANDLE_AMPLITUDE / 100;
+ static uint8_t candle_wave3_depth = CANDLE_WAVE3_MAXDEPTH * CANDLE_AMPLITUDE / 100;
+ static uint8_t candle_mode_brightness = 24;
+
+ #ifdef USE_SUNSET_TIMER
+ // let the candle "burn out" and shut itself off
+ // if the user told it to
+ // cache this in case it changes when the timer is called
+ uint8_t sunset_active = sunset_timer;
+ // clock tick
+ sunset_timer_state(event, arg);
+ // if the timer just expired, shut off
+ if (sunset_active && (! sunset_timer)) {
+ set_state(off_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ #endif // ifdef USE_SUNSET_TIMER
+
+
+ if (event == EV_enter_state) {
+ ramp_direction = 1;
+ return MISCHIEF_MANAGED;
+ }
+ #ifdef USE_SUNSET_TIMER
+ // 2 clicks: cancel timer
+ else if (event == EV_2clicks) {
+ // parent state just rotated through strobe/flasher modes,
+ // so cancel timer... in case any time was left over from earlier
+ sunset_timer = 0;
+ return MISCHIEF_MANAGED;
+ }
+ #endif // ifdef USE_SUNSET_TIMER
+ // hold: change brightness (brighter)
+ else if (event == EV_click1_hold) {
+ // ramp away from extremes
+ if (! arg) {
+ if (candle_mode_brightness >= MAX_CANDLE_LEVEL) { ramp_direction = -1; }
+ else if (candle_mode_brightness <= 1) { ramp_direction = 1; }
+ }
+ // change brightness, but not too far
+ candle_mode_brightness += ramp_direction;
+ if (candle_mode_brightness < 1) candle_mode_brightness = 1;
+ else if (candle_mode_brightness > MAX_CANDLE_LEVEL) candle_mode_brightness = MAX_CANDLE_LEVEL;
+ return MISCHIEF_MANAGED;
+ }
+ // reverse ramp direction on hold release
+ else if (event == EV_click1_hold_release) {
+ ramp_direction = -ramp_direction;
+ return MISCHIEF_MANAGED;
+ }
+ // click, hold: change brightness (dimmer)
+ else if (event == EV_click2_hold) {
+ ramp_direction = 1;
+ if (candle_mode_brightness > 1)
+ candle_mode_brightness --;
+ return MISCHIEF_MANAGED;
+ }
+ // clock tick: animate candle brightness
+ else if (event == EV_tick) {
+ // un-reverse after 1 second
+ if (arg == TICKS_PER_SECOND) ramp_direction = 1;
+
+ // 3-oscillator synth for a relatively organic pattern
+ uint8_t add;
+ add = ((triangle_wave(candle_wave1) * candle_wave1_depth) >> 8)
+ + ((triangle_wave(candle_wave2) * candle_wave2_depth) >> 8)
+ + ((triangle_wave(candle_wave3) * candle_wave3_depth) >> 8);
+ uint16_t brightness = candle_mode_brightness + add;
+
+ // self-timer dims the light during the final minute
+ #ifdef USE_SUNSET_TIMER
+ if (1 == sunset_timer) {
+ brightness = brightness
+ * ((TICKS_PER_MINUTE>>5) - (sunset_ticks>>5))
+ / (TICKS_PER_MINUTE>>5);
+ }
+ #endif // ifdef USE_SUNSET_TIMER
+
+ set_level(brightness);
+
+ // wave1: slow random LFO
+ // TODO: make wave slower and more erratic?
+ if ((arg & 1) == 0) candle_wave1 += pseudo_rand() & 1;
+ // wave2: medium-speed erratic LFO
+ candle_wave2 += candle_wave2_speed;
+ // wave3: erratic fast wave
+ candle_wave3 += pseudo_rand() % 37;
+ // S&H on wave2 frequency to make it more erratic
+ if ((pseudo_rand() & 0b00111111) == 0)
+ candle_wave2_speed = pseudo_rand() % 13;
+ // downward sawtooth on wave2 depth to simulate stabilizing
+ if ((candle_wave2_depth > 0) && ((pseudo_rand() & 0b00111111) == 0))
+ candle_wave2_depth --;
+ // random sawtooth retrigger
+ if (pseudo_rand() == 0) {
+ // random amplitude
+ //candle_wave2_depth = 2 + (pseudo_rand() % ((CANDLE_WAVE2_MAXDEPTH * CANDLE_AMPLITUDE / 100) - 2));
+ candle_wave2_depth = pseudo_rand() % (CANDLE_WAVE2_MAXDEPTH * CANDLE_AMPLITUDE / 100);
+ //candle_wave3_depth = 5;
+ candle_wave2 = 0;
+ }
+ // downward sawtooth on wave3 depth to simulate stabilizing
+ if ((candle_wave3_depth > 2) && ((pseudo_rand() & 0b00011111) == 0))
+ candle_wave3_depth --;
+ if ((pseudo_rand() & 0b01111111) == 0)
+ // random amplitude
+ //candle_wave3_depth = 2 + (pseudo_rand() % ((CANDLE_WAVE3_MAXDEPTH * CANDLE_AMPLITUDE / 100) - 2));
+ candle_wave3_depth = pseudo_rand() % (CANDLE_WAVE3_MAXDEPTH * CANDLE_AMPLITUDE / 100);
+ return MISCHIEF_MANAGED;
+ }
+ return EVENT_NOT_HANDLED;
+}
+
+
+#endif
+
diff --git a/spaghetti-monster/anduril/candle-mode.h b/spaghetti-monster/anduril/candle-mode.h
new file mode 100644
index 0000000..8859a9c
--- /dev/null
+++ b/spaghetti-monster/anduril/candle-mode.h
@@ -0,0 +1,32 @@
+/*
+ * candle-mode.h: Candle mode for Anduril.
+ *
+ * Copyright (C) 2017 Selene ToyKeeper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef CANDLE_MODE_H
+#define CANDLE_MODE_H
+
+#ifndef CANDLE_AMPLITUDE
+#define CANDLE_AMPLITUDE 25
+#endif
+
+uint8_t candle_mode_state(Event event, uint16_t arg);
+// moved to fsm-misc.c because it's also used for tint ramping power correction
+//uint8_t triangle_wave(uint8_t phase);
+
+
+#endif
diff --git a/spaghetti-monster/anduril/cfg-blf-gt-mini.h b/spaghetti-monster/anduril/cfg-blf-gt-mini.h
index 3728f98..45dfe36 100644
--- a/spaghetti-monster/anduril/cfg-blf-gt-mini.h
+++ b/spaghetti-monster/anduril/cfg-blf-gt-mini.h
@@ -2,6 +2,8 @@
#include "hwdef-BLF_GT_Mini.h"
// Same as an Emisar D1S, except it has a lighted button
#include "cfg-emisar-d1s.h"
+#undef MODEL_NUMBER
+#define MODEL_NUMBER "0322"
// the button lights up
#define USE_INDICATOR_LED
diff --git a/spaghetti-monster/anduril/cfg-blf-gt.h b/spaghetti-monster/anduril/cfg-blf-gt.h
index 6a74bbc..061685c 100644
--- a/spaghetti-monster/anduril/cfg-blf-gt.h
+++ b/spaghetti-monster/anduril/cfg-blf-gt.h
@@ -1,4 +1,5 @@
// BLF GT config options for Anduril
+#define MODEL_NUMBER "0321"
#include "hwdef-BLF_GT.h"
// the button lights up
@@ -9,7 +10,7 @@
#define TICK_DURING_STANDBY
// don't blink during ramp, it's irrelevant and annoying on this light
-#undef BLINK_AT_RAMP_CEILING
+#undef BLINK_AT_RAMP_CEIL
#undef BLINK_AT_RAMP_MIDDLE
#undef BLINK_AT_RAMP_FLOOR
@@ -32,11 +33,16 @@
// use 2.0 A as the ceiling, 2.5 A only for turbo
// start both ramps at the bottom; even moon throws a long way on the GT
#define RAMP_SMOOTH_FLOOR 1
-#define RAMP_SMOOTH_CEIL POWER_80PX
+#define RAMP_SMOOTH_CEIL POWER_80PX
#define RAMP_DISCRETE_FLOOR 1
-#define RAMP_DISCRETE_CEIL POWER_80PX
+#define RAMP_DISCRETE_CEIL POWER_80PX
#define RAMP_DISCRETE_STEPS 7
+// GT can handle heat well, so don't limit simple mode
+#define SIMPLE_UI_FLOOR RAMP_DISCRETE_FLOOR
+#define SIMPLE_UI_CEIL RAMP_DISCRETE_CEIL
+#define SIMPLE_UI_STEPS 5
+
// stop panicking at 80% power, this light has plenty of thermal mass
#define THERM_FASTER_LEVEL POWER_80PX // throttle back faster when high
diff --git a/spaghetti-monster/anduril/cfg-blf-lantern.h b/spaghetti-monster/anduril/cfg-blf-lantern.h
index e12a453..6be8ec7 100644
--- a/spaghetti-monster/anduril/cfg-blf-lantern.h
+++ b/spaghetti-monster/anduril/cfg-blf-lantern.h
@@ -1,4 +1,5 @@
// BLF Lantern config options for Anduril
+#define MODEL_NUMBER "0621"
/* BLF Lantern pinout
* ----
* Reset -|1 8|- VCC
@@ -46,17 +47,21 @@
// the default of 26 looks a bit flat, so increase it
#define CANDLE_AMPLITUDE 40
+// override default ramp style
+#undef RAMP_STYLE
+#define RAMP_STYLE 1 // 0 = smooth, 1 = stepped
// set floor and ceiling as far apart as possible
// because this lantern isn't overpowered
-#define RAMP_STYLE 1 // 0 = smooth, 1 = stepped
#define RAMP_SMOOTH_FLOOR 1
-#define RAMP_SMOOTH_CEIL 150
+#define RAMP_SMOOTH_CEIL 150
#define RAMP_DISCRETE_FLOOR 10
-#define RAMP_DISCRETE_CEIL RAMP_SMOOTH_CEIL
+#define RAMP_DISCRETE_CEIL RAMP_SMOOTH_CEIL
#define RAMP_DISCRETE_STEPS 5
-#define MUGGLE_FLOOR 15 // about 20 lm
-#define MUGGLE_CEILING 115 // about 350 lm
+// LT1 can handle heat well, so don't limit simple mode
+#define SIMPLE_UI_FLOOR RAMP_DISCRETE_FLOOR
+#define SIMPLE_UI_CEIL RAMP_DISCRETE_CEIL
+#define SIMPLE_UI_STEPS RAMP_DISCRETE_STEPS
#define USE_SOS_MODE
#define USE_SOS_MODE_IN_BLINKY_GROUP
@@ -83,6 +88,6 @@
#undef BLINK_AT_RAMP_FLOOR
#endif
// except the top... blink at the top
-#ifndef BLINK_AT_RAMP_CEILING
-#define BLINK_AT_RAMP_CEILING
+#ifndef BLINK_AT_RAMP_CEIL
+#define BLINK_AT_RAMP_CEIL
#endif
diff --git a/spaghetti-monster/anduril/cfg-blf-q8.h b/spaghetti-monster/anduril/cfg-blf-q8.h
index 970fedb..408c305 100644
--- a/spaghetti-monster/anduril/cfg-blf-q8.h
+++ b/spaghetti-monster/anduril/cfg-blf-q8.h
@@ -1,4 +1,5 @@
// BLF Q8 config options for Anduril
+#define MODEL_NUMBER "0611"
#include "hwdef-BLF_Q8.h"
// the button lights up
@@ -24,6 +25,18 @@
#define HALFSPEED_LEVEL 14
#define QUARTERSPEED_LEVEL 5
+#define RAMP_SMOOTH_FLOOR 1
+#define RAMP_SMOOTH_CEIL 120
+// 10 28 46 [65] 83 101 120
+#define RAMP_DISCRETE_FLOOR 10
+#define RAMP_DISCRETE_CEIL RAMP_SMOOTH_CEIL
+#define RAMP_DISCRETE_STEPS 7
+
+// safe limit ~50% power
+#define SIMPLE_UI_FLOOR RAMP_DISCRETE_FLOOR
+#define SIMPLE_UI_CEIL RAMP_DISCRETE_CEIL
+#define SIMPLE_UI_STEPS 5
+
// stop panicking at ~75% power or ~3000 lm, this light has high thermal mass
#define THERM_FASTER_LEVEL (RAMP_SIZE*9/10) // throttle back faster when high
diff --git a/spaghetti-monster/anduril/cfg-emisar-d1.h b/spaghetti-monster/anduril/cfg-emisar-d1.h
index 9276ba3..2427773 100644
--- a/spaghetti-monster/anduril/cfg-emisar-d1.h
+++ b/spaghetti-monster/anduril/cfg-emisar-d1.h
@@ -2,6 +2,12 @@
#include "hwdef-Emisar_D1.h"
// same as Emisar D4, mostly
#include "cfg-emisar-d4.h"
+#undef MODEL_NUMBER
+#define MODEL_NUMBER "0121"
+
+// safe limit ~50% power
+#undef SIMPLE_UI_CEIL
+#define SIMPLE_UI_CEIL 120
// stop panicking at ~75% power or ~1000 lm (D1 has a decent power-to-thermal-mass ratio)
#ifdef THERM_FASTER_LEVEL
diff --git a/spaghetti-monster/anduril/cfg-emisar-d18.h b/spaghetti-monster/anduril/cfg-emisar-d18.h
index 72f0589..6aaa693 100644
--- a/spaghetti-monster/anduril/cfg-emisar-d18.h
+++ b/spaghetti-monster/anduril/cfg-emisar-d18.h
@@ -1,21 +1,7 @@
-// Emisar FET+13+1 config options for Anduril
+// Emisar D18 (FET+13+1) config options for Anduril
+#define MODEL_NUMBER "0141"
#include "hwdef-Emisar_D18.h"
-// front-facing aux LEDs
-#define USE_INDICATOR_LED
-//#define USE_INDICATOR_LED_WHILE_RAMPING
-// enable blinking indicator LED while off
-#define TICK_DURING_STANDBY
-#define STANDBY_TICK_SPEED 3 // every 0.128 s
-#define USE_FANCIER_BLINKING_INDICATOR
-// off mode: low (1)
-// lockout: blinking (3)
-#define INDICATOR_LED_DEFAULT_MODE ((3<<2) + 1)
-
-// Emisar wanted a shortcut to this
-#define USE_TENCLICK_THERMAL_CONFIG
-
-
// level_calc.py seventh 3 150 7135 1 1.4 117.99 7135 6 1 1706.86 FET 3 10 13000
// (designed to make 1x hit at level 50, and Nx hit at level 100)
#define RAMP_LENGTH 150
@@ -30,15 +16,23 @@
// start at ~2000 lm after battery change, not ~150 lm (at Emisar's request)
//#define DEFAULT_LEVEL MAX_Nx7135
-// higher floor than default, and stop at highest regulated level
-#define RAMP_DISCRETE_FLOOR 25
-#define RAMP_DISCRETE_CEIL MAX_Nx7135
+// go up to ~4000 lm
+#define RAMP_SMOOTH_FLOOR 1
+#define RAMP_SMOOTH_CEIL 117
+// 20 36 52 68 84 [100] 117
+#define RAMP_DISCRETE_FLOOR 20
+#define RAMP_DISCRETE_CEIL RAMP_SMOOTH_CEIL
#define RAMP_DISCRETE_STEPS 7
+// safe limit ~20% power / max regulated
+#define SIMPLE_UI_FLOOR 20
+#define SIMPLE_UI_CEIL MAX_Nx7135
+#define SIMPLE_UI_STEPS 5
+
// only blink at max regulated level and ceiling
#define BLINK_AT_RAMP_MIDDLE
#define BLINK_AT_RAMP_MIDDLE_1 MAX_Nx7135
-#define BLINK_AT_RAMP_CEILING
+#define BLINK_AT_RAMP_CEIL
// stop panicking at about ~40% power or ~5000 lm
#define THERM_FASTER_LEVEL 125
diff --git a/spaghetti-monster/anduril/cfg-emisar-d1s.h b/spaghetti-monster/anduril/cfg-emisar-d1s.h
index c01c37d..e6ca0f1 100644
--- a/spaghetti-monster/anduril/cfg-emisar-d1s.h
+++ b/spaghetti-monster/anduril/cfg-emisar-d1s.h
@@ -2,6 +2,12 @@
#include "hwdef-Emisar_D1S.h"
// same as Emisar D4, mostly
#include "cfg-emisar-d4.h"
+#undef MODEL_NUMBER
+#define MODEL_NUMBER "0122"
+
+// safe limit ~50% power
+#undef SIMPLE_UI_CEIL
+#define SIMPLE_UI_CEIL 120
// stop panicking at ~90% power or ~1200 lm (D1S has a good power-to-thermal-mass ratio)
#ifdef THERM_FASTER_LEVEL
diff --git a/spaghetti-monster/anduril/cfg-emisar-d1v2.h b/spaghetti-monster/anduril/cfg-emisar-d1v2.h
index 91be3f3..c2815fd 100644
--- a/spaghetti-monster/anduril/cfg-emisar-d1v2.h
+++ b/spaghetti-monster/anduril/cfg-emisar-d1v2.h
@@ -2,6 +2,8 @@
// ATTINY: 1634
// same as Emisar D4v2, mostly
#include "cfg-emisar-d4v2.h"
+#undef MODEL_NUMBER
+#define MODEL_NUMBER "0123"
// there are no aux LEDs on a D1
#undef USE_AUX_RGB_LEDS
@@ -10,6 +12,10 @@
// no aux LEDs means no need for sleep ticks
#undef TICK_DURING_STANDBY
+// safe limit ~50% power
+#undef SIMPLE_UI_CEIL
+#define SIMPLE_UI_CEIL 120
+
// stop panicking at ~75% power or ~1000 lm (D1 has a decent power-to-thermal-mass ratio)
#ifdef THERM_FASTER_LEVEL
#undef THERM_FASTER_LEVEL
diff --git a/spaghetti-monster/anduril/cfg-emisar-d4-219c.h b/spaghetti-monster/anduril/cfg-emisar-d4-219c.h
index cf245a2..1f07008 100644
--- a/spaghetti-monster/anduril/cfg-emisar-d4-219c.h
+++ b/spaghetti-monster/anduril/cfg-emisar-d4-219c.h
@@ -2,6 +2,8 @@
// same as D4S but with FET modes limited to 80% power
// to avoid destroying the LEDs
#include "cfg-emisar-d4.h"
+#undef MODEL_NUMBER
+#define MODEL_NUMBER "0112"
#undef PWM2_LEVELS
#define PWM2_LEVELS 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,4,5,6,7,8,9,11,12,13,15,16,17,19,20,21,23,24,26,28,29,31,32,34,36,38,40,41,44,45,47,49,51,53,56,57,60,62,64,67,69,72,74,76,79,81,84,87,89,92,95,97,100,103,106,109,112,115,118,121,124,128,132,135,138,141,145,148,152,156,160,164,167,171,175,179,183,187,191,195,200,204
diff --git a/spaghetti-monster/anduril/cfg-emisar-d4.h b/spaghetti-monster/anduril/cfg-emisar-d4.h
index 7700d88..caabb0e 100644
--- a/spaghetti-monster/anduril/cfg-emisar-d4.h
+++ b/spaghetti-monster/anduril/cfg-emisar-d4.h
@@ -1,4 +1,5 @@
// Emisar D4 config options for Anduril
+#define MODEL_NUMBER "0111"
#include "hwdef-Emisar_D4.h"
// ../../bin/level_calc.py 1 65 7135 1 0.8 150
@@ -12,12 +13,17 @@
#define QUARTERSPEED_LEVEL 6
#define RAMP_SMOOTH_FLOOR 1
-#define RAMP_SMOOTH_CEIL 120
+#define RAMP_SMOOTH_CEIL 120
// 10, 28, 46, [65], 83, 101, 120
#define RAMP_DISCRETE_FLOOR 10
-#define RAMP_DISCRETE_CEIL RAMP_SMOOTH_CEIL
+#define RAMP_DISCRETE_CEIL RAMP_SMOOTH_CEIL
#define RAMP_DISCRETE_STEPS 7
+// safe limit ~20% power
+#define SIMPLE_UI_FLOOR RAMP_DISCRETE_FLOOR
+#define SIMPLE_UI_CEIL 95
+#define SIMPLE_UI_STEPS 5
+
// stop panicking at ~30% power or ~1200 lm
#define THERM_FASTER_LEVEL 105
diff --git a/spaghetti-monster/anduril/cfg-emisar-d4s-219c.h b/spaghetti-monster/anduril/cfg-emisar-d4s-219c.h
index 0c68bc4..ef783d8 100644
--- a/spaghetti-monster/anduril/cfg-emisar-d4s-219c.h
+++ b/spaghetti-monster/anduril/cfg-emisar-d4s-219c.h
@@ -2,6 +2,8 @@
// same as D4S but with FET modes limited to 80% power
// to avoid destroying the LEDs
#include "cfg-emisar-d4s.h"
+#undef MODEL_NUMBER
+#define MODEL_NUMBER "0132"
#undef PWM2_LEVELS
#define PWM2_LEVELS 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,4,5,6,8,8,10,11,12,14,15,16,18,20,21,23,24,27,28,30,32,34,36,38,40,43,45,48,50,52,55,57,60,63,66,69,72,76,79,82,85,89,92,96,100,104,108,112,116,121,126,130,135,140,145,150,156,161,167,172,178,184,191,197,204
diff --git a/spaghetti-monster/anduril/cfg-emisar-d4s.h b/spaghetti-monster/anduril/cfg-emisar-d4s.h
index f5addb2..fdab906 100644
--- a/spaghetti-monster/anduril/cfg-emisar-d4s.h
+++ b/spaghetti-monster/anduril/cfg-emisar-d4s.h
@@ -1,4 +1,5 @@
// Emisar D4S config options for Anduril
+#define MODEL_NUMBER "0131"
#include "hwdef-Emisar_D4S.h"
// the button lights up
@@ -19,17 +20,25 @@
#define RAMP_LENGTH 150
// 3x7135 + FET
-// ../../bin/level_calc.py 2 150 7135 1 11.2 450 FET 1 10 4000
-// (with a x**9 curve instead of x**3)
-// (because it made the ramp look better than accurate values)
+// level_calc.py ninth 2 150 7135 1 11.2 450 FET 1 10 4000
#define PWM1_LEVELS 1,1,2,2,3,3,4,4,5,5,6,6,7,8,8,9,10,10,11,12,13,14,15,16,17,18,19,21,22,23,25,26,27,29,31,32,34,36,38,40,42,44,46,49,51,54,56,59,62,65,68,71,74,78,81,85,89,93,97,101,106,110,115,120,125,130,136,141,147,153,160,166,173,180,187,195,202,210,219,227,236,245,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0
#define PWM2_LEVELS 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,3,4,5,7,8,10,11,13,14,16,18,19,21,23,25,27,29,31,34,36,38,41,43,46,48,51,54,57,60,63,66,69,72,76,79,83,87,91,95,99,103,107,112,116,121,126,131,136,141,146,152,158,163,169,175,182,188,195,202,209,216,223,231,239,247,255
#define MAX_1x7135 83
#define HALFSPEED_LEVEL 13
#define QUARTERSPEED_LEVEL 6
-// ceiling is level 120/150
-#define RAMP_SMOOTH_CEIL (MAX_LEVEL*4/5)
+#define RAMP_SMOOTH_FLOOR 1
+#define RAMP_SMOOTH_CEIL 120
+// 10, 28, 46, 65, [83], 101, 120
+#define RAMP_DISCRETE_FLOOR 10
+#define RAMP_DISCRETE_CEIL RAMP_SMOOTH_CEIL
+#define RAMP_DISCRETE_STEPS 7
+
+// safe limit ~30% power
+// 10 34 59 [83] 108
+#define SIMPLE_UI_FLOOR RAMP_DISCRETE_FLOOR
+#define SIMPLE_UI_CEIL 108
+#define SIMPLE_UI_STEPS 5
// thermal regulation parameters
#ifdef MIN_THERM_STEPDOWN
diff --git a/spaghetti-monster/anduril/cfg-emisar-d4sv2-219.h b/spaghetti-monster/anduril/cfg-emisar-d4sv2-219.h
index 9898246..65bf116 100644
--- a/spaghetti-monster/anduril/cfg-emisar-d4sv2-219.h
+++ b/spaghetti-monster/anduril/cfg-emisar-d4sv2-219.h
@@ -1,5 +1,7 @@
// Emisar D4Sv2-219 config options for Anduril
#include "cfg-emisar-d4sv2.h"
+#undef MODEL_NUMBER
+#define MODEL_NUMBER "0134"
// ATTINY: 1634
#undef PWM1_LEVELS
diff --git a/spaghetti-monster/anduril/cfg-emisar-d4sv2.h b/spaghetti-monster/anduril/cfg-emisar-d4sv2.h
index 0f3a217..ad6de03 100644
--- a/spaghetti-monster/anduril/cfg-emisar-d4sv2.h
+++ b/spaghetti-monster/anduril/cfg-emisar-d4sv2.h
@@ -1,4 +1,5 @@
// Emisar D4S V2 config options for Anduril
+#define MODEL_NUMBER "0133"
#include "hwdef-Emisar_D4Sv2.h"
// ATTINY: 1634
@@ -26,25 +27,26 @@
#define HALFSPEED_LEVEL 18
#define QUARTERSPEED_LEVEL 8
+//#define DEFAULT_LEVEL MAX_Nx7135
+
#define RAMP_SMOOTH_FLOOR 1
-#define RAMP_SMOOTH_CEIL 130
+#define RAMP_SMOOTH_CEIL 130
// 20, 38, 56, 75, [93], 111, 130
#define RAMP_DISCRETE_FLOOR 20
-#define RAMP_DISCRETE_CEIL RAMP_SMOOTH_CEIL
+#define RAMP_DISCRETE_CEIL RAMP_SMOOTH_CEIL
#define RAMP_DISCRETE_STEPS 7
-//#define DEFAULT_LEVEL MAX_Nx7135
-#define STROBE_BRIGHTNESS MAX_LEVEL
+// safe limit ~35% power, 150% of sustainable thermal power
+// 20 44 69 [93] 118
+#define SIMPLE_UI_FLOOR RAMP_DISCRETE_FLOOR
+#define SIMPLE_UI_CEIL 118
+#define SIMPLE_UI_STEPS 5
-#define MUGGLE_FLOOR RAMP_DISCRETE_FLOOR
-#define MUGGLE_CEILING MAX_Nx7135
+#define STROBE_BRIGHTNESS MAX_LEVEL
// stop panicking at ~50% power or ~2000 lm
#define THERM_FASTER_LEVEL 130
-// easier access to thermal config mode, for Emisar
-#define USE_TENCLICK_THERMAL_CONFIG
-
#ifdef BLINK_AT_RAMP_MIDDLE
#undef BLINK_AT_RAMP_MIDDLE
#endif
diff --git a/spaghetti-monster/anduril/cfg-emisar-d4v2-219.h b/spaghetti-monster/anduril/cfg-emisar-d4v2-219.h
index 0770935..cef89be 100644
--- a/spaghetti-monster/anduril/cfg-emisar-d4v2-219.h
+++ b/spaghetti-monster/anduril/cfg-emisar-d4v2-219.h
@@ -1,5 +1,7 @@
// Emisar D4v2-219 config options for Anduril
#include "cfg-emisar-d4v2.h"
+#undef MODEL_NUMBER
+#define MODEL_NUMBER "0114"
// ATTINY: 1634
#undef PWM1_LEVELS
diff --git a/spaghetti-monster/anduril/cfg-emisar-d4v2-nofet.h b/spaghetti-monster/anduril/cfg-emisar-d4v2-nofet.h
index cbb5891..717afcf 100644
--- a/spaghetti-monster/anduril/cfg-emisar-d4v2-nofet.h
+++ b/spaghetti-monster/anduril/cfg-emisar-d4v2-nofet.h
@@ -1,5 +1,7 @@
// Emisar D4v2-noFET config options for Anduril
#include "cfg-emisar-d4v2.h"
+#undef MODEL_NUMBER
+#define MODEL_NUMBER "0115"
// ATTINY: 1634
#undef PWM_CHANNELS
@@ -23,9 +25,15 @@
#undef RAMP_DISCRETE_CEIL
#undef RAMP_DISCRETE_STEPS
#define RAMP_DISCRETE_FLOOR 10
-#define RAMP_DISCRETE_CEIL 150
+#define RAMP_DISCRETE_CEIL 150
#define RAMP_DISCRETE_STEPS 5
+// safe limit ~100% power because no FET
+#undef SIMPLE_UI_FLOOR
+#undef SIMPLE_UI_CEIL
+#define SIMPLE_UI_FLOOR RAMP_DISCRETE_FLOOR
+#define SIMPLE_UI_CEIL RAMP_DISCRETE_CEIL
+
#define CANDLE_AMPLITUDE 60
#undef THERM_FASTER_LEVEL
diff --git a/spaghetti-monster/anduril/cfg-emisar-d4v2.h b/spaghetti-monster/anduril/cfg-emisar-d4v2.h
index 241ca7e..6e389aa 100644
--- a/spaghetti-monster/anduril/cfg-emisar-d4v2.h
+++ b/spaghetti-monster/anduril/cfg-emisar-d4v2.h
@@ -1,4 +1,5 @@
// Emisar D4 config options for Anduril
+#define MODEL_NUMBER "0113"
#include "hwdef-Emisar_D4v2.h"
// ATTINY: 1634
@@ -31,16 +32,18 @@
#define QUARTERSPEED_LEVEL 6
#define RAMP_SMOOTH_FLOOR 1
-#define RAMP_SMOOTH_CEIL 120
+#define RAMP_SMOOTH_CEIL 120
// 10, 28, 46, [65], 83, 101, 120
#define RAMP_DISCRETE_FLOOR 10
-#define RAMP_DISCRETE_CEIL RAMP_SMOOTH_CEIL
+#define RAMP_DISCRETE_CEIL RAMP_SMOOTH_CEIL
#define RAMP_DISCRETE_STEPS 7
+// safe limit ~20% power
+#define SIMPLE_UI_FLOOR RAMP_DISCRETE_FLOOR
+#define SIMPLE_UI_CEIL 95
+#define SIMPLE_UI_STEPS 5
+
// stop panicking at ~30% power or ~1200 lm
#define THERM_FASTER_LEVEL 105
-// easier access to thermal config mode, for Emisar
-#define USE_TENCLICK_THERMAL_CONFIG
-
#define THERM_CAL_OFFSET 5
diff --git a/spaghetti-monster/anduril/cfg-ff-e01.h b/spaghetti-monster/anduril/cfg-ff-e01.h
index 3b47165..c035180 100644
--- a/spaghetti-monster/anduril/cfg-ff-e01.h
+++ b/spaghetti-monster/anduril/cfg-ff-e01.h
@@ -1,9 +1,11 @@
// Fireflies E01 SST-40 thrower config options for Anduril
// most of the good stuff is in the FFUI config; just copy it
#include "../fireflies-ui/cfg-ff-e01.h"
+#undef MODEL_NUMBER
+#define MODEL_NUMBER "0441"
-#ifndef BLINK_AT_RAMP_CEILING
-#define BLINK_AT_RAMP_CEILING
+#ifndef BLINK_AT_RAMP_CEIL
+#define BLINK_AT_RAMP_CEIL
#endif
// 20, 38, 56, 75, [93], 111, 130 (93 is highest regulated)
@@ -11,5 +13,9 @@
#undef RAMP_DISCRETE_STEPS
#define RAMP_DISCRETE_STEPS 7
-// shortcut for first-time setup
-#define USE_TENCLICK_THERMAL_CONFIG
+// safe limit ~50% power
+// 20 56 [93] 130
+#define SIMPLE_UI_FLOOR 20
+#define SIMPLE_UI_CEIL 130
+#define SIMPLE_UI_STEPS 4
+
diff --git a/spaghetti-monster/anduril/cfg-ff-pl47-219.h b/spaghetti-monster/anduril/cfg-ff-pl47-219.h
index ad66adb..efba8d3 100644
--- a/spaghetti-monster/anduril/cfg-ff-pl47-219.h
+++ b/spaghetti-monster/anduril/cfg-ff-pl47-219.h
@@ -2,6 +2,8 @@
// same as PL47 but with FET modes limited to 67% power
// to avoid destroying the LEDs
#include "cfg-ff-pl47.h"
+#undef MODEL_NUMBER
+#define MODEL_NUMBER "0422"
#undef PWM1_LEVELS
#undef PWM2_LEVELS
diff --git a/spaghetti-monster/anduril/cfg-ff-pl47.h b/spaghetti-monster/anduril/cfg-ff-pl47.h
index e6907c1..1bb6a2f 100644
--- a/spaghetti-monster/anduril/cfg-ff-pl47.h
+++ b/spaghetti-monster/anduril/cfg-ff-pl47.h
@@ -1,4 +1,5 @@
// Fireflies PL47 config options for Anduril
+#define MODEL_NUMBER "0421"
#include "hwdef-FF_PL47.h"
// the button lights up
@@ -48,20 +49,26 @@
// 10, 28, 46, 65, 83, 101, 120 (83 is highest regulated)
#define RAMP_DISCRETE_FLOOR 10
-#define RAMP_DISCRETE_CEIL 120
+#define RAMP_DISCRETE_CEIL 120
#define RAMP_DISCRETE_STEPS 7
+// safe limit ~25% power / ~1000 lm
+// 10 34 59 [83] 108
+#define SIMPLE_UI_FLOOR RAMP_DISCRETE_FLOOR
+#define SIMPLE_UI_CEIL 108
+#define SIMPLE_UI_STEPS 5
+
// ~25 lm to ~300 lm
-#define MUGGLE_FLOOR 30
-#define MUGGLE_CEILING MAX_1x7135
+//#define SIMPLE_UI_FLOOR 30
+//#define SIMPLE_UI_CEIL MAX_1x7135
// ~50 lm to ~500 lm
-//#define MUGGLE_FLOOR 40
-//#define MUGGLE_CEILING 90
+//#define SIMPLE_UI_FLOOR 40
+//#define SIMPLE_UI_CEIL 90
// regulate down faster when the FET is active, slower otherwise
#define THERM_FASTER_LEVEL 135 // throttle back faster when high
// don't do this
#undef BLINK_AT_RAMP_MIDDLE
-#undef BLINK_AT_RAMP_CEILING
+#undef BLINK_AT_RAMP_CEIL
diff --git a/spaghetti-monster/anduril/cfg-ff-pl47g2.h b/spaghetti-monster/anduril/cfg-ff-pl47g2.h
index cab008c..7e63ec3 100644
--- a/spaghetti-monster/anduril/cfg-ff-pl47g2.h
+++ b/spaghetti-monster/anduril/cfg-ff-pl47g2.h
@@ -1,4 +1,5 @@
// Fireflies PL47 G2 config options for Anduril
+#define MODEL_NUMBER "0423"
#include "hwdef-FF_PL47.h"
// the button lights up
@@ -39,17 +40,19 @@
// 10, 28, 46, 65, [83], 101, 120
#define RAMP_DISCRETE_FLOOR 10
-#define RAMP_DISCRETE_CEIL 120
+#define RAMP_DISCRETE_CEIL 120
#define RAMP_DISCRETE_STEPS 7
-// ~25 lm to ~300 lm
-#define MUGGLE_FLOOR 30
-#define MUGGLE_CEILING MAX_1x7135
+// safe limit ~25% power / ~1000 lm
+// 10 34 59 [83] 108
+#define SIMPLE_UI_FLOOR RAMP_DISCRETE_FLOOR
+#define SIMPLE_UI_CEIL 108
+#define SIMPLE_UI_STEPS 5
// regulate down faster when the FET is active, slower otherwise
#define THERM_FASTER_LEVEL 135 // throttle back faster when high
// don't do this
#undef BLINK_AT_RAMP_MIDDLE
-#undef BLINK_AT_RAMP_CEILING
+#undef BLINK_AT_RAMP_CEIL
diff --git a/spaghetti-monster/anduril/cfg-ff-rot66-219.h b/spaghetti-monster/anduril/cfg-ff-rot66-219.h
index efaad94..086f8ac 100644
--- a/spaghetti-monster/anduril/cfg-ff-rot66-219.h
+++ b/spaghetti-monster/anduril/cfg-ff-rot66-219.h
@@ -1,6 +1,8 @@
// Fireflies ROT66-219 (7x7135) config options for Anduril
// same as regular ROT66, but calibrated for Nichia 219B with 7x7135 chips
#include "cfg-ff-rot66.h"
+#undef MODEL_NUMBER
+#define MODEL_NUMBER "0412"
// driver is a FET+N+1,
// where N=6 for the 219b version
diff --git a/spaghetti-monster/anduril/cfg-ff-rot66.h b/spaghetti-monster/anduril/cfg-ff-rot66.h
index a87b66d..9922681 100644
--- a/spaghetti-monster/anduril/cfg-ff-rot66.h
+++ b/spaghetti-monster/anduril/cfg-ff-rot66.h
@@ -1,4 +1,5 @@
// Fireflies ROT66 (14x7135) config options for Anduril
+#define MODEL_NUMBER "0411"
#include "hwdef-FF_ROT66.h"
// the button lights up
@@ -35,10 +36,16 @@
#define HALFSPEED_LEVEL 14
#define QUARTERSPEED_LEVEL 8
+// safe limit max regulated power
+// 20 46 72 98 [125]
+#define SIMPLE_UI_FLOOR RAMP_DISCRETE_FLOOR
+#define SIMPLE_UI_CEIL MAX_Nx7135
+#define SIMPLE_UI_STEPS 5
+
// regulate down faster when the FET is active, slower otherwise
#define THERM_FASTER_LEVEL 130 // throttle back faster when high
// don't do this
#undef BLINK_AT_RAMP_MIDDLE
-#undef BLINK_AT_RAMP_CEILING
+#undef BLINK_AT_RAMP_CEIL
diff --git a/spaghetti-monster/anduril/cfg-ff-rot66g2.h b/spaghetti-monster/anduril/cfg-ff-rot66g2.h
index 4e353a8..9a70498 100644
--- a/spaghetti-monster/anduril/cfg-ff-rot66g2.h
+++ b/spaghetti-monster/anduril/cfg-ff-rot66g2.h
@@ -1,5 +1,7 @@
// Fireflies ROT66 G2 config options for Anduril
#include "cfg-ff-rot66.h"
+#undef MODEL_NUMBER
+#define MODEL_NUMBER "0413"
// if the "low" mode was disabled, turn it back on
#ifdef INDICATOR_LED_SKIP_LOW
@@ -40,10 +42,15 @@
// higher floor than default, and stop at highest regulated level
#define RAMP_SMOOTH_FLOOR 1 // ~0.3 lm
-#define RAMP_SMOOTH_CEIL MAX_Nx7135 // ~1200 lm
+#define RAMP_SMOOTH_CEIL MAX_Nx7135 // ~1200 lm
// 10, 28, 46, [65], 83, 101, [120]
#define RAMP_DISCRETE_FLOOR 10
-#define RAMP_DISCRETE_CEIL MAX_Nx7135
+#define RAMP_DISCRETE_CEIL MAX_Nx7135
#define RAMP_DISCRETE_STEPS 7
+// safe limit max regulated power
+// 10 37 65 92 [120]
+#define SIMPLE_UI_FLOOR RAMP_DISCRETE_FLOOR
+#define SIMPLE_UI_CEIL MAX_Nx7135
+#define SIMPLE_UI_STEPS 5
diff --git a/spaghetti-monster/anduril/cfg-fw3a-219.h b/spaghetti-monster/anduril/cfg-fw3a-219.h
index 1d8e4a1..c9bf0c1 100644
--- a/spaghetti-monster/anduril/cfg-fw3a-219.h
+++ b/spaghetti-monster/anduril/cfg-fw3a-219.h
@@ -1,5 +1,7 @@
// FW3A-219 config options for Anduril
#include "cfg-fw3a.h"
+#undef MODEL_NUMBER
+#define MODEL_NUMBER "0312"
#undef PWM1_LEVELS
#undef PWM2_LEVELS
diff --git a/spaghetti-monster/anduril/cfg-fw3a-nofet.h b/spaghetti-monster/anduril/cfg-fw3a-nofet.h
index a6be10d..7752c10 100644
--- a/spaghetti-monster/anduril/cfg-fw3a-nofet.h
+++ b/spaghetti-monster/anduril/cfg-fw3a-nofet.h
@@ -1,5 +1,7 @@
// FW3A with the FET disabled
#include "cfg-fw3a.h"
+#undef MODEL_NUMBER
+#define MODEL_NUMBER "0313"
// don't use channel 3 (FET)
#undef PWM_CHANNELS
@@ -28,9 +30,14 @@
#undef RAMP_DISCRETE_STEPS
#define RAMP_SMOOTH_FLOOR 1
-#define RAMP_SMOOTH_CEIL 150
+#define RAMP_SMOOTH_CEIL 150
// 10, 33, 56, 80, 103, 126, 150
#define RAMP_DISCRETE_FLOOR 10
-#define RAMP_DISCRETE_CEIL RAMP_SMOOTH_CEIL
+#define RAMP_DISCRETE_CEIL RAMP_SMOOTH_CEIL
#define RAMP_DISCRETE_STEPS 7
+// safe limit ~25% power
+// 10 37 65 92 120
+#undef SIMPLE_UI_CEIL
+#define SIMPLE_UI_CEIL 120
+
diff --git a/spaghetti-monster/anduril/cfg-fw3a.h b/spaghetti-monster/anduril/cfg-fw3a.h
index 489766c..0ef2e15 100644
--- a/spaghetti-monster/anduril/cfg-fw3a.h
+++ b/spaghetti-monster/anduril/cfg-fw3a.h
@@ -1,4 +1,5 @@
// FW3A config options for Anduril
+#define MODEL_NUMBER "0311"
#include "hwdef-FW3A.h"
// ../../bin/level_calc.py 1 65 7135 1 0.8 150
@@ -13,10 +14,14 @@
#define HALFSPEED_LEVEL 14
#define QUARTERSPEED_LEVEL 5
+// safe limit ~20% power
+// 20 40 60 80 100
+#define SIMPLE_UI_FLOOR RAMP_DISCRETE_FLOOR
+#define SIMPLE_UI_CEIL 100
+#define SIMPLE_UI_STEPS 5
+
// stop panicking at about 3A or ~1100 lm, this light is a hotrod
#define THERM_FASTER_LEVEL MAX_Nx7135
-#define USE_TENCLICK_THERMAL_CONFIG
-
// can't reset the normal way because power is connected before the button
#define USE_SOFT_FACTORY_RESET
diff --git a/spaghetti-monster/anduril/cfg-mateminco-mf01-mini.h b/spaghetti-monster/anduril/cfg-mateminco-mf01-mini.h
index 28c77c2..c66525c 100644
--- a/spaghetti-monster/anduril/cfg-mateminco-mf01-mini.h
+++ b/spaghetti-monster/anduril/cfg-mateminco-mf01-mini.h
@@ -1,4 +1,5 @@
// Mateminco/Astrolux MF01-Mini options for Anduril
+#define MODEL_NUMBER "0521"
#include "hwdef-Mateminco_MF01-Mini.h"
// the button lights up
@@ -16,7 +17,7 @@
// don't blink during ramp, it's irrelevant and annoying on this light
-#define BLINK_AT_RAMP_CEILING
+#define BLINK_AT_RAMP_CEIL
#undef BLINK_AT_RAMP_MIDDLE
#undef BLINK_AT_RAMP_FLOOR
@@ -37,14 +38,19 @@
#define QUARTERSPEED_LEVEL 8
#define RAMP_SMOOTH_FLOOR 1 // ~0.3 lm
-#define RAMP_SMOOTH_CEIL 130 // ~??? lm
+#define RAMP_SMOOTH_CEIL 130 // ~??? lm
// 14/135/6 = 14, 38, 62, 86, [110], 135
// 20/110/7 = 20, 35, 50, [65], 80, 95, [110]
// 15/130/7 = 15, 34, 53, 72, 91, [110], 130 <--
#define RAMP_DISCRETE_FLOOR 15 // ~?? lm
-#define RAMP_DISCRETE_CEIL 130 // ~??? lm
+#define RAMP_DISCRETE_CEIL 130 // ~??? lm
#define RAMP_DISCRETE_STEPS 7 // ??, ??, ... lm
+// safe limit max regulated power
+// 15 38 62 86 [110]
+#define SIMPLE_UI_FLOOR RAMP_DISCRETE_FLOOR
+#define SIMPLE_UI_CEIL 110
+#define SIMPLE_UI_STEPS 5
+
-#define USE_TENCLICK_THERMAL_CONFIG // by request
#define THERM_FASTER_LEVEL 130 // throttle back faster when high
diff --git a/spaghetti-monster/anduril/cfg-mateminco-mf01s.h b/spaghetti-monster/anduril/cfg-mateminco-mf01s.h
index 0585b38..9a8b641 100644
--- a/spaghetti-monster/anduril/cfg-mateminco-mf01s.h
+++ b/spaghetti-monster/anduril/cfg-mateminco-mf01s.h
@@ -1,4 +1,5 @@
// Mateminco/Astrolux MF01S options for Anduril
+#define MODEL_NUMBER "0511"
#include "hwdef-Mateminco_MF01S.h"
// the button lights up
@@ -15,7 +16,7 @@
// don't blink during ramp, it's irrelevant and annoying on this light
-#define BLINK_AT_RAMP_CEILING
+#define BLINK_AT_RAMP_CEIL
#undef BLINK_AT_RAMP_MIDDLE
#undef BLINK_AT_RAMP_FLOOR
@@ -33,13 +34,18 @@
#define QUARTERSPEED_LEVEL 6
#define RAMP_SMOOTH_FLOOR 1 // ~2.5 lm
-#define RAMP_SMOOTH_CEIL 120 // ~5400 lm
+#define RAMP_SMOOTH_CEIL 120 // ~5400 lm
// 20, 36, 53, [70], 86, 103, 120
#define RAMP_DISCRETE_FLOOR 20 // 35 lm
-#define RAMP_DISCRETE_CEIL 120 // ~5400 lm
+#define RAMP_DISCRETE_CEIL 120 // ~5400 lm
#define RAMP_DISCRETE_STEPS 7 // 35, 108, 280, 626, 1500, 2930, 5400 lm
-#define USE_TENCLICK_THERMAL_CONFIG // by request
+// safe limit ~25% power
+// 18 35 52 [70] 87 105
+#define SIMPLE_UI_FLOOR 18
+#define SIMPLE_UI_CEIL 105
+#define SIMPLE_UI_STEPS 6
+
#define THERM_FASTER_LEVEL 125 // throttle back faster when high (>6000 lm)
#define THERM_HARD_TURBO_DROP // this light is massively overpowered
diff --git a/spaghetti-monster/anduril/cfg-noctigon-k1-12v.h b/spaghetti-monster/anduril/cfg-noctigon-k1-12v.h
index a00a9b1..b1a12eb 100644
--- a/spaghetti-monster/anduril/cfg-noctigon-k1-12v.h
+++ b/spaghetti-monster/anduril/cfg-noctigon-k1-12v.h
@@ -1,4 +1,5 @@
// Noctigon K1 12V config options for Anduril
+#define MODEL_NUMBER "0253"
#include "hwdef-Noctigon_K1-12V.h"
// ATTINY: 1634
@@ -14,8 +15,6 @@
#define USE_AUX_RGB_LEDS
#define USE_AUX_RGB_LEDS_WHILE_ON
#define USE_INDICATOR_LED_WHILE_RAMPING
-#define RGB_LED_OFF_DEFAULT 0x18 // low, voltage
-#define RGB_LED_LOCKOUT_DEFAULT 0x37 // blinking, rainbow
// enable blinking aux LEDs
#define TICK_DURING_STANDBY
@@ -41,14 +40,16 @@
#endif
#define RAMP_SMOOTH_FLOOR 1
-#define RAMP_SMOOTH_CEIL 130
+#define RAMP_SMOOTH_CEIL 130
// 10, 30, [50], 70, 90, 110, 130
#define RAMP_DISCRETE_FLOOR 10
-#define RAMP_DISCRETE_CEIL RAMP_SMOOTH_CEIL
+#define RAMP_DISCRETE_CEIL RAMP_SMOOTH_CEIL
#define RAMP_DISCRETE_STEPS 7
-#define MUGGLE_FLOOR RAMP_DISCRETE_FLOOR
-#define MUGGLE_CEILING 70
+// safe limit ~50% power
+#define SIMPLE_UI_FLOOR RAMP_DISCRETE_FLOOR
+#define SIMPLE_UI_CEIL 120
+#define SIMPLE_UI_STEPS 5
// make candle mode wobble more
#define CANDLE_AMPLITUDE 32
@@ -59,9 +60,6 @@
#define THERM_RESPONSE_MAGNITUDE 32 // smaller adjustments, this host changes temperature slowly
#define THERM_NEXT_WARNING_THRESHOLD 32 // more error tolerance before adjusting
-// easier access to thermal config mode, for Noctigon
-#define USE_TENCLICK_THERMAL_CONFIG
-
// slow down party strobe; this driver can't pulse for 1ms or less
#define PARTY_STROBE_ONTIME 4
diff --git a/spaghetti-monster/anduril/cfg-noctigon-k1-sbt90.h b/spaghetti-monster/anduril/cfg-noctigon-k1-sbt90.h
index fd98979..99813d1 100644
--- a/spaghetti-monster/anduril/cfg-noctigon-k1-sbt90.h
+++ b/spaghetti-monster/anduril/cfg-noctigon-k1-sbt90.h
@@ -1,5 +1,6 @@
// Noctigon K1-SBT90.2 config options for Anduril
// (is a K1 host with a KR4-like driver and a really high-powered LED)
+#define MODEL_NUMBER "0252"
#include "hwdef-Noctigon_K1-SBT90.h"
// ATTINY: 1634
@@ -11,9 +12,7 @@
#define USE_AUX_RGB_LEDS
#define USE_AUX_RGB_LEDS_WHILE_ON
#define USE_INDICATOR_LED_WHILE_RAMPING
-#define RGB_LED_OFF_DEFAULT 0x18 // low, voltage
-#define RGB_LED_LOCKOUT_DEFAULT 0x37 // blinking, rainbow
-#define RGB_RAINBOW_SPEED 0x03 // half a second per color
+#define RGB_RAINBOW_SPEED 0x03 // half a second per color in rainbow mode
// enable blinking aux LEDs
#define TICK_DURING_STANDBY
@@ -36,24 +35,23 @@
#define QUARTERSPEED_LEVEL 2
#define RAMP_SMOOTH_FLOOR 3 // level 1 is unreliable
-#define RAMP_SMOOTH_CEIL 120
+#define RAMP_SMOOTH_CEIL 120
// 10, 28, [46], 65, 83, 101, [120]
#define RAMP_DISCRETE_FLOOR 10
-#define RAMP_DISCRETE_CEIL RAMP_SMOOTH_CEIL
+#define RAMP_DISCRETE_CEIL RAMP_SMOOTH_CEIL
#define RAMP_DISCRETE_STEPS 7
-#define MUGGLE_FLOOR RAMP_DISCRETE_FLOOR
-#define MUGGLE_CEILING 65
+// safe limit ~33% power
+#define SIMPLE_UI_FLOOR RAMP_DISCRETE_FLOOR
+#define SIMPLE_UI_CEIL 110
+#define SIMPLE_UI_STEPS 5
-// stop panicking at ~25% power or ~1000 lm
+// stop panicking at ~40% power or ~1700 lm
#define THERM_FASTER_LEVEL 120
#define MIN_THERM_STEPDOWN DEFAULT_LEVEL
//#define THERM_NEXT_WARNING_THRESHOLD 16 // accumulate less error before adjusting
//#define THERM_RESPONSE_MAGNITUDE 128 // bigger adjustments
-// easier access to thermal config mode, for Noctigon
-#define USE_TENCLICK_THERMAL_CONFIG
-
// slow down party strobe; this driver can't pulse for 1ms or less
//#define PARTY_STROBE_ONTIME 2
diff --git a/spaghetti-monster/anduril/cfg-noctigon-k1.h b/spaghetti-monster/anduril/cfg-noctigon-k1.h
index 4f2c2cc..6531ecd 100644
--- a/spaghetti-monster/anduril/cfg-noctigon-k1.h
+++ b/spaghetti-monster/anduril/cfg-noctigon-k1.h
@@ -1,4 +1,5 @@
// Noctigon K1 config options for Anduril
+#define MODEL_NUMBER "0251"
// (originally known as Emisar D1S v2)
#include "hwdef-Noctigon_K1.h"
// ATTINY: 1634
@@ -11,8 +12,6 @@
#define USE_AUX_RGB_LEDS
#define USE_AUX_RGB_LEDS_WHILE_ON
#define USE_INDICATOR_LED_WHILE_RAMPING
-#define RGB_LED_OFF_DEFAULT 0x18 // low, voltage
-#define RGB_LED_LOCKOUT_DEFAULT 0x37 // blinking, rainbow
// enable blinking aux LEDs
#define TICK_DURING_STANDBY
@@ -38,14 +37,16 @@
#endif
#define RAMP_SMOOTH_FLOOR 1
-#define RAMP_SMOOTH_CEIL 130
+#define RAMP_SMOOTH_CEIL 130
// 10, 30, [50], 70, 90, 110, 130
#define RAMP_DISCRETE_FLOOR 10
-#define RAMP_DISCRETE_CEIL RAMP_SMOOTH_CEIL
+#define RAMP_DISCRETE_CEIL RAMP_SMOOTH_CEIL
#define RAMP_DISCRETE_STEPS 7
-#define MUGGLE_FLOOR RAMP_DISCRETE_FLOOR
-#define MUGGLE_CEILING 70
+// safe limit ~75% power
+#define SIMPLE_UI_FLOOR RAMP_DISCRETE_FLOOR
+#define SIMPLE_UI_CEIL RAMP_DISCRETE_CEIL
+#define SIMPLE_UI_STEPS 5
// make candle mode wobble more
#define CANDLE_AMPLITUDE 32
@@ -56,9 +57,6 @@
#define THERM_RESPONSE_MAGNITUDE 32 // smaller adjustments, this host changes temperature slowly
#define THERM_NEXT_WARNING_THRESHOLD 32 // more error tolerance before adjusting
-// easier access to thermal config mode, for Noctigon
-#define USE_TENCLICK_THERMAL_CONFIG
-
// slow down party strobe; this driver can't pulse for 1ms or less
#define PARTY_STROBE_ONTIME 2
diff --git a/spaghetti-monster/anduril/cfg-noctigon-kr4-219.h b/spaghetti-monster/anduril/cfg-noctigon-kr4-219.h
index 0cfccf2..083995f 100644
--- a/spaghetti-monster/anduril/cfg-noctigon-kr4-219.h
+++ b/spaghetti-monster/anduril/cfg-noctigon-kr4-219.h
@@ -1,5 +1,7 @@
// Noctigon KR4 (75% FET) config options for Anduril
#include "cfg-noctigon-kr4.h"
+#undef MODEL_NUMBER
+#define MODEL_NUMBER "0213"
// ATTINY: 1634
// don't turn off first channel at turbo level
diff --git a/spaghetti-monster/anduril/cfg-noctigon-kr4-nofet.h b/spaghetti-monster/anduril/cfg-noctigon-kr4-nofet.h
index 9f5f57f..3043035 100644
--- a/spaghetti-monster/anduril/cfg-noctigon-kr4-nofet.h
+++ b/spaghetti-monster/anduril/cfg-noctigon-kr4-nofet.h
@@ -2,6 +2,8 @@
// (and Noctigon KR1)
// (and Emisar D4v2 E21A, a.k.a. "D4v2.5")
#include "cfg-noctigon-kr4.h"
+#undef MODEL_NUMBER
+#define MODEL_NUMBER "0212"
// ATTINY: 1634
// brightness w/ SST-20 4000K LEDs:
@@ -27,16 +29,18 @@
#undef RAMP_DISCRETE_STEPS
#define RAMP_SMOOTH_FLOOR 3 // level 1 is unreliable
-#define RAMP_SMOOTH_CEIL 130
+#define RAMP_SMOOTH_CEIL 130
// 10, 30, [50], 70, 90, 110, 130 (plus [150] on turbo)
#define RAMP_DISCRETE_FLOOR 10
-#define RAMP_DISCRETE_CEIL RAMP_SMOOTH_CEIL
+#define RAMP_DISCRETE_CEIL RAMP_SMOOTH_CEIL
#define RAMP_DISCRETE_STEPS 7
-#undef MUGGLE_FLOOR
-#undef MUGGLE_CEILING
-#define MUGGLE_FLOOR RAMP_DISCRETE_FLOOR
-#define MUGGLE_CEILING 70
+// safe limit ~67% power / ~1200 lm (can sustain 900 lm)
+#undef SIMPLE_UI_FLOOR
+#undef SIMPLE_UI_CEIL
+#define SIMPLE_UI_FLOOR RAMP_DISCRETE_FLOOR
+#define SIMPLE_UI_CEIL RAMP_DISCRETE_CEIL
+
// make candle mode wobble more
#define CANDLE_AMPLITUDE 32
diff --git a/spaghetti-monster/anduril/cfg-noctigon-kr4.h b/spaghetti-monster/anduril/cfg-noctigon-kr4.h
index ed15bde..17c3c00 100644
--- a/spaghetti-monster/anduril/cfg-noctigon-kr4.h
+++ b/spaghetti-monster/anduril/cfg-noctigon-kr4.h
@@ -1,5 +1,6 @@
// Noctigon KR4 config options for Anduril
// (and Emisar D4v2.5, which uses KR4 driver plus a button LED)
+#define MODEL_NUMBER "0211"
#include "hwdef-Noctigon_KR4.h"
// ATTINY: 1634
@@ -15,9 +16,6 @@
#ifdef USE_INDICATOR_LED_WHILE_RAMPING
#undef USE_INDICATOR_LED_WHILE_RAMPING
#endif
-#define RGB_LED_OFF_DEFAULT 0x17 // low, rainbow
-#define RGB_LED_LOCKOUT_DEFAULT 0x37 // blinking, rainbow
-#define RGB_RAINBOW_SPEED 0x03 // half a second per color
// enable blinking aux LEDs
#define TICK_DURING_STANDBY
@@ -40,14 +38,16 @@
#define QUARTERSPEED_LEVEL 2
#define RAMP_SMOOTH_FLOOR 3 // level 1 is unreliable
-#define RAMP_SMOOTH_CEIL 120
+#define RAMP_SMOOTH_CEIL 120
// 10, 28, [46], 65, 83, 101, [120]
#define RAMP_DISCRETE_FLOOR 10
-#define RAMP_DISCRETE_CEIL RAMP_SMOOTH_CEIL
+#define RAMP_DISCRETE_CEIL RAMP_SMOOTH_CEIL
#define RAMP_DISCRETE_STEPS 7
-#define MUGGLE_FLOOR RAMP_DISCRETE_FLOOR
-#define MUGGLE_CEILING 65
+// safe limit ~30% power / ~1400 lm (can sustain 900 lm)
+#define SIMPLE_UI_FLOOR RAMP_DISCRETE_FLOOR
+#define SIMPLE_UI_CEIL 110
+#define SIMPLE_UI_STEPS 5
// stop panicking at ~25% power or ~1000 lm
#define THERM_FASTER_LEVEL 100
@@ -55,9 +55,6 @@
#define THERM_NEXT_WARNING_THRESHOLD 16 // accumulate less error before adjusting
#define THERM_RESPONSE_MAGNITUDE 128 // bigger adjustments
-// easier access to thermal config mode
-#define USE_TENCLICK_THERMAL_CONFIG
-
// slow down party strobe; this driver can't pulse for 1ms or less
// (only needed on no-FET build)
//#define PARTY_STROBE_ONTIME 2
diff --git a/spaghetti-monster/anduril/cfg-sofirn-sp36.h b/spaghetti-monster/anduril/cfg-sofirn-sp36.h
index d808e2a..af8c18b 100644
--- a/spaghetti-monster/anduril/cfg-sofirn-sp36.h
+++ b/spaghetti-monster/anduril/cfg-sofirn-sp36.h
@@ -1,6 +1,8 @@
// Sofirn SP36 (small Q8) config options for Anduril
// same as the BLF Q8, mostly
#include "cfg-blf-q8.h"
+#undef MODEL_NUMBER
+#define MODEL_NUMBER "0612"
// voltage readings were a little high with the Q8 value
#undef VOLTAGE_FUDGE_FACTOR
@@ -19,8 +21,8 @@
#ifdef BLINK_AT_RAMP_MIDDLE
#undef BLINK_AT_RAMP_MIDDLE
#endif
-#ifdef BLINK_AT_RAMP_CEILING
-#undef BLINK_AT_RAMP_CEILING
+#ifdef BLINK_AT_RAMP_CEIL
+#undef BLINK_AT_RAMP_CEIL
#endif
// stop panicking at ~60% power or ~3000 lm
diff --git a/spaghetti-monster/anduril/config-default.h b/spaghetti-monster/anduril/config-default.h
new file mode 100644
index 0000000..31fcbf4
--- /dev/null
+++ b/spaghetti-monster/anduril/config-default.h
@@ -0,0 +1,160 @@
+/*
+ * config-default.h: Default configuration for Anduril.
+ *
+ * Copyright (C) 2017 Selene ToyKeeper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef CONFIG_DEFAULT_H
+#define CONFIG_DEFAULT_H
+
+/*
+ * This file specifies the default settings for Anduril.
+ *
+ * These settings can be overridden per build target, in cfg-*.h files...
+ * ... but most are not. So changing one here will make it change in
+ * almost every build target.
+ *
+ * Some configurable settings are also in other *.h files.
+ */
+
+/********* User-configurable options *********/
+// low voltage protection (also required for battery check mode)
+#define USE_LVP
+
+// overheat protection
+#define USE_THERMAL_REGULATION
+#define DEFAULT_THERM_CEIL 45 // try not to get hotter than this (in C)
+
+
+// Include a simplified UI for non-enthusiasts?
+#define USE_SIMPLE_UI
+
+
+///// Ramp mode options /////
+
+// button timing for turning light on/off:
+// B_PRESS_T: activate as soon as button is pressed
+// B_RELEASE_T: activate when user lets go of button
+// B_TIMEOUT_T: activate when we're sure the user won't double-click
+// defaults are release on, timeout off
+#define B_TIMING_ON B_RELEASE_T
+#define B_TIMING_OFF B_TIMEOUT_T
+
+// default ramp style: 0 = smooth, 1 = stepped
+#define RAMP_STYLE 0
+
+// short blip when crossing from "click" to "hold" from off
+// (helps the user hit moon mode exactly, instead of holding too long
+// or too short)
+#define MOON_TIMING_HINT // only applies if B_TIMING_ON == B_PRESS_T
+// short blips while ramping
+#define BLINK_AT_RAMP_MIDDLE
+//#define BLINK_AT_RAMP_FLOOR
+#define BLINK_AT_RAMP_CEIL
+//#define BLINK_AT_STEPS // whenever a discrete ramp mode is passed in smooth mode
+
+// Uncomment for Anduril1 "Ramp 2C" behavior:
+// - Ramp 2C goes to turbo (advanced UI) or ceiling (simple UI), like in Anduril1
+// Or comment out to use Anduril2 behavior instead:
+// - Ramp 2C goes to ceiling, unless already at ceiling or in simple UI.
+// (Advanced UI ceiling 2C goes to turbo)
+//#define USE_2C_MAX_TURBO
+
+// make the ramps configurable by the user
+#define USE_RAMP_CONFIG
+
+// adds a runtime option to switch between automatic memory (default)
+// and manual memory (only available if compiled in)
+// (manual memory makes 1-click-from-off start at the same level each time)
+// (the level can be set explicitly with 10 clicks from on,
+// or the user can go back to automatic with 10H)
+#define USE_MANUAL_MEMORY
+// if enabled, user can use "hybrid memory"
+// The light will use automatic or manual memory level, depending on how long
+// the light was off. Short off = automatic, long off = manual.
+// This also remaps 10C/10H:
+// - 10C toggles manual mem on/off at current level.
+// - 10H configures the timer value.
+#define USE_MANUAL_MEMORY_TIMER
+
+// enable sunset timer (ramp-down and automatic shutoff)
+// timer is available in regular ramp mode and candle mode
+#define USE_SUNSET_TIMER
+
+
+///// What to do when power is connected /////
+// factory reset function erases user's runtime configuration in eeprom
+#define USE_FACTORY_RESET
+//#define USE_SOFT_FACTORY_RESET // only needed on models which can't use hold-button-at-boot
+
+// dual-switch support (second switch is a tail clicky)
+// (currently incompatible with factory reset)
+//#define START_AT_MEMORIZED_LEVEL
+
+
+///// extra modes (enable / disable / configure each mode) /////
+
+// include a function to blink out the firmware version
+#define USE_VERSION_CHECK
+
+// enable the battery check mode (shows remaining charge) (requires USE_LVP)
+#define USE_BATTCHECK_MODE
+// battery readout style (pick one)
+// TODO: allow VpT and 4-bar simultaneously,
+// so one can be in "simple mode" and the other in "advanced mode"
+#define BATTCHECK_VpT
+//#define BATTCHECK_8bars // FIXME: breaks build
+//#define BATTCHECK_4bars // FIXME: breaks build
+// allow the user to calibrate the voltage readings?
+// (adjust in 0.05V increments from -0.30V to +0.30V)
+// (1 = -0.30V, 2 = -0.25V, ... 7 = 0V, ... 13 = +0.30V)
+#define USE_VOLTAGE_CORRECTION
+
+// enable beacon mode
+#define USE_BEACON_MODE
+
+// enable/disable various strobe modes
+#define USE_BIKE_FLASHER_MODE
+#define USE_PARTY_STROBE_MODE
+#define USE_TACTICAL_STROBE_MODE
+#define USE_LIGHTNING_MODE
+#define USE_CANDLE_MODE
+
+// boring strobes nobody really likes, but sometimes flashlight companies want
+// (these replace the fun strobe group,
+// so don't enable them at the same time as any of the above strobes)
+//#define USE_POLICE_STROBE_MODE
+//#define USE_SOS_MODE
+//#define USE_SOS_MODE_IN_FF_GROUP // put SOS in the "boring strobes" mode
+//#define USE_SOS_MODE_IN_BLINKY_GROUP // put SOS in the blinkies mode group
+
+// enable a mode for locking the light for safe carry
+#define USE_LOCKOUT_MODE
+// should lockout mode function as a momentary moon mode?
+#define USE_MOON_DURING_LOCKOUT_MODE
+// add an optional setting to lock the light after being off for a while
+#define USE_AUTOLOCK
+
+// enable momentary mode
+#define USE_MOMENTARY_MODE
+
+
+// cut clock speed at very low modes for better efficiency
+// (defined here so config files can override it)
+#define USE_DYNAMIC_UNDERCLOCKING
+
+
+#endif
diff --git a/spaghetti-monster/anduril/config-mode.c b/spaghetti-monster/anduril/config-mode.c
new file mode 100644
index 0000000..f89a922
--- /dev/null
+++ b/spaghetti-monster/anduril/config-mode.c
@@ -0,0 +1,164 @@
+/*
+ * config-mode.c: Config mode base functions for Anduril.
+ *
+ * Copyright (C) 2017 Selene ToyKeeper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef CONFIG_MODE_C
+#define CONFIG_MODE_C
+
+#include "config-mode.h"
+
+// general helper function for config modes
+uint8_t number_entry_state(Event event, uint16_t arg);
+// return value from number_entry_state()
+volatile uint8_t number_entry_value;
+
+
+// allow the user to set a new value for a config option
+// can be called two ways:
+// - with a "click" action: Configures first menu item only.
+// - with a "hold" action: Sets user select a menu item and then
+// choose a new value for it. User should hold button until light
+// blinks N times, to choose menu item N. Then let go, and light
+// should give a buzzing prompt to enter a number. Click N times
+// at the prompt to set the new value to N.
+// after completing this process, config state calls the savefunc callback
+// and then returns to caller/parent state
+uint8_t config_state_base(
+ Event event,
+ uint16_t arg,
+ uint8_t num_config_steps,
+ void (*savefunc)(uint8_t step, uint8_t value)) {
+
+ static uint8_t config_step;
+ if (event == EV_enter_state) {
+ config_step = 0;
+ set_level(0);
+ // if button isn't held, configure first menu item
+ if (! button_last_state) {
+ config_step ++;
+ push_state(number_entry_state, 0);
+ }
+ }
+
+ // if initial "hold" event still active
+ // blink while holding to indicate option number
+ #define B_CLICK_FLAGS (B_CLICK|B_HOLD|B_PRESS|B_RELEASE|B_TIMEOUT)
+ #define B_ANY_HOLD (B_CLICK|B_HOLD|B_PRESS)
+ #define B_ANY_HOLD_RELEASE (B_CLICK|B_HOLD|B_RELEASE|B_TIMEOUT)
+ else if ((event & B_CLICK_FLAGS) == B_ANY_HOLD) {
+ if (config_step <= num_config_steps) {
+ if (2 == (arg % (TICKS_PER_SECOND*3/2))) {
+ config_step ++;
+ // blink when config step advances
+ if (config_step <= num_config_steps)
+ set_level(RAMP_SIZE * 3 / 8);
+ }
+ else {
+ // stay on at a low level to indicate menu is active
+ set_level(RAMP_SIZE * 1 / 8);
+ }
+ } else {
+ // turn off when end of menu is reached
+ set_level(0);
+ }
+ }
+
+ // button release: activate number entry for one menu item
+ else if ((event & B_CLICK_FLAGS) == B_ANY_HOLD_RELEASE) {
+ // ask the user for a number, if they selected a menu item
+ if (config_step && config_step <= num_config_steps) {
+ push_state(number_entry_state, 0);
+ }
+ // exit after falling out of end of menu
+ else {
+ pop_state();
+ }
+ }
+
+ // an option was set (return from number_entry_state)
+ else if (event == EV_reenter_state) {
+ // process value with parent's callback
+ savefunc(config_step, number_entry_value);
+ // make changes persist in eeprom
+ save_config();
+ pop_state();
+ }
+
+ // eat all other events; don't pass any through to parent
+ return EVENT_HANDLED;
+}
+
+uint8_t number_entry_state(Event event, uint16_t arg) {
+ static uint8_t entry_step;
+
+ if (event == EV_enter_state) {
+ number_entry_value = 0;
+ entry_step = 0;
+ set_level(0); // initial pause should be dark
+ }
+
+ // advance through the process:
+ // 0: wait a moment
+ // 1: "buzz" while counting clicks
+ // 2: save and exit
+ else if (event == EV_tick) {
+ // wait a moment
+ if (entry_step == 0) {
+ if (arg > TICKS_PER_SECOND/2) {
+ entry_step ++;
+ empty_event_sequence(); // reset tick counter to 0
+ }
+ }
+ // buzz while waiting for a number to be entered
+ else if (entry_step == 1) {
+ // time out and exit after 3 seconds
+ if (arg > TICKS_PER_SECOND*3) {
+ entry_step ++;
+ set_level(0);
+ }
+ // buzz for N seconds after last event
+ // (flicker every other frame,
+ // except first frame (so we can see flashes after each click))
+ else if (arg) {
+ set_level( (RAMP_SIZE/8)
+ + ((arg&2)<<1) );
+ }
+ }
+ // all done, save result and return to parent state
+ else {
+ pop_state();
+ }
+ return MISCHIEF_MANAGED;
+ }
+
+ // count clicks
+ else if (event == EV_click1_release) {
+ entry_step = 1; // in case user clicked during initial delay
+ number_entry_value ++; // update the result
+ empty_event_sequence(); // reset FSM's click count
+ set_level(RAMP_SIZE/2); // flash briefly
+ return MISCHIEF_MANAGED;
+ }
+
+ // eat all other events; don't pass any through to parent
+ return EVENT_HANDLED;
+}
+
+
+#endif
+
diff --git a/spaghetti-monster/anduril/config-mode.h b/spaghetti-monster/anduril/config-mode.h
new file mode 100644
index 0000000..7cbc534
--- /dev/null
+++ b/spaghetti-monster/anduril/config-mode.h
@@ -0,0 +1,32 @@
+/*
+ * config-mode.h: Config mode base functions for Anduril.
+ *
+ * Copyright (C) 2017 Selene ToyKeeper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef CONFIG_MODE_H
+#define CONFIG_MODE_H
+
+// config menu
+uint8_t config_state_base(
+ Event event,
+ uint16_t arg,
+ uint8_t num_config_steps,
+ void (*savefunc)(uint8_t step, uint8_t value)
+ );
+
+
+#endif
diff --git a/spaghetti-monster/anduril/factory-reset-fsm.h b/spaghetti-monster/anduril/factory-reset-fsm.h
new file mode 100644
index 0000000..a8fb0d9
--- /dev/null
+++ b/spaghetti-monster/anduril/factory-reset-fsm.h
@@ -0,0 +1,28 @@
+/*
+ * factory-reset-fsm.h: FSM config options to enable factory reset in Anduril.
+ *
+ * Copyright (C) 2017 Selene ToyKeeper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef FACTORY_RESET_FSM_H
+#define FACTORY_RESET_FSM_H
+
+#ifdef USE_SOFT_FACTORY_RESET
+#define USE_REBOOT
+#endif
+
+
+#endif
diff --git a/spaghetti-monster/anduril/factory-reset.c b/spaghetti-monster/anduril/factory-reset.c
new file mode 100644
index 0000000..c327e65
--- /dev/null
+++ b/spaghetti-monster/anduril/factory-reset.c
@@ -0,0 +1,71 @@
+/*
+ * factory-reset.c: Factory reset functions for Anduril.
+ *
+ * Copyright (C) 2017 Selene ToyKeeper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef FACTORY_RESET_C
+#define FACTORY_RESET_C
+
+#include "factory-reset.h"
+
+void factory_reset() {
+ // display a warning for a few seconds before doing the actual reset,
+ // so the user has time to abort if they want
+ #define SPLODEY_TIME 2500
+ #define SPLODEY_STEPS 64
+ #define SPLODEY_TIME_PER_STEP (SPLODEY_TIME/SPLODEY_STEPS)
+ uint8_t bright;
+ uint8_t reset = 1;
+ // wind up to an explosion
+ for (bright=0; bright<SPLODEY_STEPS; bright++) {
+ set_level(bright);
+ nice_delay_ms(SPLODEY_TIME_PER_STEP/2);
+ set_level(bright>>1);
+ nice_delay_ms(SPLODEY_TIME_PER_STEP/2);
+ if (! button_is_pressed()) {
+ reset = 0;
+ break;
+ }
+ }
+ // explode, if button pressed long enough
+ if (reset) {
+ #ifdef USE_THERMAL_REGULATION
+ // auto-calibrate temperature... assume current temperature is 21 C
+ thermal_config_save(1, 21);
+ #endif
+ // save all settings to eeprom
+ // (assuming they're all at default because we haven't loaded them yet)
+ save_config();
+
+ bright = MAX_LEVEL;
+ for (; bright > 0; bright--) {
+ set_level(bright);
+ nice_delay_ms(SPLODEY_TIME_PER_STEP/8);
+ }
+ }
+ // explosion cancelled, fade away
+ else {
+ for (; bright > 0; bright--) {
+ set_level(bright);
+ nice_delay_ms(SPLODEY_TIME_PER_STEP/3);
+ }
+ }
+}
+
+
+#endif
+
diff --git a/spaghetti-monster/anduril/factory-reset.h b/spaghetti-monster/anduril/factory-reset.h
new file mode 100644
index 0000000..9f0af38
--- /dev/null
+++ b/spaghetti-monster/anduril/factory-reset.h
@@ -0,0 +1,26 @@
+/*
+ * factory-reset.h: Factory reset functions for Anduril.
+ *
+ * Copyright (C) 2017 Selene ToyKeeper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef FACTORY_RESET_H
+#define FACTORY_RESET_H
+
+void factory_reset();
+
+
+#endif
diff --git a/spaghetti-monster/anduril/ff-strobe-modes.c b/spaghetti-monster/anduril/ff-strobe-modes.c
new file mode 100644
index 0000000..4c12630
--- /dev/null
+++ b/spaghetti-monster/anduril/ff-strobe-modes.c
@@ -0,0 +1,81 @@
+/*
+ * ff-strobe-modes.c: Fireflies Flashlights strobe modes for Anduril.
+ *
+ * Copyright (C) 2017 Selene ToyKeeper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef FF_STROBE_MODES_C
+#define FF_STROBE_MODES_C
+
+#include "ff-strobe-modes.h"
+
+uint8_t boring_strobe_state(Event event, uint16_t arg) {
+ // police strobe and SOS, meh
+ // 'st' reduces ROM size slightly
+ uint8_t st = boring_strobe_type;
+
+ if (event == EV_enter_state) {
+ return MISCHIEF_MANAGED;
+ }
+ // 1 click: off
+ else if (event == EV_1click) {
+ // reset to police strobe for next time
+ boring_strobe_type = 0;
+ set_state(off_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ // 2 clicks: rotate through strobe/flasher modes
+ else if (event == EV_2clicks) {
+ boring_strobe_type = (st + 1) % NUM_BORING_STROBES;
+ return MISCHIEF_MANAGED;
+ }
+ return EVENT_NOT_HANDLED;
+}
+
+inline void boring_strobe_state_iter() {
+ switch(boring_strobe_type) {
+ #ifdef USE_POLICE_STROBE_MODE
+ case 0: // police strobe
+ police_strobe_iter();
+ break;
+ #endif
+
+ #ifdef USE_SOS_MODE_IN_FF_GROUP
+ default: // SOS
+ sos_mode_iter();
+ break;
+ #endif
+ }
+}
+
+#ifdef USE_POLICE_STROBE_MODE
+inline void police_strobe_iter() {
+ // one iteration of main loop()
+ // flash at 16 Hz then 8 Hz, 8 times each
+ for (uint8_t del=41; del<100; del+=41) {
+ for (uint8_t f=0; f<8; f++) {
+ set_level(STROBE_BRIGHTNESS);
+ nice_delay_ms(del >> 1);
+ set_level(0);
+ nice_delay_ms(del);
+ }
+ }
+}
+#endif
+
+
+#endif
+
diff --git a/spaghetti-monster/anduril/ff-strobe-modes.h b/spaghetti-monster/anduril/ff-strobe-modes.h
new file mode 100644
index 0000000..a3e0a27
--- /dev/null
+++ b/spaghetti-monster/anduril/ff-strobe-modes.h
@@ -0,0 +1,33 @@
+/*
+ * ff-strobe-modes.h: Fireflies Flashlights strobe modes for Anduril.
+ *
+ * Copyright (C) 2017 Selene ToyKeeper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef FF_STROBE_MODES_H
+#define FF_STROBE_MODES_H
+
+uint8_t boring_strobe_state(Event event, uint16_t arg);
+inline void boring_strobe_state_iter();
+uint8_t boring_strobe_type = 0;
+void sos_blink(uint8_t num, uint8_t dah);
+#ifdef USE_POLICE_STROBE_MODE
+inline void police_strobe_iter();
+#endif
+#define NUM_BORING_STROBES 2
+
+
+#endif
diff --git a/spaghetti-monster/anduril/load-save-config-fsm.h b/spaghetti-monster/anduril/load-save-config-fsm.h
new file mode 100644
index 0000000..885fb72
--- /dev/null
+++ b/spaghetti-monster/anduril/load-save-config-fsm.h
@@ -0,0 +1,89 @@
+/*
+ * load-save-config-fsm.h: FSM config for eeprom configuration in Anduril.
+ *
+ * Copyright (C) 2017 Selene ToyKeeper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LOAD_SAVE_CONFIG_FSM_H
+#define LOAD_SAVE_CONFIG_FSM_H
+
+// auto-detect how many eeprom bytes
+#define USE_EEPROM
+typedef enum {
+ ramp_style_e,
+ #ifdef USE_RAMP_CONFIG
+ ramp_smooth_floor_e,
+ ramp_smooth_ceil_e,
+ ramp_discrete_floor_e,
+ ramp_discrete_ceil_e,
+ ramp_discrete_steps_e,
+ #endif
+ #ifdef USE_MANUAL_MEMORY
+ manual_memory_e,
+ #ifdef USE_MANUAL_MEMORY_TIMER
+ manual_memory_timer_e,
+ #endif
+ #endif
+ #ifdef USE_TINT_RAMPING
+ tint_e,
+ #endif
+ #ifdef USE_STROBE_STATE
+ strobe_type_e,
+ #endif
+ #if defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE)
+ strobe_delays_0_e,
+ strobe_delays_1_e,
+ #endif
+ #ifdef USE_BIKE_FLASHER_MODE
+ bike_flasher_brightness_e,
+ #endif
+ #ifdef USE_BEACON_MODE
+ beacon_seconds_e,
+ #endif
+ #ifdef USE_SIMPLE_UI
+ simple_ui_active_e,
+ simple_ui_floor_e,
+ simple_ui_ceil_e,
+ simple_ui_steps_e,
+ #endif
+ #ifdef USE_THERMAL_REGULATION
+ therm_ceil_e,
+ therm_cal_offset_e,
+ #endif
+ #ifdef USE_VOLTAGE_CORRECTION
+ voltage_correction_e,
+ #endif
+ #ifdef USE_INDICATOR_LED
+ indicator_led_mode_e,
+ #endif
+ #ifdef USE_AUX_RGB_LEDS
+ rgb_led_off_mode_e,
+ rgb_led_lockout_mode_e,
+ #endif
+ #ifdef USE_AUTOLOCK
+ autolock_time_e,
+ #endif
+ eeprom_indexes_e_END
+} eeprom_indexes_e;
+#define EEPROM_BYTES eeprom_indexes_e_END
+
+#ifdef START_AT_MEMORIZED_LEVEL
+#define USE_EEPROM_WL
+#define EEPROM_WL_BYTES 1
+#endif
+
+
+#endif
diff --git a/spaghetti-monster/anduril/load-save-config.c b/spaghetti-monster/anduril/load-save-config.c
new file mode 100644
index 0000000..2880c6d
--- /dev/null
+++ b/spaghetti-monster/anduril/load-save-config.c
@@ -0,0 +1,152 @@
+/*
+ * load-save-config.c: Load/save/eeprom functions for Anduril.
+ *
+ * Copyright (C) 2017 Selene ToyKeeper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LOAD_SAVE_CONFIG_C
+#define LOAD_SAVE_CONFIG_C
+
+#include "load-save-config-fsm.h"
+#include "load-save-config.h"
+
+void load_config() {
+ if (load_eeprom()) {
+ ramp_style = eeprom[ramp_style_e];
+ #ifdef USE_RAMP_CONFIG
+ ramp_floors[0] = eeprom[ramp_smooth_floor_e];
+ ramp_ceils[0] = eeprom[ramp_smooth_ceil_e];
+ ramp_floors[1] = eeprom[ramp_discrete_floor_e];
+ ramp_ceils[1] = eeprom[ramp_discrete_ceil_e];
+ ramp_stepss[1] = eeprom[ramp_discrete_steps_e];
+ #endif
+ #ifdef USE_MANUAL_MEMORY
+ manual_memory = eeprom[manual_memory_e];
+ #ifdef USE_MANUAL_MEMORY_TIMER
+ manual_memory_timer = eeprom[manual_memory_timer_e];
+ #endif
+ #endif
+ #ifdef USE_TINT_RAMPING
+ tint = eeprom[tint_e];
+ #endif
+ #if defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE)
+ strobe_type = eeprom[strobe_type_e]; // TODO: move this to eeprom_wl?
+ strobe_delays[0] = eeprom[strobe_delays_0_e];
+ strobe_delays[1] = eeprom[strobe_delays_1_e];
+ #endif
+ #ifdef USE_BIKE_FLASHER_MODE
+ bike_flasher_brightness = eeprom[bike_flasher_brightness_e];
+ #endif
+ #ifdef USE_BEACON_MODE
+ beacon_seconds = eeprom[beacon_seconds_e];
+ #endif
+ #ifdef USE_SIMPLE_UI
+ simple_ui_active = eeprom[simple_ui_active_e];
+ ramp_floors[2] = eeprom[simple_ui_floor_e];
+ ramp_ceils[2] = eeprom[simple_ui_ceil_e];
+ ramp_stepss[2] = eeprom[simple_ui_steps_e];
+ #endif
+ #ifdef USE_THERMAL_REGULATION
+ therm_ceil = eeprom[therm_ceil_e];
+ therm_cal_offset = eeprom[therm_cal_offset_e];
+ #endif
+ #ifdef USE_VOLTAGE_CORRECTION
+ voltage_correction = eeprom[voltage_correction_e];
+ #endif
+ #ifdef USE_INDICATOR_LED
+ indicator_led_mode = eeprom[indicator_led_mode_e];
+ #endif
+ #ifdef USE_AUX_RGB_LEDS
+ rgb_led_off_mode = eeprom[rgb_led_off_mode_e];
+ rgb_led_lockout_mode = eeprom[rgb_led_lockout_mode_e];
+ #endif
+ #ifdef USE_AUTOLOCK
+ autolock_time = eeprom[autolock_time_e];
+ #endif
+ }
+ #ifdef START_AT_MEMORIZED_LEVEL
+ if (load_eeprom_wl()) {
+ memorized_level = eeprom_wl[0];
+ }
+ #endif
+}
+
+void save_config() {
+ eeprom[ramp_style_e] = ramp_style;
+ #ifdef USE_RAMP_CONFIG
+ eeprom[ramp_smooth_floor_e] = ramp_floors[0];
+ eeprom[ramp_smooth_ceil_e] = ramp_ceils[0];
+ eeprom[ramp_discrete_floor_e] = ramp_floors[1];
+ eeprom[ramp_discrete_ceil_e] = ramp_ceils[1];
+ eeprom[ramp_discrete_steps_e] = ramp_stepss[1];
+ #endif
+ #ifdef USE_MANUAL_MEMORY
+ eeprom[manual_memory_e] = manual_memory;
+ #ifdef USE_MANUAL_MEMORY_TIMER
+ eeprom[manual_memory_timer_e] = manual_memory_timer;
+ #endif
+ #endif
+ #ifdef USE_TINT_RAMPING
+ eeprom[tint_e] = tint;
+ #endif
+ #if defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE)
+ eeprom[strobe_type_e] = strobe_type; // TODO: move this to eeprom_wl?
+ eeprom[strobe_delays_0_e] = strobe_delays[0];
+ eeprom[strobe_delays_1_e] = strobe_delays[1];
+ #endif
+ #ifdef USE_BIKE_FLASHER_MODE
+ eeprom[bike_flasher_brightness_e] = bike_flasher_brightness;
+ #endif
+ #ifdef USE_BEACON_MODE
+ eeprom[beacon_seconds_e] = beacon_seconds;
+ #endif
+ #ifdef USE_SIMPLE_UI
+ eeprom[simple_ui_active_e] = simple_ui_active;
+ eeprom[simple_ui_floor_e] = ramp_floors[2];
+ eeprom[simple_ui_ceil_e] = ramp_ceils[2];
+ eeprom[simple_ui_steps_e] = ramp_stepss[2];
+ #endif
+ #ifdef USE_THERMAL_REGULATION
+ eeprom[therm_ceil_e] = therm_ceil;
+ eeprom[therm_cal_offset_e] = therm_cal_offset;
+ #endif
+ #ifdef USE_VOLTAGE_CORRECTION
+ eeprom[voltage_correction_e] = voltage_correction;
+ #endif
+ #ifdef USE_INDICATOR_LED
+ eeprom[indicator_led_mode_e] = indicator_led_mode;
+ #endif
+ #ifdef USE_AUX_RGB_LEDS
+ eeprom[rgb_led_off_mode_e] = rgb_led_off_mode;
+ eeprom[rgb_led_lockout_mode_e] = rgb_led_lockout_mode;
+ #endif
+ #ifdef USE_AUTOLOCK
+ eeprom[autolock_time_e] = autolock_time;
+ #endif
+
+ save_eeprom();
+}
+
+#ifdef START_AT_MEMORIZED_LEVEL
+void save_config_wl() {
+ eeprom_wl[0] = memorized_level;
+ save_eeprom_wl();
+}
+#endif
+
+
+#endif
+
diff --git a/spaghetti-monster/anduril/load-save-config.h b/spaghetti-monster/anduril/load-save-config.h
new file mode 100644
index 0000000..29c1a69
--- /dev/null
+++ b/spaghetti-monster/anduril/load-save-config.h
@@ -0,0 +1,31 @@
+/*
+ * load-save-config.h: Load/save/eeprom functions for Anduril.
+ *
+ * Copyright (C) 2017 Selene ToyKeeper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LOAD_SAVE_CONFIG_H
+#define LOAD_SAVE_CONFIG_H
+
+// remember stuff even after battery was changed
+void load_config();
+void save_config();
+#ifdef START_AT_MEMORIZED_LEVEL
+void save_config_wl();
+#endif
+
+
+#endif
diff --git a/spaghetti-monster/anduril/lockout-mode-fsm.h b/spaghetti-monster/anduril/lockout-mode-fsm.h
new file mode 100644
index 0000000..bc18ed3
--- /dev/null
+++ b/spaghetti-monster/anduril/lockout-mode-fsm.h
@@ -0,0 +1,29 @@
+/*
+ * lockout-mode-fsm.h: FSM config for lockout mode in Anduril.
+ *
+ * Copyright (C) 2017 Selene ToyKeeper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LOCKOUT_MODE_FSM_H
+#define LOCKOUT_MODE_FSM_H
+
+// autolock function requires the ability to measure time while "off"
+#ifdef USE_AUTOLOCK
+#define TICK_DURING_STANDBY
+#endif
+
+
+#endif
diff --git a/spaghetti-monster/anduril/lockout-mode.c b/spaghetti-monster/anduril/lockout-mode.c
new file mode 100644
index 0000000..8c83cff
--- /dev/null
+++ b/spaghetti-monster/anduril/lockout-mode.c
@@ -0,0 +1,198 @@
+/*
+ * lockout-mode.c: Lockout mode for Anduril.
+ *
+ * Copyright (C) 2017 Selene ToyKeeper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LOCKOUT_MODE_C
+#define LOCKOUT_MODE_C
+
+#include "lockout-mode.h"
+
+uint8_t lockout_state(Event event, uint16_t arg) {
+ #ifdef USE_MOON_DURING_LOCKOUT_MODE
+ // momentary(ish) moon mode during lockout
+ // button is being held
+ #ifdef USE_AUX_RGB_LEDS
+ // don't turn on during RGB aux LED configuration
+ if (event == EV_click7_hold) { set_level(0); } else
+ #endif
+ if ((event & (B_CLICK | B_PRESS)) == (B_CLICK | B_PRESS)) {
+ // hold: lowest floor
+ // click, hold: highest floor (or manual mem level)
+ uint8_t lvl = ramp_floors[0];
+ if ((event & 0x0f) == 2) { // second click
+ if (ramp_floors[1] > lvl) lvl = ramp_floors[1];
+ #ifdef USE_MANUAL_MEMORY
+ if (manual_memory) lvl = manual_memory;
+ #endif
+ } else { // anything except second click
+ if (ramp_floors[1] < lvl) lvl = ramp_floors[1];
+ }
+ set_level(lvl);
+ }
+ // button was released
+ else if ((event & (B_CLICK | B_PRESS)) == (B_CLICK)) {
+ set_level(0);
+ }
+ #endif // ifdef USE_MOON_DURING_LOCKOUT_MODE
+
+ // regular event handling
+ // conserve power while locked out
+ // (allow staying awake long enough to exit, but otherwise
+ // be persistent about going back to sleep every few seconds
+ // even if the user keeps pressing the button)
+ #ifdef USE_INDICATOR_LED
+ if (event == EV_enter_state) {
+ indicator_led(indicator_led_mode >> 2);
+ } else
+ #elif defined(USE_AUX_RGB_LEDS)
+ if (event == EV_enter_state) {
+ rgb_led_update(rgb_led_lockout_mode, 0);
+ } else
+ #endif
+ if (event == EV_tick) {
+ if (arg > HOLD_TIMEOUT) {
+ go_to_standby = 1;
+ #ifdef USE_INDICATOR_LED
+ indicator_led(indicator_led_mode >> 2);
+ #elif defined(USE_AUX_RGB_LEDS)
+ rgb_led_update(rgb_led_lockout_mode, arg);
+ #endif
+ }
+ return MISCHIEF_MANAGED;
+ }
+ #if defined(TICK_DURING_STANDBY) && (defined(USE_INDICATOR_LED) || defined(USE_AUX_RGB_LEDS))
+ else if (event == EV_sleep_tick) {
+ #if defined(USE_INDICATOR_LED)
+ if ((indicator_led_mode & 0b00001100) == 0b00001100) {
+ indicator_blink(arg);
+ }
+ #elif defined(USE_AUX_RGB_LEDS)
+ rgb_led_update(rgb_led_lockout_mode, arg);
+ #endif
+ return MISCHIEF_MANAGED;
+ }
+ #endif
+
+ // 4 clicks: exit and turn on
+ else if (event == EV_4clicks) {
+ #ifdef USE_MANUAL_MEMORY
+ if (manual_memory)
+ set_state(steady_state, manual_memory);
+ else
+ #endif
+ set_state(steady_state, memorized_level);
+ return MISCHIEF_MANAGED;
+ }
+ // 4 clicks, but hold last: exit and start at floor
+ else if (event == EV_click4_hold) {
+ blink_once();
+ // reset button sequence to avoid activating anything in ramp mode
+ current_event = 0;
+ // ... and back to ramp mode
+ set_state(steady_state, 1);
+ return MISCHIEF_MANAGED;
+ }
+ // 5 clicks: exit and turn on at ceiling level
+ else if (event == EV_5clicks) {
+ set_state(steady_state, MAX_LEVEL);
+ return MISCHIEF_MANAGED;
+ }
+
+ ////////// Every action below here is blocked in the simple UI //////////
+ #ifdef USE_SIMPLE_UI
+ if (simple_ui_active) {
+ return EVENT_NOT_HANDLED;
+ }
+ #endif
+
+ #ifdef USE_AUTOLOCK
+ // 10H: configure the autolock option
+ else if (event == EV_click10_hold) {
+ push_state(autolock_config_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ #endif
+
+ #if defined(USE_INDICATOR_LED)
+ // 7 clicks: rotate through indicator LED modes (lockout mode)
+ else if (event == EV_7clicks) {
+ #if defined(USE_INDICATOR_LED)
+ uint8_t mode = indicator_led_mode >> 2;
+ #ifdef TICK_DURING_STANDBY
+ mode = (mode + 1) & 3;
+ #else
+ mode = (mode + 1) % 3;
+ #endif
+ #ifdef INDICATOR_LED_SKIP_LOW
+ if (mode == 1) { mode ++; }
+ #endif
+ indicator_led_mode = (mode << 2) + (indicator_led_mode & 0x03);
+ indicator_led(mode);
+ #elif defined(USE_AUX_RGB_LEDS)
+ #endif
+ save_config();
+ return MISCHIEF_MANAGED;
+ }
+ #elif defined(USE_AUX_RGB_LEDS)
+ // 7 clicks: change RGB aux LED pattern
+ else if (event == EV_7clicks) {
+ uint8_t mode = (rgb_led_lockout_mode >> 4) + 1;
+ mode = mode % RGB_LED_NUM_PATTERNS;
+ rgb_led_lockout_mode = (mode << 4) | (rgb_led_lockout_mode & 0x0f);
+ rgb_led_update(rgb_led_lockout_mode, 0);
+ save_config();
+ blink_once();
+ return MISCHIEF_MANAGED;
+ }
+ // 7H: change RGB aux LED color
+ else if (event == EV_click7_hold) {
+ setting_rgb_mode_now = 1;
+ if (0 == (arg & 0x3f)) {
+ uint8_t mode = (rgb_led_lockout_mode & 0x0f) + 1;
+ mode = mode % RGB_LED_NUM_COLORS;
+ rgb_led_lockout_mode = mode | (rgb_led_lockout_mode & 0xf0);
+ //save_config();
+ }
+ rgb_led_update(rgb_led_lockout_mode, arg);
+ return MISCHIEF_MANAGED;
+ }
+ // 7H, release: save new color
+ else if (event == EV_click7_hold_release) {
+ setting_rgb_mode_now = 0;
+ save_config();
+ return MISCHIEF_MANAGED;
+ }
+ #endif
+
+ return EVENT_NOT_HANDLED;
+}
+
+#ifdef USE_AUTOLOCK
+// set the auto-lock timer to N minutes, where N is the number of clicks
+void autolock_config_save(uint8_t step, uint8_t value) {
+ autolock_time = value;
+}
+
+uint8_t autolock_config_state(Event event, uint16_t arg) {
+ return config_state_base(event, arg, 1, autolock_config_save);
+}
+#endif // #ifdef USE_AUTOLOCK
+
+
+#endif
+
diff --git a/spaghetti-monster/anduril/lockout-mode.h b/spaghetti-monster/anduril/lockout-mode.h
new file mode 100644
index 0000000..021d34a
--- /dev/null
+++ b/spaghetti-monster/anduril/lockout-mode.h
@@ -0,0 +1,32 @@
+/*
+ * lockout-mode.h: Lockout mode for Anduril.
+ *
+ * Copyright (C) 2017 Selene ToyKeeper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LOCKOUT_MODE_H
+#define LOCKOUT_MODE_H
+
+// soft lockout
+uint8_t lockout_state(Event event, uint16_t arg);
+
+#ifdef USE_AUTOLOCK
+uint8_t autolock_time = 0;
+uint8_t autolock_config_state(Event event, uint16_t arg);
+#endif
+
+
+#endif
diff --git a/spaghetti-monster/anduril/misc.c b/spaghetti-monster/anduril/misc.c
new file mode 100644
index 0000000..63b8f48
--- /dev/null
+++ b/spaghetti-monster/anduril/misc.c
@@ -0,0 +1,61 @@
+/*
+ * misc.c: Misc extra functions for Anduril.
+ *
+ * Copyright (C) 2017 Selene ToyKeeper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MISC_C
+#define MISC_C
+
+#include "misc.h"
+
+/* no longer used
+void blink_confirm(uint8_t num) {
+ uint8_t brightness = actual_level;
+ uint8_t bump = actual_level + (MAX_LEVEL/6);
+ if (bump > MAX_LEVEL) bump = 0;
+ for (; num>0; num--) {
+ set_level(bump);
+ delay_4ms(10/4);
+ set_level(brightness);
+ if (num > 1) { delay_4ms(100/4); }
+ }
+}
+*/
+
+// make a short, visible pulse
+// (either brighter or darker, depending on current brightness)
+void blink_once() {
+ uint8_t brightness = actual_level;
+ uint8_t bump = brightness + (MAX_LEVEL/6);
+ if (bump > MAX_LEVEL) bump = 0;
+
+ set_level(bump);
+ delay_4ms(10/4);
+ set_level(brightness);
+}
+
+// Just go dark for a moment to indicate to user that something happened
+void blip() {
+ uint8_t temp = actual_level;
+ set_level(0);
+ delay_4ms(3);
+ set_level(temp);
+}
+
+
+#endif
+
diff --git a/spaghetti-monster/anduril/misc.h b/spaghetti-monster/anduril/misc.h
new file mode 100644
index 0000000..5febbc7
--- /dev/null
+++ b/spaghetti-monster/anduril/misc.h
@@ -0,0 +1,28 @@
+/*
+ * misc.h: Misc extra functions for Anduril.
+ *
+ * Copyright (C) 2017 Selene ToyKeeper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MISC_H
+#define MISC_H
+
+//void blink_confirm(uint8_t num); // no longer used
+void blink_once();
+void blip();
+
+
+#endif
diff --git a/spaghetti-monster/anduril/momentary-mode.c b/spaghetti-monster/anduril/momentary-mode.c
new file mode 100644
index 0000000..8d111c7
--- /dev/null
+++ b/spaghetti-monster/anduril/momentary-mode.c
@@ -0,0 +1,86 @@
+/*
+ * momentary-mode.c: Momentary mode for Anduril.
+ *
+ * Copyright (C) 2017 Selene ToyKeeper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MOMENTARY_MODE_C
+#define MOMENTARY_MODE_C
+
+#include "momentary-mode.h"
+
+uint8_t momentary_state(Event event, uint16_t arg) {
+ // init strobe mode, if relevant
+ #ifdef USE_STROBE_STATE
+ if ((event == EV_enter_state) && (momentary_mode == 1)) {
+ strobe_state(event, arg);
+ }
+ #endif
+
+ // light up when the button is pressed; go dark otherwise
+ // button is being held
+ if ((event & (B_CLICK | B_PRESS)) == (B_CLICK | B_PRESS)) {
+ momentary_active = 1;
+ // 0 = ramping, 1 = strobes
+ if (momentary_mode == 0) {
+ set_level(memorized_level);
+ }
+ return MISCHIEF_MANAGED;
+ }
+ // button was released
+ else if ((event & (B_CLICK | B_PRESS)) == (B_CLICK)) {
+ momentary_active = 0;
+ set_level(0);
+ //go_to_standby = 1; // sleep while light is off
+ return MISCHIEF_MANAGED;
+ }
+
+ // Sleep, dammit! (but wait a few seconds first)
+ // (because standby mode uses such little power that it can interfere
+ // with exiting via tailcap loosen+tighten unless you leave power
+ // disconnected for several seconds, so we want to be awake when that
+ // happens to speed up the process)
+ else if (event == EV_tick) {
+ #ifdef USE_STROBE_STATE
+ if (momentary_active) {
+ // 0 = ramping, 1 = strobes
+ if (momentary_mode == 1) {
+ return strobe_state(event, arg);
+ }
+ }
+ else {
+ #endif
+ if (arg > TICKS_PER_SECOND*5) { // sleep after 5 seconds
+ go_to_standby = 1; // sleep while light is off
+ // turn off lighted button
+ #ifdef USE_INDICATOR_LED
+ indicator_led(0);
+ #elif defined(USE_AUX_RGB_LEDS)
+ rgb_led_update(0, 0);
+ #endif
+ }
+ #ifdef USE_STROBE_STATE
+ }
+ #endif
+ return MISCHIEF_MANAGED;
+ }
+
+ return EVENT_NOT_HANDLED;
+}
+
+
+#endif
+
diff --git a/spaghetti-monster/anduril/momentary-mode.h b/spaghetti-monster/anduril/momentary-mode.h
new file mode 100644
index 0000000..c5ccf0f
--- /dev/null
+++ b/spaghetti-monster/anduril/momentary-mode.h
@@ -0,0 +1,29 @@
+/*
+ * momentary-mode.h: Momentary mode for Anduril.
+ *
+ * Copyright (C) 2017 Selene ToyKeeper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MOMENTARY_MODE_H
+#define MOMENTARY_MODE_H
+
+// momentary / signalling mode
+uint8_t momentary_state(Event event, uint16_t arg);
+uint8_t momentary_mode = 0; // 0 = ramping, 1 = strobe
+uint8_t momentary_active = 0; // boolean, true if active *right now*
+
+
+#endif
diff --git a/spaghetti-monster/anduril/off-mode.c b/spaghetti-monster/anduril/off-mode.c
new file mode 100644
index 0000000..5ebaa1d
--- /dev/null
+++ b/spaghetti-monster/anduril/off-mode.c
@@ -0,0 +1,306 @@
+/*
+ * off-mode.c: "Off" mode for Anduril.
+ *
+ * Copyright (C) 2017 Selene ToyKeeper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef OFF_MODE_C
+#define OFF_MODE_C
+
+#include "off-mode.h"
+
+#ifdef USE_SUNSET_TIMER
+#include "sunset-timer.h"
+#endif
+
+uint8_t off_state(Event event, uint16_t arg) {
+ #ifdef USE_MANUAL_MEMORY_TIMER
+ // keep track of how long the light was off,
+ // so we can do different things on waking, depending on how long asleep
+ static uint16_t off_time = 0;
+ #endif
+
+ // turn emitter off when entering state
+ if (event == EV_enter_state) {
+ set_level(0);
+ #ifdef USE_INDICATOR_LED
+ indicator_led(indicator_led_mode & 0x03);
+ #elif defined(USE_AUX_RGB_LEDS)
+ rgb_led_update(rgb_led_off_mode, 0);
+ #endif
+ #ifdef USE_SUNSET_TIMER
+ sunset_timer = 0; // needs a reset in case previous timer was aborted
+ #endif
+ // sleep while off (lower power use)
+ // (unless delay requested; give the ADC some time to catch up)
+ if (! arg) { go_to_standby = 1; }
+ return MISCHIEF_MANAGED;
+ }
+ // go back to sleep eventually if we got bumped but didn't leave "off" state
+ else if (event == EV_tick) {
+ if (arg > HOLD_TIMEOUT) {
+ go_to_standby = 1;
+ #ifdef USE_INDICATOR_LED
+ indicator_led(indicator_led_mode & 0x03);
+ #elif defined(USE_AUX_RGB_LEDS)
+ rgb_led_update(rgb_led_off_mode, arg);
+ #endif
+ }
+ return MISCHIEF_MANAGED;
+ }
+ #if defined(TICK_DURING_STANDBY)
+ // blink the indicator LED, maybe
+ else if (event == EV_sleep_tick) {
+ #ifdef USE_MANUAL_MEMORY_TIMER
+ off_time = arg;
+ #endif
+ #ifdef USE_INDICATOR_LED
+ if ((indicator_led_mode & 0b00000011) == 0b00000011) {
+ indicator_blink(arg);
+ }
+ #elif defined(USE_AUX_RGB_LEDS)
+ rgb_led_update(rgb_led_off_mode, arg);
+ #endif
+
+ #ifdef USE_AUTOLOCK
+ // lock the light after being off for N minutes
+ #ifdef USE_SIMPLE_UI
+ if (! simple_ui_active) { // no auto-lock in Simple UI
+ #endif
+ uint16_t ticks = autolock_time * SLEEP_TICKS_PER_MINUTE;
+ if ((autolock_time > 0) && (arg > ticks)) {
+ set_state(lockout_state, 0);
+ }
+ #ifdef USE_SIMPLE_UI
+ }
+ #endif
+ #endif // ifdef USE_AUTOLOCK
+ return MISCHIEF_MANAGED;
+ }
+ #endif
+ #if (B_TIMING_ON == B_PRESS_T)
+ // hold (initially): go to lowest level (floor), but allow abort for regular click
+ else if (event == EV_click1_press) {
+ set_level(nearest_level(1));
+ return MISCHIEF_MANAGED;
+ }
+ #endif // B_TIMING_ON == B_PRESS_T
+ // hold: go to lowest level
+ else if (event == EV_click1_hold) {
+ #if (B_TIMING_ON == B_PRESS_T)
+ #ifdef MOON_TIMING_HINT
+ if (arg == 0) {
+ // let the user know they can let go now to stay at moon
+ blip();
+ } else
+ #endif
+ #else // B_RELEASE_T or B_TIMEOUT_T
+ set_level(nearest_level(1));
+ #endif
+ // don't start ramping immediately;
+ // give the user time to release at moon level
+ //if (arg >= HOLD_TIMEOUT) { // smaller
+ if (arg >= (!ramp_style) * HOLD_TIMEOUT) { // more consistent
+ set_state(steady_state, 1);
+ }
+ return MISCHIEF_MANAGED;
+ }
+ // hold, release quickly: go to lowest level (floor)
+ else if (event == EV_click1_hold_release) {
+ set_state(steady_state, 1);
+ return MISCHIEF_MANAGED;
+ }
+ #if (B_TIMING_ON != B_TIMEOUT_T)
+ // 1 click (before timeout): go to memorized level, but allow abort for double click
+ else if (event == EV_click1_release) {
+ #ifdef USE_MANUAL_MEMORY
+ // for full manual memory, set manual_memory_timer to 0
+ if (manual_memory
+ #ifdef USE_MANUAL_MEMORY_TIMER
+ && (off_time >= (manual_memory_timer * SLEEP_TICKS_PER_MINUTE))
+ #endif
+ ) {
+ set_level(nearest_level(manual_memory));
+ } else
+ #endif
+ set_level(nearest_level(memorized_level));
+ return MISCHIEF_MANAGED;
+ }
+ #endif // if (B_TIMING_ON != B_TIMEOUT_T)
+ // 1 click: regular mode
+ else if (event == EV_1click) {
+ #if (B_TIMING_ON != B_TIMEOUT_T)
+ // brightness was already set; reuse previous value
+ set_state(steady_state, actual_level);
+ #else
+ // FIXME: B_TIMEOUT_T breaks manual_memory and manual_memory_timer
+ // (need to duplicate manual mem logic here, probably)
+ set_state(steady_state, memorized_level);
+ #endif
+ return MISCHIEF_MANAGED;
+ }
+ // click, hold: momentary at ceiling or turbo
+ else if (event == EV_click2_hold) {
+ if (simple_ui_active) {
+ set_level(nearest_level(MAX_LEVEL));
+ } else {
+ set_level(MAX_LEVEL);
+ }
+ return MISCHIEF_MANAGED;
+ }
+ else if (event == EV_click2_hold_release) {
+ set_level(0);
+ return MISCHIEF_MANAGED;
+ }
+ // 2 clicks: highest mode (ceiling)
+ else if (event == EV_2clicks) {
+ set_state(steady_state, MAX_LEVEL);
+ return MISCHIEF_MANAGED;
+ }
+ // 3 clicks (initial press): off, to prep for later events
+ else if (event == EV_click3_press) {
+ set_level(0);
+ return MISCHIEF_MANAGED;
+ }
+ #ifdef USE_BATTCHECK
+ // 3 clicks: battcheck mode / blinky mode group 1
+ else if (event == EV_3clicks) {
+ set_state(battcheck_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ #endif
+ #ifdef USE_LOCKOUT_MODE
+ // 4 clicks: soft lockout
+ else if (event == EV_4clicks) {
+ blink_once();
+ set_state(lockout_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ #endif
+ #if defined(USE_FACTORY_RESET) && defined(USE_SOFT_FACTORY_RESET)
+ // 13 clicks and hold the last click: invoke factory reset (reboot)
+ else if (event == EV_click13_hold) {
+ reboot();
+ return MISCHIEF_MANAGED;
+ }
+ #endif
+ #ifdef USE_VERSION_CHECK
+ // 15+ clicks: show the version number
+ else if (event == EV_15clicks) {
+ set_state(version_check_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ #endif
+
+ #ifdef USE_SIMPLE_UI
+ // 10 clicks, but hold last click: turn simple UI off (or configure it)
+ else if ((event == EV_click10_hold) && (!arg)) {
+ if (simple_ui_active) { // turn off simple UI
+ blink_once();
+ simple_ui_active = 0;
+ save_config();
+ }
+ else { // configure simple UI ramp
+ push_state(simple_ui_config_state, 0);
+ }
+ return MISCHIEF_MANAGED;
+ }
+
+ ////////// Every action below here is blocked in the simple UI //////////
+ if (simple_ui_active) {
+ return EVENT_NOT_HANDLED;
+ }
+ // 10 clicks: enable simple UI
+ else if (event == EV_10clicks) {
+ blink_once();
+ simple_ui_active = 1;
+ save_config();
+ return MISCHIEF_MANAGED;
+ }
+ #endif
+
+ // click, click, long-click: strobe mode
+ #ifdef USE_STROBE_STATE
+ else if (event == EV_click3_hold) {
+ set_state(strobe_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ #elif defined(USE_BORING_STROBE_STATE)
+ else if (event == EV_click3_hold) {
+ set_state(boring_strobe_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ #endif
+ #ifdef USE_MOMENTARY_MODE
+ // 5 clicks: momentary mode
+ else if (event == EV_5clicks) {
+ blink_once();
+ set_state(momentary_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ #endif
+ #ifdef USE_INDICATOR_LED
+ // 7 clicks: change indicator LED mode
+ else if (event == EV_7clicks) {
+ uint8_t mode = (indicator_led_mode & 3) + 1;
+ #ifdef TICK_DURING_STANDBY
+ mode = mode & 3;
+ #else
+ mode = mode % 3;
+ #endif
+ #ifdef INDICATOR_LED_SKIP_LOW
+ if (mode == 1) { mode ++; }
+ #endif
+ indicator_led_mode = (indicator_led_mode & 0b11111100) | mode;
+ indicator_led(mode);
+ save_config();
+ return MISCHIEF_MANAGED;
+ }
+ #elif defined(USE_AUX_RGB_LEDS)
+ // 7 clicks: change RGB aux LED pattern
+ else if (event == EV_7clicks) {
+ uint8_t mode = (rgb_led_off_mode >> 4) + 1;
+ mode = mode % RGB_LED_NUM_PATTERNS;
+ rgb_led_off_mode = (mode << 4) | (rgb_led_off_mode & 0x0f);
+ rgb_led_update(rgb_led_off_mode, 0);
+ save_config();
+ blink_once();
+ return MISCHIEF_MANAGED;
+ }
+ // 7 clicks (hold last): change RGB aux LED color
+ else if (event == EV_click7_hold) {
+ setting_rgb_mode_now = 1;
+ if (0 == (arg & 0x3f)) {
+ uint8_t mode = (rgb_led_off_mode & 0x0f) + 1;
+ mode = mode % RGB_LED_NUM_COLORS;
+ rgb_led_off_mode = mode | (rgb_led_off_mode & 0xf0);
+ //save_config();
+ }
+ rgb_led_update(rgb_led_off_mode, arg);
+ return MISCHIEF_MANAGED;
+ }
+ else if (event == EV_click7_hold_release) {
+ setting_rgb_mode_now = 0;
+ save_config();
+ return MISCHIEF_MANAGED;
+ }
+ #endif // end 7 clicks
+ return EVENT_NOT_HANDLED;
+}
+
+
+#endif
+
diff --git a/spaghetti-monster/anduril/off-mode.h b/spaghetti-monster/anduril/off-mode.h
new file mode 100644
index 0000000..776173c
--- /dev/null
+++ b/spaghetti-monster/anduril/off-mode.h
@@ -0,0 +1,27 @@
+/*
+ * off-mode.h: "Off" mode for Anduril.
+ *
+ * Copyright (C) 2017 Selene ToyKeeper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef OFF_MODE_H
+#define OFF_MODE_H
+
+// when the light is "off" or in standby
+uint8_t off_state(Event event, uint16_t arg);
+
+
+#endif
diff --git a/spaghetti-monster/anduril/ramp-mode-fsm.h b/spaghetti-monster/anduril/ramp-mode-fsm.h
new file mode 100644
index 0000000..997e80d
--- /dev/null
+++ b/spaghetti-monster/anduril/ramp-mode-fsm.h
@@ -0,0 +1,45 @@
+/*
+ * ramp-mode-fsm.h: FSM config for ramping functions in Anduril.
+ *
+ * Copyright (C) 2017 Selene ToyKeeper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef RAMP_MODE_FSM_H
+#define RAMP_MODE_FSM_H
+
+// enable FSM's ramping features
+#define USE_RAMPING
+
+// do smooth adjustments when compensating for temperature
+#ifdef USE_THERMAL_REGULATION
+#define USE_SET_LEVEL_GRADUALLY // isn't used except for thermal adjustments
+#endif
+
+// brightness to use when no memory is set
+// FIXME: this is only here to stop an error in fsm-ramping.c,
+// which should be fixed by using a different symbol instead
+// (like BUTTON_LED_BRIGHT_LEVEL or RAMP_HALFWAY_LEVEL or something)
+#ifndef DEFAULT_LEVEL
+#define DEFAULT_LEVEL MAX_1x7135
+#endif
+
+// requires the ability to measure time while "off"
+#ifdef USE_MANUAL_MEMORY_TIMER
+#define TICK_DURING_STANDBY
+#endif
+
+
+#endif
diff --git a/spaghetti-monster/anduril/ramp-mode.c b/spaghetti-monster/anduril/ramp-mode.c
new file mode 100644
index 0000000..9d6eba9
--- /dev/null
+++ b/spaghetti-monster/anduril/ramp-mode.c
@@ -0,0 +1,525 @@
+/*
+ * ramp-mode.c: Ramping functions for Anduril.
+ *
+ * Copyright (C) 2017 Selene ToyKeeper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef RAMP_MODE_C
+#define RAMP_MODE_C
+
+#include "ramp-mode.h"
+
+#ifdef USE_SUNSET_TIMER
+#include "sunset-timer.h"
+#endif
+
+uint8_t steady_state(Event event, uint16_t arg) {
+ static int8_t ramp_direction = 1;
+ #if (B_TIMING_OFF == B_RELEASE_T)
+ // if the user double clicks, we need to abort turning off,
+ // and this stores the level to return to
+ static uint8_t level_before_off = 0;
+ #endif
+
+ // make sure ramp globals are correct...
+ // ... but they already are; no need to do it here
+ //ramp_update_config();
+ //nearest_level(1); // same effect, takes less space
+
+ uint8_t mode_min = ramp_floor;
+ uint8_t mode_max = ramp_ceil;
+ uint8_t step_size;
+ if (ramp_style) { step_size = ramp_discrete_step_size; }
+ else { step_size = 1; }
+
+ #ifdef USE_SUNSET_TIMER
+ // handle the shutoff timer first
+ static uint8_t timer_orig_level = 0;
+ uint8_t sunset_active = sunset_timer; // save for comparison
+ // clock tick
+ sunset_timer_state(event, arg);
+ // if the timer was just turned on
+ if (sunset_timer && (! sunset_active)) {
+ timer_orig_level = actual_level;
+ }
+ // if the timer just expired, shut off
+ else if (sunset_active && (! sunset_timer)) {
+ set_state(off_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ #endif // ifdef USE_SUNSET_TIMER
+
+ // turn LED on when we first enter the mode
+ if ((event == EV_enter_state) || (event == EV_reenter_state)) {
+ #if defined(USE_MOMENTARY_MODE) && defined(USE_STROBE_STATE)
+ momentary_mode = 0; // 0 = ramping, 1 = strobes
+ #endif
+ // if we just got back from config mode, go back to memorized level
+ if (event == EV_reenter_state) {
+ arg = memorized_level;
+ }
+ // remember this level, unless it's moon or turbo
+ if ((arg > mode_min) && (arg < mode_max))
+ memorized_level = arg;
+ // use the requested level even if not memorized
+ arg = nearest_level(arg);
+ set_level_and_therm_target(arg);
+ ramp_direction = 1;
+ return MISCHIEF_MANAGED;
+ }
+ #if (B_TIMING_OFF == B_RELEASE_T)
+ // 1 click (early): off, if configured for early response
+ else if (event == EV_click1_release) {
+ level_before_off = actual_level;
+ set_level_and_therm_target(0);
+ return MISCHIEF_MANAGED;
+ }
+ // 2 clicks (early): abort turning off, if configured for early response
+ else if (event == EV_click2_press) {
+ set_level_and_therm_target(level_before_off);
+ return MISCHIEF_MANAGED;
+ }
+ #endif // if (B_TIMING_OFF == B_RELEASE_T)
+ // 1 click: off
+ else if (event == EV_1click) {
+ set_state(off_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ // 2 clicks: go to/from highest level
+ else if (event == EV_2clicks) {
+ uint8_t turbo_level;
+ #ifdef USE_2C_MAX_TURBO
+ // simple UI: to/from ceiling
+ // full UI: to/from turbo (Anduril1 behavior)
+ #ifdef USE_SIMPLE_UI
+ if (simple_ui_active) turbo_level = mode_max;
+ else
+ #endif
+ turbo_level = MAX_LEVEL;
+ #else
+ // simple UI: to/from ceiling
+ // full UI: to/from ceiling if mem < ceiling,
+ // or to/from turbo if mem >= ceiling
+ if ((memorized_level < mode_max)
+ #ifdef USE_SIMPLE_UI
+ || simple_ui_active
+ #endif
+ ) { turbo_level = mode_max; }
+ else { turbo_level = MAX_LEVEL; }
+ #endif
+
+ if (actual_level < turbo_level) {
+ set_level_and_therm_target(turbo_level);
+ }
+ else {
+ set_level_and_therm_target(memorized_level);
+ }
+ #ifdef USE_SUNSET_TIMER
+ timer_orig_level = actual_level;
+ #endif
+ return MISCHIEF_MANAGED;
+ }
+
+ #ifdef USE_LOCKOUT_MODE
+ // 4 clicks: shortcut to lockout mode
+ else if (event == EV_4clicks) {
+ set_level(0);
+ set_state(lockout_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ #endif
+
+ // hold: change brightness (brighter, dimmer)
+ // click, hold: change brightness (dimmer)
+ else if ((event == EV_click1_hold) || (event == EV_click2_hold)) {
+ // ramp slower in discrete mode
+ if (ramp_style && (arg % HOLD_TIMEOUT != 0)) {
+ return MISCHIEF_MANAGED;
+ }
+ // fix ramp direction on first frame if necessary
+ if (!arg) {
+ // click, hold should always go down if possible
+ if (event == EV_click2_hold) { ramp_direction = -1; }
+ // make it ramp down instead, if already at max
+ else if (actual_level >= mode_max) { ramp_direction = -1; }
+ // make it ramp up if already at min
+ // (off->hold->stepped_min->release causes this state)
+ else if (actual_level <= mode_min) { ramp_direction = 1; }
+ }
+ // if the button is stuck, err on the side of safety and ramp down
+ else if ((arg > TICKS_PER_SECOND * 5) && (actual_level >= mode_max)) {
+ ramp_direction = -1;
+ }
+ #ifdef USE_LOCKOUT_MODE
+ // if the button is still stuck, lock the light
+ else if ((arg > TICKS_PER_SECOND * 10) && (actual_level <= mode_min)) {
+ blink_once();
+ set_state(lockout_state, 0);
+ }
+ #endif
+ memorized_level = nearest_level((int16_t)actual_level \
+ + (step_size * ramp_direction));
+ #if defined(BLINK_AT_RAMP_CEIL) || defined(BLINK_AT_RAMP_MIDDLE)
+ // only blink once for each threshold
+ if ((memorized_level != actual_level) && (
+ 0 // for easier syntax below
+ #ifdef BLINK_AT_RAMP_MIDDLE_1
+ || (memorized_level == BLINK_AT_RAMP_MIDDLE_1)
+ #endif
+ #ifdef BLINK_AT_RAMP_MIDDLE_2
+ || (memorized_level == BLINK_AT_RAMP_MIDDLE_2)
+ #endif
+ #ifdef BLINK_AT_RAMP_CEIL
+ || (memorized_level == mode_max)
+ #endif
+ #ifdef BLINK_AT_RAMP_FLOOR
+ || (memorized_level == mode_min)
+ #endif
+ )) {
+ blip();
+ }
+ #endif
+ #if defined(BLINK_AT_STEPS)
+ uint8_t foo = ramp_style;
+ ramp_style = 1;
+ uint8_t nearest = nearest_level((int16_t)actual_level);
+ ramp_style = foo;
+ // only blink once for each threshold
+ if ((memorized_level != actual_level) &&
+ (ramp_style == 0) &&
+ (memorized_level == nearest)
+ )
+ {
+ blip();
+ }
+ #endif
+ set_level_and_therm_target(memorized_level);
+ #ifdef USE_SUNSET_TIMER
+ timer_orig_level = actual_level;
+ #endif
+ return MISCHIEF_MANAGED;
+ }
+ // reverse ramp direction on hold release
+ else if ((event == EV_click1_hold_release)
+ || (event == EV_click2_hold_release)) {
+ ramp_direction = -ramp_direction;
+ #ifdef START_AT_MEMORIZED_LEVEL
+ save_config_wl();
+ #endif
+ return MISCHIEF_MANAGED;
+ }
+
+ else if (event == EV_tick) {
+ // un-reverse after 1 second
+ if (arg == TICKS_PER_SECOND) ramp_direction = 1;
+
+ #ifdef USE_SUNSET_TIMER
+ // reduce output if shutoff timer is active
+ if (sunset_timer) {
+ uint8_t dimmed_level = timer_orig_level * (sunset_timer-1) / sunset_timer_peak;
+ if (dimmed_level < 1) dimmed_level = 1;
+ #ifdef USE_SET_LEVEL_GRADUALLY
+ set_level_gradually(dimmed_level);
+ target_level = dimmed_level;
+ #else
+ set_level_and_therm_target(dimmed_level);
+ #endif
+ }
+ #endif // ifdef USE_SUNSET_TIMER
+
+ #ifdef USE_SET_LEVEL_GRADUALLY
+ int16_t diff = gradual_target - actual_level;
+ static uint16_t ticks_since_adjust = 0;
+ ticks_since_adjust++;
+ if (diff) {
+ uint16_t ticks_per_adjust = 256;
+ if (diff < 0) {
+ //diff = -diff;
+ if (actual_level > THERM_FASTER_LEVEL) {
+ #ifdef THERM_HARD_TURBO_DROP
+ ticks_per_adjust >>= 2;
+ #endif
+ ticks_per_adjust >>= 2;
+ }
+ } else {
+ // rise at half speed
+ ticks_per_adjust <<= 1;
+ }
+ while (diff) {
+ ticks_per_adjust >>= 1;
+ //diff >>= 1;
+ diff /= 2; // because shifting produces weird behavior
+ }
+ if (ticks_since_adjust > ticks_per_adjust)
+ {
+ gradual_tick();
+ ticks_since_adjust = 0;
+ }
+ }
+ #endif // ifdef USE_SET_LEVEL_GRADUALLY
+ return MISCHIEF_MANAGED;
+ }
+
+ #ifdef USE_THERMAL_REGULATION
+ // overheating: drop by an amount proportional to how far we are above the ceiling
+ else if (event == EV_temperature_high) {
+ #if 0
+ blip();
+ #endif
+ #ifdef THERM_HARD_TURBO_DROP
+ //if (actual_level > THERM_FASTER_LEVEL) {
+ if (actual_level == MAX_LEVEL) {
+ #ifdef USE_SET_LEVEL_GRADUALLY
+ set_level_gradually(THERM_FASTER_LEVEL);
+ target_level = THERM_FASTER_LEVEL;
+ #else
+ set_level_and_therm_target(THERM_FASTER_LEVEL);
+ #endif
+ } else
+ #endif
+ if (actual_level > MIN_THERM_STEPDOWN) {
+ int16_t stepdown = actual_level - arg;
+ if (stepdown < MIN_THERM_STEPDOWN) stepdown = MIN_THERM_STEPDOWN;
+ else if (stepdown > MAX_LEVEL) stepdown = MAX_LEVEL;
+ #ifdef USE_SET_LEVEL_GRADUALLY
+ set_level_gradually(stepdown);
+ #else
+ set_level(stepdown);
+ #endif
+ }
+ return MISCHIEF_MANAGED;
+ }
+ // underheating: increase slowly if we're lower than the target
+ // (proportional to how low we are)
+ else if (event == EV_temperature_low) {
+ #if 0
+ blip();
+ #endif
+ if (actual_level < target_level) {
+ //int16_t stepup = actual_level + (arg>>1);
+ int16_t stepup = actual_level + arg;
+ if (stepup > target_level) stepup = target_level;
+ else if (stepup < MIN_THERM_STEPDOWN) stepup = MIN_THERM_STEPDOWN;
+ #ifdef USE_SET_LEVEL_GRADUALLY
+ set_level_gradually(stepup);
+ #else
+ set_level(stepup);
+ #endif
+ }
+ return MISCHIEF_MANAGED;
+ }
+ #ifdef USE_SET_LEVEL_GRADUALLY
+ // temperature is within target window
+ // (so stop trying to adjust output)
+ else if (event == EV_temperature_okay) {
+ // if we're still adjusting output... stop after the current step
+ if (gradual_target > actual_level)
+ gradual_target = actual_level + 1;
+ else if (gradual_target < actual_level)
+ gradual_target = actual_level - 1;
+ return MISCHIEF_MANAGED;
+ }
+ #endif // ifdef USE_SET_LEVEL_GRADUALLY
+ #endif // ifdef USE_THERMAL_REGULATION
+
+ ////////// Every action below here is blocked in the simple UI //////////
+ #ifdef USE_SIMPLE_UI
+ if (simple_ui_active) {
+ return EVENT_NOT_HANDLED;
+ }
+ #endif
+
+ // 3 clicks: toggle smooth vs discrete ramping
+ else if (event == EV_3clicks) {
+ ramp_style = !ramp_style;
+ save_config();
+ #ifdef START_AT_MEMORIZED_LEVEL
+ save_config_wl();
+ #endif
+ blip();
+ memorized_level = nearest_level(actual_level);
+ set_level_and_therm_target(memorized_level);
+ #ifdef USE_SUNSET_TIMER
+ timer_orig_level = actual_level;
+ #endif
+ return MISCHIEF_MANAGED;
+ }
+
+ #ifndef USE_TINT_RAMPING
+ // 3H: momentary turbo (on lights with no tint ramping)
+ else if (event == EV_click3_hold) {
+ if (! arg) { // first frame only, to allow thermal regulation to work
+ set_level_and_therm_target(MAX_LEVEL);
+ }
+ return MISCHIEF_MANAGED;
+ }
+ else if (event == EV_click3_hold_release) {
+ set_level_and_therm_target(memorized_level);
+ return MISCHIEF_MANAGED;
+ }
+ #endif // ifndef USE_TINT_RAMPING
+
+ #ifdef USE_MOMENTARY_MODE
+ // 5 clicks: shortcut to momentary mode
+ else if (event == EV_5clicks) {
+ set_level(0);
+ set_state(momentary_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ #endif
+
+ #ifdef USE_RAMP_CONFIG
+ // 7H: configure this ramp mode
+ else if (event == EV_click7_hold) {
+ push_state(ramp_config_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ #endif
+
+ #ifdef USE_MANUAL_MEMORY
+ else if (event == EV_10clicks) {
+ // turn on manual memory and save current brightness
+ manual_memory = actual_level;
+ save_config();
+ blink_once();
+ return MISCHIEF_MANAGED;
+ }
+ else if (event == EV_click10_hold) {
+ #ifdef USE_MANUAL_MEMORY_TIMER
+ // let user configure timer for manual / hybrid memory
+ push_state(manual_memory_timer_config_state, 0);
+ #else // manual mem, but no timer
+ // turn off manual memory; go back to automatic
+ if (0 == arg) {
+ manual_memory = 0;
+ save_config();
+ blink_once();
+ }
+ #endif
+ return MISCHIEF_MANAGED;
+ }
+ #endif // ifdef USE_MANUAL_MEMORY
+
+ return EVENT_NOT_HANDLED;
+}
+
+
+#ifdef USE_RAMP_CONFIG
+void ramp_config_save(uint8_t step, uint8_t value) {
+
+ // 0 = smooth ramp, 1 = stepped ramp, 2 = simple UI's ramp
+ uint8_t style = ramp_style;
+ if (current_state == simple_ui_config_state) style = 2;
+
+ // save adjusted value to the correct slot
+ if (value) {
+ // ceiling value is inverted
+ if (step == 2) value = MAX_LEVEL + 1 - value;
+
+ // which option are we configuring?
+ // TODO? maybe rearrange definitions to avoid the need for this table
+ // (move all ramp values into a single array?)
+ uint8_t *steps[] = { ramp_floors, ramp_ceils, ramp_stepss };
+ uint8_t *option;
+ option = steps[step-1];
+ option[style] = value;
+ }
+}
+
+uint8_t ramp_config_state(Event event, uint16_t arg) {
+ uint8_t num_config_steps = ramp_style + 2;
+ return config_state_base(event, arg,
+ num_config_steps, ramp_config_save);
+}
+
+#ifdef USE_SIMPLE_UI
+uint8_t simple_ui_config_state(Event event, uint16_t arg) {
+ return config_state_base(event, arg, 3, ramp_config_save);
+}
+#endif
+#endif // #ifdef USE_RAMP_CONFIG
+
+#ifdef USE_MANUAL_MEMORY_TIMER
+void manual_memory_timer_config_save(uint8_t step, uint8_t value) {
+ // item 1: disable manual memory, go back to automatic
+ if (step == 1) { manual_memory = 0; }
+ // item 2: set manual memory timer duration
+ else { manual_memory_timer = value; }
+}
+
+uint8_t manual_memory_timer_config_state(Event event, uint16_t arg) {
+ return config_state_base(event, arg, 2, manual_memory_timer_config_save);
+}
+#endif
+
+// find the ramp level closest to the target,
+// using only the levels which are allowed in the current state
+uint8_t nearest_level(int16_t target) {
+ // using int16_t here saves us a bunch of logic elsewhere,
+ // by allowing us to correct for numbers < 0 or > 255 in one central place
+
+ // ensure all globals are correct
+ ramp_update_config();
+
+ // bounds check
+ uint8_t mode_min = ramp_floor;
+ uint8_t mode_max = ramp_ceil;
+ uint8_t num_steps = ramp_stepss[1 + simple_ui_active];
+ // special case for 1-step ramp... use halfway point between floor and ceiling
+ if (ramp_style && (1 == num_steps)) {
+ uint8_t mid = (mode_max + mode_min) >> 1;
+ return mid;
+ }
+ if (target < mode_min) return mode_min;
+ if (target > mode_max) return mode_max;
+ // the rest isn't relevant for smooth ramping
+ if (! ramp_style) return target;
+
+ uint8_t ramp_range = mode_max - mode_min;
+ ramp_discrete_step_size = ramp_range / (num_steps-1);
+ uint8_t this_level = mode_min;
+
+ for(uint8_t i=0; i<num_steps; i++) {
+ this_level = mode_min + (i * (uint16_t)ramp_range / (num_steps-1));
+ int16_t diff = target - this_level;
+ if (diff < 0) diff = -diff;
+ if (diff <= (ramp_discrete_step_size>>1))
+ return this_level;
+ }
+ return this_level;
+}
+
+// ensure ramp globals are correct
+void ramp_update_config() {
+ uint8_t which = ramp_style;
+ if (simple_ui_active) { which = 2; }
+
+ ramp_floor = ramp_floors[which];
+ ramp_ceil = ramp_ceils[which];
+}
+
+#ifdef USE_THERMAL_REGULATION
+void set_level_and_therm_target(uint8_t level) {
+ target_level = level;
+ set_level(level);
+}
+#else
+#define set_level_and_therm_target(level) set_level(level)
+#endif
+
+
+#endif
+
diff --git a/spaghetti-monster/anduril/ramp-mode.h b/spaghetti-monster/anduril/ramp-mode.h
new file mode 100644
index 0000000..b772407
--- /dev/null
+++ b/spaghetti-monster/anduril/ramp-mode.h
@@ -0,0 +1,190 @@
+/*
+ * ramp-mode.h: Ramping functions for Anduril.
+ *
+ * Copyright (C) 2017 Selene ToyKeeper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef RAMP_MODE_H
+#define RAMP_MODE_H
+
+#ifndef RAMP_LENGTH
+#define RAMP_LENGTH 150 // default, if not overridden in a driver cfg file
+#endif
+
+// thermal properties, if not defined per-driver
+#ifndef MIN_THERM_STEPDOWN
+#define MIN_THERM_STEPDOWN MAX_1x7135 // lowest value it'll step down to
+#endif
+#ifndef THERM_FASTER_LEVEL
+ #ifdef MAX_Nx7135
+ #define THERM_FASTER_LEVEL MAX_Nx7135 // throttle back faster when high
+ #else
+ #define THERM_FASTER_LEVEL (RAMP_SIZE*4/5) // throttle back faster when high
+ #endif
+#endif
+
+#if defined(USE_SIMPLE_UI)
+// TODO: Move these settings to config-default.h?
+// start in the simple UI after each factory reset?
+#ifndef SIMPLE_UI_ACTIVE
+#define SIMPLE_UI_ACTIVE 1
+#endif
+#ifndef SIMPLE_UI_FLOOR
+#define SIMPLE_UI_FLOOR (RAMP_SIZE*2/15)
+#endif
+#ifndef SIMPLE_UI_CEIL
+#define SIMPLE_UI_CEIL (RAMP_SIZE*9/15)
+#endif
+#ifndef SIMPLE_UI_STEPS
+#define SIMPLE_UI_STEPS 5
+#endif
+#endif
+
+
+// configure the timing of turning on/off in regular ramp mode
+// press: react as soon as the button is pressed
+#define B_PRESS_T 0
+// release: react as soon as the button is released
+#define B_RELEASE_T 1
+// timeout: react as soon as we're sure the user isn't doing a double-click
+#define B_TIMEOUT_T 2
+// defaults are release on, timeout off
+#ifndef B_TIMING_ON
+//#define B_TIMING_ON B_PRESS_T
+#define B_TIMING_ON B_RELEASE_T
+#endif
+#ifndef B_TIMING_OFF
+//#define B_TIMING_OFF B_RELEASE_T
+#define B_TIMING_OFF B_TIMEOUT_T
+#endif
+
+// default ramp options if not overridden earlier per-driver
+#ifndef RAMP_STYLE
+#define RAMP_STYLE 0 // smooth default
+#endif
+#ifndef RAMP_SMOOTH_FLOOR
+ #define RAMP_SMOOTH_FLOOR 1
+#endif
+#ifndef RAMP_SMOOTH_CEIL
+ #if PWM_CHANNELS == 3
+ #define RAMP_SMOOTH_CEIL MAX_Nx7135
+ #else
+ #define RAMP_SMOOTH_CEIL MAX_LEVEL - 30
+ #endif
+#endif
+#ifndef RAMP_DISCRETE_FLOOR
+ #define RAMP_DISCRETE_FLOOR 20
+#endif
+#ifndef RAMP_DISCRETE_CEIL
+ #define RAMP_DISCRETE_CEIL RAMP_SMOOTH_CEIL
+#endif
+#ifndef RAMP_DISCRETE_STEPS
+ #define RAMP_DISCRETE_STEPS 7
+#endif
+
+// mile marker(s) partway up the ramp
+// default: blink only at border between regulated and FET
+#ifdef BLINK_AT_RAMP_MIDDLE
+ #if PWM_CHANNELS >= 3
+ #ifndef BLINK_AT_RAMP_MIDDLE_1
+ #define BLINK_AT_RAMP_MIDDLE_1 MAX_Nx7135
+ #ifndef BLINK_AT_RAMP_MIDDLE_2
+ #define BLINK_AT_RAMP_MIDDLE_2 MAX_1x7135
+ #endif
+ #endif
+ #else
+ #ifndef BLINK_AT_RAMP_MIDDLE_1
+ #define BLINK_AT_RAMP_MIDDLE_1 MAX_1x7135
+ #endif
+ #endif
+#endif
+
+
+// ramping mode and its related config mode
+uint8_t steady_state(Event event, uint16_t arg);
+
+#ifdef USE_RAMP_CONFIG
+uint8_t ramp_config_state(Event event, uint16_t arg);
+void ramp_config_save(uint8_t step, uint8_t value);
+#ifdef USE_SIMPLE_UI
+uint8_t simple_ui_config_state(Event event, uint16_t arg);
+#endif
+#endif
+
+#if defined(USE_MANUAL_MEMORY) && defined(USE_MANUAL_MEMORY_TIMER)
+uint8_t manual_memory_timer_config_state(Event event, uint16_t arg);
+#endif
+
+// calculate the nearest ramp level which would be valid at the moment
+// (is a no-op for smooth ramp, but limits discrete ramp to only the
+// correct levels for the user's config)
+uint8_t nearest_level(int16_t target);
+
+// ensure ramp globals are correct
+void ramp_update_config();
+
+#ifdef USE_THERMAL_REGULATION
+// brightness before thermal step-down
+uint8_t target_level = 0;
+void set_level_and_therm_target(uint8_t level);
+#else
+#define set_level_and_therm_target(level) set_level(level)
+#endif
+
+
+// brightness control
+uint8_t memorized_level = DEFAULT_LEVEL;
+#ifdef USE_MANUAL_MEMORY
+uint8_t manual_memory = 0;
+#ifdef USE_MANUAL_MEMORY_TIMER
+uint8_t manual_memory_timer = 0;
+#endif
+#endif
+#ifdef USE_SIMPLE_UI
+// whether to enable the simplified interface or not
+uint8_t simple_ui_active = SIMPLE_UI_ACTIVE;
+#endif
+// smooth vs discrete ramping
+uint8_t ramp_style = RAMP_STYLE; // 0 = smooth, 1 = discrete
+// current values, regardless of style
+uint8_t ramp_floor = RAMP_SMOOTH_FLOOR;
+uint8_t ramp_ceil = RAMP_SMOOTH_CEIL;
+// per style
+uint8_t ramp_floors[] = {
+ RAMP_SMOOTH_FLOOR,
+ RAMP_DISCRETE_FLOOR,
+ #ifdef USE_SIMPLE_UI
+ SIMPLE_UI_FLOOR,
+ #endif
+ };
+uint8_t ramp_ceils[] = {
+ RAMP_SMOOTH_CEIL,
+ RAMP_DISCRETE_CEIL,
+ #ifdef USE_SIMPLE_UI
+ SIMPLE_UI_CEIL,
+ #endif
+ };
+uint8_t ramp_stepss[] = {
+ 0,
+ RAMP_DISCRETE_STEPS,
+ #ifdef USE_SIMPLE_UI
+ SIMPLE_UI_STEPS,
+ #endif
+ };
+uint8_t ramp_discrete_step_size; // don't set this
+
+
+#endif
diff --git a/spaghetti-monster/anduril/sos-mode.c b/spaghetti-monster/anduril/sos-mode.c
new file mode 100644
index 0000000..97245c7
--- /dev/null
+++ b/spaghetti-monster/anduril/sos-mode.c
@@ -0,0 +1,75 @@
+/*
+ * sos-mode.c: SOS mode for Anduril.
+ *
+ * Copyright (C) 2017 Selene ToyKeeper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef SOS_MODE_C
+#define SOS_MODE_C
+
+#include "sos-mode.h"
+
+#ifdef USE_SOS_MODE_IN_BLINKY_GROUP
+uint8_t sos_state(Event event, uint16_t arg) {
+ // 1 click: off
+ if (event == EV_1click) {
+ set_state(off_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ // 2 clicks: next blinky mode
+ else if (event == EV_2clicks) {
+ #if defined(USE_BATTCHECK_MODE)
+ set_state(battcheck_state, 0);
+ #elif defined(USE_THERMAL_REGULATION)
+ set_state(tempcheck_state, 0);
+ #elif defined(USE_BEACON_MODE)
+ set_state(beacon_state, 0);
+ #endif
+ return MISCHIEF_MANAGED;
+ }
+ return EVENT_NOT_HANDLED;
+}
+#endif
+
+void sos_blink(uint8_t num, uint8_t dah) {
+ #define DIT_LENGTH 200
+ for (; num > 0; num--) {
+ set_level(memorized_level);
+ nice_delay_ms(DIT_LENGTH);
+ if (dah) { // dah is 3X as long as a dit
+ nice_delay_ms(DIT_LENGTH*2);
+ }
+ set_level(0);
+ // one "off" dit between blinks
+ nice_delay_ms(DIT_LENGTH);
+ }
+ // three "off" dits (or one "dah") between letters
+ // (except for SOS, which is collectively treated as a single "letter")
+ //nice_delay_ms(DIT_LENGTH*2);
+}
+
+inline void sos_mode_iter() {
+ // one iteration of main loop()
+ //nice_delay_ms(1000);
+ sos_blink(3, 0); // S
+ sos_blink(3, 1); // O
+ sos_blink(3, 0); // S
+ nice_delay_ms(2000);
+}
+
+
+#endif
+
diff --git a/spaghetti-monster/anduril/sos-mode.h b/spaghetti-monster/anduril/sos-mode.h
new file mode 100644
index 0000000..397aa3f
--- /dev/null
+++ b/spaghetti-monster/anduril/sos-mode.h
@@ -0,0 +1,29 @@
+/*
+ * sos-mode.h: SOS mode for Anduril.
+ *
+ * Copyright (C) 2017 Selene ToyKeeper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef SOS_MODE_H
+#define SOS_MODE_H
+
+#ifdef USE_SOS_MODE_IN_BLINKY_GROUP
+// automatic SOS emergency signal
+uint8_t sos_state(Event event, uint16_t arg);
+#endif
+
+
+#endif
diff --git a/spaghetti-monster/anduril/strobe-modes-fsm.h b/spaghetti-monster/anduril/strobe-modes-fsm.h
new file mode 100644
index 0000000..002a951
--- /dev/null
+++ b/spaghetti-monster/anduril/strobe-modes-fsm.h
@@ -0,0 +1,44 @@
+/*
+ * strobe-modes-fsm.h: FSM config for strobe modes in Anduril.
+ *
+ * Copyright (C) 2017 Selene ToyKeeper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef STROBE_MODES_FSM_H
+#define STROBE_MODES_FSM_H
+
+// enable the random number generator if we need it
+#if defined(USE_LIGHTNING_MODE) || defined(USE_CANDLE_MODE)
+#define USE_PSEUDO_RAND
+#endif
+
+// party strobe uses really short pulses
+#ifdef USE_PARTY_STROBE_MODE
+#define USE_DELAY_ZERO
+#endif
+
+// candle mode is basically a bunch of stacked random triangle waves
+#if defined(USE_CANDLE_MODE)
+#define USE_TRIANGLE_WAVE
+#endif
+
+// the presence of strobe mode(s) affects how many eeprom bytes we need,
+// so it's relevant for FSM configuration
+#if defined(USE_CANDLE_MODE) || defined(USE_BIKE_FLASHER_MODE) || defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE) || defined(USE_LIGHTNING_MODE)
+#define USE_STROBE_STATE
+#endif
+
+#endif
diff --git a/spaghetti-monster/anduril/strobe-modes.c b/spaghetti-monster/anduril/strobe-modes.c
new file mode 100644
index 0000000..b27f298
--- /dev/null
+++ b/spaghetti-monster/anduril/strobe-modes.c
@@ -0,0 +1,297 @@
+/*
+ * strobe-modes.c: Strobe modes for Anduril.
+ *
+ * Copyright (C) 2017 Selene ToyKeeper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef STROBE_MODES_C
+#define STROBE_MODES_C
+
+#include "strobe-modes.h"
+
+#ifdef USE_STROBE_STATE
+uint8_t strobe_state(Event event, uint16_t arg) {
+ static int8_t ramp_direction = 1;
+
+ // 'st' reduces ROM size slightly
+ strobe_mode_te st = strobe_type;
+
+ #ifdef USE_MOMENTARY_MODE
+ momentary_mode = 1; // 0 = ramping, 1 = strobes
+ #endif
+
+ #ifdef USE_CANDLE_MODE
+ // pass all events to candle mode, when it's active
+ // (the code is in its own pseudo-state to keep things cleaner)
+ if (st == candle_mode_e) {
+ candle_mode_state(event, arg);
+ }
+ #endif
+
+ if (0) {} // placeholder
+ // init anything which needs to be initialized
+ else if (event == EV_enter_state) {
+ ramp_direction = 1;
+ return MISCHIEF_MANAGED;
+ }
+ // 1 click: off
+ else if (event == EV_1click) {
+ set_state(off_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ // 2 clicks: rotate through strobe/flasher modes
+ else if (event == EV_2clicks) {
+ strobe_type = (st + 1) % NUM_STROBES;
+ save_config();
+ return MISCHIEF_MANAGED;
+ }
+ // hold: change speed (go faster)
+ // or change brightness (brighter)
+ else if (event == EV_click1_hold) {
+ if (0) {} // placeholder
+
+ // party / tactical strobe faster
+ #if defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE)
+ #ifdef USE_TACTICAL_STROBE_MODE
+ else if (st <= tactical_strobe_e) {
+ #else
+ else if (st == party_strobe_e) {
+ #endif
+ if ((arg & 1) == 0) {
+ uint8_t d = strobe_delays[st];
+ d -= ramp_direction;
+ if (d < 8) d = 8;
+ else if (d > 254) d = 254;
+ strobe_delays[st] = d;
+ }
+ }
+ #endif
+
+ // lightning has no adjustments
+ //else if (st == lightning_storm_e) {}
+
+ // biking mode brighter
+ #ifdef USE_BIKE_FLASHER_MODE
+ else if (st == bike_flasher_e) {
+ bike_flasher_brightness += ramp_direction;
+ if (bike_flasher_brightness < 2) bike_flasher_brightness = 2;
+ else if (bike_flasher_brightness > MAX_BIKING_LEVEL) bike_flasher_brightness = MAX_BIKING_LEVEL;
+ set_level(bike_flasher_brightness);
+ }
+ #endif
+
+ return MISCHIEF_MANAGED;
+ }
+ // reverse ramp direction on hold release
+ // ... and save new strobe settings
+ else if (event == EV_click1_hold_release) {
+ ramp_direction = -ramp_direction;
+ save_config();
+ return MISCHIEF_MANAGED;
+ }
+ // click, hold: change speed (go slower)
+ // or change brightness (dimmer)
+ else if (event == EV_click2_hold) {
+ ramp_direction = 1;
+
+ if (0) {} // placeholder
+
+ // party / tactical strobe slower
+ #if defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE)
+ #ifdef USE_TACTICAL_STROBE_MODE
+ else if (st <= tactical_strobe_e) {
+ #else
+ else if (st == party_strobe_e) {
+ #endif
+ if ((arg & 1) == 0) {
+ if (strobe_delays[st] < 255) strobe_delays[st] ++;
+ }
+ }
+ #endif
+
+ // lightning has no adjustments
+ //else if (st == lightning_storm_e) {}
+
+ // biking mode dimmer
+ #ifdef USE_BIKE_FLASHER_MODE
+ else if (st == bike_flasher_e) {
+ if (bike_flasher_brightness > 2)
+ bike_flasher_brightness --;
+ set_level(bike_flasher_brightness);
+ }
+ #endif
+
+ return MISCHIEF_MANAGED;
+ }
+ // release hold: save new strobe settings
+ else if (event == EV_click2_hold_release) {
+ save_config();
+ return MISCHIEF_MANAGED;
+ }
+ #ifdef USE_MOMENTARY_MODE
+ // 5 clicks: go to momentary mode (momentary strobe)
+ else if (event == EV_5clicks) {
+ set_state(momentary_state, 0);
+ set_level(0);
+ return MISCHIEF_MANAGED;
+ }
+ #endif
+ #if defined(USE_LIGHTNING_MODE) || defined(USE_CANDLE_MODE)
+ // clock tick: bump the random seed
+ else if (event == EV_tick) {
+ // un-reverse after 1 second
+ if (arg == TICKS_PER_SECOND) ramp_direction = 1;
+
+ pseudo_rand_seed += arg;
+ return MISCHIEF_MANAGED;
+ }
+ #endif
+ return EVENT_NOT_HANDLED;
+}
+
+// runs repeatedly in FSM loop() whenever UI is in strobe_state or momentary strobe
+inline void strobe_state_iter() {
+ uint8_t st = strobe_type; // can't use switch() on an enum
+
+ switch(st) {
+ #if defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE)
+ #ifdef USE_PARTY_STROBE_MODE
+ case party_strobe_e:
+ #endif
+ #ifdef USE_TACTICAL_STROBE_MODE
+ case tactical_strobe_e:
+ #endif
+ party_tactical_strobe_mode_iter(st);
+ break;
+ #endif
+
+ #ifdef USE_LIGHTNING_MODE
+ case lightning_storm_e:
+ lightning_storm_iter();
+ break;
+ #endif
+
+ #ifdef USE_BIKE_FLASHER_MODE
+ case bike_flasher_e:
+ bike_flasher_iter();
+ break;
+ #endif
+ }
+}
+#endif // ifdef USE_STROBE_STATE
+
+#if defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE)
+inline void party_tactical_strobe_mode_iter(uint8_t st) {
+ // one iteration of main loop()
+ uint8_t del = strobe_delays[st];
+ // TODO: make tac strobe brightness configurable?
+ set_level(STROBE_BRIGHTNESS);
+ if (0) {} // placeholde0
+ #ifdef USE_PARTY_STROBE_MODE
+ else if (st == party_strobe_e) { // party strobe
+ #ifdef PARTY_STROBE_ONTIME
+ nice_delay_ms(PARTY_STROBE_ONTIME);
+ #else
+ if (del < 42) delay_zero();
+ else nice_delay_ms(1);
+ #endif
+ }
+ #endif
+ #ifdef USE_TACTICAL_STROBE_MODE
+ else { //tactical strobe
+ nice_delay_ms(del >> 1);
+ }
+ #endif
+ set_level(0);
+ nice_delay_ms(del); // no return check necessary on final delay
+}
+#endif
+
+#ifdef USE_LIGHTNING_MODE
+inline void lightning_storm_iter() {
+ // one iteration of main loop()
+ int16_t brightness;
+ uint16_t rand_time;
+
+ // turn the emitter on at a random level,
+ // for a random amount of time between 1ms and 32ms
+ //rand_time = 1 << (pseudo_rand() % 7);
+ rand_time = pseudo_rand() & 63;
+ brightness = 1 << (pseudo_rand() % 7); // 1, 2, 4, 8, 16, 32, 64
+ brightness += 1 << (pseudo_rand() % 5); // 2 to 80 now
+ brightness += pseudo_rand() % brightness; // 2 to 159 now (w/ low bias)
+ if (brightness > MAX_LEVEL) brightness = MAX_LEVEL;
+ set_level(brightness);
+ nice_delay_ms(rand_time);
+
+ // decrease the brightness somewhat more gradually, like lightning
+ uint8_t stepdown = brightness >> 3;
+ if (stepdown < 1) stepdown = 1;
+ while(brightness > 1) {
+ nice_delay_ms(rand_time);
+ brightness -= stepdown;
+ if (brightness < 0) brightness = 0;
+ set_level(brightness);
+ /*
+ if ((brightness < MAX_LEVEL/2) && (! (pseudo_rand() & 15))) {
+ brightness <<= 1;
+ set_level(brightness);
+ }
+ */
+ if (! (pseudo_rand() & 3)) {
+ nice_delay_ms(rand_time);
+ set_level(brightness>>1);
+ }
+ }
+
+ // turn the emitter off,
+ // for a random amount of time between 1ms and 8192ms
+ // (with a low bias)
+ rand_time = 1 << (pseudo_rand() % 13);
+ rand_time += pseudo_rand() % rand_time;
+ set_level(0);
+ nice_delay_ms(rand_time); // no return check necessary on final delay
+}
+#endif
+
+#ifdef USE_BIKE_FLASHER_MODE
+inline void bike_flasher_iter() {
+ // one iteration of main loop()
+ uint8_t burst = bike_flasher_brightness << 1;
+ if (burst > MAX_LEVEL) burst = MAX_LEVEL;
+ for(uint8_t i=0; i<4; i++) {
+ set_level(burst);
+ nice_delay_ms(5);
+ set_level(bike_flasher_brightness);
+ nice_delay_ms(65);
+ }
+ nice_delay_ms(720); // no return check necessary on final delay
+ set_level(0);
+}
+#endif
+
+#ifdef USE_CANDLE_MODE
+#include "candle-mode.c"
+#endif
+
+
+#ifdef USE_BORING_STROBE_STATE
+#include "ff-strobe-modes.c"
+#endif
+
+
+#endif
+
diff --git a/spaghetti-monster/anduril/strobe-modes.h b/spaghetti-monster/anduril/strobe-modes.h
new file mode 100644
index 0000000..e2389ba
--- /dev/null
+++ b/spaghetti-monster/anduril/strobe-modes.h
@@ -0,0 +1,98 @@
+/*
+ * strobe-modes.h: Strobe modes for Anduril.
+ *
+ * Copyright (C) 2017 Selene ToyKeeper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef STROBE_MODES_H
+#define STROBE_MODES_H
+
+// internal numbering for strobe modes
+#ifdef USE_STROBE_STATE
+typedef enum {
+ #ifdef USE_PARTY_STROBE_MODE
+ party_strobe_e,
+ #endif
+ #ifdef USE_TACTICAL_STROBE_MODE
+ tactical_strobe_e,
+ #endif
+ #ifdef USE_LIGHTNING_MODE
+ lightning_storm_e,
+ #endif
+ #ifdef USE_CANDLE_MODE
+ candle_mode_e,
+ #endif
+ #ifdef USE_BIKE_FLASHER_MODE
+ bike_flasher_e,
+ #endif
+ strobe_mode_END
+} strobe_mode_te;
+
+const int NUM_STROBES = strobe_mode_END;
+
+// which strobe mode is active?
+#ifdef USE_CANDLE_MODE
+strobe_mode_te strobe_type = candle_mode_e;
+#else
+strobe_mode_te strobe_type = 0;
+#endif
+#endif
+
+
+// full FET strobe can be a bit much... use max regulated level instead,
+// if there's a bright enough regulated level
+#ifndef STROBE_BRIGHTNESS
+#ifdef MAX_Nx7135
+#define STROBE_BRIGHTNESS MAX_Nx7135
+#else
+#define STROBE_BRIGHTNESS MAX_LEVEL
+#endif
+#endif
+
+// party and tactical strobes
+#ifdef USE_STROBE_STATE
+uint8_t strobe_state(Event event, uint16_t arg);
+inline void strobe_state_iter();
+#endif
+
+#if defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE)
+// party / tactical strobe timing
+uint8_t strobe_delays[] = { 41, 67 }; // party strobe 24 Hz, tactical strobe 10 Hz
+inline void party_tactical_strobe_mode_iter(uint8_t st);
+#endif
+
+#ifdef USE_LIGHTNING_MODE
+inline void lightning_storm_iter();
+#endif
+
+// bike mode config options
+#ifdef USE_BIKE_FLASHER_MODE
+#define MAX_BIKING_LEVEL 120 // should be 127 or less
+uint8_t bike_flasher_brightness = MAX_1x7135;
+inline void bike_flasher_iter();
+#endif
+
+#ifdef USE_CANDLE_MODE
+#include "candle-mode.h"
+#endif
+
+
+#if defined(USE_POLICE_STROBE_MODE) || defined(USE_SOS_MODE_IN_FF_GROUP)
+#define USE_BORING_STROBE_STATE
+#include "ff-strobe-modes.h"
+#endif
+
+#endif
diff --git a/spaghetti-monster/anduril/sunset-timer.c b/spaghetti-monster/anduril/sunset-timer.c
new file mode 100644
index 0000000..d942eba
--- /dev/null
+++ b/spaghetti-monster/anduril/sunset-timer.c
@@ -0,0 +1,78 @@
+/*
+ * sunset-timer.c: Sunset / candle auto-shutoff functions for Anduril.
+ *
+ * Copyright (C) 2017 Selene ToyKeeper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef SUNSET_TIMER_C
+#define SUNSET_TIMER_C
+
+#include "sunset-timer.h"
+
+uint8_t sunset_timer_state(Event event, uint16_t arg) {
+
+ #ifdef USE_SIMPLE_UI
+ // No timer functions in Simple UI
+ if (simple_ui_active) return EVENT_NOT_HANDLED;
+ #endif
+
+ // reset on start
+ if (event == EV_enter_state) {
+ sunset_timer = 0;
+ sunset_ticks = 0;
+ return MISCHIEF_MANAGED;
+ }
+ // hold: maybe "bump" the timer if it's active and almost expired
+ else if (event == EV_hold) {
+ // ramping up should "bump" the timer to extend the deadline a bit
+ if ((sunset_timer > 0) && (sunset_timer < 4)) {
+ sunset_timer = 3;
+ sunset_timer_peak = 3;
+ }
+ }
+ // 5H: add 5m to timer, per second, until released
+ else if (event == EV_click5_hold) {
+ if (0 == (arg % TICKS_PER_SECOND)) {
+ if (sunset_timer < (255 - SUNSET_TIMER_UNIT)) {
+ // add a few minutes to the timer
+ sunset_timer += SUNSET_TIMER_UNIT;
+ sunset_timer_peak = sunset_timer; // reset ceiling
+ sunset_ticks = 0; // reset phase
+ // let the user know something happened
+ blink_once();
+ }
+ }
+ return MISCHIEF_MANAGED;
+ }
+ // tick: count down until time expires
+ else if (event == EV_tick) {
+ // time passed
+ sunset_ticks ++;
+ // did we reach a minute mark?
+ if (sunset_ticks >= TICKS_PER_MINUTE) {
+ sunset_ticks = 0;
+ if (sunset_timer > 0) {
+ sunset_timer --;
+ }
+ }
+ return MISCHIEF_MANAGED;
+ }
+ return EVENT_NOT_HANDLED;
+}
+
+
+#endif
+
diff --git a/spaghetti-monster/anduril/sunset-timer.h b/spaghetti-monster/anduril/sunset-timer.h
new file mode 100644
index 0000000..8f7840c
--- /dev/null
+++ b/spaghetti-monster/anduril/sunset-timer.h
@@ -0,0 +1,35 @@
+/*
+ * sunset-timer.h: Sunset / candle auto-shutoff functions for Anduril.
+ *
+ * Copyright (C) 2017 Selene ToyKeeper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef SUNSET_TIMER_H
+#define SUNSET_TIMER_H
+
+// how many minutes to add each time the user "bumps" the timer?
+#define SUNSET_TIMER_UNIT 5
+
+#define TICKS_PER_MINUTE (TICKS_PER_SECOND*60)
+
+// automatic shutoff timer
+uint8_t sunset_timer = 0; // minutes remaining in countdown
+uint8_t sunset_timer_peak = 0; // total minutes in countdown
+uint16_t sunset_ticks = 0; // counts from 0 to TICKS_PER_MINUTE, then repeats
+uint8_t sunset_timer_state(Event event, uint16_t arg);
+
+
+#endif
diff --git a/spaghetti-monster/anduril/tempcheck-mode.c b/spaghetti-monster/anduril/tempcheck-mode.c
new file mode 100644
index 0000000..f6e1ebe
--- /dev/null
+++ b/spaghetti-monster/anduril/tempcheck-mode.c
@@ -0,0 +1,75 @@
+/*
+ * tempcheck-mode.c: Temperature check mode for Anduril.
+ *
+ * Copyright (C) 2017 Selene ToyKeeper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TEMPCHECK_MODE_C
+#define TEMPCHECK_MODE_C
+
+#include "tempcheck-mode.h"
+
+uint8_t tempcheck_state(Event event, uint16_t arg) {
+ // 1 click: off
+ if (event == EV_1click) {
+ set_state(off_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ // 2 clicks: next blinky mode
+ else if (event == EV_2clicks) {
+ #if defined(USE_BEACON_MODE)
+ set_state(beacon_state, 0);
+ #elif defined(USE_SOS_MODE) && defined(USE_SOS_MODE_IN_BLINKY_GROUP)
+ set_state(sos_state, 0);
+ #elif defined(USE_BATTCHECK)
+ set_state(battcheck_state, 0);
+ #endif
+ return MISCHIEF_MANAGED;
+ }
+ // 7H: thermal config mode
+ else if (event == EV_click7_hold) {
+ push_state(thermal_config_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ return EVENT_NOT_HANDLED;
+}
+
+void thermal_config_save(uint8_t step, uint8_t value) {
+ if (value) {
+ // item 1: calibrate room temperature
+ if (step == 1) {
+ int8_t rawtemp = temperature - therm_cal_offset;
+ therm_cal_offset = value - rawtemp;
+ adc_reset = 2; // invalidate all recent temperature data
+ }
+
+ // item 2: set maximum heat limit
+ else {
+ therm_ceil = 30 + value - 1;
+ }
+ }
+
+ if (therm_ceil > MAX_THERM_CEIL) therm_ceil = MAX_THERM_CEIL;
+}
+
+uint8_t thermal_config_state(Event event, uint16_t arg) {
+ return config_state_base(event, arg,
+ 2, thermal_config_save);
+}
+
+
+#endif
+
diff --git a/spaghetti-monster/anduril/tempcheck-mode.h b/spaghetti-monster/anduril/tempcheck-mode.h
new file mode 100644
index 0000000..83edd9c
--- /dev/null
+++ b/spaghetti-monster/anduril/tempcheck-mode.h
@@ -0,0 +1,30 @@
+/*
+ * tempcheck-mode.h: Temperature check mode for Anduril.
+ *
+ * Copyright (C) 2017 Selene ToyKeeper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TEMPCHECK_MODE_H
+#define TEMPCHECK_MODE_H
+
+#define USE_BLINK_NUM // FIXME: this only matters in an earlier header
+
+uint8_t tempcheck_state(Event event, uint16_t arg);
+uint8_t thermal_config_state(Event event, uint16_t arg);
+void thermal_config_save(uint8_t step, uint8_t value);
+
+
+#endif
diff --git a/spaghetti-monster/anduril/tint-ramping.c b/spaghetti-monster/anduril/tint-ramping.c
new file mode 100644
index 0000000..0196bc7
--- /dev/null
+++ b/spaghetti-monster/anduril/tint-ramping.c
@@ -0,0 +1,87 @@
+/*
+ * tint-ramping.c: Tint ramping functions for Anduril.
+ *
+ * Copyright (C) 2017 Selene ToyKeeper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TINT_RAMPING_C
+#define TINT_RAMPING_C
+
+#include "tint-ramping.h"
+
+uint8_t tint_ramping_state(Event event, uint16_t arg) {
+ static int8_t tint_ramp_direction = 1;
+ static uint8_t prev_tint = 0;
+ // don't activate auto-tint modes unless the user hits the edge
+ // and keeps pressing for a while
+ static uint8_t past_edge_counter = 0;
+ // bugfix: click-click-hold from off to strobes would invoke tint ramping
+ // in addition to changing state... so ignore any tint-ramp events which
+ // don't look like they were meant to be here
+ static uint8_t active = 0;
+
+ // click, click, hold: change the tint
+ if (event == EV_click3_hold) {
+ // reset at beginning of movement
+ if (! arg) {
+ active = 1; // first frame means this is for us
+ past_edge_counter = 0; // doesn't start until user hits the edge
+ }
+ // ignore event if we weren't the ones who handled the first frame
+ if (! active) return EVENT_HANDLED;
+
+ // change normal tints
+ if ((tint_ramp_direction > 0) && (tint < 254)) {
+ tint += 1;
+ }
+ else if ((tint_ramp_direction < 0) && (tint > 1)) {
+ tint -= 1;
+ }
+ // if the user kept pressing long enough, go the final step
+ if (past_edge_counter == 64) {
+ past_edge_counter ++;
+ tint ^= 1; // 0 -> 1, 254 -> 255
+ blip();
+ }
+ // if tint change stalled, let user know we hit the edge
+ else if (prev_tint == tint) {
+ if (past_edge_counter == 0) blip();
+ // count up but don't wrap back to zero
+ if (past_edge_counter < 255) past_edge_counter ++;
+ }
+ prev_tint = tint;
+ set_level(actual_level);
+ return EVENT_HANDLED;
+ }
+
+ // click, click, hold, release: reverse direction for next ramp
+ else if (event == EV_click3_hold_release) {
+ active = 0; // ignore next hold if it wasn't meant for us
+ // reverse
+ tint_ramp_direction = -tint_ramp_direction;
+ if (tint == 0) tint_ramp_direction = 1;
+ else if (tint == 255) tint_ramp_direction = -1;
+ // remember tint after battery change
+ save_config();
+ return EVENT_HANDLED;
+ }
+
+ return EVENT_NOT_HANDLED;
+}
+
+
+#endif
+
diff --git a/spaghetti-monster/anduril/tint-ramping.h b/spaghetti-monster/anduril/tint-ramping.h
new file mode 100644
index 0000000..bd86659
--- /dev/null
+++ b/spaghetti-monster/anduril/tint-ramping.h
@@ -0,0 +1,27 @@
+/*
+ * tint-ramping.h: Tint ramping functions for Anduril.
+ *
+ * Copyright (C) 2017 Selene ToyKeeper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TINT_RAMPING_H
+#define TINT_RAMPING_H
+
+// not actually a mode, more of a fallback under other modes
+uint8_t tint_ramping_state(Event event, uint16_t arg);
+
+
+#endif
diff --git a/spaghetti-monster/anduril/version-check-mode.c b/spaghetti-monster/anduril/version-check-mode.c
new file mode 100644
index 0000000..edb1723
--- /dev/null
+++ b/spaghetti-monster/anduril/version-check-mode.c
@@ -0,0 +1,47 @@
+/*
+ * version-check-mode.c: Version check mode for Anduril.
+ *
+ * Copyright (C) 2017 Selene ToyKeeper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef VERSION_CHECK_MODE_C
+#define VERSION_CHECK_MODE_C
+
+#include "version-check-mode.h"
+
+// empty state; logic is handled in FSM loop() instead
+uint8_t version_check_state(Event event, uint16_t arg) {
+ return EVENT_NOT_HANDLED;
+}
+
+// this happens in FSM loop()
+inline void version_check_iter() {
+ for (uint8_t i=0; i<sizeof(version_number)-1; i++) {
+ blink_digit(pgm_read_byte(version_number + i) - '0');
+ nice_delay_ms(300);
+ }
+ // FIXME: when user interrupts with button, "off" takes an extra click
+ // before it'll turn back on, because the click to cancel gets sent
+ // to the "off" state instead of version_check_state
+ //while (button_is_pressed()) {}
+ //empty_event_sequence();
+
+ set_state_deferred(off_state, 0);
+}
+
+
+#endif
+
diff --git a/spaghetti-monster/anduril/version-check-mode.h b/spaghetti-monster/anduril/version-check-mode.h
new file mode 100644
index 0000000..ce02b73
--- /dev/null
+++ b/spaghetti-monster/anduril/version-check-mode.h
@@ -0,0 +1,37 @@
+/*
+ * version-check-mode.h: Version check mode for Anduril.
+ *
+ * Copyright (C) 2017 Selene ToyKeeper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef VERSION_CHECK_MODE_H
+#define VERSION_CHECK_MODE_H
+
+#define USE_BLINK_DIGIT // FIXME: does nothing unless defined earlier
+
+#ifndef MODEL_NUMBER
+// if no model number, it's a build error
+//#define MODEL_NUMBER "0000"
+#error MODEL_NUMBER undefined
+#endif
+
+#include "version.h"
+const PROGMEM uint8_t version_number[] = VERSION_NUMBER MODEL_NUMBER;
+uint8_t version_check_state(Event event, uint16_t arg);
+inline void version_check_iter();
+
+
+#endif
diff --git a/spaghetti-monster/fsm-adc.c b/spaghetti-monster/fsm-adc.c
index 91562a2..71ecf65 100644
--- a/spaghetti-monster/fsm-adc.c
+++ b/spaghetti-monster/fsm-adc.c
@@ -122,7 +122,12 @@ static inline uint8_t calc_voltage_divider(uint16_t value) {
// use 9.7 fixed-point to get sufficient precision
uint16_t adc_per_volt = ((ADC_44<<5) - (ADC_22<<5)) / (44-22);
// shift incoming value into a matching position
- uint8_t result = ((value>>1) / adc_per_volt) + VOLTAGE_FUDGE_FACTOR;
+ uint8_t result = ((value>>1) / adc_per_volt)
+ + VOLTAGE_FUDGE_FACTOR
+ #ifdef USE_VOLTAGE_CORRECTION
+ + voltage_correction - 7
+ #endif
+ ;
return result;
}
#endif
@@ -272,7 +277,12 @@ static inline void ADC_voltage_handler() {
// calculate actual voltage: volts * 10
// ADC = 1.1 * 1024 / volts
// volts = 1.1 * 1024 / ADC
- voltage = ((uint16_t)(2*1.1*1024*10)/(measurement>>6) + VOLTAGE_FUDGE_FACTOR) >> 1;
+ voltage = ((uint16_t)(2*1.1*1024*10)/(measurement>>6)
+ + VOLTAGE_FUDGE_FACTOR
+ #ifdef USE_VOLTAGE_CORRECTION
+ + voltage_correction - 7
+ #endif
+ ) >> 1;
#endif
// if low, callback EV_voltage_low / EV_voltage_critical
diff --git a/spaghetti-monster/fsm-adc.h b/spaghetti-monster/fsm-adc.h
index 36d4c9c..fc24712 100644
--- a/spaghetti-monster/fsm-adc.h
+++ b/spaghetti-monster/fsm-adc.h
@@ -57,7 +57,12 @@ uint8_t adc_deferred_enable = 0; // stop waiting and run the deferred code
void adc_deferred(); // do the actual ADC-related calculations
static inline void ADC_voltage_handler();
-volatile uint8_t voltage = 0;
+uint8_t voltage = 0;
+#ifdef USE_VOLTAGE_CORRECTION
+// same 0.05V units as fudge factor,
+// but 7 is neutral, and the expected range is from 1 to 13
+uint8_t voltage_correction = 7;
+#endif
#ifdef USE_LVP
void low_voltage();
#endif
@@ -88,7 +93,7 @@ void battcheck();
#define THERM_CAL_OFFSET 0
#endif
// temperature now, in C (ish)
-volatile int16_t temperature;
+int16_t temperature;
uint8_t therm_ceil = DEFAULT_THERM_CEIL;
int8_t therm_cal_offset = 0;
static inline void ADC_temperature_handler();
diff --git a/spaghetti-monster/fsm-events.c b/spaghetti-monster/fsm-events.c
index ad869a6..3279c14 100644
--- a/spaghetti-monster/fsm-events.c
+++ b/spaghetti-monster/fsm-events.c
@@ -23,8 +23,60 @@
#include <util/delay_basic.h>
+void append_emission(Event event, uint16_t arg) {
+ uint8_t i;
+ // find last entry
+ for(i=0;
+ (i<EMISSION_QUEUE_LEN) && (emissions[i].event != EV_none);
+ i++) { }
+ // add new entry
+ if (i < EMISSION_QUEUE_LEN) {
+ emissions[i].event = event;
+ emissions[i].arg = arg;
+ } else {
+ // TODO: if queue full, what should we do?
+ }
+}
+
+void delete_first_emission() {
+ uint8_t i;
+ for(i=0; i<EMISSION_QUEUE_LEN-1; i++) {
+ emissions[i].event = emissions[i+1].event;
+ emissions[i].arg = emissions[i+1].arg;
+ }
+ emissions[i].event = EV_none;
+ emissions[i].arg = 0;
+}
+
+void process_emissions() {
+ while (emissions[0].event != EV_none) {
+ emit_now(emissions[0].event, emissions[0].arg);
+ delete_first_emission();
+ }
+}
+
+// Call stacked callbacks for the given event until one handles it.
+uint8_t emit_now(Event event, uint16_t arg) {
+ for(int8_t i=state_stack_len-1; i>=0; i--) {
+ uint8_t err = state_stack[i](event, arg);
+ if (! err) return 0;
+ }
+ return 1; // event not handled
+}
+
+void emit(Event event, uint16_t arg) {
+ // add this event to the queue for later,
+ // so we won't use too much time during an interrupt
+ append_emission(event, arg);
+}
+
+void emit_current_event(uint16_t arg) {
+ emit(current_event, arg);
+}
+
void empty_event_sequence() {
current_event = EV_none;
+ ticks_since_last_event = 0;
// when the user completes an input sequence, interrupt any running timers
// to cancel any delays currently in progress
// This eliminates a whole bunch of extra code:
@@ -33,8 +85,9 @@ void empty_event_sequence() {
interrupt_nice_delays();
}
-uint8_t push_event(uint8_t ev_type) {
- ticks_since_last_event = 0; // something happened
+uint8_t push_event(uint8_t ev_type) { // only for use by PCINT_inner()
+ // don't do this here; do it in PCINT_inner() instead
+ //ticks_since_last_event = 0; // something happened
// only click events are sent to this function
current_event |= B_CLICK;
@@ -61,41 +114,8 @@ uint8_t push_event(uint8_t ev_type) {
}
return 0; // unexpected event type
-
-}
-
-
-void append_emission(Event event, uint16_t arg) {
- uint8_t i;
- // find last entry
- for(i=0;
- (i<EMISSION_QUEUE_LEN) && (emissions[i].event != EV_none);
- i++) { }
- // add new entry
- if (i < EMISSION_QUEUE_LEN) {
- emissions[i].event = event;
- emissions[i].arg = arg;
- } else {
- // TODO: if queue full, what should we do?
- }
}
-void delete_first_emission() {
- uint8_t i;
- for(i=0; i<EMISSION_QUEUE_LEN-1; i++) {
- emissions[i].event = emissions[i+1].event;
- emissions[i].arg = emissions[i+1].arg;
- }
- emissions[i].event = EV_none;
- emissions[i].arg = 0;
-}
-
-void process_emissions() {
- while (emissions[0].event != EV_none) {
- emit_now(emissions[0].event, emissions[0].arg);
- delete_first_emission();
- }
-}
// explicitly interrupt these "nice" delays
volatile uint8_t nice_delay_interrupt = 0;
@@ -106,7 +126,6 @@ inline void interrupt_nice_delays() { nice_delay_interrupt = 1; }
// 0: state changed
// 1: normal completion
uint8_t nice_delay_ms(uint16_t ms) {
- StatePtr old_state = current_state;
/* // delay_zero() implementation
if (ms == 0) {
CLKPR = 1<<CLKPCE; CLKPR = 0; // full speed
@@ -115,6 +134,10 @@ uint8_t nice_delay_ms(uint16_t ms) {
}
*/
while(ms-- > 0) {
+ if (nice_delay_interrupt) {
+ return 0;
+ }
+
#ifdef USE_DYNAMIC_UNDERCLOCKING
#ifdef USE_RAMPING
uint8_t level = actual_level; // volatile, avoid repeat access
@@ -148,9 +171,6 @@ uint8_t nice_delay_ms(uint16_t ms) {
// run pending system processes while we wait
handle_deferred_interrupts();
- if ((nice_delay_interrupt) || (old_state != current_state)) {
- return 0; // state changed; abort
- }
// handle events only afterward, so that any collapsed delays will
// finish running the UI's loop() code before taking any further actions
// (this helps make sure code runs in the correct order)
@@ -192,24 +212,4 @@ uint8_t nice_delay_s() {
}
*/
-// Call stacked callbacks for the given event until one handles it.
-uint8_t emit_now(Event event, uint16_t arg) {
- for(int8_t i=state_stack_len-1; i>=0; i--) {
- uint8_t err = state_stack[i](event, arg);
- if (! err) return 0;
- }
- return 1; // event not handled
-}
-
-void emit(Event event, uint16_t arg) {
- // add this event to the queue for later,
- // so we won't use too much time during an interrupt
- append_emission(event, arg);
-}
-
-void emit_current_event(uint16_t arg) {
- ticks_since_last_event = arg;
- emit(current_event, arg);
-}
-
#endif
diff --git a/spaghetti-monster/fsm-events.h b/spaghetti-monster/fsm-events.h
index a132d5a..79a0aff 100644
--- a/spaghetti-monster/fsm-events.h
+++ b/spaghetti-monster/fsm-events.h
@@ -22,6 +22,23 @@
#include <avr/pgmspace.h>
+
+// timeout durations in ticks (each tick 1/62th s)
+#ifndef HOLD_TIMEOUT
+#define HOLD_TIMEOUT 24
+#endif
+#ifndef RELEASE_TIMEOUT
+#define RELEASE_TIMEOUT 18
+#endif
+
+// return codes for Event handlers
+// Indicates whether this handler consumed (handled) the Event, or
+// if the Event should be sent to the next handler in the stack.
+#define EVENT_HANDLED 0
+#define EVENT_NOT_HANDLED 1
+#define MISCHIEF_MANAGED EVENT_HANDLED
+#define MISCHIEF_NOT_MANAGED EVENT_NOT_HANDLED
+
// typedefs
typedef uint8_t Event;
typedef struct Emission {
@@ -29,22 +46,35 @@ typedef struct Emission {
uint16_t arg;
} Emission;
-#define EVENT_HANDLED 0
-#define EVENT_NOT_HANDLED 1
-#define MISCHIEF_MANAGED EVENT_HANDLED
-#define MISCHIEF_NOT_MANAGED EVENT_NOT_HANDLED
-
Event current_event;
// at 0.016 ms per tick, 255 ticks = 4.08 s
static volatile uint16_t ticks_since_last_event = 0;
-// timeout durations in ticks (each tick 1/62th s)
-#ifndef HOLD_TIMEOUT
-#define HOLD_TIMEOUT 24
-#endif
-#ifndef RELEASE_TIMEOUT
-#define RELEASE_TIMEOUT 18
-#endif
+// maximum number of events which can be waiting at one time
+// (would probably be okay to reduce this to 4, but it's higher to be safe)
+#define EMISSION_QUEUE_LEN 16
+// was "volatile" before, changed to regular var since IRQ rewrites seem
+// to have removed the need for it to be volatile
+// no comment about "volatile emissions"
+Emission emissions[EMISSION_QUEUE_LEN];
+
+void append_emission(Event event, uint16_t arg);
+void delete_first_emission();
+void process_emissions();
+uint8_t emit_now(Event event, uint16_t arg);
+void emit(Event event, uint16_t arg);
+void emit_current_event(uint16_t arg);
+void empty_event_sequence();
+uint8_t push_event(uint8_t ev_type); // only for use by PCINT_inner()
+
+
+// TODO: Maybe move these to their own file...
+// ... this probably isn't the right place for delays.
+inline void interrupt_nice_delays();
+uint8_t nice_delay_ms(uint16_t ms);
+//uint8_t nice_delay_s();
+void delay_4ms(uint8_t ms);
+
/* Event structure
* Bit 7: 1 for a button input event, 0 for all others.
@@ -201,25 +231,4 @@ static volatile uint16_t ticks_since_last_event = 0;
#define EV_click15_hold_release (B_CLICK|B_HOLD|B_RELEASE|B_TIMEOUT|15)
-void empty_event_sequence();
-uint8_t push_event(uint8_t ev_type);
-
-
-#define EMISSION_QUEUE_LEN 16
-// no comment about "volatile emissions"
-volatile Emission emissions[EMISSION_QUEUE_LEN];
-
-void append_emission(Event event, uint16_t arg);
-void delete_first_emission();
-void process_emissions();
-//#define emit_now emit
-uint8_t emit_now(Event event, uint16_t arg);
-void emit(Event event, uint16_t arg);
-void emit_current_event(uint16_t arg);
-
-uint8_t nice_delay_ms(uint16_t ms);
-//uint8_t nice_delay_s();
-inline void interrupt_nice_delays();
-void delay_4ms(uint8_t ms);
-
#endif
diff --git a/spaghetti-monster/fsm-main.c b/spaghetti-monster/fsm-main.c
index 790cc68..b96bce8 100644
--- a/spaghetti-monster/fsm-main.c
+++ b/spaghetti-monster/fsm-main.c
@@ -129,6 +129,7 @@ int main() {
// fallback for handling a few things
#ifndef DONT_USE_DEFAULT_STATE
push_state(default_state, 0);
+ nice_delay_interrupt = 0;
#endif
// call recipe's setup
@@ -139,6 +140,14 @@ int main() {
// if event queue not empty, empty it
process_emissions();
+ // if loop() tried to change state, process that now
+ StatePtr df = deferred_state;
+ if (df) {
+ set_state(df, deferred_state_arg);
+ deferred_state = NULL;
+ //deferred_state_arg = 0; // unnecessary
+ }
+
// enter standby mode if requested
// (works better if deferred like this)
if (go_to_standby) {
@@ -164,11 +173,12 @@ int main() {
// catch up on interrupts
handle_deferred_interrupts();
+ // turn delays back on, if they were off
+ nice_delay_interrupt = 0;
+
// give the recipe some time slices
loop();
- // in case we fell through, turn delays back on
- nice_delay_interrupt = 0;
}
}
diff --git a/spaghetti-monster/fsm-misc.c b/spaghetti-monster/fsm-misc.c
index 82be745..15cb659 100644
--- a/spaghetti-monster/fsm-misc.c
+++ b/spaghetti-monster/fsm-misc.c
@@ -83,13 +83,12 @@ uint8_t blink_big_num(uint16_t num) {
#endif
#ifdef USE_BLINK_NUM
uint8_t blink_num(uint8_t num) {
- //StatePtr old_state = current_state;
- #if 0
+ #if 1
uint8_t hundreds = num / 100;
num = num % 100;
uint8_t tens = num / 10;
num = num % 10;
- #else // 8 bytes smaller
+ #else // can be smaller or larger, depending on whether divmod is used elsewhere
uint8_t hundreds = 0;
uint8_t tens = 0;
for(; num >= 100; hundreds ++, num -= 100);
@@ -102,32 +101,9 @@ uint8_t blink_num(uint8_t num) {
nice_delay_ms(200);
#endif
- #if 0
- if (hundreds) {
- if (! blink_digit(hundreds)) return 0;
- if (! blink_digit(tens)) return 0;
- }
- else if (tens) {
- if (! blink_digit(tens)) return 0;
- }
- if (! blink_digit(num)) return 0;
- return nice_delay_ms(1000);
- #else // same size :(
- if (hundreds) if (! blink_digit(hundreds)) return 0;
- if (hundreds || tens) if (! blink_digit(tens)) return 0;
- if (! blink_digit(num)) return 0;
- return nice_delay_ms(1000);
- #endif
-
- /*
- uint8_t volts, tenths;
- volts = voltage / 10;
- tenths = voltage % 10;
- if (! blink(volts)) return;
- nice_delay_ms(200);
- if (! blink(tenths)) return;
- nice_delay_ms(200);
- */
+ if (hundreds) blink_digit(hundreds);
+ if (hundreds || tens) blink_digit(tens);
+ return blink_digit(num);
}
#endif
diff --git a/spaghetti-monster/fsm-pcint.c b/spaghetti-monster/fsm-pcint.c
index d362633..24cc82c 100644
--- a/spaghetti-monster/fsm-pcint.c
+++ b/spaghetti-monster/fsm-pcint.c
@@ -93,18 +93,19 @@ ISR(PCINT0_vect) {
// should only be called from PCINT and/or WDT
// (is a separate function to reduce code duplication)
void PCINT_inner(uint8_t pressed) {
- uint8_t pushed;
+ button_last_state = pressed;
- if (pressed) {
- pushed = push_event(B_PRESS);
- } else {
- pushed = push_event(B_RELEASE);
- }
-
- // send event to the current state callback
- if (pushed) {
- button_last_state = pressed;
+ // register the change, and send event to the current state callback
+ if (pressed) { // user pressed button
+ push_event(B_PRESS);
emit_current_event(0);
+ } else { // user released button
+ // how long was the button held?
+ push_event(B_RELEASE);
+ emit_current_event(ticks_since_last_event);
}
+ ticks_since_last_event = 0;
}
+
+
#endif
diff --git a/spaghetti-monster/fsm-ramping.c b/spaghetti-monster/fsm-ramping.c
index 3791030..8ad4b65 100644
--- a/spaghetti-monster/fsm-ramping.c
+++ b/spaghetti-monster/fsm-ramping.c
@@ -218,6 +218,9 @@ void gradual_tick() {
{
actual_level = gt + 1;
}
+ #ifdef USE_DYNAMIC_UNDERCLOCKING
+ auto_clock_speed();
+ #endif
}
#endif
diff --git a/spaghetti-monster/fsm-ramping.h b/spaghetti-monster/fsm-ramping.h
index c650e21..f177db9 100644
--- a/spaghetti-monster/fsm-ramping.h
+++ b/spaghetti-monster/fsm-ramping.h
@@ -24,7 +24,7 @@
#ifdef USE_RAMPING
// actual_level: last ramp level set by set_level()
-volatile uint8_t actual_level = 0;
+uint8_t actual_level = 0;
#ifdef USE_TINT_RAMPING
uint8_t tint = 128;
@@ -33,7 +33,7 @@ uint8_t tint = 128;
#ifdef USE_SET_LEVEL_GRADUALLY
// adjust brightness very smoothly
-volatile uint8_t gradual_target;
+uint8_t gradual_target;
inline void set_level_gradually(uint8_t lvl);
void gradual_tick();
#endif
diff --git a/spaghetti-monster/fsm-standby.h b/spaghetti-monster/fsm-standby.h
index 2cea080..cd01e72 100644
--- a/spaghetti-monster/fsm-standby.h
+++ b/spaghetti-monster/fsm-standby.h
@@ -27,15 +27,15 @@ volatile uint8_t go_to_standby = 0;
#ifdef TICK_DURING_STANDBY
#ifndef STANDBY_TICK_SPEED
-#define STANDBY_TICK_SPEED 5 // every 0.512 s
+#define STANDBY_TICK_SPEED 3 // every 0.128 s
/*
* From the Attiny85 manual:
* 0: 16 ms
* 1: 32 ms
* 2: 64 ms
- * 3: 0.125 s
- * 4: 0.25 s
- * 5: 0.5 s
+ * 3: 0.128 s
+ * 4: 0.256 s
+ * 5: 0.512 s
* 6: 1.0 s
* 7: 2.0 s
* 32: 4.0 s
@@ -45,6 +45,32 @@ volatile uint8_t go_to_standby = 0;
* how it is)
*/
#endif
+
+#if (STANDBY_TICK_SPEED == 1)
+#define SLEEP_TICKS_PER_SECOND 31
+#define SLEEP_TICKS_PER_MINUTE 1800
+
+#elif (STANDBY_TICK_SPEED == 2)
+#define SLEEP_TICKS_PER_SECOND 16
+#define SLEEP_TICKS_PER_MINUTE 900
+
+#elif (STANDBY_TICK_SPEED == 3)
+#define SLEEP_TICKS_PER_SECOND 8
+#define SLEEP_TICKS_PER_MINUTE 450
+
+#elif (STANDBY_TICK_SPEED == 4)
+#define SLEEP_TICKS_PER_SECOND 4
+#define SLEEP_TICKS_PER_MINUTE 225
+
+#elif (STANDBY_TICK_SPEED == 5)
+#define SLEEP_TICKS_PER_SECOND 2
+#define SLEEP_TICKS_PER_MINUTE 113
+
+#elif (STANDBY_TICK_SPEED == 6)
+#define SLEEP_TICKS_PER_SECOND 1
+#define SLEEP_TICKS_PER_MINUTE 57
+
+#endif
#endif
#define standby_mode sleep_until_eswitch_pressed
diff --git a/spaghetti-monster/fsm-states.c b/spaghetti-monster/fsm-states.c
index f91dc4b..4d9ac08 100644
--- a/spaghetti-monster/fsm-states.c
+++ b/spaghetti-monster/fsm-states.c
@@ -39,6 +39,9 @@ void _set_state(StatePtr new_state, uint16_t arg,
current_state = new_state;
// call new state-enter hook (don't use stack)
if (new_state != NULL) current_state(enter_event, arg);
+
+ // since state changed, stop any animation in progress
+ interrupt_nice_delays();
}
int8_t push_state(StatePtr new_state, uint16_t arg) {
@@ -79,6 +82,11 @@ uint8_t set_state(StatePtr new_state, uint16_t arg) {
return push_state(new_state, arg);
}
+void set_state_deferred(StatePtr new_state, uint16_t arg) {
+ deferred_state = new_state;
+ deferred_state_arg = arg;
+}
+
#ifndef DONT_USE_DEFAULT_STATE
// bottom state on stack
// handles default actions for LVP, thermal regulation, etc
diff --git a/spaghetti-monster/fsm-states.h b/spaghetti-monster/fsm-states.h
index 7d9361b..9964bc1 100644
--- a/spaghetti-monster/fsm-states.h
+++ b/spaghetti-monster/fsm-states.h
@@ -40,6 +40,13 @@ void _set_state(StatePtr new_state, uint16_t arg,
int8_t push_state(StatePtr new_state, uint16_t arg);
StatePtr pop_state();
uint8_t set_state(StatePtr new_state, uint16_t arg);
+
+// if loop() needs to change state, use this instead of set_state()
+// (because this avoids race conditions)
+volatile StatePtr deferred_state;
+volatile uint16_t deferred_state_arg;
+void set_state_deferred(StatePtr new_state, uint16_t arg);
+
#ifndef DONT_USE_DEFAULT_STATE
uint8_t default_state(Event event, uint16_t arg);
#endif
diff --git a/spaghetti-monster/fsm-wdt.c b/spaghetti-monster/fsm-wdt.c
index 94266c1..9e8d9af 100644
--- a/spaghetti-monster/fsm-wdt.c
+++ b/spaghetti-monster/fsm-wdt.c
@@ -150,6 +150,7 @@ void WDT_inner() {
// (first frame of a "hold" event)
else {
if (ticks_since_last >= HOLD_TIMEOUT) {
+ ticks_since_last_event = 0;
current_event |= B_HOLD;
emit_current_event(0);
}
@@ -160,9 +161,8 @@ void WDT_inner() {
else if (current_event) {
// "hold" event just ended
// no timeout required when releasing a long-press
- // TODO? move this logic to PCINT() and simplify things here?
if (current_event & B_HOLD) {
- //emit_current_event(0); // should have been emitted by PCINT_inner()
+ //emit_current_event(ticks_since_last); // should have been emitted by PCINT_inner()
empty_event_sequence();
}
// end and clear event after release timeout