Moved data handling outside of main script into sleepdata module
This commit is contained in:
parent
8435b2bd69
commit
7bf0e8539d
285
bluesleep.py
285
bluesleep.py
|
@ -4,12 +4,7 @@ from bluepy import btle
|
||||||
from bluepy.btle import BTLEDisconnectError
|
from bluepy.btle import BTLEDisconnectError
|
||||||
|
|
||||||
from miband import miband
|
from miband import miband
|
||||||
|
import sleepdata
|
||||||
import matplotlib.pyplot as plt
|
|
||||||
import matplotlib.animation as animation
|
|
||||||
import csv
|
|
||||||
import random
|
|
||||||
from os import path
|
|
||||||
|
|
||||||
import threading
|
import threading
|
||||||
import re
|
import re
|
||||||
|
@ -18,69 +13,23 @@ import subprocess
|
||||||
import time
|
import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
sleep_data = {
|
|
||||||
'heartrate': {
|
|
||||||
'value_name': 'bpm',
|
|
||||||
'periods': [2, 5, 10, 15],
|
|
||||||
'raw_data': [],
|
|
||||||
'averaged_data': [],
|
|
||||||
},
|
|
||||||
'movement':{
|
|
||||||
'value_name': 'movement',
|
|
||||||
'periods': [10, 30, 60],
|
|
||||||
'raw_data': [],
|
|
||||||
'averaged_data': [],
|
|
||||||
'workspace': {
|
|
||||||
'gyro_last_x' : 0,
|
|
||||||
'gyro_last_y' : 0,
|
|
||||||
'gyro_last_z' : 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auth_key_filename = 'auth_key.txt'
|
auth_key_filename = 'auth_key.txt'
|
||||||
mac_filename = 'mac.txt'
|
mac_filename = 'mac.txt'
|
||||||
csv_filename = "sleep_data.csv"
|
csv_filename = "sleep_data.csv"
|
||||||
|
|
||||||
plt.style.use('dark_background')
|
band = None
|
||||||
graph_figure = plt.figure()
|
|
||||||
graph_axes = graph_figure.add_subplot(1, 1, 1)
|
|
||||||
graph_data = {}
|
|
||||||
|
|
||||||
last_heartrate = 0
|
|
||||||
last_tick_time = None
|
|
||||||
tick_seconds = 0.5
|
|
||||||
|
|
||||||
fieldnames = ['time']
|
|
||||||
for data_type in sleep_data:
|
|
||||||
periods = sleep_data[data_type]['periods']
|
|
||||||
for period in periods:
|
|
||||||
fieldnames.append(data_type + str(period))
|
|
||||||
|
|
||||||
|
|
||||||
#-------------------------------------------------------------------------#
|
#-------------------------------------------------------------------------#
|
||||||
|
|
||||||
|
class regex_patterns():
|
||||||
def write_csv(data):
|
mac_regex_pattern = re.compile(r'([0-9a-fA-F]{2}(?::[0-9a-fA-F]{2}){5})')
|
||||||
global fieldnames
|
authkey_regex_pattern = re.compile(r'([0-9a-fA-F]){32}')
|
||||||
global csv_filename
|
|
||||||
if not path.exists(csv_filename):
|
|
||||||
with open(csv_filename, 'w', newline='') as csvfile:
|
|
||||||
csv_writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
|
|
||||||
csv_writer.writeheader()
|
|
||||||
csv_writer.writerow(data)
|
|
||||||
else:
|
|
||||||
with open(csv_filename, 'a', newline='') as csvfile:
|
|
||||||
csv_writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
|
|
||||||
csv_writer.writerow(data)
|
|
||||||
|
|
||||||
|
|
||||||
def get_mac_address(filename):
|
def get_mac_address(filename):
|
||||||
mac_regex_pattern = re.compile(r'([0-9a-fA-F]{2}(?::[0-9a-fA-F]{2}){5})')
|
|
||||||
try:
|
try:
|
||||||
with open(filename, "r") as f:
|
with open(filename, "r") as f:
|
||||||
hwaddr_search = re.search(mac_regex_pattern, f.read().strip())
|
hwaddr_search = re.search(regex_patterns.mac_regex_pattern, f.read().strip())
|
||||||
|
|
||||||
if hwaddr_search:
|
if hwaddr_search:
|
||||||
MAC_ADDR = hwaddr_search[0]
|
MAC_ADDR = hwaddr_search[0]
|
||||||
else:
|
else:
|
||||||
|
@ -93,10 +42,9 @@ def get_mac_address(filename):
|
||||||
|
|
||||||
|
|
||||||
def get_auth_key(filename):
|
def get_auth_key(filename):
|
||||||
authkey_regex_pattern = re.compile(r'([0-9a-fA-F]){32}')
|
|
||||||
try:
|
try:
|
||||||
with open(filename, "r") as f:
|
with open(filename, "r") as f:
|
||||||
key_search = re.search(authkey_regex_pattern, f.read().strip())
|
key_search = re.search(regex_patterns.authkey_regex_pattern, f.read().strip())
|
||||||
if key_search:
|
if key_search:
|
||||||
AUTH_KEY = bytes.fromhex(key_search[0])
|
AUTH_KEY = bytes.fromhex(key_search[0])
|
||||||
else:
|
else:
|
||||||
|
@ -107,210 +55,28 @@ def get_auth_key(filename):
|
||||||
exit(1)
|
exit(1)
|
||||||
return AUTH_KEY
|
return AUTH_KEY
|
||||||
|
|
||||||
|
def process_data(data, tick_time):
|
||||||
def process_heartrate_data(heartrate_data, tick_time):
|
if data[0] == "GYRO":
|
||||||
print("BPM: " + str(heartrate_data))
|
sleepdata.process_gyro_data(data[1], tick_time)
|
||||||
if heartrate_data > 0:
|
elif data[0] == "HR":
|
||||||
value_name = sleep_data['heartrate']['value_name']
|
sleepdata.process_heartrate_data(data[1], tick_time)
|
||||||
sleep_data['heartrate']['raw_data'].append({
|
|
||||||
'time': tick_time,
|
|
||||||
value_name: heartrate_data
|
|
||||||
} )
|
|
||||||
|
|
||||||
|
|
||||||
def process_gyro_data(gyro_data, tick_time):
|
|
||||||
# Each gyro reading from miband4 comes over as a group of three,
|
|
||||||
# each containing x,y,z values. This function summarizes the
|
|
||||||
# values into a single consolidated movement value.
|
|
||||||
|
|
||||||
global sleep_data
|
|
||||||
|
|
||||||
sleep_move = sleep_data['movement']
|
|
||||||
sleep_workspace = sleep_move['workspace']
|
|
||||||
|
|
||||||
gyro_last_x = sleep_workspace['gyro_last_x']
|
|
||||||
gyro_last_y = sleep_workspace['gyro_last_y']
|
|
||||||
gyro_last_z = sleep_workspace['gyro_last_z']
|
|
||||||
value_name = sleep_move['value_name']
|
|
||||||
gyro_movement = 0
|
|
||||||
for gyro_datum in gyro_data:
|
|
||||||
gyro_delta_x = abs(gyro_datum['x'] - gyro_last_x)
|
|
||||||
gyro_last_x = gyro_datum['x']
|
|
||||||
gyro_delta_y = abs(gyro_datum['y'] - gyro_last_y)
|
|
||||||
gyro_last_y = gyro_datum['y']
|
|
||||||
gyro_delta_z = abs(gyro_datum['z'] - gyro_last_z)
|
|
||||||
gyro_last_z = gyro_datum['z']
|
|
||||||
gyro_delta_sum = gyro_delta_x + gyro_delta_y + gyro_delta_z
|
|
||||||
gyro_movement += gyro_delta_sum
|
|
||||||
|
|
||||||
sleep_workspace['gyro_last_x'] = gyro_last_x
|
|
||||||
sleep_workspace['gyro_last_y'] = gyro_last_y
|
|
||||||
sleep_workspace['gyro_last_z'] = gyro_last_z
|
|
||||||
|
|
||||||
sleep_move['raw_data'].append({
|
|
||||||
'time': tick_time,
|
|
||||||
value_name: gyro_movement
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
def flush_old_raw_data(tick_time):
|
|
||||||
global sleep_data
|
|
||||||
|
|
||||||
for data_type in sleep_data:
|
|
||||||
s_data = sleep_data[data_type]
|
|
||||||
periods = s_data['periods']
|
|
||||||
|
|
||||||
cleaned_raw_data = []
|
|
||||||
|
|
||||||
for raw_datum in s_data['raw_data']:
|
|
||||||
datum_age = tick_time - raw_datum['time']
|
|
||||||
if datum_age < max(periods):
|
|
||||||
cleaned_raw_data.append(raw_datum)
|
|
||||||
|
|
||||||
s_data['raw_data'] = cleaned_raw_data
|
|
||||||
|
|
||||||
|
|
||||||
def average_raw_data(tick_time):
|
|
||||||
global sleep_data
|
|
||||||
global last_heartrate
|
|
||||||
timestamp = datetime.fromtimestamp(tick_time)
|
|
||||||
csv_out = {'time': timestamp }
|
|
||||||
|
|
||||||
for data_type in sleep_data:
|
|
||||||
s_data = sleep_data[data_type]
|
|
||||||
period_averages_dict = {'time': timestamp}
|
|
||||||
periods = s_data['periods']
|
|
||||||
value_name = s_data['value_name']
|
|
||||||
|
|
||||||
flush_old_raw_data(tick_time)
|
|
||||||
|
|
||||||
for period_seconds in periods:
|
|
||||||
period_data = []
|
|
||||||
period_averages_dict[period_seconds] = 0
|
|
||||||
for raw_datum in s_data['raw_data']:
|
|
||||||
datum_age_seconds = tick_time - raw_datum['time']
|
|
||||||
if datum_age_seconds < period_seconds:
|
|
||||||
period_data.append(raw_datum[value_name])
|
|
||||||
|
|
||||||
if len(period_data) > 0:
|
|
||||||
period_data_average = sum(period_data) / len(period_data)
|
|
||||||
else:
|
|
||||||
print("({}) Period data empty: {}".format(data_type,
|
|
||||||
period_seconds))
|
|
||||||
if data_type == "heartrate" and period_seconds == min(periods):
|
|
||||||
period_data_average = last_heartrate
|
|
||||||
else:
|
|
||||||
period_data_average = 0
|
|
||||||
|
|
||||||
period_averages_dict[period_seconds] = zero_to_nan(period_data_average)
|
|
||||||
|
|
||||||
csv_out[data_type + str(period_seconds)] = zero_to_nan(period_data_average)
|
|
||||||
|
|
||||||
s_data['averaged_data'].append(period_averages_dict)
|
|
||||||
write_csv(csv_out)
|
|
||||||
|
|
||||||
|
|
||||||
def zero_to_nan(value):
|
|
||||||
if value == 0:
|
|
||||||
return (float('nan'))
|
|
||||||
return int(value)
|
|
||||||
|
|
||||||
|
|
||||||
def sleep_monitor_callback(data):
|
def sleep_monitor_callback(data):
|
||||||
global sleep_data
|
|
||||||
global last_tick_time
|
|
||||||
|
|
||||||
tick_time = time.time()
|
tick_time = time.time()
|
||||||
if not last_tick_time:
|
if not sleepdata.last_tick_time:
|
||||||
last_tick_time = time.time()
|
sleepdata.last_tick_time = time.time()
|
||||||
|
|
||||||
if data[0] == "GYRO":
|
process_data(data, tick_time)
|
||||||
process_gyro_data(data[1], tick_time)
|
|
||||||
elif data[0] == "HR":
|
|
||||||
process_heartrate_data(data[1], tick_time)
|
|
||||||
|
|
||||||
if (tick_time - last_tick_time) >= tick_seconds:
|
if (tick_time - sleepdata.last_tick_time) >= sleepdata.tick_seconds:
|
||||||
average_raw_data(tick_time)
|
sleepdata.average_raw_data(tick_time)
|
||||||
last_tick_time = time.time()
|
sleepdata.last_tick_time = time.time()
|
||||||
|
|
||||||
|
def connect(mac_filename, auth_key_filename):
|
||||||
def init_graph_data():
|
|
||||||
for data_type in sleep_data:
|
|
||||||
data_periods = sleep_data[data_type]['periods']
|
|
||||||
graph_data[data_type] = {
|
|
||||||
'time': [],
|
|
||||||
'data': {}
|
|
||||||
}
|
|
||||||
for period in data_periods:
|
|
||||||
graph_data[data_type]['data'][period] = []
|
|
||||||
|
|
||||||
|
|
||||||
def update_graph_data():
|
|
||||||
global sleep_data
|
|
||||||
global graph_data
|
|
||||||
|
|
||||||
for data_type in sleep_data:
|
|
||||||
s_data = sleep_data[data_type] # Re-referenced to shorten name
|
|
||||||
avg_data = s_data['averaged_data']
|
|
||||||
|
|
||||||
if len(avg_data) > 1:
|
|
||||||
|
|
||||||
g_data = graph_data[data_type] # Re-referenced to short name
|
|
||||||
data_periods = s_data['periods']
|
|
||||||
|
|
||||||
starting_index = max([(len(g_data['time']) - 1), 0])
|
|
||||||
ending_index = len(avg_data) - 1
|
|
||||||
|
|
||||||
# Re-referenced to shorten name
|
|
||||||
sleep_data_range = avg_data[starting_index:ending_index]
|
|
||||||
|
|
||||||
for sleep_datum in sleep_data_range:
|
|
||||||
g_data['time'].append(sleep_datum['time'])
|
|
||||||
for period in data_periods:
|
|
||||||
if g_data['data'][period] != 'nan':
|
|
||||||
g_data['data'][period].append(sleep_datum[period])
|
|
||||||
|
|
||||||
|
|
||||||
def graph_animation(i):
|
|
||||||
global sleep_data
|
|
||||||
global graph_axes
|
|
||||||
global graph_data
|
|
||||||
plotflag = False
|
|
||||||
|
|
||||||
if len(graph_data) == 0:
|
|
||||||
init_graph_data()
|
|
||||||
|
|
||||||
update_graph_data()
|
|
||||||
|
|
||||||
for data_type in graph_data:
|
|
||||||
if len(graph_data[data_type]['time']) > 0:
|
|
||||||
graph_axes.clear()
|
|
||||||
break
|
|
||||||
|
|
||||||
for data_type in sleep_data:
|
|
||||||
s_data = sleep_data[data_type]
|
|
||||||
g_data = graph_data[data_type]
|
|
||||||
if len(g_data['time']) > 0:
|
|
||||||
plotflag = True
|
|
||||||
data_periods = sleep_data[data_type]['periods']
|
|
||||||
for period in data_periods:
|
|
||||||
axis_label = "{} {} sec".format(s_data['value_name'], period)
|
|
||||||
graph_axes.plot(g_data['time'],
|
|
||||||
g_data['data'][period],
|
|
||||||
label=axis_label)
|
|
||||||
|
|
||||||
if plotflag:
|
|
||||||
plt.legend()
|
|
||||||
|
|
||||||
|
|
||||||
def connect():
|
|
||||||
global band
|
global band
|
||||||
global mac_filename
|
|
||||||
global auth_key_filename
|
|
||||||
|
|
||||||
success = False
|
success = False
|
||||||
timeout = 3
|
timeout = 3
|
||||||
msg = 'Connection to the MIBand failed. Trying again in {} seconds'
|
msg = 'Connection to the band failed. Trying again in {} seconds'
|
||||||
|
|
||||||
MAC_ADDR = get_mac_address(mac_filename)
|
MAC_ADDR = get_mac_address(mac_filename)
|
||||||
AUTH_KEY = get_auth_key(auth_key_filename)
|
AUTH_KEY = get_auth_key(auth_key_filename)
|
||||||
|
@ -326,10 +92,7 @@ def connect():
|
||||||
print("\nExit.")
|
print("\nExit.")
|
||||||
exit()
|
exit()
|
||||||
|
|
||||||
|
|
||||||
def start_data_pull():
|
def start_data_pull():
|
||||||
global band
|
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
band.start_heart_and_gyro(sensitivity=1, callback=sleep_monitor_callback)
|
band.start_heart_and_gyro(sensitivity=1, callback=sleep_monitor_callback)
|
||||||
|
@ -357,6 +120,7 @@ def vibrate_pattern(duration):
|
||||||
time.sleep(vibro_delay)
|
time.sleep(vibro_delay)
|
||||||
|
|
||||||
def vibrate_rolling():
|
def vibrate_rolling():
|
||||||
|
print("Sending rolling vibration...")
|
||||||
for x in range(10):
|
for x in range(10):
|
||||||
for x in range(20, 40, 1):
|
for x in range(20, 40, 1):
|
||||||
band.vibrate(x)
|
band.vibrate(x)
|
||||||
|
@ -364,11 +128,12 @@ def vibrate_rolling():
|
||||||
band.vibrate(x)
|
band.vibrate(x)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
connect()
|
connect(mac_filename, auth_key_filename)
|
||||||
|
#vibrate_pattern(10)
|
||||||
data_gather_thread = threading.Thread(target=start_data_pull)
|
data_gather_thread = threading.Thread(target=start_data_pull)
|
||||||
data_gather_thread.start()
|
data_gather_thread.start()
|
||||||
ani = animation.FuncAnimation(graph_figure, graph_animation, interval=1000)
|
sleepdata.init_graph()
|
||||||
plt.show()
|
|
||||||
|
|
||||||
|
|
||||||
#import simpleaudio as sa
|
#import simpleaudio as sa
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
|
||||||
|
class miband4():
|
||||||
|
|
||||||
|
class bytepatterns():
|
||||||
|
vibration = 'ff{:02x}00000001'
|
||||||
|
gyro_start = '01{:02x}19'
|
||||||
|
start = '0100'
|
||||||
|
stop = '0000'
|
||||||
|
heart_measure_keepalive = '16'
|
||||||
|
stop_heart_measure_continues = '150100'
|
||||||
|
start_heart_measure_continues = '150101'
|
||||||
|
stop_heart_measure_manual = '150200'
|
||||||
|
fetch_begin = '100101'
|
||||||
|
fetch_error = '100104'
|
||||||
|
fetch_continue = '100201'
|
||||||
|
fetch_complete = '100204'
|
||||||
|
auth_ok = '100301'
|
||||||
|
request_random_number = '0200'
|
||||||
|
auth_key_prefix = '0300'
|
||||||
|
|
||||||
|
def vibration(duration):
|
||||||
|
byte_pattern = miband4.bytepatterns.vibration
|
||||||
|
return bytes.fromhex(byte_pattern.format(duration))
|
||||||
|
|
||||||
|
def gyro_start(sensitivity):
|
||||||
|
byte_pattern = miband4.bytepatterns.gyro_start
|
||||||
|
return bytes.fromhex(byte_pattern.format(sensitivity))
|
||||||
|
|
||||||
|
start = bytes.fromhex(bytepatterns.start)
|
||||||
|
stop = bytes.fromhex(bytepatterns.stop)
|
||||||
|
|
||||||
|
heart_measure_keepalive = bytes.fromhex(bytepatterns.heart_measure_keepalive)
|
||||||
|
stop_heart_measure_continues = bytes.fromhex(bytepatterns.stop_heart_measure_continues)
|
||||||
|
start_heart_measure_continues = bytes.fromhex(bytepatterns.start_heart_measure_continues)
|
||||||
|
stop_heart_measure_manual = bytes.fromhex(bytepatterns.stop_heart_measure_manual)
|
||||||
|
|
||||||
|
fetch_begin = bytes.fromhex(bytepatterns.fetch_begin)
|
||||||
|
fetch_error = bytes.fromhex(bytepatterns.fetch_error)
|
||||||
|
fetch_continue = bytes.fromhex(bytepatterns.fetch_continue)
|
||||||
|
fetch_complete = bytes.fromhex(bytepatterns.fetch_complete)
|
||||||
|
|
||||||
|
auth_ok = bytes.fromhex(bytepatterns.auth_ok)
|
||||||
|
request_random_number = bytes.fromhex(bytepatterns.request_random_number)
|
||||||
|
auth_key_prefix = bytes.fromhex(bytepatterns.auth_key_prefix)
|
||||||
|
|
29
miband.py
29
miband.py
|
@ -3,6 +3,8 @@ import logging
|
||||||
import struct
|
import struct
|
||||||
import binascii
|
import binascii
|
||||||
|
|
||||||
|
from bytepatterns import miband4 as bytepattern
|
||||||
|
|
||||||
from bluepy.btle import (
|
from bluepy.btle import (
|
||||||
Peripheral, DefaultDelegate,
|
Peripheral, DefaultDelegate,
|
||||||
ADDR_TYPE_RANDOM, ADDR_TYPE_PUBLIC,
|
ADDR_TYPE_RANDOM, ADDR_TYPE_PUBLIC,
|
||||||
|
@ -55,33 +57,6 @@ class Delegate(DefaultDelegate):
|
||||||
print ("Unhandled handle: " + str(hnd) + " | Data: " + str(data))
|
print ("Unhandled handle: " + str(hnd) + " | Data: " + str(data))
|
||||||
|
|
||||||
|
|
||||||
class bytepattern():
|
|
||||||
def vibration(duration):
|
|
||||||
byte_pattern = 'ff{:02x}00000001'
|
|
||||||
return bytes.fromhex(byte_pattern.format(duration))
|
|
||||||
|
|
||||||
def gyro_start(sensitivity):
|
|
||||||
byte_pattern = '01{:02x}19'
|
|
||||||
return bytes.fromhex(byte_pattern.format(sensitivity))
|
|
||||||
|
|
||||||
start = bytes.fromhex('0100')
|
|
||||||
stop = bytes.fromhex('0000')
|
|
||||||
|
|
||||||
heart_measure_keepalive = bytes.fromhex('16')
|
|
||||||
stop_heart_measure_continues = bytes.fromhex('150100')
|
|
||||||
start_heart_measure_continues = bytes.fromhex('150101')
|
|
||||||
stop_heart_measure_manual = bytes.fromhex('150200')
|
|
||||||
|
|
||||||
fetch_begin = bytes.fromhex('100101')
|
|
||||||
fetch_error = bytes.fromhex('100104')
|
|
||||||
|
|
||||||
fetch_continue = bytes.fromhex('100201')
|
|
||||||
fetch_complete = bytes.fromhex('100204')
|
|
||||||
|
|
||||||
auth_ok = bytes.fromhex('100301')
|
|
||||||
|
|
||||||
request_random_number = bytes.fromhex('0200')
|
|
||||||
auth_key_prefix = bytes.fromhex('0300')
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
220
sleepdata.py
220
sleepdata.py
|
@ -1,9 +1,16 @@
|
||||||
class Sleep_Data(object):
|
from datetime import datetime
|
||||||
def __init__(self):
|
from os import path
|
||||||
print("init")
|
|
||||||
|
import csv
|
||||||
|
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import matplotlib.animation as animation
|
||||||
|
|
||||||
|
#Todo: separate graph animation from data averaging
|
||||||
|
#Todo: log raw data separately from average data
|
||||||
|
|
||||||
|
|
||||||
sleep_data = {
|
sleep_data = {
|
||||||
'heartrate': {
|
'heartrate': {
|
||||||
'value_name': 'bpm',
|
'value_name': 'bpm',
|
||||||
'periods': [2, 5, 10, 15],
|
'periods': [2, 5, 10, 15],
|
||||||
|
@ -22,3 +29,208 @@ class Sleep_Data(object):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
last_heartrate = 0
|
||||||
|
last_tick_time = None
|
||||||
|
tick_seconds = 0.5
|
||||||
|
|
||||||
|
csv_filename = "sleep_data.csv"
|
||||||
|
|
||||||
|
fieldnames = ['time']
|
||||||
|
for data_type in sleep_data:
|
||||||
|
periods = sleep_data[data_type]['periods']
|
||||||
|
for period in periods:
|
||||||
|
fieldnames.append(data_type + str(period))
|
||||||
|
|
||||||
|
|
||||||
|
plt.style.use('dark_background')
|
||||||
|
graph_figure = plt.figure()
|
||||||
|
graph_axes = graph_figure.add_subplot(1, 1, 1)
|
||||||
|
graph_data = {}
|
||||||
|
|
||||||
|
|
||||||
|
def write_csv(data):
|
||||||
|
global fieldnames
|
||||||
|
global csv_filename
|
||||||
|
if not path.exists(csv_filename):
|
||||||
|
with open(csv_filename, 'w', newline='') as csvfile:
|
||||||
|
csv_writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
|
||||||
|
csv_writer.writeheader()
|
||||||
|
csv_writer.writerow(data)
|
||||||
|
else:
|
||||||
|
with open(csv_filename, 'a', newline='') as csvfile:
|
||||||
|
csv_writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
|
||||||
|
csv_writer.writerow(data)
|
||||||
|
|
||||||
|
|
||||||
|
def flush_old_raw_data(tick_time):
|
||||||
|
for data_type in sleep_data:
|
||||||
|
s_data = sleep_data[data_type]
|
||||||
|
periods = s_data['periods']
|
||||||
|
|
||||||
|
cleaned_raw_data = []
|
||||||
|
|
||||||
|
for raw_datum in s_data['raw_data']:
|
||||||
|
datum_age = tick_time - raw_datum['time']
|
||||||
|
if datum_age < max(periods):
|
||||||
|
cleaned_raw_data.append(raw_datum)
|
||||||
|
|
||||||
|
s_data['raw_data'] = cleaned_raw_data
|
||||||
|
|
||||||
|
def write_csv(data):
|
||||||
|
a = ''
|
||||||
|
|
||||||
|
|
||||||
|
def average_raw_data(tick_time):
|
||||||
|
global last_heartrate
|
||||||
|
timestamp = datetime.fromtimestamp(tick_time)
|
||||||
|
csv_out = {'time': timestamp }
|
||||||
|
|
||||||
|
for data_type in sleep_data:
|
||||||
|
s_data = sleep_data[data_type]
|
||||||
|
period_averages_dict = {'time': timestamp}
|
||||||
|
periods = s_data['periods']
|
||||||
|
value_name = s_data['value_name']
|
||||||
|
|
||||||
|
flush_old_raw_data(tick_time)
|
||||||
|
|
||||||
|
for period_seconds in periods:
|
||||||
|
period_data = []
|
||||||
|
period_averages_dict[period_seconds] = 0
|
||||||
|
for raw_datum in s_data['raw_data']:
|
||||||
|
datum_age_seconds = tick_time - raw_datum['time']
|
||||||
|
if datum_age_seconds < period_seconds:
|
||||||
|
period_data.append(raw_datum[value_name])
|
||||||
|
|
||||||
|
if len(period_data) > 0:
|
||||||
|
period_data_average = sum(period_data) / len(period_data)
|
||||||
|
else:
|
||||||
|
print("({}) Period data empty: {}".format(data_type,
|
||||||
|
period_seconds))
|
||||||
|
if data_type == "heartrate" and period_seconds == min(periods):
|
||||||
|
period_data_average = last_heartrate
|
||||||
|
else:
|
||||||
|
period_data_average = 0
|
||||||
|
|
||||||
|
period_averages_dict[period_seconds] = zero_to_nan(period_data_average)
|
||||||
|
|
||||||
|
csv_out[data_type + str(period_seconds)] = zero_to_nan(period_data_average)
|
||||||
|
|
||||||
|
s_data['averaged_data'].append(period_averages_dict)
|
||||||
|
write_csv(csv_out)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def process_gyro_data(gyro_data, tick_time):
|
||||||
|
# Each gyro reading from miband4 comes over as a group of three,
|
||||||
|
# each containing x,y,z values. This function summarizes the
|
||||||
|
# values into a single consolidated movement value.
|
||||||
|
|
||||||
|
sleep_move = sleep_data['movement']
|
||||||
|
sleep_workspace = sleep_move['workspace']
|
||||||
|
|
||||||
|
gyro_last_x = sleep_workspace['gyro_last_x']
|
||||||
|
gyro_last_y = sleep_workspace['gyro_last_y']
|
||||||
|
gyro_last_z = sleep_workspace['gyro_last_z']
|
||||||
|
value_name = sleep_move['value_name']
|
||||||
|
gyro_movement = 0
|
||||||
|
for gyro_datum in gyro_data:
|
||||||
|
gyro_delta_x = abs(gyro_datum['x'] - gyro_last_x)
|
||||||
|
gyro_last_x = gyro_datum['x']
|
||||||
|
gyro_delta_y = abs(gyro_datum['y'] - gyro_last_y)
|
||||||
|
gyro_last_y = gyro_datum['y']
|
||||||
|
gyro_delta_z = abs(gyro_datum['z'] - gyro_last_z)
|
||||||
|
gyro_last_z = gyro_datum['z']
|
||||||
|
gyro_delta_sum = gyro_delta_x + gyro_delta_y + gyro_delta_z
|
||||||
|
gyro_movement += gyro_delta_sum
|
||||||
|
|
||||||
|
sleep_workspace['gyro_last_x'] = gyro_last_x
|
||||||
|
sleep_workspace['gyro_last_y'] = gyro_last_y
|
||||||
|
sleep_workspace['gyro_last_z'] = gyro_last_z
|
||||||
|
|
||||||
|
sleep_move['raw_data'].append({
|
||||||
|
'time': tick_time,
|
||||||
|
value_name: gyro_movement
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def process_heartrate_data(heartrate_data, tick_time):
|
||||||
|
print("BPM: " + str(heartrate_data))
|
||||||
|
if heartrate_data > 0:
|
||||||
|
value_name = sleep_data['heartrate']['value_name']
|
||||||
|
sleep_data['heartrate']['raw_data'].append({
|
||||||
|
'time': tick_time,
|
||||||
|
value_name: heartrate_data
|
||||||
|
} )
|
||||||
|
|
||||||
|
def zero_to_nan(value):
|
||||||
|
if value == 0:
|
||||||
|
return (float('nan'))
|
||||||
|
return int(value)
|
||||||
|
|
||||||
|
def update_graph_data():
|
||||||
|
for data_type in sleep_data:
|
||||||
|
s_data = sleep_data[data_type] # Re-referenced to shorten name
|
||||||
|
avg_data = s_data['averaged_data']
|
||||||
|
|
||||||
|
if len(avg_data) > 1:
|
||||||
|
|
||||||
|
g_data = graph_data[data_type] # Re-referenced to short name
|
||||||
|
data_periods = s_data['periods']
|
||||||
|
|
||||||
|
starting_index = max([(len(g_data['time']) - 1), 0])
|
||||||
|
ending_index = len(avg_data) - 1
|
||||||
|
|
||||||
|
# Re-referenced to shorten name
|
||||||
|
sleep_data_range = avg_data[starting_index:ending_index]
|
||||||
|
|
||||||
|
for sleep_datum in sleep_data_range:
|
||||||
|
g_data['time'].append(sleep_datum['time'])
|
||||||
|
for period in data_periods:
|
||||||
|
if g_data['data'][period] != 'nan':
|
||||||
|
g_data['data'][period].append(sleep_datum[period])
|
||||||
|
|
||||||
|
def init_graph_data():
|
||||||
|
for data_type in sleep_data:
|
||||||
|
data_periods = sleep_data[data_type]['periods']
|
||||||
|
graph_data[data_type] = {
|
||||||
|
'time': [],
|
||||||
|
'data': {}
|
||||||
|
}
|
||||||
|
for period in data_periods:
|
||||||
|
graph_data[data_type]['data'][period] = []
|
||||||
|
|
||||||
|
|
||||||
|
def graph_animation(i):
|
||||||
|
global graph_axes
|
||||||
|
global graph_data
|
||||||
|
plotflag = False
|
||||||
|
|
||||||
|
if len(graph_data) == 0:
|
||||||
|
init_graph_data()
|
||||||
|
|
||||||
|
update_graph_data()
|
||||||
|
|
||||||
|
for data_type in graph_data:
|
||||||
|
if len(graph_data[data_type]['time']) > 0:
|
||||||
|
graph_axes.clear()
|
||||||
|
break
|
||||||
|
|
||||||
|
for data_type in sleep_data:
|
||||||
|
s_data = sleep_data[data_type]
|
||||||
|
g_data = graph_data[data_type]
|
||||||
|
if len(g_data['time']) > 0:
|
||||||
|
plotflag = True
|
||||||
|
data_periods = sleep_data[data_type]['periods']
|
||||||
|
for period in data_periods:
|
||||||
|
axis_label = "{} {} sec".format(s_data['value_name'], period)
|
||||||
|
graph_axes.plot(g_data['time'],
|
||||||
|
g_data['data'][period],
|
||||||
|
label=axis_label)
|
||||||
|
|
||||||
|
if plotflag:
|
||||||
|
plt.legend()
|
||||||
|
|
||||||
|
def init_graph():
|
||||||
|
ani = animation.FuncAnimation(graph_figure, graph_animation, interval=1000)
|
||||||
|
plt.show()
|
||||||
|
|
Loading…
Reference in New Issue