From 2dfd78ce9271ec958b152d6364271336677ae402 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Elio=20Petten=C3=B2?= Date: Mon, 15 Feb 2016 21:34:57 +0000 Subject: accuchek_reports: add a new driver for Accu-Chek Mobile meters. The default USB connection provides a USB storage device with a CSV datafile. This driver works by looking for that file and importing it. This does not support setting the time, nor reading the actual device time, but it is at least a good way to import data from different meters under the same format. --- README | 2 + glucometerutils/drivers/accuchek_reports.py | 129 ++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 glucometerutils/drivers/accuchek_reports.py 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)) -- cgit v1.2.3