128 bin/oref0-autotune.py 100755 → 100644
@@ -38,7 +38,20 @@
TERMINAL_LOGGING = True
RECOMMENDS_REPORT = True



def get_input_arguments():
"""Get command line arguments
Gets the command line arguments, and then returns them to main.
Args:
None
Returns:
args (argparse.Namespace): Namespace object containing all of the command line arguments
"""

parser = argparse.ArgumentParser(description='Autotune')

# Required
@@ -82,7 +95,20 @@ def get_input_arguments():

return parser.parse_args()



def assign_args_to_variables(args):
"""Assign arguments to variables
Takes the arguments passed to the script and assigns them to variables.
Args:
args (argparse.Namespace): Arguments passed to the script
Returns:
None
"""

# TODO: Input checking.

global DIR, NIGHTSCOUT_HOST, START_DATE, END_DATE, NUMBER_OF_RUNS, \
@@ -108,13 +134,39 @@ def assign_args_to_variables(args):
if args.log is not None:
RECOMMENDS_REPORT = args.logs



def get_nightscout_profile(nightscout_host):
"""Get Nightscout profile from Nightscout host
Gets the Nightscout profile from the Nightscout host, and then saves it to the autotune directory.
Args:
nightscout_host (str): Nightscout URL (required)
Returns:
None
"""

#TODO: Add ability to use API secret for Nightscout.
res = requests.get(nightscout_host + '/api/v1/profile.json')
with open(os.path.join(autotune_directory, 'nightscout.profile.json'), 'w') as f: # noqa: F821
f.write(res.text)



def get_openaps_profile(directory):
"""Get the current profile from openaps
Copies the current profile from openaps into the autotune directory.
Args:
directory (str): Path to autotune directory (required)
Returns:
None
"""

shutil.copy(os.path.join(directory, 'settings', 'pumpprofile.json'), os.path.join(directory, 'autotune', 'profile.pump.json'))

# If a previous valid settings/autotune.json exists, use that; otherwise start from settings/profile.json
@@ -139,7 +191,23 @@ def get_openaps_profile(directory):
#TODO: Do the correct copying here.
# cat autotune/profile.json | json | grep -q start || cp autotune/profile.pump.json autotune/profile.json'])



def get_nightscout_carb_and_insulin_treatments(nightscout_host, start_date, end_date, directory):
"""Grab treatments.json from Nightscout
Gets the treatments.json file from Nightscout, and saves it to the autotune directory.
Args:
nightscout_host (str): Nightscout URL (required)
start_date (datetime): Start date (required)
end_date (datetime): End date (required)
directory (str): Autotune directory (required)
Returns:
None
"""

logging.info('Grabbing NIGHTSCOUT treatments.json for date range: {0} to {1}'.format(start_date, end_date))
# TODO: What does 'T20:00-05:00' mean?
output_file_name = os.path.join(directory, 'autotune', 'ns-treatments.json')
@@ -151,7 +219,23 @@ def get_nightscout_carb_and_insulin_treatments(nightscout_host, start_date, end_
with open(output_file_name, 'w') as f:
f.write(res.text.encode('utf-8'))



def get_nightscout_bg_entries(nightscout_host, start_date, end_date, directory):
"""Grab entries/sgv.json from nightscout for date range
Gets the entries/sgv.json from nightscout for the specified date range.
Args:
nightscout_host (str): Nightscout URL (required)
start_date (datetime): Start date (required)
end_date (datetime): End date (required)
directory (str): Directory to save files to (required)
Returns:
None
"""

logging.info('Grabbing NIGHTSCOUT enries/sgv.json for date range: {0} to {1}'.format(start_date.strftime("%Y-%m-%d"), end_date.strftime("%Y-%m-%d")))
date_list = [start_date + datetime.timedelta(days=x) for x in range(0, (end_date - start_date).days)]

@@ -163,7 +247,23 @@ def get_nightscout_bg_entries(nightscout_host, start_date, end_date, directory):
with open(os.path.join(directory, 'autotune', 'ns-entries.{date}.json'.format(date=date.strftime("%Y-%m-%d"))), 'w') as f:
f.write(res.text.encode('utf-8'))



def run_autotune(start_date, end_date, number_of_runs, directory):
"""Run autotune for a given number of runs
Runs autotune for a given number of runs, for a given date range.
Args:
start_date (datetime): Start date of date range (required)
end_date (datetime): End date of date range (required)
number_of_runs (int): Number of runs (required)
directory (str): Directory to store autotune files (required)
Returns:
None
"""

date_list = [start_date + datetime.timedelta(days=x) for x in range(0, (end_date - start_date).days)]
autotune_directory = os.path.join(directory, 'autotune')
for run_number in range(1, number_of_runs + 1):
@@ -210,11 +310,39 @@ def run_autotune(start_date, end_date, number_of_runs, directory):
shutil.copy(os.path.join(autotune_directory, 'newprofile.{run_number}.{date}.json'.format(run_number=run_number, date=date.strftime("%Y-%m-%d"))),
os.path.join(autotune_directory, 'profile.json'))



def export_to_excel(output_directory, output_excel_filename):
"""Export autotune data to Excel
Exports autotune data to Excel.
Args:
output_directory (str): Output directory (required)
output_excel_filename (str): Output Excel filename (required)
Returns:
None
"""

autotune_export_to_xlsx = 'oref0-autotune-export-to-xlsx --dir {0} --output {1}'.format(output_directory, output_excel_filename)
call(autotune_export_to_xlsx, shell=True)



def create_summary_report_and_display_results(output_directory):
"""Create a summary report and display the results
Creates a summary report of the autotune results, and then displays the results
to the terminal.
Args:
output_directory (str): Output directory (required)
Returns:
None
"""

print()
print("Autotune pump profile recommendations:")
print("---------------------------------------------------------")
@@ -28,20 +28,57 @@
import argparse
import re



def parseDateAndRun(filename):
"""Parse date and run from filename
Parses the date and run from a filename.
Args:
filename (str): Filename (required)
Returns:
tuple: (date, run)
"""

m=re.match( r'profile.(?P<run>.*).(?P<date>20[0-9][0-9]-[01][0-9]-[0-3][0-9]).json', filename)
if m:
return (m.group('date'), m.group('run'))
else: # not found
return ('-','-')



def calc_minutes(timestr):
"""Returns the number of minutes from midnight. Seconds are ignored
Args:
timestr (str): Time string in HH:MM format (required)
Returns:
int: Number of minutes from midnight
"""

# returns the number of minutes from midnight. seconds are ignored
# based on http://stackoverflow.com/questions/10663720/converting-a-time-string-to-seconds-in-python
ftr = [60,1,0] # ignore seconds, count minutes, and use 60 minutes per hour
return sum([a*b for a,b in zip(ftr, map(int,timestr.split(':')))])



def expandProfile(l, valueField, offsetField):
"""Expand a profile to cover the full day
Args:
l (list): Profile list (required)
valueField (str): Value field (required)
offsetField (str): Offset field (required)
Returns:
list: Expanded profile
"""

r=[]
minutes=0
value=l[0][valueField]
@@ -63,7 +100,22 @@ def expandProfile(l, valueField, offsetField):
# return the expanded profile
return r



def writeExcelHeader(ws, date_format, headerFormat):
"""Write header to Excel worksheet
Writes the header to the Excel worksheet.
Args:
ws (xlsxwriter.worksheet.Worksheet): Excel worksheet (required)
date_format (xlsxwriter.format.Format): Excel date format (required)
headerFormat (xlsxwriter.format.Format): Excel header format (required)
Returns:
None
"""

ws.write_string(0,0, 'Filename', headerFormat)
ws.write_string(0,1, 'Date', headerFormat)
ws.write_string(0,2, 'Run', headerFormat)
@@ -74,7 +126,23 @@ def writeExcelHeader(ws, date_format, headerFormat):
ws.write_datetime(0, col, dt, date_format)
col=col+1



def write_excel_profile(worksheet, row, expandedList, excel_number_format):
"""Write profile to Excel worksheet
Writes a profile to an Excel worksheet.
Args:
worksheet (xlsxwriter.worksheet.Worksheet): Excel worksheet (required)
row (int): Row number (required)
expandedList (list): List of expanded profile entries (required)
excel_number_format (xlsxwriter.format.Format): Excel number format (required)
Returns:
None
"""

worksheet.write_string(row, 0, filename)
date, run = parseDateAndRun(filename)
worksheet.write_string(row, 1, date)
@@ -84,7 +152,20 @@ def write_excel_profile(worksheet, row, expandedList, excel_number_format):
worksheet.write_number(row, col, expandedList[i], excel_number_format)
col=col+1



def excel_init_workbook(workbook):
"""Initialize the Excel workbook
Initializes the Excel workbook, and returns the worksheet objects for the basal and isf profiles.
Args:
workbook (xlsxwriter.Workbook): Workbook object (required)
Returns:
(xlsxwriter.Worksheet, xlsxwriter.Worksheet): Worksheet objects for basal and isf profiles
"""

#see http://xlsxwriter.readthedocs.io/format.html#format for documentation on the Excel format's
excel_hour_format = workbook.add_format({'num_format': 'hh:mm', 'bold': True, 'font_color': 'black'})
excel_2decimals_format = workbook.add_format({'num_format': '0.00', 'font_size': '16'})
64 bin/oref0_nightscout_check.py 100755 → 100644
@@ -21,13 +21,39 @@
token_dict["exp"]=-1
auth_headers={}



def init(args):
"""Initialize logging
Sets up logging, and sets the log level based on the verbose flag.
Args:
args (argparse.Namespace): Arguments passed to the script
Returns:
None
"""

if args.verbose:
logging.basicConfig(level=logging.DEBUG, stream=sys.stdout, format='%(asctime)s %(levelname)s %(message)s')
else:
logging.basicConfig(level=logging.INFO, stream=sys.stdout, format='%(asctime)s %(levelname)s %(message)s')



def parse_ns_ini(filename):
"""Parse Nightscout ini file
Parses the Nightscout ini file, and sets the global variables nightscout_host, api_secret, and token_secret.
Args:
filename (str): Nightscout ini file (required)
Returns:
None
"""

global nightscout_host, api_secret, token_secret
logging.debug("Parsing %s" % filename)
config = configparser.ConfigParser()
@@ -64,7 +90,19 @@ def parse_ns_ini(filename):
sys.exit(1)



def get_nightscout_authorization_token():
"""Get Nightscout authorization token
Gets an authorization token from Nightscout.
Args:
None
Returns:
None
"""

global nightscout_host, token_secret, token_dict, auth_headers
logging.debug("get_nightscout_authorization_token")
try:
@@ -85,12 +123,38 @@ def get_nightscout_authorization_token():
sys.exit(1)



def startup_checks(args):
"""Perform startup checks
Checks for the existence of the nightscout.ini file, and then checks
the nightscout host and token.
Args:
args (argparse.Namespace): Arguments passed to the script
Returns:
None
"""

parse_ns_ini(args.nsini)
logging.info("Nightscout host: %s" % nightscout_host)
get_nightscout_authorization_token()



def check_permissions():
"""Check if the token has the required permissions
Checks if the token has the required permissions to use the API.
Args:
None
Returns:
None
"""

global token_dict
pg=token_dict['permissionGroups'][0]
if pg==["*"]: # admin role