#include <Arduino.h>
#include <cstdint>
#include <Adafruit_NeoPixel.h>
#include <LiquidCrystal_I2C.h>
// @type.hpp
#define PTR_SIZE sizeof(void*)
typedef uint64_t u64;
typedef uint32_t u32;
typedef uint16_t u16;
typedef uint8_t u8;
typedef int64_t i64;
typedef int32_t i32;
typedef int16_t i16;
typedef int8_t i8;
typedef float f32;
// @func.hpp
inline uint16_t min(uint16_t min, uint16_t val) {
return (val < min) ? min : val;
}
inline uint16_t max(uint16_t max, uint16_t val) {
return (val > max) ? max : val;
}
// @settings.hpp
// @@added
#define GET_UI_SETTINGS
#define GET_LIGHT_SETTINGS
#define GET_MAIN_SETTINGS
// @@endadded
// ui settings
#ifdef GET_UI_SETTINGS
#define LCD_ADDR 0x27
#define LCD_COLS 20
#define LCD_ROWS 4
#define UP_BUTT_PIN 12
#define DOWN_BUTT_PIN 14
#define SEL_BUTT_PIN 27
#define BACK_UI_CHAR "<"
#endif // #ifdef GET_UI_SETTING
// light settings
#ifdef GET_LIGHT_SETTINGS
// prevent redefinition
#ifndef GET_GLOBAL_SETTINGS
// NP_COUNT and LDR_COUNT always have to be the same
#define NP_COUNT 4
#define LDR_COUNT NP_COUNT
#endif // #ifndef GET_GLOBAL_SETTINGS
constexpr u8 LDR_PINS[] = {33, 32, 35, 34};
constexpr u8 NP_PINS[] = {17, 18, 16, 19};
#define NP_ROWS 2
#define NP_COLS 2
#define DEF_NP_R 255
#define DEF_NP_G 0
#define DEF_NP_B 0
#define LDR_MAX_ADC 4063
// RNF - READING_TO_BRIGHTNESS_NORMALIZATION_FACTOR
#define RTBNF ((float)(LDR_MAX_ADC) / 100.0f)
// BPWM - BRIGHTNESS_PROPAGATION_WEIGHT_MULTIPLIER
#define BPWM 0.1
#endif // #ifdef GET_LIGHT_SETTINGS
#ifdef GET_MAIN_SETTINGS
#define LOG
#define SETUP_DELAY 500
#define LOOP_DELAY 50
#define UI_THREAD_STACK_SIZE 128
#endif //#ifdef GET_MAIN_SETTINGS
// use when including setting.hpp in a header file
#ifdef GET_GLOBAL_SETTINGS
// prevent redefinition
#ifndef GET_LIGHT_SETTINGS
// NP_COUNT and LDR_COUNT always have to be the same
#define NP_COUNT 4
#define LDR_COUNT NP_COUNT
#endif // #ifndef GET_LIGHT_SETTINGS
#endif // #ifdef GET_GLOBAL_SETTING
// @neopixel.hpp
typedef Adafruit_NeoPixel Neopixel;
namespace np {
typedef struct {
Neopixel* neopixel;
u8 pin;
u16 count;
} pixel_t;
void make(pixel_t* pixel, u8 pin, u16 count);
void destroy(pixel_t* pixel);
void brightness(pixel_t* pixel, u8 brightness);
void color(pixel_t* pixel, u8 r, u8 g, u8 b);
void color(pixel_t* pixel, u16 i, u8 r, u8 g, u8 b);
}
// @neopixel.cpp
namespace np {
void make(pixel_t* pixel, u8 pin, u16 count) {
pixel->neopixel = new Neopixel(count, pin);
pixel->neopixel->begin();
pixel->count = count;
}
void destroy(pixel_t* pixel) {
pixel->neopixel->clear();
delete pixel->neopixel;
}
void brightness(pixel_t* pixel, u8 brightness) {
pixel->neopixel->setBrightness(brightness);
pixel->neopixel->show();
}
void color(pixel_t* pixel, u8 r, u8 g, u8 b) {
pixel->neopixel->clear();
for (u16 i = 0; i < pixel->count; i++) {
pixel->neopixel->setPixelColor(i, r, g, b);
}
pixel->neopixel->show();
}
void color(pixel_t* pixel, u16 i, u8 r, u8 g, u8 b) {
pixel->neopixel->clear();
pixel->neopixel->setPixelColor(i, r, g, b);
pixel->neopixel->show();
}
}
// @slwindow.hpp
typedef struct {
u16 size;
u16 add_i;
u16 data_size;
void** data;
} slwin_t;
// @added
static void alloc_data(slwin_t* win);
inline u16 slwin_cal_i(slwin_t* win, u16 i);
inline void* slwin_get(slwin_t* win, u16 i);
inline void* slwin_get_newest(slwin_t* win);
inline void slwin_set(slwin_t* win, u16 i, void* val);
inline void slwin_set_newest(slwin_t* win, void* val);
// @endadded
void slwin_make(slwin_t* win, u16 size, u16 data_size);
void slwin_destroy(slwin_t* win);
void slwin_slide(slwin_t* win, void* data);
inline u16 slwin_cal_i(slwin_t* win, u16 i) {
return (win->add_i + i) % win->size;
}
inline void* slwin_get(slwin_t* win, u16 i) {
return win->data[slwin_cal_i(win, i)];
}
// get the last element of a sliding window
inline void* slwin_get_newest(slwin_t* win) {
return slwin_get(win, win->size - 1);
}
inline void slwin_set(slwin_t* win, u16 i, void* val) {
memcpy(win->data[slwin_cal_i(win, i)], val, win->data_size);
}
inline void slwin_set_newest(slwin_t* win, void* val) {
memcpy(
win->data[slwin_cal_i(win, win->size - 1)],
val,
win->data_size
);
}
// @slwindow.cpp
static void alloc_data(slwin_t* win) {
win->data = (void**)calloc(win->size, sizeof(void*));
for (u16 i = 0; i < win->size; i++) {
win->data[i] = malloc(win->data_size);
memset(win->data[i], 0, win->data_size);
}
}
void slwin_make(slwin_t* win, u16 size, u16 data_size) {
win->size = size;
win->add_i = 0;
win->data_size = data_size;
alloc_data(win);
}
void slwin_destroy(slwin_t* win) {
for (u8 i = 0; i < win->size; i++) {
free(win->data[i]);
}
free(win->data);
}
void slwin_slide(slwin_t* win, void* data) {
if (win->add_i != 0 && win->add_i % win->size == 0) {
win->add_i = 0;
}
memcpy(win->data[win->add_i], data, win->data_size);
win->add_i++;
}
// @ldr.hpp
#define LDR_CACHE_SIZE 4
namespace ldr {
typedef struct {
u8 pin;
slwin_t cache;
} ldr_t;
void make(ldr_t* ldr, u8 pin);
// read and cache the reading
void cache_reading(ldr_t* ldr);
// read and return reading
inline u16 read(ldr_t* ldr) {
return analogRead(ldr->pin);
}
// return the most recent cache
inline u16 get_cache(ldr_t* ldr) {
return *(u16*)slwin_get_newest(&ldr->cache);
}
inline u16 get_cache(ldr_t* ldr, u8 i) {
return *(u16*)slwin_get(&ldr->cache, 1);
}
inline void set_cache(ldr_t* ldr, void* val) {
slwin_set_newest(&ldr->cache, val);
}
inline void set_cache(ldr_t* ldr, u8 i, void* val) {
slwin_set(&ldr->cache, i, val);
}
}
// @ldr.cpp
namespace ldr {
void make(ldr_t* ldr, u8 pin) {
ldr->pin = pin;
pinMode(pin, INPUT);
slwin_make(&ldr->cache, LDR_CACHE_SIZE, sizeof(u16));
}
void cache_reading(ldr_t* ldr) {
u16 reading = ldr::read(ldr);
slwin_slide(&ldr->cache, (void*)&reading);
}
}
// @button.hpp
namespace button {
typedef struct {
u8 pin;
bool old_state;
} butt_t;
void make(butt_t* butt, u8 pin);
void destroy(butt_t* butt);
bool state(butt_t* butt);
}
// @button.cpp
namespace button {
void make(butt_t* butt, u8 pin) {
butt->pin = pin;
butt->old_state = false;
pinMode(pin, INPUT);
}
void destroy(butt_t* butt) {
pinMode(butt->pin, OUTPUT);
}
bool state(butt_t* butt) {
bool ret_val;
bool state = digitalRead(butt->pin);
ret_val = (state && !butt->old_state);
butt->old_state = state;
return ret_val;
}
}
// @ui.hpp
#define NO_WRAP 0
typedef LiquidCrystal_I2C I2C;
namespace ui {
typedef enum : u8 {
TXT,
OPT
} type_t;
// ui element
typedef struct elem {
type_t type;
// the element count at the time of creation
// it will be the row that the element is on
u8 id;
// what will be show on the screen
char* text;
} elem_t;
typedef struct {
// how many element exist in this group
// dont decrease when an element is destroy
u8 count;
// what element is the selecter on
// store as the id of the element
u8 select;
// lcd that will be use to show the ui
I2C* screen;
u8 rows;
u8 cols;
// elements in element group
elem_t** elems;
} group_t;
struct selector_in {
u8 id;
type_t type;
const char* text;
};
void make(elem_t* elem, const char* text, type_t type);
void destroy(elem_t* elem);
void group(group_t* group, I2C* screen, u8 rows, u8 cols, int count, ...);
void ungroup(group_t* group);
void selector_up(group_t* group);
void selector_down(group_t* group);
struct selector_in selector_on(group_t* group);
// show plain txt with wraping
void show(const char* text, I2C* screen, u8 x, u8 y, u8 wrap_at);
// show group of element does not support wraping yet
void show(group_t* group);
}
// @ui.cpp
#define SELECT_INDICATOR '>'
namespace ui {
static constexpr u16 ELEM_SIZE = sizeof(elem_t);
static constexpr u16 GROUP_SIZE = sizeof(group_t);
static constexpr u16 ELEM_PTR_SIZE = sizeof(elem_t*);
void make(elem_t* elem, const char* text, type_t type) {
const u16 TEXT_SIZE = strlen(text) + 1;
elem->type = type;
// id will be assing when put in group
elem->id = 0;
elem->text = (char*)malloc(TEXT_SIZE);
memset(elem->text, '\0', TEXT_SIZE);
memcpy(elem->text, text, TEXT_SIZE);
}
// this is only for freeing element memories
void destroy(elem_t* elem) {
if (elem == NULL) {
return;
}
if (elem->text != NULL) {
free(elem->text);
}
}
// use int for count to prevent argument promotion
void group(group_t* group, I2C* screen, u8 rows, u8 cols, int count, ...) {
va_list elems;
va_start(elems, count);
group->count = count;
group->select = 0;
group->screen = screen;
group->rows = rows;
group->cols = cols;
group->elems = (elem_t**)calloc(count, ELEM_PTR_SIZE);
// add all element to the group
for (u8 i = 0; i < count; i++) {
group->elems[i] = va_arg(elems, elem_t*);
group->elems[i]->id = i;
}
va_end(elems);
// move select indicator to the first option element
// in the group
if (group->elems[0]->type != OPT) {
selector_down(group);
}
}
void ungroup(group_t* group) {
if (group == NULL) {
return;
}
free(group->elems);
free(group);
}
void selector_up(group_t* group) {
// move up skipping the currently selected option
// untill find an option or untill reach top row
for (i8 i = group->select - 1; i >= 0; i--) {
elem_t* elem = group->elems[i];
if (elem->type == OPT) {
group->select = elem->id;
return;
}
}
}
void selector_down(group_t* group) {
// move down skipping the currently selected option
// untill find an option or untill reach bottom row
for (u8 i = group->select + 1; i != group->count; i++) {
elem_t* elem = group->elems[i];
if (elem->type == OPT) {
group->select = elem->id;
return;
}
}
}
struct selector_in selector_on(group_t* group) {
elem_t* elem = group->elems[group->select];
struct selector_in info = {
.id = group->select,
.type = elem->type,
.text = elem->text
};
return info;
}
void show(const char* text, I2C* screen, u8 x, u8 y, u8 wrap_at) {
const u8 text_size = strlen(text);
screen->clear();
// print without wraping
if (wrap_at == NO_WRAP) {
screen->print(text);
return;
}
// print with wraping
u8 current_y = 0;
u8 current_x = 0;
for (u8 i = x; i < text_size; i++) {
if (current_x >= wrap_at) {
current_x = 0;
current_y++;
}
screen->setCursor(current_x, current_y);
screen->print(text[i]);
current_x++;
}
}
static void show_handle_txt(u8 line, group_t* group, elem_t* elem) {
group->screen->setCursor(0, line);
group->screen->print(elem->text);
}
static void show_handle_opt(u8 line, group_t* group, elem_t* elem) {
group->screen->setCursor(0, line);
if (elem->id == group->select) {
// print the selector indicator first
group->screen->print(SELECT_INDICATOR);
group->screen->setCursor(1, line);
}
group->screen->print(elem->text);
}
void show(group_t* group) {
u8 start = 0;
u8 line = 0;
group->screen->clear();
// scrolling
if (group->select >= group->rows) {
start += (group->select - group->rows) + 1;
}
for (u8 i = start; i < group->count && i < (group->rows + start); i++) {
elem_t* elem = group->elems[i];
switch (elem->type) {
case TXT:
show_handle_txt(line, group, elem);
break;
case OPT:
show_handle_opt(line, group, elem);
break;
}
line++;
}
}
}
// @light_prog.hpp
// @@hereonly
namespace program {
struct light_data {
ldr::ldr_t ldrs[LDR_COUNT];
np::pixel_t pixels[NP_COUNT];
u8 brightness[NP_COUNT];
struct {
struct {
u8 r;
u8 g;
u8 b;
} color;
struct {
bool manual;
bool on;
} mode;
} settings;
};
void light_init(struct light_data* light);
void light_loop(struct light_data* light);
/* use for communicating with UI */
inline void light_set_mode_manu(struct light_data* light, bool val) {
light->settings.mode.manual = val;
}
inline void light_set_on(struct light_data* light, bool val) {
light->settings.mode.on = val;
}
inline bool light_is_manu(struct light_data* light) {
return light->settings.mode.manual;
}
}
// @@hereonlyend
// @ui_prog.hpp
namespace program {
typedef enum : u8 {
MAIN,
SETTING,
MODE_SETTING,
QUICK_ACT,
TOGGLE_QUICKACT
} ui_menu_t;
struct ui_data {
ui_menu_t on_menu;
// whether the ui have to be update
bool update;
I2C* lcd;
// use for communicating with light
struct light_data* light;
struct {
ui::elem_t header_txt;
ui::elem_t setting_opt;
ui::elem_t quickact_opt;
ui::group_t group;
} main;
struct {
ui::elem_t header_txt;
ui::elem_t mode_opt;
ui::elem_t back_opt;
ui::group_t group;
} setting;
struct {
ui::elem_t header_txt;
ui::elem_t auto_opt;
ui::elem_t manu_opt;
ui::elem_t back_opt;
ui::group_t group;
} setting_mode;
struct {
ui::elem_t header_txt;
ui::elem_t toggle_opt;
ui::elem_t back_opt;
ui::group_t group;
} quickact;
struct {
ui::elem_t header_txt;
ui::elem_t on_opt;
ui::elem_t off_opt;
ui::elem_t back_opt;
ui::group_t group;
} quickact_toggle;
button::butt_t up_button;
button::butt_t down_button;
button::butt_t sel_button;
};
void ui_init(struct ui_data* ui, struct light_data* light);
void ui_loop(struct ui_data* ui);
}
// @ui_prog.cpp
#define LCD_ADDR 0x27
#define LCD_COLS 20
#define LCD_ROWS 4
#define UP_BUTT_PIN 12
#define DOWN_BUTT_PIN 14
#define SEL_BUTT_PIN 27
#define BACK_UI_CHAR "<"
#define MENU_GROUP(MENU, COUNT, ...) \
do { \
ui::group( \
&(MENU).group, \
ui->lcd, \
LCD_ROWS, \
LCD_COLS, \
(COUNT), \
__VA_ARGS__ \
); \
} while (0)
#define DEF_MENU_LOOP(MENU) \
do { \
ui::show(&(MENU).group); \
control_loop(ui, &(MENU).group); \
} while (0)
#define NOT_MANU_ERR_MSG "Enter manual mode first"
#define MENU_GROUP(MENU, COUNT, ...) \
do { \
ui::group( \
&(MENU).group, \
ui->lcd, \
LCD_ROWS, \
LCD_COLS, \
(COUNT), \
__VA_ARGS__ \
); \
} while (0)
#define DEF_MENU_LOOP(MENU) \
do { \
if (ui->update) { \
ui::show(&(MENU).group); \
ui->update = false; \
} \
\
control_loop(ui, &(MENU).group); \
} while (0)
#define NOT_MANU_ERR_MSG "Enter manual mode first"
namespace program {
static void init_main_menu(struct ui_data* ui) {
ui::make(&ui->main.header_txt, "Welcome!", ui::TXT);
ui::make(&ui->main.setting_opt, "Settings", ui::OPT);
ui::make(&ui->main.quickact_opt, "QuickAct", ui::OPT);
MENU_GROUP(
ui->main,
3,
&ui->main.header_txt,
&ui->main.setting_opt,
&ui->main.quickact_opt
);
}
static void init_setting_menu(struct ui_data* ui) {
ui::make(&ui->setting.header_txt, "Settings", ui::TXT);
ui::make(&ui->setting.mode_opt, "mode", ui::OPT);
ui::make(&ui->setting.back_opt, BACK_UI_CHAR, ui::OPT);
MENU_GROUP(
ui->setting,
3,
&ui->setting.header_txt,
&ui->setting.mode_opt,
&ui->setting.back_opt
);
}
static void init_setting_mode_menu(struct ui_data* ui) {
ui::make(&ui->setting_mode.header_txt, "Mode", ui::TXT);
ui::make(&ui->setting_mode.auto_opt, "auto", ui::OPT);
ui::make(&ui->setting_mode.manu_opt, "manual", ui::OPT);
ui::make(&ui->setting_mode.back_opt, BACK_UI_CHAR, ui::OPT);
MENU_GROUP(
ui->setting_mode,
4,
&ui->setting_mode.header_txt,
&ui->setting_mode.auto_opt,
&ui->setting_mode.manu_opt,
&ui->setting_mode.back_opt
);
}
static void init_quickact_menu(struct ui_data* ui) {
ui::make(&ui->quickact.header_txt, "Quick Action", ui::TXT);
ui::make(&ui->quickact.toggle_opt, "toggle", ui::OPT);
ui::make(&ui->quickact.back_opt, BACK_UI_CHAR, ui::OPT);
MENU_GROUP(
ui->quickact,
3,
&ui->quickact.header_txt,
&ui->quickact.toggle_opt,
&ui->quickact.back_opt
);
}
static void init_quickact_toggle_menu(struct ui_data* ui) {
ui::make(&ui->quickact_toggle.header_txt, "Toggle", ui::TXT);
ui::make(&ui->quickact_toggle.on_opt, "on", ui::OPT);
ui::make(&ui->quickact_toggle.off_opt, "off", ui::OPT);
ui::make(&ui->quickact_toggle.back_opt, BACK_UI_CHAR, ui::OPT);
MENU_GROUP(
ui->quickact_toggle,
4,
&ui->quickact_toggle.header_txt,
&ui->quickact_toggle.on_opt,
&ui->quickact_toggle.off_opt,
&ui->quickact_toggle.back_opt
);
}
static void init_button(struct ui_data* ui) {
button::make(&ui->up_button, UP_BUTT_PIN);
button::make(&ui->down_button, DOWN_BUTT_PIN);
button::make(&ui->sel_button, SEL_BUTT_PIN);
}
void ui_init(struct ui_data* ui, struct light_data* light) {
ui->on_menu = MAIN;
// init lcd
ui->lcd = new I2C(LCD_ADDR, LCD_COLS, LCD_ROWS);
ui->lcd->init();
ui->light = light;
init_main_menu(ui);
init_setting_menu(ui);
init_setting_mode_menu(ui);
init_quickact_menu(ui);
init_quickact_toggle_menu(ui);
ui::show(&ui->main.group);
init_button(ui);
}
static void control_main_handle_sel(
struct ui_data* ui,
ui::group_t* group
) {
switch (ui::selector_on(group).id) {
case 0: // header_txt
break;
case 1: // setting_opt
ui->on_menu = SETTING;
break;
case 2: // quickact_opt:
ui->on_menu = QUICK_ACT;
break;
}
}
static void control_setting_handle_sel(
struct ui_data* ui,
ui::group_t* group
) {
switch (ui::selector_on(group).id) {
case 0: // header_txt
break;
case 1: // mode_opt
ui->on_menu = MODE_SETTING;
break;
case 2: // back_opt
ui->on_menu = MAIN;
break;
}
}
static void control_setting_mode_handle_sel(
struct ui_data* ui,
ui::group_t* group
) {
switch (ui::selector_on(group).id) {
case 0: // header_txt
break;
case 1: // auto_opt
light_set_mode_manu(ui->light, false);
break;
case 2: // manu_opt
light_set_mode_manu(ui->light, true);
break;
case 3: // back_opt
ui->on_menu = SETTING;
break;
}
}
static void control_quickact_handle_sel(
struct ui_data* ui,
ui::group_t* group
) {
switch (ui::selector_on(group).id) {
case 0: // header_txt
break;
case 1: // toggle
ui->on_menu = TOGGLE_QUICKACT;
break;
case 2: // back_opt
ui->on_menu = MAIN;
break;
}
}
static void control_quickact_toggle_handle_sel(
struct ui_data* ui,
ui::group_t* group
) {
switch (ui::selector_on(group).id) {
case 0: // header_txt
break;
case 1: // on_opt
light_set_on(ui->light, true);
break;
case 2: // off_opt
light_set_on(ui->light, false);
break;
case 3: // back_opt
ui->on_menu = QUICK_ACT;
}
}
static void control_handle_sel(struct ui_data* ui, ui::group_t* group) {
switch (ui->on_menu) {
case MAIN:
control_main_handle_sel(ui, group);
break;
case SETTING:
control_setting_handle_sel(ui, group);
break;
case MODE_SETTING:
control_setting_mode_handle_sel(ui, group);
break;
case QUICK_ACT:
control_quickact_handle_sel(ui, group);
break;
case TOGGLE_QUICKACT:
control_quickact_toggle_handle_sel(ui, group);
break;
}
}
static void control_loop(struct ui_data* ui, ui::group_t* group) {
if (button::state(&ui->up_button)) {
ui::selector_up(group);
ui->update = true;
} else if (button::state(&ui->down_button)) {
ui::selector_down(group);
ui->update = true;
} else if (button::state(&ui->sel_button)) {
control_handle_sel(ui, group);
ui->update = true;
}
}
// quickact toggle menu only work in manual mode
static bool quickact_can_access(struct ui_data* ui) {
// show error and go back to main menu if not in manual mode
if (!light_is_manu(ui->light)) {
ui::show(NOT_MANU_ERR_MSG, ui->lcd, 0, 0, LCD_COLS);
// delay 3 seconds
vTaskDelay(3000 / portTICK_PERIOD_MS);
ui->on_menu = MAIN;
return false;
}
return true;
}
void ui_loop(struct ui_data* ui) {
switch (ui->on_menu) {
case MAIN:
DEF_MENU_LOOP(ui->main);
break;
case SETTING:
DEF_MENU_LOOP(ui->setting);
break;
case MODE_SETTING:
DEF_MENU_LOOP(ui->setting_mode);
break;
case QUICK_ACT:
// quickact can only be access in manual mode
if (quickact_can_access(ui)) {
DEF_MENU_LOOP(ui->quickact);
}
break;
case TOGGLE_QUICKACT:
DEF_MENU_LOOP(ui->quickact_toggle);
break;
}
}
}
// @light_prog.cpp
namespace program {
static void set_all_color(struct light_data* light, u8 r, u8 g, u8 b) {
for (u8 i = 0; i < NP_COUNT; i++) {
np::color(&light->pixels[i], r, g, b);
}
}
static void init_setting(struct light_data* light) {
light->settings.color = {
.r = DEF_NP_R,
.g = DEF_NP_G,
.b = DEF_NP_B
};
light->settings.mode = {
.manual = false,
.on = true
};
}
void light_init(struct light_data* light) {
// init ldr
for (u8 i = 0; i < LDR_COUNT; i++) {
ldr::make(&light->ldrs[i], LDR_PINS[i]);
}
// init neopixel
for (u8 i = 0; i < NP_COUNT; i++) {
np::make(&light->pixels[i], NP_PINS[i], 1);
}
set_all_color(light, DEF_NP_R, DEF_NP_G, DEF_NP_B);
// init setting
init_setting(light);
}
static void set_all_brightness(
struct light_data* light,
u8 brightness
) {
// if brightness is turn down to 0 neopixel wont turn back on
brightness = min(1, brightness);
for (u8 i = 0; i < NP_COUNT; i++) {
np::brightness(&light->pixels[i], brightness);
}
}
static void manual_mode_loop(struct light_data* light) {
if (light->settings.mode.on) {
set_all_brightness(light, 100);
} else {
set_all_brightness(light, 0);
}
}
// caching all ldr reading
static void cache_ldr_reading(struct light_data* light) {
for (u8 i = 0; i < LDR_COUNT; i++) {
ldr::cache_reading(&light->ldrs[i]);
}
}
// (i1 + i2 + i3 ...) / LDR_CACHE_SIZE
static void smooth_reading(struct light_data* light) {
for (u8 i = 0; i < LDR_COUNT; i++) {
u16 sum = 0;
for (u8 j = 0; j < LDR_CACHE_SIZE; j++) {
sum += ldr::get_cache(&light->ldrs[i], j);
}
u16 mean = max((u16)round((f32)sum / (f32)LDR_CACHE_SIZE), LDR_MAX_ADC);
if (mean != 0) {
ldr::set_cache(&light->ldrs[i], (void*)&mean);
}
}
}
// normalize all reading to be between 0 - 100
static void reading_to_brightness(struct light_data* light) {
for (u8 i = 0; i < LDR_COUNT; i++) {
light->brightness[i] = max(
(u8)round((f32)ldr::get_cache(&light->ldrs[i]) / (f32)RTBNF),
100
);
}
}
// calculate distance between 2 neopixel (this is unreadable)
static inline u8 cal_np_distance(u8 np1_i, u8 np2_i) {
// |x1 - x2| + |y1 - y2|
return fabs(
(np1_i % NP_COLS) - (np2_i % NP_COLS)
) + fabs(
(floor((f32)np1_i / (f32)NP_COLS)) - (floor((f32)np2_i / (f32)NP_COLS))
);
}
// src_ldr_i is the index of the ldr that is not a neighbors
// light propagate will only work if have more than one light
static void propagate_brightness(struct light_data* light) {
#if NP_COUNT > 1
for (u8 src = 0; src < LDR_COUNT; src++) {
u8 result = light->brightness[src];
// propagate reading
for (u8 nbr = 0; nbr < LDR_COUNT; nbr++) {
// skip the current src
if (nbr == src) {
continue;
}
// compute weight
f32 w = (f32)BPWM / (f32)cal_np_distance(src, nbr);
// compute result
result += (u8)round(w * (f32)light->brightness[nbr]);
}
// cap the result to 100 and cache it
light->brightness[src] = max(100, result);
}
#endif // #if NP_COUNT != 1
}
// turn pure AO from LDR to brightness for each neopixel ranging between
// 0 - 100 calculation result get store in ldr cache
static inline void cal_brightness(struct light_data* light) {
cache_ldr_reading(light);
//smooth_reading(light);
reading_to_brightness(light);
propagate_brightness(light);
}
// inline because I want this to be very fast
static inline void auto_mode_loop(struct light_data* light) {
cal_brightness(light);
for (u8 i = 0; i < NP_COUNT; i++) {
//Serial.print("brightness: ");
//Serial.println(light->brightness[i]);
//Serial.print("reading: ");
//Serial.println(ldr::get_cache(&light->ldrs[i]));
np::brightness(&light->pixels[i], light->brightness[i]);
}
}
void light_loop(struct light_data* light) {
if (light->settings.mode.manual) {
manual_mode_loop(light);
} else {
auto_mode_loop(light);
}
}
}
// @program.hpp
namespace program {
struct data {
struct ui_data ui;
struct light_data light;
};
void setup(struct data* data);
void loop(struct data* data);
}
// @program.cpp
namespace program {
static void light_thread(void* arg) {
/*
struct light_data* light = (struct light_data*)arg;
light_init(light);
while (true) {
light_loop(light);
vTaskDelay(LOOP_DELAY / portTICK_PERIOD_MS);
}
*/
while (1) {
Serial.println("light");
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
static void ui_thread(void* arg) {
/*
struct data* data = (struct data*)arg;
ui_init(&data->ui, &data->light);
while (true) {
ui_loop(&data->ui);
vTaskDelay(LOOP_DELAY / portTICK_PERIOD_MS);
}
*/
while (1) {
Serial.println("UI");
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
void setup(struct data* data) {
#ifdef LOG
Serial.begin(9600);
#endif // #ifdef LOG
light_init(&data->light);
ui_init(&data->ui, &data->light);
// delay to make sure everything is ready
delay(SETUP_DELAY);
/*
xTaskCreate(
ui_thread,
"ui_thread",
UI_THREAD_STACK_SIZE,
NULL,
1,
NULL
);
xTaskCreate(
light_thread,
"light_thread",
UI_THREAD_STACK_SIZE,
(void*)data,
1,
NULL
);
vTaskStartScheduler();
Serial.println(esp_get_free_heap_size());
*/
}
void loop(struct data* data) {
light_loop(&data->light);
ui_loop(&data->ui);
delay(LOOP_DELAY);
}
}
// @main.cpp
struct program::data program_data;
void setup() {
Serial.begin(9600);
program::setup(&program_data);
}
void loop() {
program::loop(&program_data);
}