summaryrefslogtreecommitdiffstats
path: root/glucometerutils/drivers/accuchek_reports.py
blob: 941491e8f2f604f5d75febac736382943268074a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
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))