Skip to content

Commit 51c7ea0

Browse files
authored
feat: lighting effects, configuration, unsolicited gyro readings (#2)
1 parent a6f3a47 commit 51c7ea0

8 files changed

Lines changed: 562 additions & 47 deletions

File tree

.github/workflows/build.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,12 @@ jobs:
8282
with:
8383
name: zwa2_controller.hex
8484
path: artifact/zwa2_controller.hex
85+
86+
- name: Copy MAP file
87+
run: cp build/release/nc_controller_ncp.map artifact/zwa2_controller.map
88+
89+
- name: Upload MAP
90+
uses: actions/upload-artifact@v4
91+
with:
92+
name: zwa2_controller.map
93+
path: artifact/zwa2_controller.map

app.c

Lines changed: 234 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
#include "ZAF_PrintAppInfo.h"
4343
#endif
4444
#include "ZAF_AppName.h"
45+
#include <ZAF_nvm_app.h>
4546

4647
#include <assert.h>
4748

@@ -66,7 +67,24 @@ static TaskHandle_t m_xTaskHandleBackgroundHw = NULL;
6667
static StaticTask_t BackgroundHwTaskBuffer;
6768
static uint8_t BackgroundHwStackBuffer[HW_TASK_STACK_SIZE];
6869

70+
// Gyro measurements
6971
bool bRequestGyroMeasurement = false;
72+
gyro_reading_t last_gyro_reading = {0};
73+
int stable_gyro_readings = 0;
74+
75+
bool bAwaitingConnection = false;
76+
// Whether incorrect tilt was detected and should be indicated
77+
bool bTiltDetected = false;
78+
bool bEnableTiltDetection = true;
79+
80+
// Default LED effect when no other effect is set
81+
LedEffect_t ledEffectDefault = {0};
82+
// LED state set by the user
83+
LedEffect_t ledEffectUser = {0};
84+
// Separate high priority LED state for system indication
85+
LedEffect_t ledEffectSystem = {0};
86+
// LED state to indicate incorrect tilt
87+
LedEffect_t ledEffectTilt = {0};
7088

7189
static void ApplicationInitSW(void);
7290
static void ApplicationTask(SApplicationHandles *pAppHandles);
@@ -529,6 +547,11 @@ ApplicationTask(SApplicationHandles* pAppHandles)
529547

530548
static void SerialAPICommandHandler(void)
531549
{
550+
if (bAwaitingConnection) {
551+
bAwaitingConnection = false;
552+
zaf_event_distributor_enqueue_proprietary_app_event(EVENT_APP_CONNECTED, NULL);
553+
}
554+
532555
const bool handler_invoked = invoke_cmd_handler(serial_frame);
533556
if (!handler_invoked)
534557
{
@@ -785,49 +808,211 @@ ZCB_WakeupTimeout(__attribute__((unused)) SSwTimer *pTimer)
785808
DPRINT("ZCB_WakeupTimeout\n");
786809
}
787810

811+
/* Returns the current effect that should be used for the LED */
812+
LedEffect_t*
813+
get_current_led_effect(void) {
814+
if (ledEffectSystem.type != LED_EFFECT_NOT_SET) {
815+
return &ledEffectSystem;
816+
} else if (bEnableTiltDetection && bTiltDetected) {
817+
return &ledEffectTilt;
818+
} else if (ledEffectUser.type != LED_EFFECT_NOT_SET) {
819+
return &ledEffectUser;
820+
} else {
821+
return &ledEffectDefault;
822+
}
823+
}
788824

825+
void
826+
trigger_led_effect_refresh(void) {
827+
if (ledEffectUser.type == LED_EFFECT_SOLID) {
828+
ledEffectUser.effect.solid.modified = true;
829+
}
830+
if (ledEffectDefault.type == LED_EFFECT_SOLID) {
831+
ledEffectDefault.effect.solid.modified = true;
832+
}
833+
}
789834

790835
void
791836
zaf_event_distributor_app_proprietary(event_nc_t *event)
792837
{
793838
// Handles NC-specific proprietary events
794839
EVENT_APP event_nc = (EVENT_APP) event->event;
795840
switch (event_nc) {
796-
case EVENT_APP_USERTASK_READY:
797-
// Indicate that the firmware is ready by enabling the LED at low power
798-
rgb_t color = {4, 0, 0};
799-
set_color_buffer(color);
841+
case EVENT_APP_USERTASK_READY: {
842+
// Fade slowly to white
843+
LedEffectFade_t fade = {
844+
.color = white,
845+
.brightness = 0xff,
846+
.increasing = false,
847+
.ticksPerStep = 2,
848+
.tickCounter = 0
849+
};
850+
ledEffectDefault = (LedEffect_t) {
851+
.type = LED_EFFECT_FADE,
852+
.effect.fade = fade
853+
};
854+
bAwaitingConnection = true;
800855
break;
856+
}
801857

802-
case EVENT_APP_USERTASK_GYRO_MEASUREMENT:
803-
if (!bRequestGyroMeasurement) {
804-
return;
858+
case EVENT_APP_CONNECTED: {
859+
if (ledEffectDefault.type == LED_EFFECT_FADE) {
860+
// Stop the animation to indicate that we're connected
861+
ledEffectDefault.effect.fade.stopAtMax = true;
862+
} else {
863+
// If we were not in fade mode, set the color to white
864+
LedEffectSolid_t solid = {
865+
.color = white,
866+
.modified = true
867+
};
868+
ledEffectDefault = (LedEffect_t) {
869+
.type = LED_EFFECT_SOLID,
870+
.effect.solid = solid
871+
};
805872
}
806-
bRequestGyroMeasurement = false;
873+
break;
874+
}
807875

876+
case EVENT_APP_USERTASK_GYRO_MEASUREMENT: {
808877
// A gyro measurement was requested
809878
gyro_reading_t gyro_reading = event->payload->gyro_reading;
810879

811-
uint8_t cmd[8];
812-
uint8_t i=0;
813-
cmd[i++] = NABU_CASA_GYRO_MEASURE;
814-
cmd[i++] = gyro_reading.x >> 8;
815-
cmd[i++] = gyro_reading.x & 0xFF;
816-
cmd[i++] = gyro_reading.y >> 8;
817-
cmd[i++] = gyro_reading.y & 0xFF;
818-
cmd[i++] = gyro_reading.z >> 8;
819-
cmd[i++] = gyro_reading.z & 0xFF;
820-
RequestUnsolicited(
821-
FUNC_ID_NABU_CASA,
822-
cmd,
823-
i
824-
);
880+
// Debounce the readings
881+
if (last_gyro_reading.x == 0 && last_gyro_reading.y == 0 && last_gyro_reading.z == 0) {
882+
last_gyro_reading = gyro_reading;
883+
return;
884+
} else if (abs(gyro_reading.x - last_gyro_reading.x) < 25 &&
885+
abs(gyro_reading.y - last_gyro_reading.y) < 25 &&
886+
abs(gyro_reading.z - last_gyro_reading.z) < 25) {
887+
stable_gyro_readings++;
888+
} else {
889+
stable_gyro_readings = 0;
890+
}
891+
last_gyro_reading = gyro_reading;
892+
893+
if (stable_gyro_readings < 3) {
894+
// Not enough stable readings, ignore this one
895+
return;
896+
}
897+
898+
// Indicate bad orientation (more than 20° from vertical) in calibration mode
899+
if (gyro_reading.z < -960 || gyro_reading.z > 960) {
900+
if (bTiltDetected) {
901+
// Mark the user LED effect as modified, so it gets used again
902+
trigger_led_effect_refresh();
903+
}
904+
bTiltDetected = false;
905+
} else if (!bTiltDetected) {
906+
LedEffectFade_t fade = {
907+
.color = red,
908+
.brightness = 0xff,
909+
.increasing = false,
910+
.ticksPerStep = 1,
911+
.tickCounter = 0
912+
};
913+
ledEffectTilt = (LedEffect_t) {
914+
.type = LED_EFFECT_FADE,
915+
.effect.fade = fade
916+
};
917+
bTiltDetected = true;
918+
}
919+
920+
// Send a gyro measurement whenever the user requests it
921+
// or the orientation has just stabilized
922+
if (bRequestGyroMeasurement || stable_gyro_readings == 7) {
923+
bRequestGyroMeasurement = false;
924+
925+
uint8_t cmd[8];
926+
uint8_t i=0;
927+
cmd[i++] = NABU_CASA_GYRO_MEASURE;
928+
cmd[i++] = gyro_reading.x >> 8;
929+
cmd[i++] = gyro_reading.x & 0xFF;
930+
cmd[i++] = gyro_reading.y >> 8;
931+
cmd[i++] = gyro_reading.y & 0xFF;
932+
cmd[i++] = gyro_reading.z >> 8;
933+
cmd[i++] = gyro_reading.z & 0xFF;
934+
RequestUnsolicited(
935+
FUNC_ID_NABU_CASA,
936+
cmd,
937+
i
938+
);
939+
}
940+
break;
941+
}
942+
943+
case EVENT_APP_USERTASK_TICK_LED: {
944+
LedEffect_t* ledEffect = get_current_led_effect();
945+
946+
switch (ledEffect->type) {
947+
case LED_EFFECT_SOLID: {
948+
// For solid LED effect, set the color once
949+
if (ledEffect->effect.solid.modified) {
950+
ledEffect->effect.solid.modified = false;
951+
set_color_buffer(ledEffect->effect.solid.color);
952+
}
953+
break;
954+
}
955+
956+
case LED_EFFECT_FADE: {
957+
// For fading, change the brightness every N ticks,
958+
// and update the color
959+
LedEffectFade_t fade = ledEffect->effect.fade;
960+
961+
if (fade.brightness > 0xe0 && fade.stopAtMax) {
962+
// Switch to solid mode
963+
LedEffectSolid_t solid = {
964+
.color = fade.color,
965+
.modified = true
966+
};
967+
*ledEffect = (LedEffect_t) {
968+
.type = LED_EFFECT_SOLID,
969+
.effect.solid = solid
970+
};
971+
break;
972+
}
973+
974+
if (fade.tickCounter == 0) {
975+
if (fade.increasing) {
976+
fade.brightness++;
977+
if (fade.brightness == 0xff) {
978+
fade.increasing = false;
979+
}
980+
} else {
981+
fade.brightness--;
982+
if (fade.brightness == 0) {
983+
fade.increasing = true;
984+
}
985+
}
986+
987+
uint16_t r = ((uint16_t) fade.color.R) * ((uint16_t) fade.brightness) / 0xff;
988+
uint16_t g = ((uint16_t) fade.color.G) * ((uint16_t) fade.brightness) / 0xff;
989+
uint16_t b = ((uint16_t) fade.color.B) * ((uint16_t) fade.brightness) / 0xff;
990+
991+
rgb_t color = {
992+
(uint8_t) g,
993+
(uint8_t) r,
994+
(uint8_t) b
995+
};
996+
997+
set_color_buffer(color);
998+
}
999+
1000+
fade.tickCounter = (fade.tickCounter + 1) % fade.ticksPerStep;
1001+
ledEffect->effect.fade = fade;
1002+
break;
1003+
}
1004+
1005+
default:
1006+
// Nothing to do
1007+
break;
1008+
} // switch (ledEffect->type)
8251009
break;
1010+
}
8261011

8271012
default:
8281013
// Nothing to do
8291014
break;
830-
}
1015+
} // switch (event_nc)
8311016
}
8321017

8331018
// Called when the button next to the USB port is pressed or released
@@ -957,6 +1142,32 @@ ApplicationInit(
9571142
initWs2812();
9581143
initqma6100p();
9591144

1145+
// Try to restore the user-defined color from NVM
1146+
NabuCasaLedStorage_t ledStorage = {0};
1147+
if (
1148+
ZPAL_STATUS_OK == ZAF_nvm_app_read(FILE_ID_NABUCASA_LED, &ledStorage, sizeof(ledStorage))
1149+
&& ledStorage.valid
1150+
) {
1151+
rgb_t color = {
1152+
.G = ledStorage.g,
1153+
.R = ledStorage.r,
1154+
.B = ledStorage.b
1155+
};
1156+
ledEffectUser = (LedEffect_t) {
1157+
.type = LED_EFFECT_SOLID,
1158+
.effect.solid = {
1159+
.color = color,
1160+
.modified = true
1161+
}
1162+
};
1163+
set_color_buffer(color);
1164+
} else {
1165+
set_color_buffer(white);
1166+
}
1167+
1168+
// Try to restore settings from NVM
1169+
bEnableTiltDetection = nc_config_get(NC_CFG_ENABLE_TILT_INDICATOR);
1170+
9601171
/*************************************************************************************
9611172
* CREATE USER TASKS - ZW_ApplicationRegisterTask() and ZW_UserTask_CreateTask()
9621173
*************************************************************************************

app.h

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
#include <ZW_controller_api.h>
3636
#endif
3737

38+
#include "drivers/ws2812.h"
39+
3840
/* Serial API version */
3941
#define SERIAL_API_VER 10
4042

@@ -270,4 +272,34 @@ extern void ApplicationNodeUpdate(uint8_t bStatus, uint16_t nodeID, uint8_t *pCm
270272

271273
extern uint8_t compl_workbuf[BUF_SIZE_TX];
272274

275+
void trigger_led_effect_refresh(void);
276+
277+
typedef struct LedEffectSolid {
278+
rgb_t color;
279+
bool modified;
280+
} LedEffectSolid_t;
281+
282+
typedef struct LedEffectFade {
283+
rgb_t color;
284+
uint8_t brightness;
285+
uint8_t ticksPerStep;
286+
uint8_t tickCounter;
287+
bool increasing;
288+
bool stopAtMax;
289+
} LedEffectFade_t;
290+
291+
typedef enum {
292+
LED_EFFECT_NOT_SET,
293+
LED_EFFECT_SOLID,
294+
LED_EFFECT_FADE
295+
} LedEffectType_t;
296+
297+
typedef struct {
298+
LedEffectType_t type;
299+
union {
300+
LedEffectSolid_t solid;
301+
LedEffectFade_t fade;
302+
} effect;
303+
} LedEffect_t;
304+
273305
#endif /* _SERIALAPPL_H_ */

app_events.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ typedef enum EVENT_APP_NC
2121
*/
2222
EVENT_APP_USERTASK_READY,
2323
EVENT_APP_USERTASK_GYRO_MEASUREMENT,
24+
EVENT_APP_USERTASK_TICK_LED,
25+
EVENT_APP_CONNECTED,
2426
}
2527
EVENT_APP;
2628

0 commit comments

Comments
 (0)