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;
6667static StaticTask_t BackgroundHwTaskBuffer ;
6768static uint8_t BackgroundHwStackBuffer [HW_TASK_STACK_SIZE ];
6869
70+ // Gyro measurements
6971bool 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
7189static void ApplicationInitSW (void );
7290static void ApplicationTask (SApplicationHandles * pAppHandles );
@@ -529,6 +547,11 @@ ApplicationTask(SApplicationHandles* pAppHandles)
529547
530548static 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
790835void
791836zaf_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 *************************************************************************************
0 commit comments