summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README2
-rw-r--r--glucometerutils/drivers/accuchek_reports.py129
2 files changed, 131 insertions, 0 deletions
diff --git a/README b/README
index c99df81..161c94c 100644
--- a/README
+++ b/README
@@ -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))