Broke vibration out into its own module.
This commit is contained in:
parent
91d17efa96
commit
45e603270e
112
bluesleep.py
112
bluesleep.py
|
@ -1,31 +1,26 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
from bluepy import btle
|
import time, re, threading
|
||||||
from bluepy.btle import BTLEDisconnectError
|
from bluepy.btle import BTLEDisconnectError
|
||||||
|
|
||||||
from miband import miband
|
from miband import miband
|
||||||
import sleepdata
|
import sleepdata, vibrate
|
||||||
|
|
||||||
import threading
|
|
||||||
import re
|
|
||||||
import random
|
|
||||||
|
|
||||||
import subprocess
|
|
||||||
import time
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
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"
|
|
||||||
|
vibration_settings = {
|
||||||
|
'interval_minutes': 0.2,
|
||||||
|
'duration_seconds': 5,
|
||||||
|
'type': 'random'
|
||||||
|
}
|
||||||
|
|
||||||
band = None
|
band = None
|
||||||
|
|
||||||
buzz_timer = time.time()
|
|
||||||
buzz_minutes = 45
|
|
||||||
buzz_delay = buzz_minutes * 60
|
|
||||||
|
|
||||||
#-------------------------------------------------------------------------#
|
#-------------------------------------------------------------------------#
|
||||||
|
|
||||||
|
|
||||||
class regex_patterns():
|
class regex_patterns():
|
||||||
mac_regex_pattern = re.compile(r'([0-9a-fA-F]{2}(?::[0-9a-fA-F]{2}){5})')
|
mac_regex_pattern = re.compile(r'([0-9a-fA-F]{2}(?::[0-9a-fA-F]{2}){5})')
|
||||||
authkey_regex_pattern = re.compile(r'([0-9a-fA-F]){32}')
|
authkey_regex_pattern = re.compile(r'([0-9a-fA-F]){32}')
|
||||||
|
@ -60,67 +55,27 @@ def get_auth_key(filename):
|
||||||
exit(1)
|
exit(1)
|
||||||
return AUTH_KEY
|
return AUTH_KEY
|
||||||
|
|
||||||
def process_data(data, tick_time):
|
|
||||||
if data[0] == "GYRO":
|
|
||||||
sleepdata.process_gyro_data(data[1], tick_time)
|
|
||||||
elif data[0] == "HR":
|
|
||||||
sleepdata.process_heartrate_data(data[1], tick_time)
|
|
||||||
|
|
||||||
def average_data(tick_time):
|
def average_data(tick_time):
|
||||||
if (tick_time - sleepdata.last_tick_time) >= sleepdata.tick_seconds:
|
if (tick_time - sleepdata.last_tick_time) >= sleepdata.tick_seconds:
|
||||||
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 timed_buzzing(buzz_delay, buzz_duration):
|
|
||||||
buzz_timer = time.time()
|
|
||||||
tick_time = time.time()
|
|
||||||
while True:
|
|
||||||
elapsed_time = tick_time - buzz_timer
|
|
||||||
if elapsed_time >= buzz_delay:
|
|
||||||
print("Buzz timer expired, buzzing")
|
|
||||||
vibrate_random(buzz_duration)
|
|
||||||
buzz_timer = tick_time
|
|
||||||
else:
|
|
||||||
tick_time = time.time()
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
def generate_random_vibration_pattern(count):
|
|
||||||
pulse_pattern = []
|
|
||||||
pulse_range = [120, 240]
|
|
||||||
pulse_interval_range = [1, 8]
|
|
||||||
for _ in range(count):
|
|
||||||
buzz_pulse = random.randrange(pulse_range[0], pulse_range[1])
|
|
||||||
buzz_delay = random.randrange(pulse_interval_range[0], pulse_interval_range[1])/10
|
|
||||||
pulse_pattern.append([buzz_pulse, buzz_delay])
|
|
||||||
return pulse_pattern
|
|
||||||
|
|
||||||
def vibrate_random(duration):
|
|
||||||
print("Sending random vibration...")
|
|
||||||
duration_start = time.time()
|
|
||||||
|
|
||||||
pulse_pattern = generate_random_vibration_pattern(20)
|
|
||||||
|
|
||||||
while True:
|
|
||||||
if (time.time() - duration_start) >= duration:
|
|
||||||
print ("Stopping vibration")
|
|
||||||
band.vibrate(0)
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
for pattern in pulse_pattern:
|
|
||||||
if (time.time() - duration_start) >= duration:
|
|
||||||
break
|
|
||||||
vibrate_ms = pattern[0]
|
|
||||||
vibro_delay = pattern[1]
|
|
||||||
band.vibrate(vibrate_ms)
|
|
||||||
time.sleep(vibro_delay)
|
|
||||||
|
|
||||||
def sleep_monitor_callback(data):
|
def sleep_monitor_callback(data):
|
||||||
tick_time = time.time()
|
tick_time = time.time()
|
||||||
|
|
||||||
if not sleepdata.last_tick_time:
|
if not sleepdata.last_tick_time:
|
||||||
sleepdata.last_tick_time = time.time()
|
sleepdata.last_tick_time = time.time()
|
||||||
process_data(data, tick_time)
|
|
||||||
|
if data[0] == "GYRO":
|
||||||
|
sleepdata.process_gyro_data(data[1], tick_time)
|
||||||
|
elif data[0] == "HR":
|
||||||
|
sleepdata.process_heartrate_data(data[1], tick_time)
|
||||||
|
|
||||||
average_data(tick_time)
|
average_data(tick_time)
|
||||||
|
|
||||||
|
|
||||||
def connect():
|
def connect():
|
||||||
global band
|
global band
|
||||||
success = False
|
success = False
|
||||||
|
@ -134,6 +89,7 @@ def connect():
|
||||||
try:
|
try:
|
||||||
band = miband(MAC_ADDR, AUTH_KEY, debug=True)
|
band = miband(MAC_ADDR, AUTH_KEY, debug=True)
|
||||||
success = band.initialize()
|
success = band.initialize()
|
||||||
|
vibrate.band = band
|
||||||
except BTLEDisconnectError:
|
except BTLEDisconnectError:
|
||||||
print(msg.format(timeout))
|
print(msg.format(timeout))
|
||||||
time.sleep(timeout)
|
time.sleep(timeout)
|
||||||
|
@ -141,6 +97,7 @@ def connect():
|
||||||
print("\nExit.")
|
print("\nExit.")
|
||||||
exit()
|
exit()
|
||||||
|
|
||||||
|
|
||||||
def start_data_pull():
|
def start_data_pull():
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
|
@ -149,37 +106,20 @@ def start_data_pull():
|
||||||
band.gyro_started_flag = False
|
band.gyro_started_flag = False
|
||||||
connect()
|
connect()
|
||||||
|
|
||||||
def vibrate_pattern(duration):
|
|
||||||
print("Sending vibration...")
|
|
||||||
duration_start = time.time()
|
|
||||||
pulse_pattern = [[30, 0.01], [60, 0.01], [90, 0.01], [120, 0.01], [150, 0.01], [180, 0.01]]
|
|
||||||
|
|
||||||
|
def start_vibration():
|
||||||
while True:
|
while True:
|
||||||
if (time.time() - duration_start) >= duration:
|
try:
|
||||||
print ("Stopping vibration")
|
vibrate.timed_vibration(vibration_settings)
|
||||||
band.vibrate(0)
|
except BTLEDisconnectError:
|
||||||
break
|
print("Vibration thread waiting for band reconnect...")
|
||||||
else:
|
time.sleep(1)
|
||||||
for pattern in pulse_pattern:
|
|
||||||
if (time.time() - duration_start) >= duration:
|
|
||||||
break
|
|
||||||
vibrate_ms = pattern[0]
|
|
||||||
vibro_delay = pattern[1]
|
|
||||||
band.vibrate(vibrate_ms)
|
|
||||||
time.sleep(vibro_delay)
|
|
||||||
|
|
||||||
def vibrate_rolling():
|
|
||||||
print("Sending rolling vibration...")
|
|
||||||
for x in range(10):
|
|
||||||
for x in range(20, 40, 1):
|
|
||||||
band.vibrate(x)
|
|
||||||
for x in range(40, 20, -1):
|
|
||||||
band.vibrate(x)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
connect()
|
connect()
|
||||||
threading.Thread(target=start_data_pull).start()
|
threading.Thread(target=start_data_pull).start()
|
||||||
threading.Thread(target=timed_buzzing, args=([buzz_delay, 15])).start()
|
threading.Thread(target=start_vibration).start()
|
||||||
#sleepdata.init_graph()
|
#sleepdata.init_graph()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,8 @@ class miband4():
|
||||||
|
|
||||||
class bytepatterns():
|
class bytepatterns():
|
||||||
vibration = 'ff{:02x}00000001'
|
vibration = 'ff{:02x}00000001'
|
||||||
|
vibration_stop = 'ff0000000000'
|
||||||
|
|
||||||
gyro_start = '01{:02x}19'
|
gyro_start = '01{:02x}19'
|
||||||
start = '0100'
|
start = '0100'
|
||||||
stop = '0000'
|
stop = '0000'
|
||||||
|
@ -19,6 +21,9 @@ class miband4():
|
||||||
auth_key_prefix = '0300'
|
auth_key_prefix = '0300'
|
||||||
|
|
||||||
def vibration(duration):
|
def vibration(duration):
|
||||||
|
if duration == 0:
|
||||||
|
byte_pattern = miband4.bytepatterns.vibration_stop
|
||||||
|
else:
|
||||||
byte_pattern = miband4.bytepatterns.vibration
|
byte_pattern = miband4.bytepatterns.vibration
|
||||||
return bytes.fromhex(byte_pattern.format(duration))
|
return bytes.fromhex(byte_pattern.format(duration))
|
||||||
|
|
||||||
|
|
18
miband.py
18
miband.py
|
@ -224,12 +224,22 @@ class miband(Peripheral):
|
||||||
except Empty:
|
except Empty:
|
||||||
break
|
break
|
||||||
|
|
||||||
def vibrate(self, ms):
|
def vibrate(self, value):
|
||||||
|
if value == 255 or value == 0:
|
||||||
|
# '255' means 'continuous vibration'
|
||||||
|
# I've arbitrarily assigned the otherwise pointless value of '0' to indicate 'stop_vibration'
|
||||||
|
# These modes do not require pulse timing to avoid strange behavior.
|
||||||
|
self.write_cmd(self._char_alert, bytepattern.vibration(value), queued=True)
|
||||||
|
else:
|
||||||
|
# A value of '150' will vibrate for ~200ms, hence vibration_scaler.
|
||||||
|
# This isn't exact however, but does leave a ~5ms gap between pulses.
|
||||||
|
# A scaler any lower causes the pulses to be indistinguishable from each other to a human.
|
||||||
|
# I considered making this function accept a desired amount of vibration time in ms,
|
||||||
|
# however it was fiddly and I couldn't get it right. More work could be done here.
|
||||||
vibration_scaler = 0.75
|
vibration_scaler = 0.75
|
||||||
ms = min([round(ms / vibration_scaler), 255])
|
ms = round(value / vibration_scaler)
|
||||||
sent_value = int(ms / 2)
|
|
||||||
vibration_duration = ms / 1000
|
vibration_duration = ms / 1000
|
||||||
self.write_cmd(self._char_alert, bytepattern.vibration(sent_value), queued=True)
|
self.write_cmd(self._char_alert, bytepattern.vibration(value), queued=True)
|
||||||
time.sleep(vibration_duration)
|
time.sleep(vibration_duration)
|
||||||
|
|
||||||
def write_cmd(self, characteristic, data, response=False, queued=False):
|
def write_cmd(self, characteristic, data, response=False, queued=False):
|
||||||
|
|
|
@ -26,9 +26,8 @@ sleep_data = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
last_heartrate = 0
|
|
||||||
last_tick_time = None
|
|
||||||
tick_seconds = 0.5
|
tick_seconds = 0.5
|
||||||
|
last_tick_time = None
|
||||||
|
|
||||||
datestamp = datetime.now().strftime("%Y_%m_%d")
|
datestamp = datetime.now().strftime("%Y_%m_%d")
|
||||||
csv_header_name_format = '{}_{}'
|
csv_header_name_format = '{}_{}'
|
||||||
|
@ -39,7 +38,7 @@ graph_figure = plt.figure()
|
||||||
graph_axes = graph_figure.add_subplot(1, 1, 1)
|
graph_axes = graph_figure.add_subplot(1, 1, 1)
|
||||||
graph_data = {}
|
graph_data = {}
|
||||||
|
|
||||||
|
last_heartrate = 0
|
||||||
|
|
||||||
class Average_Gyro_Data():
|
class Average_Gyro_Data():
|
||||||
gyro_last_x = 0
|
gyro_last_x = 0
|
||||||
|
@ -62,7 +61,6 @@ class Average_Gyro_Data():
|
||||||
return gyro_movement
|
return gyro_movement
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def write_csv(data, name):
|
def write_csv(data, name):
|
||||||
fieldnames = ['time']
|
fieldnames = ['time']
|
||||||
for fieldname in data[0]:
|
for fieldname in data[0]:
|
||||||
|
@ -108,6 +106,7 @@ def flush_old_raw_data(tick_time):
|
||||||
if old_raw_data:
|
if old_raw_data:
|
||||||
write_csv(old_raw_data, 'raw')
|
write_csv(old_raw_data, 'raw')
|
||||||
|
|
||||||
|
|
||||||
def average_raw_data(tick_time):
|
def average_raw_data(tick_time):
|
||||||
global last_heartrate
|
global last_heartrate
|
||||||
timestamp = datetime.fromtimestamp(tick_time)
|
timestamp = datetime.fromtimestamp(tick_time)
|
||||||
|
@ -151,7 +150,7 @@ def process_gyro_data(gyro_data, tick_time):
|
||||||
sleep_move = sleep_data['movement']
|
sleep_move = sleep_data['movement']
|
||||||
value_name = sleep_move['value_name']
|
value_name = sleep_move['value_name']
|
||||||
gyro_movement = average_gyro_data.process(gyro_data)
|
gyro_movement = average_gyro_data.process(gyro_data)
|
||||||
print("Gyro: {}".format(gyro_movement))
|
#print("Gyro: {}".format(gyro_movement))
|
||||||
sleep_move['raw_data'].append({
|
sleep_move['raw_data'].append({
|
||||||
'time': tick_time,
|
'time': tick_time,
|
||||||
value_name: gyro_movement
|
value_name: gyro_movement
|
||||||
|
|
|
@ -0,0 +1,152 @@
|
||||||
|
import time
|
||||||
|
import random
|
||||||
|
import logging
|
||||||
|
|
||||||
|
band = None
|
||||||
|
|
||||||
|
# Notes:
|
||||||
|
# The miband4 does not (seem to) support different vibration intensities, rather the values sent (2-255)
|
||||||
|
# represent how long the vibration motor runs. A value of 30 roughly corresponds to 60ms of motor run time.
|
||||||
|
# Sending a value of 255 triggers continuous vibration.
|
||||||
|
# Currently "continuous" mode doesn't work, as it doesn't turn off.
|
||||||
|
# This will be fixed shortly.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == 'vibrate':
|
||||||
|
FORMAT = '%(asctime)-15s %(name)s (%(levelname)s) > %(message)s'
|
||||||
|
logging.basicConfig(format=FORMAT)
|
||||||
|
vibration_log_level = logging.INFO
|
||||||
|
vibration_log = logging.getLogger(__name__)
|
||||||
|
vibration_log.setLevel(vibration_log_level)
|
||||||
|
|
||||||
|
|
||||||
|
def timed_vibration(settings):
|
||||||
|
interval_minutes = settings['interval_minutes']
|
||||||
|
duration_seconds = settings['duration_seconds']
|
||||||
|
type = settings['type']
|
||||||
|
|
||||||
|
buzz_timer = time.time()
|
||||||
|
tick_time = time.time()
|
||||||
|
buzz_delay = interval_minutes * 60
|
||||||
|
|
||||||
|
vibration_log.info("Starting vibration timer: {} minutes".format(interval_minutes))
|
||||||
|
|
||||||
|
if type not in ['random', 'pattern', 'rolling', 'continuous']:
|
||||||
|
vibration_log.warn("Invalid or no vibration type specified: {}".format(type))
|
||||||
|
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:
|
||||||
|
print("Buzz timer expired, buzzing")
|
||||||
|
if type == 'random':
|
||||||
|
vibrate_random(duration_seconds)
|
||||||
|
elif type == 'pattern':
|
||||||
|
vibrate_pattern(duration_seconds)
|
||||||
|
elif type == 'rolling':
|
||||||
|
vibrate_rolling(duration_seconds)
|
||||||
|
elif type == 'continuous':
|
||||||
|
vibrate_continuous(duration_seconds)
|
||||||
|
|
||||||
|
buzz_timer = tick_time
|
||||||
|
else:
|
||||||
|
tick_time = time.time()
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_random_vibration_pattern(pulse_count):
|
||||||
|
#pulse_duration_range and pulse_interval_range_ms are arbitrary
|
||||||
|
pulse_duration_range = {
|
||||||
|
'low': 60,
|
||||||
|
'high': 100
|
||||||
|
}
|
||||||
|
pulse_interval_range_ms = {
|
||||||
|
'low': 100,
|
||||||
|
'high': 800
|
||||||
|
}
|
||||||
|
|
||||||
|
output_pulse_pattern = []
|
||||||
|
for _ in range(pulse_count):
|
||||||
|
pulse_duration = random.randrange(pulse_duration_range['low'], pulse_duration_range['high'])
|
||||||
|
pulse_interval = random.randrange(pulse_interval_range_ms['low'], pulse_interval_range_ms['high'])/1000
|
||||||
|
output_pulse_pattern.append([pulse_duration, pulse_interval])
|
||||||
|
return output_pulse_pattern
|
||||||
|
|
||||||
|
|
||||||
|
def vibrate_random(duration_seconds):
|
||||||
|
print("Sending random vibration...")
|
||||||
|
duration_start = time.time()
|
||||||
|
|
||||||
|
pattern_length = 20 #This value is arbitrary
|
||||||
|
|
||||||
|
pulse_pattern = generate_random_vibration_pattern(pattern_length)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
if (time.time() - duration_start) >= duration_seconds:
|
||||||
|
print ("Stopping vibration")
|
||||||
|
band.vibrate(0)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
for pattern in pulse_pattern:
|
||||||
|
if (time.time() - duration_start) >= duration_seconds:
|
||||||
|
break
|
||||||
|
vibrate_ms = pattern[0]
|
||||||
|
vibro_delay = pattern[1]
|
||||||
|
band.vibrate(vibrate_ms)
|
||||||
|
time.sleep(vibro_delay)
|
||||||
|
|
||||||
|
|
||||||
|
def vibrate_pattern(duration_seconds):
|
||||||
|
print("Sending vibration...")
|
||||||
|
duration_start = time.time()
|
||||||
|
|
||||||
|
#This pattern is an example.
|
||||||
|
pulse_pattern = [[30, 0.01], [60, 0.01], [90, 0.01], [120, 0.01], [150, 0.01], [180, 0.01]]
|
||||||
|
|
||||||
|
while True:
|
||||||
|
if (time.time() - duration_start) >= duration_seconds:
|
||||||
|
print ("Stopping vibration")
|
||||||
|
band.vibrate(0)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
for pattern in pulse_pattern:
|
||||||
|
if (time.time() - duration_start) >= duration_seconds:
|
||||||
|
break
|
||||||
|
vibrate_ms = pattern[0]
|
||||||
|
vibro_delay = pattern[1]
|
||||||
|
band.vibrate(vibrate_ms)
|
||||||
|
time.sleep(vibro_delay)
|
||||||
|
|
||||||
|
|
||||||
|
def vibrate_rolling(duration_seconds):
|
||||||
|
print("Sending rolling vibration...")
|
||||||
|
|
||||||
|
duration_start = time.time()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
if (time.time() - duration_start) >= duration_seconds:
|
||||||
|
print ("Stopping vibration")
|
||||||
|
band.vibrate(0)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
for x in range(10):
|
||||||
|
for x in range(20, 40, 1):
|
||||||
|
band.vibrate(x)
|
||||||
|
for x in range(40, 20, -1):
|
||||||
|
band.vibrate(x)
|
||||||
|
|
||||||
|
def vibrate_continuous(duration_seconds):
|
||||||
|
#Currently broken, still working on this bit.
|
||||||
|
print("Sending continuous vibration...")
|
||||||
|
|
||||||
|
duration_start = time.time()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
if (time.time() - duration_start) >= duration_seconds:
|
||||||
|
print ("Stopping vibration")
|
||||||
|
band.vibrate(0)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
band.vibrate(1)
|
Loading…
Reference in New Issue