# -*- 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_meter_info(self): return common.MeterInfo( '%s glucometer' % self.get_model(), serial_number=self.get_serial_number(), native_unit=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))