diff options
author | Christian Chapman <1360262+enthdegree@users.noreply.github.com> | 2023-04-16 10:05:55 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-04-16 10:05:55 -0400 |
commit | 462f24b31321107834d00016d0b07aa044be5d7e (patch) | |
tree | 53937086ab72d3b9bf05001e5e39ae7bb84dcc66 /movement | |
parent | bfde33c946ee7f49a2f8bf9f253b2801bd169638 (diff) | |
download | Sensor-Watch-462f24b31321107834d00016d0b07aa044be5d7e.tar.gz Sensor-Watch-462f24b31321107834d00016d0b07aa044be5d7e.tar.bz2 Sensor-Watch-462f24b31321107834d00016d0b07aa044be5d7e.zip |
Aperture priority light meter face (#230)
* initial commit, added opt3001 light meter test app
* tested working light meter board, i2c communication still has issues
* fixed i2c; rudimentary lightmeter works!
* added aperture priority ui
* added aperture priority ui
* added README
* adjusted cal
* fixed bugs (HI shutter speed, lux mode toggle)
* made it possible to advance to the next face
* initialized lux variable
* lowered tolerance for HI and LO
* Changed EV display from always showing EV100 to showing EV[iso setting]
* dont display old ev when ISO changes
* changed mode and light behavior
* updated readme
* fixed indentation
* made lightmeter display logic more consistent
* made lightmeter display logic more consistent
* reverted rules.mk (for merge into upstream)
* reverted rules.mk (for merge into upstream)
* removed OPT3001 PCB model
* made lux mode default, corrected timeout behavior
---------
Co-authored-by: Christian Chapman <user@debian>
Diffstat (limited to 'movement')
-rw-r--r-- | movement/make/Makefile | 1 | ||||
-rw-r--r-- | movement/movement_config.h | 2 | ||||
-rw-r--r-- | movement/movement_faces.h | 1 | ||||
-rw-r--r-- | movement/watch_faces/sensor/lightmeter_face.c | 206 | ||||
-rw-r--r-- | movement/watch_faces/sensor/lightmeter_face.h | 160 |
5 files changed, 370 insertions, 0 deletions
diff --git a/movement/make/Makefile b/movement/make/Makefile index d403045d..4614cd27 100644 --- a/movement/make/Makefile +++ b/movement/make/Makefile @@ -101,6 +101,7 @@ SRCS += \ ../watch_faces/complication/activity_face.c \ ../watch_faces/demo/chirpy_demo_face.c \ ../watch_faces/complication/ships_bell_face.c \ + ../watch_faces/sensor/lightmeter_face.c \ ../watch_faces/complication/discgolf_face.c \ ../watch_faces/complication/habit_face.c \ ../watch_faces/complication/breathing_face.c \ diff --git a/movement/movement_config.h b/movement/movement_config.h index 9e446d4d..3f6a0dc1 100644 --- a/movement/movement_config.h +++ b/movement/movement_config.h @@ -29,6 +29,8 @@ const watch_face_t watch_faces[] = { simple_clock_face, + lightmeter_face, + thermistor_readout_face, world_clock_face, sunrise_sunset_face, moon_phase_face, diff --git a/movement/movement_faces.h b/movement/movement_faces.h index b8584756..afa8f14a 100644 --- a/movement/movement_faces.h +++ b/movement/movement_faces.h @@ -76,6 +76,7 @@ #include "activity_face.h" #include "chirpy_demo_face.h" #include "ships_bell_face.h" +#include "lightmeter_face.h" #include "discgolf_face.h" #include "habit_face.h" #include "breathing_face.h" diff --git a/movement/watch_faces/sensor/lightmeter_face.c b/movement/watch_faces/sensor/lightmeter_face.c new file mode 100644 index 00000000..861e28d8 --- /dev/null +++ b/movement/watch_faces/sensor/lightmeter_face.c @@ -0,0 +1,206 @@ +/* + * MIT License + * + * Copyright (c) 2022 CC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* Aperture-priority Light Meter Face + * + * Tested with the "Q3Q-SWAB-A1-00 Temperature + Test Points + OPT3001" flexboard. + * This flexboard could use a revision: + * + * - The thermistor components should be moved west a mm or flipped to the backside + * to avoid stressing the flexboard against the processor so much. + * - The 'no connect' pad falls off easily. + * + * Controls: + * + * - Trigger a measurement by long-pressing Alarm. + * Sensor integration is happening when the Signal indicator is on. + * + * - ISO setting can be cycled by long-pressing Light. + * During integration the current ISO setting will be displayed. + * + * - EV measurement in the top right: "LAP" indicates "half stop". + * So "LAP -1" means EV = -1.5. Likewise "LAP 13" means EV = +13.5 + * + * - Aperture in the bottom right: the last 3 main digits are the f-stop. + * Adjust this number in half-stop increments using Alarm = +1/2 and Light = -1/2. + * + * - Best shutter speed in the bottom left: the first 3 digits are the shutter speed. + * Some special chars are needed here: "-" = seconds, "h" = extra half second, "K" = thousands. + * "HI" or "LO" if there's no shutter in the dictionary within 0.5 stops of correct exposure. + * + * - Mode long-press changes the main digits to show raw sensor lux measurements. + * + */ + +#include <stdlib.h> +#include <string.h> +#include <math.h> +#include "lightmeter_face.h" +#include "watch_utility.h" +#include "watch_slcd.h" + +uint16_t lightmeter_mod(uint16_t m, uint16_t n) { return (m%n + n)%n; } + +void lightmeter_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { + (void) settings; + (void) watch_face_index; + if (*context_ptr == NULL) { + *context_ptr = malloc(sizeof(lightmeter_state_t)); + lightmeter_state_t *state = (lightmeter_state_t*) *context_ptr; + state->waiting_for_conversion = 0; + state->lux = 0.0; + state->mode = 0; + state->iso = LIGHTMETER_ISO_100; + state->ap = LIGHTMETER_AP_4P0; + } +} + +void lightmeter_face_activate(movement_settings_t *settings, void *context) { + (void) settings; + lightmeter_state_t *state = (lightmeter_state_t*) context; + state->waiting_for_conversion = 0; + lightmeter_show_ev(state); // Print most current reading + watch_enable_i2c(); + return; +} + +void lightmeter_show_ev(lightmeter_state_t *state) { + + float ev = max(min( + log2(state->lux) + + lightmeter_isos[state->iso].ev + + LIGHTMETER_CALIBRATION, + 99), -9); + int evt = round(2*ev); // Truncated EV + + // Print EV + char strbuff[7]; + watch_clear_all_indicators(); + watch_display_string("EV ", 0); + + sprintf(strbuff, "%2i", (uint16_t) abs(evt/2)); // Print whole part of EV + watch_display_string(strbuff, 2); + if(evt%2) watch_set_indicator(WATCH_INDICATOR_LAP); // Indicate half stop + if(ev<0) watch_set_pixel(1,9); // Indicate negative EV + + // Handle lux mode + if(state->mode == 1) { + sprintf(strbuff, "%6.0f", min(state->lux, 999999.0)); + watch_display_string(strbuff, 4); + return; + } + + // Find and print best shutter speed + uint16_t bestsh = 0; + float besterr = 1.0/0.0; + float errbuf = 1.0/0.0; + float comp_ev = ev + lightmeter_aps[state->ap].ev; + for(uint16_t ind = 2; ind < LIGHTMETER_N_SHS; ind++) { + errbuf = comp_ev + lightmeter_shs[ind].ev; + if( fabs(errbuf) < fabs(besterr)) { + besterr = errbuf; + bestsh = ind; + } + } + if(besterr >= 0.5) watch_display_string(lightmeter_shs[LIGHTMETER_SH_HIGH].str, 4); + else if(besterr <= -0.5) watch_display_string(lightmeter_shs[LIGHTMETER_SH_LOW].str, 4); + else watch_display_string(lightmeter_shs[bestsh].str, 4); + + // Print aperture + watch_display_string(lightmeter_aps[state->ap].str, 7); + return; +} + +bool lightmeter_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { + (void) settings; + lightmeter_state_t *state = (lightmeter_state_t*) context; + + opt3001_Config_t c; + switch (event.event_type) { + case EVENT_TICK: + if(state->waiting_for_conversion) { // Check if measurement is ready... + c = opt3001_readConfig(lightmeter_addr); + if(c.ConversionReady) { + state->waiting_for_conversion = 0; + opt3001_t result = opt3001_readResult(lightmeter_addr); + state->lux = result.lux; + lightmeter_show_ev(state); + } + } + break; + + case EVENT_ALARM_BUTTON_UP: // Increment aperture + state->ap = lightmeter_mod(state->ap+1, LIGHTMETER_N_APS); + + lightmeter_show_ev(state); + break; + + case EVENT_LIGHT_BUTTON_UP: // Decrement aperture + if(state->ap == 0) state->ap = LIGHTMETER_N_APS-1; + else state->ap = lightmeter_mod(state->ap-1, LIGHTMETER_N_APS); + + lightmeter_show_ev(state); + break; + + case EVENT_LIGHT_LONG_PRESS: // Cycle ISO + state->iso = lightmeter_mod(state->iso+1, LIGHTMETER_N_ISOS); + + watch_clear_all_indicators(); + watch_display_string("EV ", 0); + watch_display_string(lightmeter_isos[state->iso].str, 4); + break; + + case EVENT_ALARM_LONG_PRESS: // Take measurement + opt3001_writeConfig(lightmeter_addr, lightmeter_takeNewReading); + state->waiting_for_conversion = 1; + + watch_clear_all_indicators(); + watch_display_string("EV ", 0); + watch_display_string(lightmeter_isos[state->iso].str, 4); + watch_set_indicator(WATCH_INDICATOR_SIGNAL); + break; + + case EVENT_MODE_LONG_PRESS: // Toggle mode + state->mode = !state->mode; + lightmeter_show_ev(state); + break; + + case EVENT_TIMEOUT: + movement_move_to_face(0); + break; + + default: + movement_default_loop_handler(event, settings); + break; + } + return true; +} + +void lightmeter_face_resign(movement_settings_t *settings, void *context) { + (void) settings; + (void) context; + opt3001_writeConfig(lightmeter_addr, lightmeter_off); + watch_disable_i2c(); + return; +} diff --git a/movement/watch_faces/sensor/lightmeter_face.h b/movement/watch_faces/sensor/lightmeter_face.h new file mode 100644 index 00000000..2f8813f4 --- /dev/null +++ b/movement/watch_faces/sensor/lightmeter_face.h @@ -0,0 +1,160 @@ +/* + * MIT License + * + * Copyright (c) 2023 CC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef LIGHTMETER_FACE_H_ +#define LIGHTMETER_FACE_H_ + +#include "movement.h" +#include "opt3001.h" + +#define LIGHTMETER_CALIBRATION 2.58 +typedef struct { + char * str; + float ev; +} lightmeter_ev_t; + +static const lightmeter_ev_t lightmeter_isos[] = { + {" i 25", -2}, + {" i 50", -1}, + {" i 100", 0}, + {" i 160", 0.68}, + {" i 200", 1}, + {" i 400", 2}, + {" i 800", 3}, + {" i1600", 4}}; +typedef enum { + LIGHTMETER_ISO_25, LIGHTMETER_ISO_50, LIGHTMETER_ISO_100, LIGHTMETER_ISO_160, LIGHTMETER_ISO_200, LIGHTMETER_ISO_400, LIGHTMETER_ISO_800, LIGHTMETER_ISO_1600, + LIGHTMETER_N_ISOS +} lightmeter_iso_t; + +static const lightmeter_ev_t lightmeter_aps[] = { + {"1.4", 0}, + {"1.8", -0.5}, + {"2.0", -1}, + {"2.4", -1.5}, + {"2.8", -2}, + {"3.3", -2.5}, + {"4.0", -3}, + {"4.8", -3.5}, + {"5.6", -4}, + {"6.7", -4.5}, + {"8.0", -5}, + {"9.5", -5.5}, + {"11.", -6}, + {"13.", -6.5}, + {"16.", -7}, + {"19.", -7.5}, + {"22.", -8}}; +typedef enum { + LIGHTMETER_AP_1P4, LIGHTMETER_AP_1P8, LIGHTMETER_AP_2P0, LIGHTMETER_AP_2P4, LIGHTMETER_AP_2P8, LIGHTMETER_AP_3P3, LIGHTMETER_AP_4P0, LIGHTMETER_AP_4P8, LIGHTMETER_AP_5P6, LIGHTMETER_AP_6P7, LIGHTMETER_AP_8P0, LIGHTMETER_AP_9P5, + LIGHTMETER_AP_11, LIGHTMETER_AP_13, LIGHTMETER_AP_16, LIGHTMETER_AP_19, LIGHTMETER_AP_22, + LIGHTMETER_N_APS +} lightmeter_ap_t; + +static const lightmeter_ev_t lightmeter_shs[] = { + {"LO-", 99}, + {"HI ", -99}, + {"30-", 5.0}, + {"20-", 4.5}, + {"15-", 4.0}, + {"11-", 3.5}, + {"8- ", 3.0}, + {"6- ", 2.5}, + {"4- ", 2.0}, + {"3- ", 1.5}, + {"2- ", 1.0}, + {"1h-", 0.5}, + {"1 ", 0.0}, + {"1h ", -0.5}, + {"2 ", -1.0}, + {"3 ", -1.5}, + {"4 ", -2.0}, + {"6 ", -2.5}, + {"8 ", -3.0}, + {"12 ", -3.5}, + {"15 ", -4.0}, + {"20 ", -4.5}, + {"30 ", -5.0}, + {"45 ", -5.5}, + {"60 ", -6.0}, + {"90 ", -6.5}, + {"125", -7.0}, + {"180", -7.5}, + {"250", -8.0}, + {"350", -8.5}, + {"500", -9.0}, + {"750", -9.5}, + {"1K ", -10.0}, + {"1K5", -10.5}, + {"2K ", -11.0}, + {"3K ", -11.5}, + {"4K ", -12.0}, + {"6K ", -12.5}, + {"8K ", -13.0}}; +typedef enum { + LIGHTMETER_SH_LOW, LIGHTMETER_SH_HIGH, + LIGHTMETER_SH_30S, LIGHTMETER_SH_20S, LIGHTMETER_SH_15S, LIGHTMETER_SH_11S, LIGHTMETER_SH_8S, LIGHTMETER_SH_6S, LIGHTMETER_SH_3S, LIGHTMETER_SH_4S, LIGHTMETER_SH_2S, LIGHTMETER_SH_1HS, + LIGHTMETER_SH_1, LIGHTMETER_SH_1H, LIGHTMETER_SH_2, LIGHTMETER_SH_3, LIGHTMETER_SH_4, LIGHTMETER_SH_6, LIGHTMETER_SH_8, LIGHTMETER_SH_12, LIGHTMETER_SH_15, LIGHTMETER_SH_20, LIGHTMETER_SH_30, LIGHTMETER_SH_45, LIGHTMETER_SH_60, LIGHTMETER_SH_90, LIGHTMETER_SH_125, LIGHTMETER_SH_180, LIGHTMETER_SH_250, LIGHTMETER_SH_350, LIGHTMETER_SH_500, LIGHTMETER_SH_750, + LIGHTMETER_SH_1K, LIGHTMETER_SH_1K5, LIGHTMETER_SH_2K, LIGHTMETER_SH_3K, LIGHTMETER_SH_4K, LIGHTMETER_SH_6K, LIGHTMETER_SH_8K, + LIGHTMETER_N_SHS +} lightmeter_sh_t; + +typedef struct { + lightmeter_iso_t iso; + lightmeter_ap_t ap; + bool waiting_for_conversion; + float lux; + int mode; +} lightmeter_state_t; + +static const opt3001_Config_t lightmeter_takeNewReading = { + .RangeNumber = 0B1100, + .ConversionTime = 0B1, + .Latch = 0B1, + .ModeOfConversionOperation = 0B01 +}; + +static const opt3001_Config_t lightmeter_off = { + .ModeOfConversionOperation = 0B00 +}; + +uint16_t lightmeter_mod(uint16_t m, uint16_t n); + +void lightmeter_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); +void lightmeter_face_activate(movement_settings_t *settings, void *context); +void lightmeter_show_ev(lightmeter_state_t *state); +bool lightmeter_face_loop(movement_event_t event, movement_settings_t *settings, void *context); +void lightmeter_face_resign(movement_settings_t *settings, void *context); + +static const uint8_t lightmeter_addr = 0x44; + +#define lightmeter_face ((const watch_face_t){ \ + lightmeter_face_setup, \ + lightmeter_face_activate, \ + lightmeter_face_loop, \ + lightmeter_face_resign, \ + NULL, \ +}) + +#endif // LIGHTMETER_FACE_H_ |