summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDiego Elio Pettenò <flameeyes@flameeyes.eu>2013-08-03 22:28:43 +0200
committerDiego Elio Pettenò <flameeyes@flameeyes.eu>2013-08-03 22:28:43 +0200
commitf7fde8b5a659b5b7de7d00cddb28c20d3c691d37 (patch)
treee97fc56403c13ea8f21e6cb4243d50d83ae9bacd
parentglucometer: remove unused argv usage. (diff)
downloadglucometerutils-f7fde8b5a659b5b7de7d00cddb28c20d3c691d37.tar
glucometerutils-f7fde8b5a659b5b7de7d00cddb28c20d3c691d37.tar.gz
glucometerutils-f7fde8b5a659b5b7de7d00cddb28c20d3c691d37.tar.bz2
glucometerutils-f7fde8b5a659b5b7de7d00cddb28c20d3c691d37.tar.lz
glucometerutils-f7fde8b5a659b5b7de7d00cddb28c20d3c691d37.tar.xz
glucometerutils-f7fde8b5a659b5b7de7d00cddb28c20d3c691d37.tar.zst
glucometerutils-f7fde8b5a659b5b7de7d00cddb28c20d3c691d37.zip
-rw-r--r--glucometerutils/drivers/lifescan_common.py70
-rw-r--r--glucometerutils/drivers/otultra2.py33
-rw-r--r--glucometerutils/exceptions.py13
-rw-r--r--test/test_lifescan.py29
-rw-r--r--test/test_otultra2.py23
5 files changed, 139 insertions, 29 deletions
diff --git a/glucometerutils/drivers/lifescan_common.py b/glucometerutils/drivers/lifescan_common.py
new file mode 100644
index 0000000..ac25d22
--- /dev/null
+++ b/glucometerutils/drivers/lifescan_common.py
@@ -0,0 +1,70 @@
+# -*- coding: utf-8 -*-
+"""Common utility functions for LifeScan meters."""
+
+__author__ = 'Diego Elio Pettenò'
+__email__ = 'flameeyes@flameeyes.eu'
+__copyright__ = 'Copyright © 2013, Diego Elio Pettenò'
+__license__ = 'GPL v3 or later'
+
+import ctypes
+
+from glucometerutils import exceptions
+
+
+class MissingChecksum(exceptions.InvalidResponse):
+ """The response misses the expected 4-digits checksum."""
+ def __init__(self, response):
+ self.message = 'Response is missing checksum: %s' % response
+
+
+class InvalidChecksum(exceptions.InvalidResponse):
+ def __init__(self, expected, gotten):
+ self.message = (
+ 'Response checksum not matching: %04x expected, %04x gotten' %
+ (expected, gotten))
+
+
+class InvalidSerialNumber(exceptions.Error):
+ """The serial number is not as expected."""
+ def __init__(self, serial_number):
+ self.message = 'Serial number %s is invalid.' % serial_number
+
+
+def calculate_checksum(bytestring):
+ """Calculate the "CRC16 Sick" style checksum for LifeScan protocols.
+
+ Args:
+ bytestring: the string of which the checksum has to be calculated.
+
+ Returns:
+ A 16-bit integer that is the checksum for the input.
+
+ Credits for this code go to Christian Navalici, who implemented it in his
+ library at https://github.com/cristianav/PyCRC/ .
+ """
+ crcValue = 0x0000
+ prev_c = 0x0000
+
+ for idx, c in enumerate(bytestring):
+ short_c = 0x00ff & c
+
+ idx_previous = idx - 1
+ short_p = ( 0x00ff & prev_c) << 8;
+
+ if ( crcValue & 0x8000 ):
+ crcValue = ctypes.c_ushort(crcValue << 1).value ^ 0x8005
+ else:
+ crcValue = ctypes.c_ushort(crcValue << 1).value
+
+ crcValue &= 0xffff
+ crcValue ^= ( short_c | short_p )
+
+ prev_c = short_c
+
+ # After processing, the one's complement of the CRC is calcluated and the
+ # two bytes of the CRC are swapped.
+ low_byte = (crcValue & 0xff00) >> 8
+ high_byte = (crcValue & 0x00ff) << 8
+ crcValue = low_byte | high_byte;
+
+ return crcValue
diff --git a/glucometerutils/drivers/otultra2.py b/glucometerutils/drivers/otultra2.py
index f453dc9..ebfda8f 100644
--- a/glucometerutils/drivers/otultra2.py
+++ b/glucometerutils/drivers/otultra2.py
@@ -13,19 +13,7 @@ import serial
from glucometerutils import common
from glucometerutils import exceptions
-
-
-class MissingChecksum(exceptions.InvalidResponse):
- """The response misses the expected 4-digits checksum."""
- def __init__(self, response):
- self.response = response
-
- def __str__(self):
- return 'Response is missing the OT2 checksum: %s' % self.response
-
-
-class InvalidSerialNumber(exceptions.Error):
- """The serial number is not ending with Y as expected."""
+from glucometerutils.drivers import lifescan_common
class Device(object):
@@ -62,11 +50,22 @@ class Device(object):
match = self._RESPONSE_MATCH.match(line)
if not match:
- raise MissingChecksum(line)
+ raise lifescan_common.MissingChecksum(line)
+
+ response, checksum_string = match.groups()
+
+ try:
+ checksum_given = int(checksum_string, 16)
+ checksum_calculated = lifescan_common.calculate_checksum(
+ bytes(response, 'ascii'))
- response, checksum = match.groups()
+ if checksum_given != checksum_calculated:
+ raise lifescan_common.InvalidChecksum(checksum_given,
+ checksum_calculated)
+ except ValueError:
+ raise lifescan_common.InvalidChecksum(checksum_given,
+ None)
- # TODO(flameeyes) check that the checksum is actually valid
return response
def _send_oneliner_command(self, cmd):
@@ -122,7 +121,7 @@ class Device(object):
# 'Y' at the far right of the serial number is the indication of a OneTouch
# Ultra2 device, as per specs.
if serial_number[-1] != 'Y':
- raise InvalidSerialNumber('Serial number %s is invalid.' % serial_number)
+ raise lifescan_common.InvalidSerialNumber(serial_number)
return serial_number
diff --git a/glucometerutils/exceptions.py b/glucometerutils/exceptions.py
index 00c9e91..1acf9a4 100644
--- a/glucometerutils/exceptions.py
+++ b/glucometerutils/exceptions.py
@@ -9,22 +9,19 @@ __license__ = 'GPL v3 or later'
class Error(Exception):
"""Base class for the errors."""
+ def __str__(self):
+ return self.message
+
class InvalidResponse(Error):
"""The response received from the meter was not understood"""
def __init__(self, response):
- self.response = response
-
- def __str__(self):
- return 'Invalid response received:\n%s' % self.response
+ self.message = 'Invalid response received:\n%s' % response
class InvalidGlucoseUnit(Error):
"""Unable to parse the given glucose unit"""
def __init__(self, unit):
- self.unit = unit
-
- def __str__(self):
- return 'Invalid glucose unit received:\n%s' % self.unit
+ self.message = 'Invalid glucose unit received:\n%s' % unit
diff --git a/test/test_lifescan.py b/test/test_lifescan.py
new file mode 100644
index 0000000..774567d
--- /dev/null
+++ b/test/test_lifescan.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+"""Tests for the LifeScan Common functions."""
+
+__author__ = 'Diego Elio Pettenò'
+__email__ = 'flameeyes@flameeyes.eu'
+__copyright__ = 'Copyright © 2013, Diego Elio Pettenò'
+__license__ = 'GPL v3 or later'
+
+import os
+import sys
+import unittest
+
+sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+
+from glucometerutils import common
+from glucometerutils.drivers import lifescan_common
+from glucometerutils import exceptions
+
+
+class TestOTUltra2(unittest.TestCase):
+ def testChecksum(self):
+ checksum = lifescan_common.calculate_checksum(bytes('T', 'ascii'))
+ self.assertEqual(0x5400, checksum)
+
+ checksum = lifescan_common.calculate_checksum(bytes('TestString', 'ascii'))
+ self.assertEqual(0x0643, checksum)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/test/test_otultra2.py b/test/test_otultra2.py
index ba577c3..6ffea83 100644
--- a/test/test_otultra2.py
+++ b/test/test_otultra2.py
@@ -15,6 +15,7 @@ import mock
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from glucometerutils import common
+from glucometerutils.drivers import lifescan_common
from glucometerutils.drivers import otultra2
from glucometerutils import exceptions
@@ -30,7 +31,7 @@ class TestOTUltra2(unittest.TestCase):
def testMissingChecksum(self):
self.mock_readline.return_value = bytes('INVALID', 'ascii')
- self.assertRaises(otultra2.MissingChecksum,
+ self.assertRaises(lifescan_common.MissingChecksum,
self.device.get_serial_number)
def testShortResponse(self):
@@ -40,16 +41,30 @@ class TestOTUltra2(unittest.TestCase):
self.device.get_serial_number)
def testInvalidResponse(self):
- self.mock_readline.return_value = bytes('% 1337\r', 'ascii')
+ self.mock_readline.return_value = bytes('% 2500\r', 'ascii')
self.assertRaises(exceptions.InvalidResponse,
self.device.get_serial_number)
def testInvalidSerialNumber(self):
self.mock_readline.return_value = bytes(
- '@ "12345678O" 1337\r', 'ascii')
+ '@ "12345678O" E105\r', 'ascii')
- self.assertRaises(otultra2.InvalidSerialNumber,
+ self.assertRaises(lifescan_common.InvalidSerialNumber,
+ self.device.get_serial_number)
+
+ def testInvalidChecksum(self):
+ self.mock_readline.return_value = bytes(
+ '% 1337\r', 'ascii')
+
+ self.assertRaises(lifescan_common.InvalidChecksum,
+ self.device.get_serial_number)
+
+ def testBrokenChecksum(self):
+ self.mock_readline.return_value = bytes(
+ '% 13AZ\r', 'ascii')
+
+ self.assertRaises(lifescan_common.MissingChecksum,
self.device.get_serial_number)
if __name__ == '__main__':