diff options
authorDaniel Silverstone <>2014-06-22 18:52:11 +0100
committerDaniel Silverstone <>2014-06-22 18:52:11 +0100
commitc6044dfefab8fb1d0ff641ef097dc0028748302e (patch)
parentec1cde0b639da0a1f8cc02149af11c94bcbc3fdd (diff)
A bunch of notes
4 files changed, 210 insertions, 0 deletions
diff --git a/notes/accelerometer.mdwn b/notes/accelerometer.mdwn
new file mode 100644
index 0000000..4afc564
--- /dev/null
+++ b/notes/accelerometer.mdwn
@@ -0,0 +1,45 @@
+Using the accelerometer in the pebble
+After reasonably extensive testing I determined that the accelerometer in the
+pebble is sufficiently noisy that we may as well drop the bottom 2 to 4 bits
+of the signal for our actigraphic work. This takes us from +/- 12 bits to
+an 8 bit value (since we do not care about the direction) for each channel.
+The accelerometer is a 3 axis beastie so we have to then compute the magnitude
+of the force vector which is essentially `sqrt(x*x+y*y+z*z)` (hence the sign of
+the data is irrelevant since we're squaring everything). Conveniently this
+takes us to a useful data range of 0-255 for the magnitude of the force vector
+(it's actually 0-221 I think).
+The pebble is a low power microcontroller so we want to use an approximating
+integer square root function in the above calculation. There's a good 4 cycle
+per bit algorithm on <> written
+by Wilco Dijkstra at ARM. While the algorithm is clearly written for arm32 we
+can reuse it in thumb2 without too much pain. That it is in C is for the best.
+The Pebble accelerometer runs (at its slowest) at 10Hz assuming we want to
+subscribe to the data service (which we do). 10Hz is great because it means
+the highest frequency noise we will get will be 5Hz which is only a little less
+than twice the frequency we want to limit at. This means we can use a one pole
+IIR to low-pass filter the magnitude stream so that we reduce the noise to
+something manageable with very little CPU.
+IIRs typically are written in the floating point domain. Ours is integer and
+we achieve a fairly good IIR by making each input sample count for a 25% shift
+which we can do with some shifts and adds and subs (nice and efficient). In
+theory this IIR has a horrible Fc but it seems to work for the data we're
+working with and thus I'm happy. (Nominally such an IIR has an Fc of something
+awful like 0.05 rather than the 0.33 we'd ideally aim for) yet somehow it seems
+to behave more like the ideal than we were expecting (and it's CHEAP to run).
+All this results in a (time-delta,smoothed-magnitude) tuple stream coming out
+at somewhere around 10 per second. Some will be missing because of the watch
+doing extra work and some will be missing due to the vibrator running (such
+samples are best discarded).
+In some debug modes, it's possible to get this tuple stream out of the watch on
+a data logging channel.
diff --git a/notes/reporting.mdwn b/notes/reporting.mdwn
new file mode 100644
index 0000000..1dab4f7
--- /dev/null
+++ b/notes/reporting.mdwn
@@ -0,0 +1,46 @@
+Reporting all this to the phone
+Obviously recording data is only useful if we report it to the phone so that it
+can be recorded and analysed over time. To do this we use the Pebble data
+logging API. Since having large infrequent logging dumps is worthwhile, we can
+limit ourselves to a data logging packet once a minute during active phases
+(detecting steps) and once every 5 to 10 minutes during idle phases. Since
+sleeping is a unilaterally non-interacting state to be in, we can limit the
+actigraphy logging during sleep even more if we choose.
+In theory if we're interested in logging steps such that we have the time (to
+the ms) when the step occurs then we should expect to log step events at
+ca. 2Hz. In practice humans are not interested in data at that level of detail
+for day-to-day readings. Instead we can group the day into one minute epochs
+and simply log the step data for a minute into that. We don't really need more
+than N steps in that epoch to be recorded. This boils down to a very small
+packet, perhaps a time_t and a type marker and a step counter. Obviously we
+can refresh the screen more often than that.
+Idle phases (i.e. where we're not recording any data) could be less frequent
+since clearly you're not doing anything so there's nothing to log. If we
+extend the log packet to be two time_t's (start and end) the type marker and
+the step counter, then we're able to bunch up idle periods and just log one
+idle period when we detect a step for the first time in a while.
+Sleep is a different beastie, we clearly want to record per epoch sleep states
+because we want to see if we go into and out of light and deep sleep
+frequently, but we also want to have that data not clog the link. As such,
+again simply logging the start and end time and the type of sleep when we
+transition between sleep phases would be good enough and result in maybe 20 to
+50 packets per night which is lower frequency than daytime.
+Add in a need to record various events such that further analysis can be done
+at a later date and we have a data logging packet shape of:
+ typedef struct {
+ time_t period_start;
+ time_t period_end;
+ uint16_t type;
+ uint16_t value;
+ } datalog_packet_t;
+Not all types of packet will end up needing both time_ts, but we need to have
+a consistent byte size for the datalogging packets and making it a multiple of
+4 bytes makes my inner geek happy.
diff --git a/notes/sleep-actigraphy.mdwn b/notes/sleep-actigraphy.mdwn
new file mode 100644
index 0000000..32861b8
--- /dev/null
+++ b/notes/sleep-actigraphy.mdwn
@@ -0,0 +1,71 @@
+Sleep Actigraphy
+This is a much discussed topic. There are many papers out there on sleep
+actigraphy using wrist-mounted accelerometers. Many of the fitness bands
+support "sleep monitoring". The Jawbone Up also supports a smart-alarm feature
+where it tries to wake you close to your desired alarm time by vibrating when
+you should already be nearly awake.
+This is a feature I really wanted to ensure I had in *Askel ja Uni* so here's
+where I got to...
+Sleep is typically split into REM and non-REM (NREM) sleep stages. In
+addition, NREM is split into three stages (N1, N2 and N3) where N1 is very
+close to wakefulness and N3 is very deep sleep. Sleep goes in cycles, from N1
+to N3, then back toward N1, entering REM before N1, then N1 to N3 etc.
+You typically have more REM in the later stages of your sleep period and more
+N3 in the early stages of your sleep period. This means that when you're in N1
+toward the end of your sleep period (effectively when you want to wake up) you
+are in the ideal state to be woken by the app.
+REM (and N3) are characterised by stillness in the body. N3 because you are
+very deeply asleep (hardest point to wake you) and REM because otherwise you
+might cause yourself damage by acting out your dreams (most annoying time to be
+woken). As such, we can look for movement in the sleep period to indicate how
+asleep you are, and then run the alarm at the right moment when you're close to
+wakefulness (N1 or even 'awake') in your chosen alarm period.
+Characterising sleep with the Pebble turned out to be really hard. Some papers
+recommended correlating the actigraphic data with polysomnographs or other
+detectors such as oxygen consumption detectors in order to calibrate the
+actigraphic detection of sleep states. Clearly I don't have access to that
+kind of equipment so instead I relied on correlating my results with that of
+the Jawbone Up worn next to the Pebble through five or six long sleep cycles.
+Then, I combined it with ideas gleaned from a large number of google searches
+on sleep actigraphy and came up with the following heuristic for estimating
+sleep modes. I decided to limit my sleep mode estimations to AWAKE vs. LIGHT
+vs. DEEP sleep (the same characterisations as the Jawbone uses) and this is
+what I came up with:
+1. Gather the total absolute movement deltas in 1 minute epochs
+2. Use thresholding to characterise an epoch as MAYBE-AWAKE, MAYBE-LIGHT and
+ MAYBE-DEEP at (<10 (deep), <50 (light), rest awake).
+3. Put these maybe values into a 20 value history buffer (prefilled with awake
+ since clearly you start awake).
+4. Count the number of MAYBE-LIGHT and MAYBE-DEEP entries in the history
+ buffer. If there are > 16 of them, then characterise this epoch as
+ 'asleep', otherwise characterise it as 'awake'.
+5. If we're 'asleep' then sum the history buffer, giving deep the value 0,
+ light the value 1 and awake the value 2. If the buffer totals 0, 1 or 2
+ then we're in 'deep sleep' (this allows for up to 2 light sleep epochs
+ or one "false" awake epoch).
+6. Record each epoch along with either awake, light or deep sleep.
+Note, this algorithm does mean that we cannot characterise the first 16 minutes
+as sleep but since in general it takes me 20-30 minutes to properly fall asleep
+after pressing the sleep button, this seems okay to me.
+Finally, within the window for the smart alarm we can simply start to vibrate
+when we detect we are in the light or awake states. Obviously if we don't
+reach that state by the time the alarm is due, we should start to vibrate
+anyway in case we're rising towards N1 but haven't started moving yet.
diff --git a/notes/step-detection.mdwn b/notes/step-detection.mdwn
new file mode 100644
index 0000000..6635ab2
--- /dev/null
+++ b/notes/step-detection.mdwn
@@ -0,0 +1,48 @@
+Step Detection
+From the (time-delta, smoothed-magnitude) tuple stream we can create a sequence
+of magnitude deltas. These tell us the change in acceleration over time. We
+do not need to care about the exact direction of the acceleration vector since
+we're looking for jolts (local maxima) in the vector magnitude (which indicate
+a possible step event).
+By viewing graphs of the delta vs. time, we determined that step events were
+best detected by looking for a +ve to -ve zero-crossing of the delta. This
+gives us a 'maybe step' event. Two such events separated by a suitable time
+period gives us a likely step event which we then record. If a likely step
+occurs directly after a maybe step then we also count the maybe step since we
+can retroactively assume that the maybe step counts too.
+Human walking pace is typically somewhere between 0.8 and 2.5 Hz. This also
+covers the low end of jogging/running which typically maxes out at 3Hz. This
+means that the time-difference between maybe-step events should be somewhere
+between 333 and 1200 milliseconds. Since we're receiving events in 1 second
+packets and also since I (as a very unfit person) seem to walk around 1.8Hz or
+stroll at around 1.2Hz I chose limits of 275 to 825 milliseconds (1.2-3.5Hz).
+Looking at the spread of times for steps I take when I consider myself to be
+walking at a steady pace, I decided that a window of +/- 150ms from the current
+'expected' gap would allow detection of likely steps without over-detecting
+(false steps) being too numerous. (The "slow" end might seem very slow but
+frankly if I'm walking below around 1.2Hz then it's hardly exercise and I don't
+think it should count :-)
+As such, we maintain an expected gap value which starts life at 500ms and will
+be migrated toward the detected gap values with a similar IIR to that used to
+smooth the magnitude data stream. Also the expected gap will be capped
+minimally at 425ms (-150ms => 275ms) and maximally at 675ms (+150ms => 825ms)
+On a test dataset I took where I walked approximately 0.6 miles in
+approximately 11 minutes covering around 1100 to 1150 steps, the algorithms
+described in the accelerometer sheet and this result in a recorded 965 steps
+which close to the 90% goal I set myself. For reference, my Jawbone Up (worn
+on the same wrist next to the Pebble) registered 1180 steps for that journey
+which is better, but also overreading. Most documentation I find says that the
+Jawbone tends to do well on short journeys but will overread by up to 25% on
+longer ones, so I think that it's reasonable that my values are underreading.
+In conclusion, we're only recording ca. 80-85% of the steps on the test
+journeys but since the purpose of this is to measure my relative activity and
+to encourage more exercise by letting me graph my data against various other
+bits of my life, I'm happy with this bit.