blesleep/sleepdata.py

237 lines
7.3 KiB
Python

from datetime import datetime
from os import path
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 = {
'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
}
}
}
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()