@@ -920,15 +920,28 @@ TouchGestureType touchGesture; /// is JSBT_SWIPE is set, what happened?
920920
921921/// How often should we fire 'health' events?
922922#define HEALTH_INTERVAL 600000 // 10 minutes (600 seconds)
923+ /// What is the current activity. In order of priority
924+ typedef enum {
925+ HSA_UNKNOWN ,
926+ HSA_NOT_WORN ,
927+ HSA_WALKING ,
928+ HSA_EXERCISE ,
929+ } PACKED_FLAGS HealthStateActivity ;
930+ /// Strings for HealthStateActivity, matching https://codeberg.org/Freeyourgadget/Gadgetbridge/src/branch/master/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityKind.java
931+ #define HSA_STRINGS "UNKNOWN","NOT_WORN","WALKING","EXERCISE"
932+ #define HSA_STRINGS_LEN 4
923933/// Struct with currently tracked health info
924934typedef struct {
925935 uint8_t index ; ///< time_in_ms / HEALTH_INTERVAL - we fire a new Health event when this changes
926936 uint32_t movement ; ///< total accelerometer difference. Used for activity tracking.
927937 uint16_t movementSamples ; ///< Number of samples added to movement
928938 uint16_t stepCount ; ///< steps during current period
929939 uint16_t bpm10 ; ///< beats per minute (x10)
940+ uint16_t bpm10min , bpm10max ; ///< beats per minute min and max (x10)
930941 uint8_t bpmConfidence ; ///< confidence of current BPM figure
931- } HealthState ;
942+ uint16_t sportActivityTime ; ///< Time in msec spent doing high acceleration activity
943+ HealthStateActivity activity ; ///< what's the current activity
944+ } PACKED_FLAGS HealthState ;
932945/// Currently tracked health info during this period
933946HealthState healthCurrent ;
934947/// Health info during the last period, used when firing a health event
@@ -1541,15 +1554,27 @@ void peripheralPollHandler() {
15411554#ifdef HEARTRATE_VC31_BINARY
15421555 // Activity detection
15431556 hrmSportActivity = ((hrmSportActivity * 63 )+ MIN (accDiff ,4096 ))>>6 ; // running average
1544- if (hrmSportTimer < TIMER_MAX ) {
1557+ if (hrmSportTimer < TIMER_MAX )
15451558 hrmSportTimer += pollInterval ;
1546- }
1547- if (hrmSportActivity > HRM_SPORT_ACTIVITY_THRESHOLD ) // if enough movement, zero timer (enter sport mode)
1559+ if (hrmSportActivity > HRM_SPORT_ACTIVITY_THRESHOLD ) { // if enough movement, zero timer (enter sport mode)
15481560 hrmSportTimer = 0 ;
1561+ if (healthCurrent .sportActivityTime < TIMER_MAX )
1562+ healthCurrent .sportActivityTime += pollInterval ;
1563+ // If we've been very active for over 30 seconds in our 10 minute slot, assume we're doing sport
1564+ if (healthCurrent .sportActivityTime > 30000 ) { // remember it can't be more than TIMER_MAX(60k)
1565+ if (healthCurrent .activity < HSA_EXERCISE )
1566+ healthCurrent .activity = HSA_EXERCISE ;
1567+ }
1568+ }
15491569 if (hrmSportMode >=0 ) // if HRM sport mode is forced, just use that
15501570 hrmInfo .sportMode = hrmSportMode ;
1551- else // else set to running mode if we've had enough activity recently
1552- hrmInfo .sportMode = (hrmSportTimer < HRM_SPORT_ACTIVITY_TIMEOUT ) ? SPORT_TYPE_RUNNING : SPORT_TYPE_NORMAL ;
1571+ else { // else set to running mode if we've had enough activity recently
1572+ if (hrmSportTimer < HRM_SPORT_ACTIVITY_TIMEOUT ) {
1573+ hrmInfo .sportMode = SPORT_TYPE_RUNNING ;
1574+ } else {
1575+ hrmInfo .sportMode = SPORT_TYPE_NORMAL ;
1576+ }
1577+ }
15531578#endif
15541579 // Power saving
15551580 if (bangleFlags & JSBF_POWER_SAVE ) {
@@ -1612,6 +1637,8 @@ void peripheralPollHandler() {
16121637 stepCounter += newSteps ;
16131638 healthCurrent .stepCount += newSteps ;
16141639 healthDaily .stepCount += newSteps ;
1640+ if (healthCurrent .stepCount > 200 && healthCurrent .activity < HSA_WALKING ) // if 200 steps in this 10 minute chunk, assume walking
1641+ healthCurrent .activity = HSA_WALKING ;
16151642 bangleTasks |= JSBT_STEP_EVENT ;
16161643 jshHadEvent ();
16171644 }
@@ -1681,8 +1708,15 @@ void peripheralPollHandler() {
16811708 // Did we enter a new 10 minute interval?
16821709 JsVarFloat msecs = jshGetMillisecondsFromTime (time );
16831710 uint8_t healthIndex = (uint8_t )(msecs /HEALTH_INTERVAL );
1684- if (healthIndex != healthCurrent .index ) {
1685- // we did - fire 'Bangle.health' event
1711+ if (healthIndex != healthCurrent .index ) { // we did
1712+ // quick check - if we don't know what's happening and the Bangle isn't moving, assume it's not worn
1713+ if (healthCurrent .activity == HSA_UNKNOWN ) {
1714+ uint32_t movement = healthCurrent .movement / healthCurrent .movementSamples ;
1715+ if (movement < 120 ) healthCurrent .activity = HSA_NOT_WORN ;
1716+ }
1717+ if (healthDaily .activity < healthCurrent .activity )
1718+ healthDaily .activity = healthCurrent .activity ;
1719+ // now fire 'Bangle.health' event and reset the current health status
16861720 healthLast = healthCurrent ;
16871721 healthStateClear (& healthCurrent );
16881722 healthCurrent .index = healthIndex ;
@@ -1727,8 +1761,20 @@ static void hrmHandler(int ppgValue) {
17271761 healthDaily .bpmConfidence = hrmInfo .confidence ;
17281762 healthDaily .bpm10 = hrmInfo .bpm10 ;
17291763 }
1764+ if (hrmInfo .confidence >= 90 ) {
1765+ if (hrmInfo .bpm10 < healthCurrent .bpm10min ) healthCurrent .bpm10min = hrmInfo .bpm10 ;
1766+ if (hrmInfo .bpm10 < healthDaily .bpm10min ) healthDaily .bpm10min = hrmInfo .bpm10 ;
1767+ if (hrmInfo .bpm10 > healthCurrent .bpm10max ) healthCurrent .bpm10max = hrmInfo .bpm10 ;
1768+ if (hrmInfo .bpm10 > healthDaily .bpm10max ) healthDaily .bpm10max = hrmInfo .bpm10 ;
1769+ }
17301770 jshHadEvent ();
17311771 }
1772+ #ifdef HEARTRATE_VC31_BINARY
1773+ if (!hrmInfo .isWorn ) {
1774+ if (healthCurrent .activity == HSA_UNKNOWN )
1775+ healthCurrent .activity = HSA_NOT_WORN ;
1776+ }
1777+ #endif
17321778 if (bangleFlags & JSBF_HRM_INSTANT_LISTENER ) {
17331779 // what if we already have HRM data that was queued - eg if working with FIFO?
17341780 /*if (bangleTasks & JSBT_HRM_INSTANT_DATA)
@@ -3529,6 +3575,8 @@ JsVar *jswrap_banglejs_getAccel() {
35293575* `steps` is the number of steps during this period
35303576* `bpm` the best BPM reading from HRM sensor during this period
35313577* `bpmConfidence` best BPM confidence (0-100%) during this period
3578+ * `bpmMin`/`bpmMax` (2v26+) the minimum/maximum BPM reading from HRM sensor during this period (where confidence is over 90)
3579+ * `activity` (2v26+) the currently assumed activity, one of "UNKNOWN","NOT_WORN","WALKING","EXERCISE"
35323580
35333581*/
35343582static JsVar * _jswrap_banglejs_getHealthStatusObject (HealthState * health ) {
@@ -3540,7 +3588,12 @@ static JsVar *_jswrap_banglejs_getHealthStatusObject(HealthState *health) {
35403588#ifdef HEARTRATE
35413589 jsvObjectSetChildAndUnLock (o ,"bpm" ,jsvNewFromFloat (health -> bpm10 / 10.0 ));
35423590 jsvObjectSetChildAndUnLock (o ,"bpmConfidence" ,jsvNewFromInteger (health -> bpmConfidence ));
3591+ jsvObjectSetChildAndUnLock (o ,"bpmMin" ,jsvNewFromFloat (health -> bpm10min / 10.0 ));
3592+ jsvObjectSetChildAndUnLock (o ,"bpmMax" ,jsvNewFromFloat (health -> bpm10max / 10.0 ));
35433593#endif
3594+ const char * ACT_STRINGS [HSA_STRINGS_LEN ] = { HSA_STRINGS };
3595+ if (health -> activity < HSA_STRINGS_LEN )
3596+ jsvObjectSetChildAndUnLock (o ,"activity" ,jsvNewFromString (ACT_STRINGS [health -> activity ]));
35443597 }
35453598 return o ;
35463599}
0 commit comments