Compare commits
1 Commits
master
...
miband5_de
Author | SHA1 | Date |
---|---|---|
![]() |
a94b699bbd |
|
@ -1,6 +1,5 @@
|
||||||
mac*.txt
|
mac.txt
|
||||||
auth_key*.txt
|
auth_key.txt
|
||||||
*.sh
|
|
||||||
*.pyc
|
*.pyc
|
||||||
/__pycache__
|
/__pycache__
|
||||||
*.swp
|
*.swp
|
||||||
|
@ -9,6 +8,3 @@ auth_key*.txt
|
||||||
*.code-workspace
|
*.code-workspace
|
||||||
/.vscode
|
/.vscode
|
||||||
/data
|
/data
|
||||||
datafilter.py
|
|
||||||
crymonitor.py
|
|
||||||
|
|
||||||
|
|
11
README
11
README
|
@ -10,8 +10,10 @@ You will need to create two .txt files in the base directory:
|
||||||
|
|
||||||
|
|
||||||
CURRENT STATUS:
|
CURRENT STATUS:
|
||||||
The project now supports basic "heartrate alarming". Currently it's configured to send a 10-second random vibration pattern if the heartrate increase percentage goes above 17% of the lowest HR value for the last 10 readings, with a 20-minute delay between vibrations. This was tested on the Miband5, which has a better HR sensor, though I've not yet fully figured out the gyroscope data. Right now only HR and vibration are supported on the Miband5.
|
Right now the project does not yet fulfill its primary purpose (responding to biometrics).
|
||||||
|
If executed now, it will:
|
||||||
|
* Log raw and averaged biometric data to disk
|
||||||
|
* Vibrate in a random pattern every 30 minutes (adjustable)
|
||||||
|
|
||||||
GOALS:
|
GOALS:
|
||||||
* Publish statistics to Google Sheets or other easy-to-use target. I'd like to make this usable by non-technical folks, so S3 buckets and CloudWatch are out of scope.
|
* Publish statistics to Google Sheets or other easy-to-use target. I'd like to make this usable by non-technical folks, so S3 buckets and CloudWatch are out of scope.
|
||||||
|
@ -22,4 +24,7 @@ GOALS:
|
||||||
|
|
||||||
|
|
||||||
DISCLAIMER:
|
DISCLAIMER:
|
||||||
None of the statements on this web site have been evaluated by the FDA. Furthermore, none of the statements herein should be construed as dispensing medical advice, or making claims regarding the cure or treatment of diseases. These statements have not been evaluated by the Food and Drug Administration. This project is not intended to diagnose, treat, cure, or prevent any diseases.
|
None of the statements on this web site have been evaluated by the FDA.
|
||||||
|
Furthermore, none of the statements herein should be construed as dispensing medical advice, or making claims regarding the cure or treatment of diseases.
|
||||||
|
These statements have not been evaluated by the Food and Drug Administration.
|
||||||
|
These project is not intended to diagnose, treat, cure, or prevent any diseases.
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
c04bc1eb2efaea8efc20882624b9a0f6
|
16
bluesleep.py
16
bluesleep.py
|
@ -15,10 +15,9 @@ mac_filename = 'mac.txt'
|
||||||
maximize_graph = False
|
maximize_graph = False
|
||||||
|
|
||||||
vibration_settings = {
|
vibration_settings = {
|
||||||
'interval_minutes': 20,
|
'interval_minutes': 45,
|
||||||
'duration_seconds': 10,
|
'duration_seconds': 5,
|
||||||
'type': 'random',
|
'type': 'random'
|
||||||
'heartrate_alarm_pct': 17
|
|
||||||
}
|
}
|
||||||
|
|
||||||
band = None
|
band = None
|
||||||
|
@ -66,7 +65,7 @@ def average_data(tick_time):
|
||||||
sleepdata.average_raw_data(tick_time)
|
sleepdata.average_raw_data(tick_time)
|
||||||
sleepdata.last_tick_time = time.time()
|
sleepdata.last_tick_time = time.time()
|
||||||
|
|
||||||
|
|
||||||
def sleep_monitor_callback(data):
|
def sleep_monitor_callback(data):
|
||||||
tick_time = time.time()
|
tick_time = time.time()
|
||||||
|
|
||||||
|
@ -80,9 +79,6 @@ def sleep_monitor_callback(data):
|
||||||
|
|
||||||
average_data(tick_time)
|
average_data(tick_time)
|
||||||
|
|
||||||
vibration.heartrate_increase_pct = sleepdata.analyze_heartrate(10)
|
|
||||||
print("HR increase percent: {}".format(vibration.heartrate_increase_pct))
|
|
||||||
|
|
||||||
|
|
||||||
def connect():
|
def connect():
|
||||||
global band
|
global band
|
||||||
|
@ -117,9 +113,7 @@ def start_data_pull():
|
||||||
def start_vibration():
|
def start_vibration():
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
#vibration.timed_vibration(vibration_settings)
|
vibration.timed_vibration(vibration_settings)
|
||||||
vibration.heartrate_alarm(vibration_settings)
|
|
||||||
|
|
||||||
except BTLEDisconnectError:
|
except BTLEDisconnectError:
|
||||||
print("Vibration thread waiting for band reconnect...")
|
print("Vibration thread waiting for band reconnect...")
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
import pyaudio, time, threading
|
||||||
|
import numpy as np
|
||||||
|
from matplotlib import pyplot as plt
|
||||||
|
import matplotlib.animation as animation
|
||||||
|
|
||||||
|
|
||||||
|
CHUNKSIZE = 1024 # fixed chunk size
|
||||||
|
|
||||||
|
# initialize portaudio
|
||||||
|
p = pyaudio.PyAudio()
|
||||||
|
|
||||||
|
info = p.get_host_api_info_by_index(0)
|
||||||
|
numdevices = info.get('deviceCount')
|
||||||
|
for i in range(0, numdevices):
|
||||||
|
if (p.get_device_info_by_host_api_device_index(0, i).get('maxInputChannels')) > 0:
|
||||||
|
print("Input Device id ", i, " - ", p.get_device_info_by_host_api_device_index(0, i).get('name'))
|
||||||
|
|
||||||
|
stream = p.open(
|
||||||
|
format=pyaudio.paInt16,
|
||||||
|
channels=1,
|
||||||
|
rate=44100,
|
||||||
|
input=True,
|
||||||
|
frames_per_buffer=CHUNKSIZE,
|
||||||
|
input_device_index=18
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
plt.style.use('dark_background')
|
||||||
|
graph_figure = plt.figure()
|
||||||
|
graph_figure.canvas.set_window_title('blesleep')
|
||||||
|
|
||||||
|
graph_axes = graph_figure.add_subplot(1, 1, 1)
|
||||||
|
graph_data = {}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def graph_animation(i):
|
||||||
|
graph_axes.clear()
|
||||||
|
|
||||||
|
graph_axes.plot(numpydata)
|
||||||
|
|
||||||
|
ani = animation.FuncAnimation(graph_figure, graph_animation, interval=1000)
|
||||||
|
|
||||||
|
def get_audio_data():
|
||||||
|
global numpydata
|
||||||
|
while True:
|
||||||
|
data = stream.read(CHUNKSIZE)
|
||||||
|
numpydata = np.frombuffer(data, dtype=np.int16)
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
|
||||||
|
threading.Thread(target=get_audio_data).start()
|
||||||
|
|
||||||
|
|
||||||
|
plt.show()
|
||||||
|
|
||||||
|
|
||||||
|
# close stream
|
||||||
|
stream.stop_stream()
|
||||||
|
stream.close()
|
||||||
|
p.terminate()
|
|
@ -0,0 +1,76 @@
|
||||||
|
import time, os, csv
|
||||||
|
from datetime import datetime
|
||||||
|
from matplotlib import pyplot as plt
|
||||||
|
|
||||||
|
datapath = '/home/daddy/Projects/miband/data/2021_02_07/'
|
||||||
|
window_min = 30
|
||||||
|
figure_height = 9
|
||||||
|
figure_width = 18
|
||||||
|
fullscreen = False
|
||||||
|
|
||||||
|
|
||||||
|
files = os.listdir(datapath)
|
||||||
|
wavs = []
|
||||||
|
csvs = []
|
||||||
|
for file in files:
|
||||||
|
if 'wav' in file:
|
||||||
|
wavs.append(file)
|
||||||
|
elif 'csv' in file and 'raw' in file:
|
||||||
|
csvs.append(file)
|
||||||
|
|
||||||
|
|
||||||
|
event_data_list = []
|
||||||
|
for wav in wavs:
|
||||||
|
event_dict = {
|
||||||
|
'name': wav,
|
||||||
|
'data': {
|
||||||
|
'mov_x': [],
|
||||||
|
'mov_y': [],
|
||||||
|
'bpm_x': [],
|
||||||
|
'bpm_y': []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wavtime = datetime.strptime(wav, '%Y_%m_%d__%H_%M_%S.wav')
|
||||||
|
wavestamp = wavtime.timestamp()
|
||||||
|
|
||||||
|
plotdata_mov_x = []
|
||||||
|
plotdata_mov_y = []
|
||||||
|
plotdata_bpm_x = []
|
||||||
|
plotdata_bpm_y = []
|
||||||
|
for mycsv in csvs:
|
||||||
|
with open((datapath + mycsv), newline='') as csvfile:
|
||||||
|
csvreader = csv.DictReader(csvfile, delimiter=',')
|
||||||
|
for row in csvreader:
|
||||||
|
if abs((float(wavestamp) - float(row['time']))) <= (window_min * 60):
|
||||||
|
if 'bpm' in row:
|
||||||
|
event_dict['data']['bpm_x'].append( int(row['bpm']) )
|
||||||
|
event_dict['data']['bpm_y'].append( datetime.fromtimestamp(float(row['time'])) )
|
||||||
|
elif 'movement' in row:
|
||||||
|
event_dict['data']['mov_x'].append( int(row['movement']) )
|
||||||
|
event_dict['data']['mov_y'].append( datetime.fromtimestamp(float(row['time'])) )
|
||||||
|
|
||||||
|
event_data_list.append(event_dict)
|
||||||
|
|
||||||
|
|
||||||
|
for event_data in event_data_list:
|
||||||
|
event_name = event_data['name'].rsplit('.', 1)[0]
|
||||||
|
output_png_filename = '{}{}'.format(event_name, ".png")
|
||||||
|
data = event_data['data']
|
||||||
|
plt.close("all")
|
||||||
|
|
||||||
|
fig, ax = plt.subplots()
|
||||||
|
ax2 = ax.twinx()
|
||||||
|
ax.plot(data['bpm_y'], data['bpm_x'], color="red")
|
||||||
|
ax2.plot(data['mov_y'], data['mov_x'], color="blue")
|
||||||
|
|
||||||
|
fig.set_figheight(figure_height)
|
||||||
|
fig.set_figwidth(figure_width)
|
||||||
|
fig.autofmt_xdate()
|
||||||
|
|
||||||
|
if fullscreen:
|
||||||
|
plt.get_current_fig_manager().full_screen_toggle()
|
||||||
|
plt.title(event_name)
|
||||||
|
if data['bpm_y']:
|
||||||
|
plt.savefig(datapath + output_png_filename)
|
||||||
|
plt.show()
|
|
@ -0,0 +1 @@
|
||||||
|
EB:DD:13:97:9C:0D
|
|
@ -231,7 +231,7 @@ class miband(Peripheral):
|
||||||
def _parse_heart_measure(self, bytes):
|
def _parse_heart_measure(self, bytes):
|
||||||
res = struct.unpack('bb', bytes)[1]
|
res = struct.unpack('bb', bytes)[1]
|
||||||
return_tuple = ["HR", res]
|
return_tuple = ["HR", res]
|
||||||
#print("BPM: {}".format(res))
|
print("BPM: {}".format(res))
|
||||||
return return_tuple
|
return return_tuple
|
||||||
|
|
||||||
|
|
||||||
|
|
17
sleepdata.py
17
sleepdata.py
|
@ -12,7 +12,6 @@ sleep_data = {
|
||||||
'periods': [2, 5, 10, 15],
|
'periods': [2, 5, 10, 15],
|
||||||
'raw_data': [],
|
'raw_data': [],
|
||||||
'averaged_data': [],
|
'averaged_data': [],
|
||||||
'last_hr': []
|
|
||||||
},
|
},
|
||||||
'movement':{
|
'movement':{
|
||||||
'value_name': 'movement',
|
'value_name': 'movement',
|
||||||
|
@ -174,7 +173,6 @@ def process_gyro_data(gyro_data, tick_time):
|
||||||
|
|
||||||
|
|
||||||
def process_heartrate_data(heartrate_data, tick_time):
|
def process_heartrate_data(heartrate_data, tick_time):
|
||||||
last_heartrate_count = 20
|
|
||||||
print("BPM: " + str(heartrate_data))
|
print("BPM: " + str(heartrate_data))
|
||||||
if heartrate_data > 0:
|
if heartrate_data > 0:
|
||||||
value_name = sleep_data['heartrate']['value_name']
|
value_name = sleep_data['heartrate']['value_name']
|
||||||
|
@ -183,21 +181,6 @@ def process_heartrate_data(heartrate_data, tick_time):
|
||||||
value_name: heartrate_data
|
value_name: heartrate_data
|
||||||
} )
|
} )
|
||||||
|
|
||||||
if len(sleep_data['heartrate']['last_hr']) > last_heartrate_count:
|
|
||||||
sleep_data['heartrate']['last_hr'].pop(0)
|
|
||||||
sleep_data['heartrate']['last_hr'].append(heartrate_data)
|
|
||||||
|
|
||||||
|
|
||||||
def analyze_heartrate(hr_count):
|
|
||||||
# Finds the pct change between the lowest HR in the last $hr_count samples and the current HR
|
|
||||||
pct_heartrate_increase = 0
|
|
||||||
if len(sleep_data['heartrate']['last_hr']) >= hr_count:
|
|
||||||
last_heartrate_list = sleep_data['heartrate']['last_hr'][-hr_count:]
|
|
||||||
last_heartrate_min = min(last_heartrate_list)
|
|
||||||
current_heartrate = last_heartrate_list[-1]
|
|
||||||
pct_heartrate_increase = int((current_heartrate - last_heartrate_min)/last_heartrate_min*100)
|
|
||||||
return pct_heartrate_increase
|
|
||||||
|
|
||||||
|
|
||||||
def zero_to_nan(value):
|
def zero_to_nan(value):
|
||||||
if value == 0:
|
if value == 0:
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
import numpy as np
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
x = np.arange(0, 10, 0.1)
|
||||||
|
y1 = 0.05 * x**2
|
||||||
|
y2 = -1 *y1
|
||||||
|
|
||||||
|
fig, ax1 = plt.subplots()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ax2 = ax1.twinx()
|
||||||
|
figures=[manager.canvas.figure
|
||||||
|
for manager in plt._pylab_helpers.Gcf.get_all_fig_managers()]
|
||||||
|
print(figures)
|
||||||
|
|
||||||
|
ax1.plot(x, y1, 'g-')
|
||||||
|
ax2.plot(x, y2, 'b-')
|
||||||
|
|
||||||
|
plt.show()
|
45
vibrate.py
45
vibrate.py
|
@ -12,8 +12,6 @@ import logging
|
||||||
class Vibrate():
|
class Vibrate():
|
||||||
vibrate_band = None
|
vibrate_band = None
|
||||||
vibration_log = None
|
vibration_log = None
|
||||||
heartrate_increase_pct = 0
|
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, band):
|
def __init__(self, band):
|
||||||
self.vibrate_band = band
|
self.vibrate_band = band
|
||||||
|
@ -24,53 +22,12 @@ class Vibrate():
|
||||||
self.vibration_log = logging.getLogger(__name__)
|
self.vibration_log = logging.getLogger(__name__)
|
||||||
self.vibration_log.setLevel(vibration_log_level)
|
self.vibration_log.setLevel(vibration_log_level)
|
||||||
|
|
||||||
|
|
||||||
def heartrate_alarm(self, settings):
|
|
||||||
interval_minutes = settings['interval_minutes']
|
|
||||||
duration_seconds = settings['duration_seconds']
|
|
||||||
vibration_type = settings['type']
|
|
||||||
heartrate_alarm_pct = settings['heartrate_alarm_pct']
|
|
||||||
|
|
||||||
tick_time = time.time()
|
|
||||||
buzz_delay = interval_minutes * 60
|
|
||||||
buzz_timer = tick_time - buzz_delay
|
|
||||||
|
|
||||||
|
|
||||||
self.vibration_log.info("Starting heartrate alarm timer, alarming at {} percent for {} seconds with a {} minute interval".format(
|
|
||||||
heartrate_alarm_pct,
|
|
||||||
duration_seconds,
|
|
||||||
interval_minutes))
|
|
||||||
if vibration_type not in ['random', 'pattern', 'rolling', 'continuous']:
|
|
||||||
self.vibration_log.warn("Invalid or no vibration type specified: {}".format(type))
|
|
||||||
self.vibration_log.warn("Must be one of these: random, pattern, rolling, continuous")
|
|
||||||
return
|
|
||||||
|
|
||||||
while True:
|
|
||||||
elapsed_time = tick_time - buzz_timer
|
|
||||||
if elapsed_time >= buzz_delay and self.heartrate_increase_pct >= heartrate_alarm_pct:
|
|
||||||
self.vibration_log.info("Heartrate alarm triggered at {} percent, buzzing".format(self.heartrate_increase_pct))
|
|
||||||
if vibration_type == 'random':
|
|
||||||
self.vibrate_random(duration_seconds)
|
|
||||||
elif vibration_type == 'pattern':
|
|
||||||
self.vibrate_pattern(duration_seconds)
|
|
||||||
elif vibration_type == 'rolling':
|
|
||||||
self.vibrate_rolling(duration_seconds)
|
|
||||||
elif vibration_type == 'continuous':
|
|
||||||
self.vibrate_continuous(duration_seconds)
|
|
||||||
buzz_timer = tick_time
|
|
||||||
elif not elapsed_time >= buzz_delay and self.heartrate_increase_pct >= heartrate_alarm_pct:
|
|
||||||
self.vibration_log.info("Heartrate alarm threshold reached ({} percent) but timout not expired".format(self.heartrate_increase_pct))
|
|
||||||
else:
|
|
||||||
tick_time = time.time()
|
|
||||||
time.sleep(0.5)
|
|
||||||
|
|
||||||
|
|
||||||
def timed_vibration(self, settings):
|
def timed_vibration(self, settings):
|
||||||
interval_minutes = settings['interval_minutes']
|
interval_minutes = settings['interval_minutes']
|
||||||
duration_seconds = settings['duration_seconds']
|
duration_seconds = settings['duration_seconds']
|
||||||
type = settings['type']
|
type = settings['type']
|
||||||
|
|
||||||
buzz_timer = time.time()
|
buzz_timer = time.time()
|
||||||
tick_time = time.time()
|
tick_time = time.time()
|
||||||
buzz_delay = interval_minutes * 60
|
buzz_delay = interval_minutes * 60
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue