@@ -920,15 +920,28 @@ TouchGestureType touchGesture; /// is JSBT_SWIPE is set, what happened?
920
920
921
921
/// How often should we fire 'health' events?
922
922
#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
923
933
/// Struct with currently tracked health info
924
934
typedef struct {
925
935
uint8_t index ; ///< time_in_ms / HEALTH_INTERVAL - we fire a new Health event when this changes
926
936
uint32_t movement ; ///< total accelerometer difference. Used for activity tracking.
927
937
uint16_t movementSamples ; ///< Number of samples added to movement
928
938
uint16_t stepCount ; ///< steps during current period
929
939
uint16_t bpm10 ; ///< beats per minute (x10)
940
+ uint16_t bpm10min , bpm10max ; ///< beats per minute min and max (x10)
930
941
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 ;
932
945
/// Currently tracked health info during this period
933
946
HealthState healthCurrent ;
934
947
/// Health info during the last period, used when firing a health event
@@ -1541,15 +1554,27 @@ void peripheralPollHandler() {
1541
1554
#ifdef HEARTRATE_VC31_BINARY
1542
1555
// Activity detection
1543
1556
hrmSportActivity = ((hrmSportActivity * 63 )+ MIN (accDiff ,4096 ))>>6 ; // running average
1544
- if (hrmSportTimer < TIMER_MAX ) {
1557
+ if (hrmSportTimer < TIMER_MAX )
1545
1558
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)
1548
1560
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
+ }
1549
1569
if (hrmSportMode >=0 ) // if HRM sport mode is forced, just use that
1550
1570
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
+ }
1553
1578
#endif
1554
1579
// Power saving
1555
1580
if (bangleFlags & JSBF_POWER_SAVE ) {
@@ -1612,6 +1637,8 @@ void peripheralPollHandler() {
1612
1637
stepCounter += newSteps ;
1613
1638
healthCurrent .stepCount += newSteps ;
1614
1639
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 ;
1615
1642
bangleTasks |= JSBT_STEP_EVENT ;
1616
1643
jshHadEvent ();
1617
1644
}
@@ -1681,8 +1708,15 @@ void peripheralPollHandler() {
1681
1708
// Did we enter a new 10 minute interval?
1682
1709
JsVarFloat msecs = jshGetMillisecondsFromTime (time );
1683
1710
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
1686
1720
healthLast = healthCurrent ;
1687
1721
healthStateClear (& healthCurrent );
1688
1722
healthCurrent .index = healthIndex ;
@@ -1727,8 +1761,20 @@ static void hrmHandler(int ppgValue) {
1727
1761
healthDaily .bpmConfidence = hrmInfo .confidence ;
1728
1762
healthDaily .bpm10 = hrmInfo .bpm10 ;
1729
1763
}
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
+ }
1730
1770
jshHadEvent ();
1731
1771
}
1772
+ #ifdef HEARTRATE_VC31_BINARY
1773
+ if (!hrmInfo .isWorn ) {
1774
+ if (healthCurrent .activity == HSA_UNKNOWN )
1775
+ healthCurrent .activity = HSA_NOT_WORN ;
1776
+ }
1777
+ #endif
1732
1778
if (bangleFlags & JSBF_HRM_INSTANT_LISTENER ) {
1733
1779
// what if we already have HRM data that was queued - eg if working with FIFO?
1734
1780
/*if (bangleTasks & JSBT_HRM_INSTANT_DATA)
@@ -3529,6 +3575,8 @@ JsVar *jswrap_banglejs_getAccel() {
3529
3575
* `steps` is the number of steps during this period
3530
3576
* `bpm` the best BPM reading from HRM sensor during this period
3531
3577
* `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"
3532
3580
3533
3581
*/
3534
3582
static JsVar * _jswrap_banglejs_getHealthStatusObject (HealthState * health ) {
@@ -3540,7 +3588,12 @@ static JsVar *_jswrap_banglejs_getHealthStatusObject(HealthState *health) {
3540
3588
#ifdef HEARTRATE
3541
3589
jsvObjectSetChildAndUnLock (o ,"bpm" ,jsvNewFromFloat (health -> bpm10 / 10.0 ));
3542
3590
jsvObjectSetChildAndUnLock (o ,"bpmConfidence" ,jsvNewFromInteger (health -> bpmConfidence ));
3591
+ jsvObjectSetChildAndUnLock (o ,"bpmMin" ,jsvNewFromFloat (health -> bpm10min / 10.0 ));
3592
+ jsvObjectSetChildAndUnLock (o ,"bpmMax" ,jsvNewFromFloat (health -> bpm10max / 10.0 ));
3543
3593
#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 ]));
3544
3597
}
3545
3598
return o ;
3546
3599
}
0 commit comments