diff options
-rw-r--r-- | README | 2 | ||||
-rw-r--r-- | glucometerutils/drivers/accuchek_reports.py | 129 |
2 files changed, 131 insertions, 0 deletions
@@ -29,6 +29,7 @@ information on each of the devices. | LifeScan | OneTouch Verio (USB) | `otverio2015` | | LifeScan | OneTouch Select Plus | `otverio2015` | | Abbott | FreeStyle Optium | `freestyle_optium` | +| Roche | Accu-Chek Mobile | `accuchek_reports` | ### Driver features @@ -38,6 +39,7 @@ information on each of the devices. | `otultraeasy` | serialno, swver, unit | get and set | not supported by device | yes | | `otverio2015` | serialno, swver | get and set | no | yes | | `freestyle_optium` | serialno, swver, unit | get and set | not supported by device | not supported by device | +| `accuchek_reports` | serialno, unit | no | yes | not supported by device | ### Driver dependencies diff --git a/glucometerutils/drivers/accuchek_reports.py b/glucometerutils/drivers/accuchek_reports.py new file mode 100644 index 0000000..941491e --- /dev/null +++ b/glucometerutils/drivers/accuchek_reports.py @@ -0,0 +1,129 @@ +# -*- coding: utf-8 -*- +"""Driver for Accu-Chek Mobile devices with reports mode. + +This driver expects a mountpoint as the device name, and will read the +data off the CSV file found there. This means it is read-only access, +but has no dependencies at all. + +The Accu-Chek Mobile meters should be set to "Reports" mode. + +""" + +__author__ = 'Diego Elio Pettenò' +__email__ = 'flameeyes@flameeyes.eu' +__copyright__ = 'Copyright © 2016, Diego Elio Pettenò' +__license__ = 'MIT' + +import csv +import datetime +import glob +import os + +from glucometerutils import common +from glucometerutils import exceptions + +_UNIT_MAP = { + 'mmol/l': common.UNIT_MMOLL, + 'mg/dl': common.UNIT_MGDL, +} + +_DATE_CSV_KEY = 'Date' +_TIME_CSV_KEY = 'Time' +_RESULT_CSV_KEY = 'Result' +_UNIT_CSV_KEY = 'Unit' +_TEMPWARNING_CSV_KEY = 'Temperature warning' # ignored +_OUTRANGE_CSV_KEY = 'Out of target range' # ignored +_OTHER_CSV_KEY = 'Other' # ignored +_BEFORE_MEAL_CSV_KEY = 'Before meal' +_AFTER_MEAL_CSV_KEY = 'After meal' +# Control test has extra whitespace which is not ignored. +_CONTROL_CSV_KEY = 'Control test' + ' '*197 + +_DATE_FORMAT = '%d.%m.%Y' +_TIME_FORMAT = '%H:%M' + +_DATETIME_FORMAT = ' '.join((_DATE_FORMAT, _TIME_FORMAT)) + +class Device(object): + def __init__(self, device): + report_files = glob.glob(os.path.join(device, '*', 'Reports', '*.csv')) + if not report_files: + raise exceptions.ConnectionFailed( + 'No report file found in path "%s".' % reports_path) + + self.report_file = report_files[0] + + def _get_records_reader(self): + self.report.seek(0) + # Skip the first two lines + next(self.report) + next(self.report) + + return csv.DictReader( + self.report, delimiter=';', skipinitialspace=True, quoting=csv.QUOTE_NONE) + + def connect(self): + self.report = open(self.report_file, 'r', newline='\r\n', encoding='utf-8') + + def disconnect(self): + self.report.close() + + def get_information_string(self): + return ('%s glucometer\n' + 'Serial number: %s\n' + 'Default unit: %s' % ( + self.get_model(), + self.get_serial_number(), + self.get_glucose_unit())) + + def get_model(self): + # $device/MODEL/Reports/*.csv + return os.path.basename(os.path.dirname(os.path.dirname(self.report_file))) + + def get_serial_number(self): + self.report.seek(0) + # ignore the first line. + next(self.report) + # The second line of the CSV is serial-no;report-date;report-time;;;;;;; + return next(self.report).split(';')[0] + + def get_glucose_unit(self): + # Get the first record available and parse that. + record = next(self._get_records_reader()) + return _UNIT_MAP[record[_UNIT_CSV_KEY]] + + def get_datetime(self): + raise NotImplemented + + def set_datetime(self, date=None): + raise NotImplemented + + def zero_log(self): + raise NotImplemented + + def _extract_datetime(self, record): + # Date and time are in separate column, but we want to parse them + # together. + date_and_time = ' '.join((record[_DATE_CSV_KEY], record[_TIME_CSV_KEY])) + return datetime.datetime.strptime(date_and_time, _DATETIME_FORMAT) + + def _extract_meal(self, record): + if record[_AFTER_MEAL_CSV_KEY] and record[_BEFORE_MEAL_CSV_KEY]: + raise InvalidResponse('Reading cannot be before and after meal.') + elif record[_AFTER_MEAL_CSV_KEY]: + return common.AFTER_MEAL + elif record[_BEFORE_MEAL_CSV_KEY]: + return common.BEFORE_MEAL + else: + return common.NO_MEAL + + def get_readings(self): + for record in self._get_records_reader(): + if record[_RESULT_CSV_KEY] is None: + continue + + yield common.Reading( + self._extract_datetime(record), + common.convert_glucose_unit(float(record[_RESULT_CSV_KEY]), + _UNIT_MAP[record[_UNIT_CSV_KEY]]), + meal=self._extract_meal(record)) |