From 0a801bff0f22be650aed6c3724c41cae03814d8f Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Thu, 31 Aug 2017 23:45:36 -0600 Subject: Started a Meteor M43 clone UI. So far, UI1 and battcheck both work. UI2 and UI3 and other blinkies aren't implement yet. Added 6-bar battcheck style to match Meteor (ish). Increased maximum number of clicks to 12, because WTF. If your UI needs 12 clicks, what are you even doing in life? --- spaghetti-monster/meteor/meteor.c | 413 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 413 insertions(+) create mode 100644 spaghetti-monster/meteor/meteor.c (limited to 'spaghetti-monster/meteor') diff --git a/spaghetti-monster/meteor/meteor.c b/spaghetti-monster/meteor/meteor.c new file mode 100644 index 0000000..9a630cd --- /dev/null +++ b/spaghetti-monster/meteor/meteor.c @@ -0,0 +1,413 @@ +/* + * Meteor: Meteor M43 clone UI for SpaghettiMonster. + * + * 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 . + */ + +#define FSM_EMISAR_D4_DRIVER +#define USE_LVP +#define USE_THERMAL_REGULATION +#define DEFAULT_THERM_CEIL 45 +#define USE_DELAY_4MS +#define USE_RAMPING +#define RAMP_LENGTH 150 +#define USE_BATTCHECK +#define BATTCHECK_6bars +#define DONT_DELAY_AFTER_BATTCHECK +//#define USE_EEPROM +//#define EEPROM_BYTES 5 +#define MAX_CLICKS 11 +#include "spaghetti-monster.h" + +// FSM states +uint8_t base_off_state(EventPtr event, uint16_t arg); +uint8_t ui1_off_state(EventPtr event, uint16_t arg); +uint8_t ui2_off_state(EventPtr event, uint16_t arg); +uint8_t ui3_off_state(EventPtr event, uint16_t arg); +uint8_t base_on_state(EventPtr event, uint16_t arg, uint8_t *mode, uint8_t *group); +uint8_t ui1_on_state(EventPtr event, uint16_t arg); +uint8_t ui2_on_state(EventPtr event, uint16_t arg); +uint8_t ui3_on_state(EventPtr event, uint16_t arg); +uint8_t beacon_state(EventPtr event, uint16_t arg); +uint8_t battcheck_state(EventPtr event, uint16_t arg); +uint8_t strobe_state(EventPtr event, uint16_t arg); +uint8_t biking_state(EventPtr event, uint16_t arg); +uint8_t lockout_state(EventPtr event, uint16_t arg); +uint8_t momentary_state(EventPtr event, uint16_t arg); +// Not a FSM state, just handles stuff common to all low/med/hi states +uint8_t any_mode_state(EventPtr event, uint16_t arg, uint8_t *primary, uint8_t *secondary, uint8_t *modes); + +#ifdef USE_EEPROM +void load_config(); +void save_config(); +#endif + +// fixed output levels +uint8_t levels[] = {3, 16, 30, 43, 56, 70, 83, 96, 110, 123, 137, MAX_LEVEL}; +// select an interface +uint8_t UI = 1; // 1, 2, or 3 +// UI1 +uint8_t UI1_mode = 0; +uint8_t UI1_mode1 = 1; +uint8_t UI1_mode2 = 1; +uint8_t UI1_group1[] = {0, 2}; +uint8_t UI1_group2[] = {6, 9}; +// UI2 +uint8_t UI2_mode = 0; +uint8_t UI2_mode1 = 1; +uint8_t UI2_mode2 = 0; +uint8_t UI2_mode3 = 0; +uint8_t UI2_group1[] = {0, 2}; +uint8_t UI2_group2[] = {4, 6}; +uint8_t UI2_group3[] = {8, 10}; +// UI3 can access all levels, with 3 different mode memory slots +uint8_t UI3_mode = 0; +uint8_t UI3_mode1 = 2; +uint8_t UI3_mode2 = 5; +uint8_t UI3_mode3 = 8; + +// deferred "off" so we won't suspend in a weird state +volatile uint8_t go_to_standby = 0; + +#ifdef USE_THERMAL_REGULATION +// brightness before thermal step-down +uint8_t target_level = 0; +#endif + +void set_any_mode(uint8_t mode, uint8_t *group) { + set_level(levels[group[mode]]); + #ifdef USE_THERMAL_REGULATION + target_level = actual_level; + #endif +} + +uint8_t base_off_state(EventPtr event, uint16_t arg) { + // turn emitter off when entering state + if (event == EV_enter_state) { + set_level(0); + // sleep while off (lower power use) + go_to_standby = 1; + // ensure we're in a real off state, not the base + switch(UI) { + case 1: set_state(ui1_off_state, 0); break; + case 2: set_state(ui2_off_state, 0); break; + default: set_state(ui3_off_state, 0); break; + } + return EVENT_HANDLED; + } + // 3 clicks: strobe mode + else if (event == EV_3clicks) { + set_state(beacon_state, 0); + return EVENT_HANDLED; + } + // 4 clicks: battcheck mode + else if (event == EV_4clicks) { + set_state(battcheck_state, 0); + return EVENT_HANDLED; + } + // 5 clicks: battcheck mode + else if (event == EV_5clicks) { + set_state(biking_state, 0); + return EVENT_HANDLED; + } + // 6 clicks: soft lockout mode + else if (event == EV_6clicks) { + set_state(lockout_state, 0); + return EVENT_HANDLED; + } + // 9 clicks: activate UI1 + else if (event == EV_9clicks) { + set_state(ui1_off_state, 0); + return EVENT_HANDLED; + } + // 10 clicks: activate UI2 + else if (event == EV_10clicks) { + set_state(ui2_off_state, 0); + return EVENT_HANDLED; + } + // 11 clicks: activate UI3 + else if (event == EV_11clicks) { + set_state(ui3_off_state, 0); + return EVENT_HANDLED; + } + return EVENT_NOT_HANDLED; +} + +uint8_t ui1_off_state(EventPtr event, uint16_t arg) { + UI = 1; + if (event == EV_enter_state) { + return EVENT_HANDLED; + } + // 1 click: low modes + if (event == EV_1click) { + set_any_mode(UI1_mode1, UI1_group1); + set_state(ui1_on_state, 0); + return EVENT_HANDLED; + } + // 2 clicks: high modes + else if (event == EV_2clicks) { + set_any_mode(UI1_mode2, UI1_group2); + set_state(ui1_on_state, 1); + return EVENT_HANDLED; + } + // hold: turbo + else if (event == EV_hold) { + if (arg == 0) { + set_level(MAX_LEVEL); + } + //set_state(ui1_on_state, 3); + return EVENT_HANDLED; + } + // release hold: off + else if (event == EV_click1_hold_release) { + set_state(base_off_state, 0); + return EVENT_HANDLED; + } + return base_off_state(event, arg); +} + +uint8_t ui2_off_state(EventPtr event, uint16_t arg) { + UI = 2; + if (event == EV_enter_state) { + return EVENT_HANDLED; + } + return base_off_state(event, arg); +} + +uint8_t ui3_off_state(EventPtr event, uint16_t arg) { + UI = 3; + if (event == EV_enter_state) { + return EVENT_HANDLED; + } + return base_off_state(event, arg); +} + +uint8_t base_on_state(EventPtr event, uint16_t arg, uint8_t *mode, uint8_t *group) { + // 1 click: off + if (event == EV_1click) { + set_state(base_off_state, 0); + 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 (actual_level > MAX_LEVEL/4) { + uint8_t stepdown = actual_level - arg; + if (stepdown < MAX_LEVEL/4) stepdown = MAX_LEVEL/4; + set_level(stepdown); + } + return EVENT_HANDLED; + } + // underheating: increase slowly if we're lower than the target + // (proportional to how low we are) + else if (event == EV_temperature_low) { + if (actual_level < target_level) { + uint8_t stepup = actual_level + (arg>>1); + if (stepup > target_level) stepup = target_level; + set_level(stepup); + } + return EVENT_HANDLED; + } + #endif + return EVENT_NOT_HANDLED; +} + +uint8_t ui1_on_state(EventPtr event, uint16_t arg) { + // turn on LED when entering the mode + static uint8_t *mode = &UI1_mode1; + static uint8_t *group = UI1_group1; + if (event == EV_enter_state) { + UI1_mode = arg; + } + if (UI1_mode == 0) { + mode = &UI1_mode1; + group = UI1_group1; + } + else { + mode = &UI1_mode2; + group = UI1_group2; + } + + if (event == EV_enter_state) { + set_any_mode(*mode, group); + return EVENT_HANDLED; + } + // 2 clicks: toggle moon/low or mid/high + else if (event == EV_2clicks) { + *mode ^= 1; + set_any_mode(*mode, group); + return MISCHIEF_MANAGED; + } + // hold: turbo + else if (event == EV_hold) { + if (arg == 0) set_level(MAX_LEVEL); + return MISCHIEF_MANAGED; + } + // release: exit turbo + else if (event == EV_click1_hold_release) { + set_any_mode(*mode, group); + return MISCHIEF_MANAGED; + } + return base_on_state(event, arg, &UI1_mode1, UI1_group1); +} + +uint8_t ui2_on_state(EventPtr event, uint16_t arg) { + return base_on_state(event, arg, &UI2_mode1, UI2_group1); +} + +uint8_t ui3_on_state(EventPtr event, uint16_t arg) { + return base_on_state(event, arg, &UI3_mode1, levels); +} + + +uint8_t blinky_base_state(EventPtr event, uint16_t arg) { + // 1 click: off + if (event == EV_1click) { + set_state(base_off_state, 0); + return MISCHIEF_MANAGED; + } + return EVENT_NOT_HANDLED; +} + +uint8_t beacon_state(EventPtr event, uint16_t arg) { + return blinky_base_state(event, arg); +} + +uint8_t battcheck_state(EventPtr event, uint16_t arg) { + return EVENT_NOT_HANDLED; +} + +uint8_t strobe_state(EventPtr event, uint16_t arg) { + return blinky_base_state(event, arg); +} + +uint8_t biking_state(EventPtr event, uint16_t arg) { + return blinky_base_state(event, arg); +} + +uint8_t lockout_state(EventPtr event, uint16_t arg) { + return blinky_base_state(event, arg); +} + +uint8_t momentary_state(EventPtr event, uint16_t arg) { + return blinky_base_state(event, arg); +} + + +void low_voltage() { + if ((current_state == ui1_on_state) || + (current_state == ui2_on_state) || + (current_state == ui3_on_state)) { + if (actual_level > 5) { + set_level(actual_level >> 1); + } + else { + set_state(base_off_state, 0); + } + } + /* + // "step down" from blinkies to low + else if (current_state == strobe_beacon_state) { + set_state(low_mode_state, 0); + } + */ +} + +void strobe(uint8_t level, uint16_t ontime, uint16_t offtime) { + set_level(level); + if (! nice_delay_ms(ontime)) return; + set_level(0); + nice_delay_ms(offtime); +} + +#ifdef USE_EEPROM +void load_config() { + if (load_eeprom()) { + H1 = !(!(eeprom[0] & 0b00000100)); + M1 = !(!(eeprom[0] & 0b00000010)); + L1 = !(!(eeprom[0] & 0b00000001)); + H2 = eeprom[1]; + M2 = eeprom[2]; + L2 = eeprom[3]; + strobe_beacon_mode = eeprom[4]; + } +} + +void save_config() { + eeprom[0] = (H1<<2) | (M1<<1) | (L1); + eeprom[1] = H2; + eeprom[2] = M2; + eeprom[3] = L2; + eeprom[4] = strobe_beacon_mode; + + save_eeprom(); +} +#endif + +void setup() { + set_level(RAMP_SIZE/8); + delay_4ms(3); + set_level(0); + + #ifdef USE_EEPROM + load_config(); + #endif + + push_state(base_off_state, 0); +} + +void loop() { + // deferred "off" so we won't suspend in a weird state + // (like... during the middle of a strobe pulse) + if (go_to_standby) { + go_to_standby = 0; + set_level(0); + standby_mode(); + } + + /* + if (current_state == strobe_beacon_state) { + switch(strobe_beacon_mode) { + // 0.2 Hz beacon at L1 + case 0: + strobe(low_modes[0], 500, 4500); + break; + // 0.2 Hz beacon at H1 + case 1: + strobe(hi_modes[0], 500, 4500); + break; + // 4 Hz tactical strobe at H1 + case 2: + strobe(hi_modes[0], 83, 167); + break; + // 19 Hz tactical strobe at H1 + case 3: + strobe(hi_modes[0], 17, 35); + break; + } + } + */ + + #ifdef USE_BATTCHECK + else if (current_state == battcheck_state) { + nice_delay_ms(500); // wait a moment to measure voltage + battcheck(); + set_state(base_off_state, 0); + } + #endif +} + + -- cgit v1.2.3 From bb6314450b170220a884df49baf0b76cf549b3bc Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Fri, 1 Sep 2017 00:16:03 -0600 Subject: Added UI2. UI2 is weird. Not sure I got everything the same; will have to try UI2 on my actual Meteor to see how it works. --- spaghetti-monster/meteor/meteor.c | 91 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 86 insertions(+), 5 deletions(-) (limited to 'spaghetti-monster/meteor') diff --git a/spaghetti-monster/meteor/meteor.c b/spaghetti-monster/meteor/meteor.c index 9a630cd..c2ea6ec 100644 --- a/spaghetti-monster/meteor/meteor.c +++ b/spaghetti-monster/meteor/meteor.c @@ -70,9 +70,11 @@ uint8_t UI2_mode = 0; uint8_t UI2_mode1 = 1; uint8_t UI2_mode2 = 0; uint8_t UI2_mode3 = 0; -uint8_t UI2_group1[] = {0, 2}; -uint8_t UI2_group2[] = {4, 6}; -uint8_t UI2_group3[] = {8, 10}; +uint8_t UI2_mode4 = 0; // doesn't matter, makes other code easier +uint8_t UI2_group1[] = { 0, 2}; // moon, low +uint8_t UI2_group2[] = { 4, 6}; // mid1, mid2 +uint8_t UI2_group3[] = { 8, 10}; // high1, high2 +uint8_t UI2_group4[] = {11, 11}; // turbo only // UI3 can access all levels, with 3 different mode memory slots uint8_t UI3_mode = 0; uint8_t UI3_mode1 = 2; @@ -94,6 +96,12 @@ void set_any_mode(uint8_t mode, uint8_t *group) { #endif } +void blink_fast() { + set_level(MAX_LEVEL/2); + delay_4ms(8/4); + set_level(0); +} + uint8_t base_off_state(EventPtr event, uint16_t arg) { // turn emitter off when entering state if (event == EV_enter_state) { @@ -130,16 +138,19 @@ uint8_t base_off_state(EventPtr event, uint16_t arg) { } // 9 clicks: activate UI1 else if (event == EV_9clicks) { + blink_fast(); set_state(ui1_off_state, 0); return EVENT_HANDLED; } // 10 clicks: activate UI2 else if (event == EV_10clicks) { + blink_fast(); set_state(ui2_off_state, 0); return EVENT_HANDLED; } // 11 clicks: activate UI3 else if (event == EV_11clicks) { + blink_fast(); set_state(ui3_off_state, 0); return EVENT_HANDLED; } @@ -184,6 +195,31 @@ uint8_t ui2_off_state(EventPtr event, uint16_t arg) { if (event == EV_enter_state) { return EVENT_HANDLED; } + // 1 click: low modes + if (event == EV_1click) { + set_any_mode(UI2_mode1, UI2_group1); + set_state(ui2_on_state, 0); + return EVENT_HANDLED; + } + // 2 clicks: high modes + else if (event == EV_2clicks) { + set_any_mode(UI2_mode3, UI2_group3); + set_state(ui2_on_state, 2); + return EVENT_HANDLED; + } + // hold: turbo + else if (event == EV_hold) { + if (arg == 0) { + set_level(MAX_LEVEL); + } + //set_state(ui1_on_state, 3); + return EVENT_HANDLED; + } + // release hold: off + else if (event == EV_click1_hold_release) { + set_state(base_off_state, 0); + return EVENT_HANDLED; + } return base_off_state(event, arg); } @@ -261,11 +297,56 @@ uint8_t ui1_on_state(EventPtr event, uint16_t arg) { set_any_mode(*mode, group); return MISCHIEF_MANAGED; } - return base_on_state(event, arg, &UI1_mode1, UI1_group1); + return base_on_state(event, arg, mode, group); } uint8_t ui2_on_state(EventPtr event, uint16_t arg) { - return base_on_state(event, arg, &UI2_mode1, UI2_group1); + // turn on LED when entering the mode + static uint8_t *mode = &UI2_mode1; + static uint8_t *group = UI2_group1; + if (event == EV_enter_state) { + UI2_mode = arg; + } + switch (UI2_mode) { + case 0: + mode = &UI2_mode1; + group = UI2_group1; + break; + case 1: + mode = &UI2_mode2; + group = UI2_group2; + break; + case 2: + mode = &UI2_mode3; + group = UI2_group3; + break; + default: // turbo only + mode = &UI2_mode4; + group = UI2_group4; + break; + } + + if (event == EV_enter_state) { + set_any_mode(*mode, group); + return EVENT_HANDLED; + } + // 2 clicks: toggle moon/low, mid1/mid2, or high1/high2 + else if (event == EV_2clicks) { + *mode ^= 1; + set_any_mode(*mode, group); + return MISCHIEF_MANAGED; + } + // hold: rotate through low/mid/high/turbo + else if (event == EV_hold) { + if (arg % HOLD_TIMEOUT == 0) { + UI2_mode = (UI2_mode + 1) & 3; + } + else if (arg % HOLD_TIMEOUT == 1) { + set_any_mode(*mode, group); + } + return MISCHIEF_MANAGED; + } + return base_on_state(event, arg, mode, group); } uint8_t ui3_on_state(EventPtr event, uint16_t arg) { -- cgit v1.2.3 From 8b9cb8116df4e9bd06b38e3a3c2994520e88006f Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Fri, 22 Sep 2017 07:24:23 -0600 Subject: Updated Meteor UI to use provided go_to_standby. Started on UI3, but it's not really working yet. Needs some underlying plumbing changes first, adding support for medium-length clicks. --- spaghetti-monster/meteor/meteor.c | 86 +++++++++++++++++++++++++++++++++------ 1 file changed, 74 insertions(+), 12 deletions(-) (limited to 'spaghetti-monster/meteor') diff --git a/spaghetti-monster/meteor/meteor.c b/spaghetti-monster/meteor/meteor.c index c2ea6ec..3ec79f4 100644 --- a/spaghetti-monster/meteor/meteor.c +++ b/spaghetti-monster/meteor/meteor.c @@ -81,9 +81,6 @@ uint8_t UI3_mode1 = 2; uint8_t UI3_mode2 = 5; uint8_t UI3_mode3 = 8; -// deferred "off" so we won't suspend in a weird state -volatile uint8_t go_to_standby = 0; - #ifdef USE_THERMAL_REGULATION // brightness before thermal step-down uint8_t target_level = 0; @@ -228,6 +225,37 @@ uint8_t ui3_off_state(EventPtr event, uint16_t arg) { if (event == EV_enter_state) { return EVENT_HANDLED; } + // 1 click: memory slot 1 + if (event == EV_1click) { + set_level(levels[UI3_mode1]); + set_state(ui3_on_state, 0); + return EVENT_HANDLED; + } + // 2 clicks: memory slot 2 + else if (event == EV_2clicks) { + set_level(levels[UI3_mode2]); + set_state(ui3_on_state, 1); + return EVENT_HANDLED; + } + // Click, hold: memory slot 3 + else if (event == EV_click2_hold) { + set_level(levels[UI3_mode3]); + set_state(ui3_on_state, 2); + return EVENT_HANDLED; + } + // hold: turbo + else if (event == EV_hold) { + if (arg == 0) { + set_level(MAX_LEVEL); + } + //set_state(ui1_on_state, 3); + return EVENT_HANDLED; + } + // release hold: off + else if (event == EV_click1_hold_release) { + set_state(base_off_state, 0); + return EVENT_HANDLED; + } return base_off_state(event, arg); } @@ -350,7 +378,49 @@ uint8_t ui2_on_state(EventPtr event, uint16_t arg) { } uint8_t ui3_on_state(EventPtr event, uint16_t arg) { - return base_on_state(event, arg, &UI3_mode1, levels); + // turn on LED when entering the mode + static uint8_t *mode = &UI3_mode1; + if (event == EV_enter_state) { + UI3_mode = arg; + } + // 2 clicks: rotate through mode1/mode2/mode3 + else if (event == EV_2clicks) { + UI3_mode = (UI3_mode + 1) % 3; + } + // short click, long click: rotate through mode3/mode2/mode1 + /* + else if (event == EV_click1_hold) { + if (arg % HOLD_TIMEOUT == 0) + UI3_mode = (UI3_mode + 4) % 3; + } + */ + switch (UI3_mode) { + case 0: + mode = &UI3_mode1; + break; + case 1: + mode = &UI3_mode2; + break; + default: + mode = &UI3_mode3; + break; + } + + if ((event == EV_enter_state) || (event == EV_2clicks)) { + set_level(levels[*mode]); + return EVENT_HANDLED; + } + // short click, long click: rotate through mode3/mode2/mode1 + /* + else if (event == EV_click1_hold) { + set_level(levels[*mode]); + return MISCHIEF_MANAGED; + } + */ + // hold: turbo + // Click, hold: ramp up + // release hold, hold again: ramp in opposite direction + return base_on_state(event, arg, mode, levels); } @@ -451,14 +521,6 @@ void setup() { } void loop() { - // deferred "off" so we won't suspend in a weird state - // (like... during the middle of a strobe pulse) - if (go_to_standby) { - go_to_standby = 0; - set_level(0); - standby_mode(); - } - /* if (current_state == strobe_beacon_state) { switch(strobe_beacon_mode) { -- cgit v1.2.3