parent
8435b2bd69
commit
7bf0e8539d
@ -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) |
||||
|
@ -1,24 +1,236 @@ |
||||
class Sleep_Data(object): |
||||
def __init__(self): |
||||
print("init") |
||||
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 = [] |
||||
|
||||
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 |
||||
} |
||||
} |
||||
} |
||||
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