@@ -72,6 +72,7 @@ static char bleDeviceName[BLE_DEVICE_NAME_LEN] = "Buddy";
7272// --- Buddy config frames ---
7373#define BUDDY_BRIGHTNESS_FRAME 0xFE
7474#define BUDDY_ORIENTATION_FRAME 0xFD
75+ #define BUDDY_TASK_FRAME 0xF2 // Mac-authoritative task timer: [0xF2, taskId, elapsedHi, elapsedLo]
7576#define BUDDY_BRIGHTNESS_MIN_PERCENT 10
7677#define BUDDY_BRIGHTNESS_MAX_PERCENT 100
7778#define BUDDY_BRIGHTNESS_DEFAULT_PERCENT 70
@@ -138,7 +139,7 @@ volatile unsigned long lastBleData = 0;
138139volatile unsigned long lastWakeActivity = 0 ;
139140volatile bool taskTimerActive = false ;
140141volatile unsigned long taskTimerStart = 0 ;
141- volatile uint8_t taskTimerSourceId = 0xFF ;
142+ volatile uint8_t taskTimerId = 0 ; // Mac-provided rolling task id (0 = no active task)
142143volatile uint8_t buddyBrightnessPercent = BUDDY_BRIGHTNESS_DEFAULT_PERCENT ;
143144volatile uint8_t buddyScreenOrientation = BUDDY_SCREEN_UP ;
144145volatile bool buddyOrientationDirty = false ;
@@ -337,10 +338,6 @@ bool statusKeepsScreenAwake(uint8_t status) {
337338 return status == 1 || status == 2 || status == 3 || status == 4 ;
338339}
339340
340- bool statusHasTaskTimer (uint8_t status) {
341- return status == 1 || status == 2 || status == 3 || status == 4 ;
342- }
343-
344341uint8_t clampBuddyBrightness (uint8_t percent) {
345342 if (percent < BUDDY_BRIGHTNESS_MIN_PERCENT ) return BUDDY_BRIGHTNESS_MIN_PERCENT ;
346343 if (percent > BUDDY_BRIGHTNESS_MAX_PERCENT ) return BUDDY_BRIGHTNESS_MAX_PERCENT ;
@@ -741,6 +738,32 @@ class CharCallbacks : public BLECharacteristicCallbacks {
741738 return ;
742739 }
743740
741+ // Task timer frame (0xF2): Mac is the timing authority. The firmware no longer
742+ // estimates the task start itself — it only adopts the Mac-provided elapsed time
743+ // as a baseline and ticks locally via millis() between updates.
744+ if (len >= 4 && data[0 ] == BUDDY_TASK_FRAME ) {
745+ uint8_t tid = data[1 ];
746+ uint16_t elapsed = ((uint16_t )data[2 ] << 8 ) | data[3 ];
747+ unsigned long now_t = millis ();
748+ portENTER_CRITICAL (&bleMux);
749+ if (tid == 0 ) {
750+ taskTimerActive = false ;
751+ taskTimerStart = 0 ;
752+ taskTimerId = 0 ;
753+ } else {
754+ if (tid != taskTimerId) { // new task → re-baseline the local clock
755+ taskTimerId = tid;
756+ unsigned long offset = (unsigned long )elapsed * 1000UL ;
757+ taskTimerStart = (now_t >= offset) ? (now_t - offset) : 0 ;
758+ }
759+ taskTimerActive = true ; // same task → keep baseline, tick smoothly
760+ }
761+ lastBleData = now_t ;
762+ portEXIT_CRITICAL (&bleMux);
763+ Serial.printf (" [BLE] Task: id=%d elapsed=%us\n " , tid, elapsed);
764+ return ;
765+ }
766+
744767#ifdef BUDDY_OTA_ENABLED
745768 // OTA SSID frame (0xF4)
746769 if (len >= 2 && data[0 ] == 0xF4 ) {
@@ -833,17 +856,7 @@ class CharCallbacks : public BLECharacteristicCallbacks {
833856 portENTER_CRITICAL (&bleMux);
834857 if (bleSourceId != nextSourceId) headerDirty = true ;
835858 if (bleStatusId != nextStatusId) infoDirty = true ;
836- if (statusHasTaskTimer (nextStatusId)) {
837- if (!taskTimerActive || taskTimerSourceId != nextSourceId) {
838- taskTimerActive = true ;
839- taskTimerStart = now_write;
840- taskTimerSourceId = nextSourceId;
841- }
842- } else {
843- taskTimerActive = false ;
844- taskTimerStart = 0 ;
845- taskTimerSourceId = 0xFF ;
846- }
859+ // Task timer is driven entirely by the Mac via the 0xF2 task frame (see below).
847860 bleSourceId = nextSourceId;
848861 bleStatusId = nextStatusId;
849862 if (statusKeepsScreenAwake (bleStatusId)) {
@@ -1576,7 +1589,7 @@ void loop() {
15761589 pendingAnim = ANIM_NONE ;
15771590 taskTimerActive = false ;
15781591 taskTimerStart = 0 ;
1579- taskTimerSourceId = 0xFF ;
1592+ taskTimerId = 0 ;
15801593 infoDirty = true ;
15811594 clearedStaleAgentState = true ;
15821595 }
0 commit comments