summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorDaniel Friesel <daniel.friesel@uos.de>2019-11-13 16:02:36 +0100
committerDaniel Friesel <daniel.friesel@uos.de>2019-11-13 16:02:36 +0100
commitc5b30ab84544f8b393a31ab6358b3a36c0f9df3a (patch)
treeda853c95162cb26726752ea08adde72ec54ef76c /lib
parent8c8545553910a75a6c53900f7b729465a03f4846 (diff)
use barcode synchronization when running EnergyTrace benchmarks
Diffstat (limited to 'lib')
-rw-r--r--lib/harness.py25
-rw-r--r--lib/pubcode/__init__.py6
-rw-r--r--lib/pubcode/code128.py348
3 files changed, 374 insertions, 5 deletions
diff --git a/lib/harness.py b/lib/harness.py
index a4724f1..cd9a477 100644
--- a/lib/harness.py
+++ b/lib/harness.py
@@ -5,6 +5,7 @@ tbd
"""
import subprocess
import re
+from pubcode import Code128
# TODO prepare benchmark log JSON with parameters etc.
# Should be independent of PTA class, as benchmarks may also be
@@ -27,7 +28,7 @@ class TransitionHarness:
primitive values (-> set by the return value of the current run, not necessarily constan)
* `args`: function arguments, if isa == 'transition'
"""
- def __init__(self, gpio_pin = None, pta = None, log_return_values = False, repeat = 0, post_transition_delay_us = 0):
+ def __init__(self, gpio_pin = None, gpio_mode = 'around', pta = None, log_return_values = False, repeat = 0, post_transition_delay_us = 0):
"""
Create a new TransitionHarness
@@ -42,6 +43,7 @@ class TransitionHarness:
lower than the expected minimum transition duration.
"""
self.gpio_pin = gpio_pin
+ self.gpio_mode = gpio_mode
self.pta = pta
self.log_return_values = log_return_values
self.repeat = repeat
@@ -49,7 +51,7 @@ class TransitionHarness:
self.reset()
def copy(self):
- new_object = __class__(gpio_pin = self.gpio_pin, pta = self.pta, log_return_values = self.log_return_values, repeat = self.repeat, post_transition_delay_us = self.post_transition_delay_us)
+ new_object = __class__(gpio_pin = self.gpio_pin, gpio_mode = self.gpio_mode, pta = self.pta, log_return_values = self.log_return_values, repeat = self.repeat, post_transition_delay_us = self.post_transition_delay_us)
new_object.traces = self.traces.copy()
new_object.trace_id = self.trace_id
return new_object
@@ -81,6 +83,10 @@ class TransitionHarness:
ret = ''
if self.gpio_pin != None:
ret += '#define PTALOG_GPIO {}\n'.format(self.gpio_pin)
+ if self.gpio_mode == 'before':
+ ret += '#define PTALOG_GPIO_BEFORE\n'
+ elif self.gpio_mode == 'bar':
+ ret += '#define PTALOG_GPIO_BAR\n'
if self.log_return_values:
ret += '#define PTALOG_WITH_RETURNVALUES\n'
ret += 'uint16_t transition_return_value;\n'
@@ -135,6 +141,15 @@ class TransitionHarness:
"""Return C++ code used to start a new run/trace."""
return 'ptalog.reset();\n'
+ def _pass_transition_call(self, transition_id):
+ if self.gpio_mode == 'bar':
+ barcode_bits = Code128('T{}'.format(transition_id), charset='B').modules
+ barcode_bytes = [int("".join(map(str, reversed(barcode_bits[i:i+8]))), 2) for i in range(0, len(barcode_bits), 8)]
+ inline_array = "".join(map(lambda s: '\\x{:02x}'.format(s), barcode_bytes))
+ return 'ptalog.startTransition("{}", {});\n'.format(inline_array, len(barcode_bytes))
+ else:
+ return 'ptalog.startTransition();\n'
+
def pass_transition(self, transition_id, transition_code, transition: object = None):
"""
Return C++ code used to pass a transition, including the corresponding function call.
@@ -143,7 +158,7 @@ class TransitionHarness:
`post_transition_delay_us` is set.
"""
ret = 'ptalog.passTransition({:d});\n'.format(transition_id)
- ret += 'ptalog.startTransition();\n'
+ ret += self._pass_transition_call(transition_id)
if self.log_return_values and transition and len(transition.return_value_handlers):
ret += 'transition_return_value = {}\n'.format(transition_code)
ret += 'ptalog.logReturn(transition_return_value);\n'
@@ -243,7 +258,7 @@ class OnboardTimerHarness(TransitionHarness):
self.one_cycle_in_us, self.one_overflow_in_us, self.counter_max_overflow = counter_limits
def copy(self):
- new_harness = __class__((self.one_cycle_in_us, self.one_overflow_in_us, self.counter_max_overflow), gpio_pin = self.gpio_pin, pta = self.pta, log_return_values = self.log_return_values, repeat = self.repeat)
+ new_harness = __class__((self.one_cycle_in_us, self.one_overflow_in_us, self.counter_max_overflow), gpio_pin = self.gpio_pin, gpio_mode = self.gpio_mode, pta = self.pta, log_return_values = self.log_return_values, repeat = self.repeat)
new_harness.traces = self.traces.copy()
new_harness.trace_id = self.trace_id
return new_harness
@@ -263,7 +278,7 @@ class OnboardTimerHarness(TransitionHarness):
def pass_transition(self, transition_id, transition_code, transition: object = None):
ret = 'ptalog.passTransition({:d});\n'.format(transition_id)
- ret += 'ptalog.startTransition();\n'
+ ret += self._pass_transition_call(transition_id)
ret += 'counter.start();\n'
if self.log_return_values and transition and len(transition.return_value_handlers):
ret += 'transition_return_value = {}\n'.format(transition_code)
diff --git a/lib/pubcode/__init__.py b/lib/pubcode/__init__.py
new file mode 100644
index 0000000..30c8490
--- /dev/null
+++ b/lib/pubcode/__init__.py
@@ -0,0 +1,6 @@
+"""A simple module for creating barcodes.
+
+"""
+__version__ = '1.1.0'
+
+from .code128 import Code128
diff --git a/lib/pubcode/code128.py b/lib/pubcode/code128.py
new file mode 100644
index 0000000..1c37f37
--- /dev/null
+++ b/lib/pubcode/code128.py
@@ -0,0 +1,348 @@
+# -*- coding: utf-8 -*-
+# MIT Licensed, Copyright (c) 2015 Ari Koivula
+# https://github.com/Venti-/pubcode
+from __future__ import absolute_import, division, print_function, unicode_literals
+from builtins import * # Use Python3-like builtins for Python2.
+import base64
+import io
+try:
+ from PIL import Image
+except ImportError:
+ # PIL is needed only for creating images of the barcode. Set Image to None to signify that PIL is missing.
+ Image = None
+
+
+class Code128(object):
+ class Error(Exception):
+ pass
+
+ class CharsetError(Error):
+ pass
+
+ class CharsetLengthError(Error):
+ pass
+
+ class IncompatibleCharsetError(Error):
+ pass
+
+ class MissingDependencyError(Error):
+ pass
+
+ class UnknownFormatError(Error):
+ pass
+
+ # List of bar and space weights, indexed by symbol character values (0-105), and the STOP character (106).
+ # The first weights is a bar and then it alternates.
+ _val2bars = [
+ '212222', '222122', '222221', '121223', '121322', '131222', '122213', '122312', '132212', '221213',
+ '221312', '231212', '112232', '122132', '122231', '113222', '123122', '123221', '223211', '221132',
+ '221231', '213212', '223112', '312131', '311222', '321122', '321221', '312212', '322112', '322211',
+ '212123', '212321', '232121', '111323', '131123', '131321', '112313', '132113', '132311', '211313',
+ '231113', '231311', '112133', '112331', '132131', '113123', '113321', '133121', '313121', '211331',
+ '231131', '213113', '213311', '213131', '311123', '311321', '331121', '312113', '312311', '332111',
+ '314111', '221411', '431111', '111224', '111422', '121124', '121421', '141122', '141221', '112214',
+ '112412', '122114', '122411', '142112', '142211', '241211', '221114', '413111', '241112', '134111',
+ '111242', '121142', '121241', '114212', '124112', '124211', '411212', '421112', '421211', '212141',
+ '214121', '412121', '111143', '111341', '131141', '114113', '114311', '411113', '411311', '113141',
+ '114131', '311141', '411131', '211412', '211214', '211232', '2331112'
+ ]
+
+ class Special(object):
+ """These are special characters used by the Code128 encoding."""
+ START_A = '[Start Code A]'
+ START_B = '[Start Code B]'
+ START_C = '[Start Code C]'
+ CODE_A = '[Code A]'
+ CODE_B = '[Code B]'
+ CODE_C = '[Code C]'
+ SHIFT_A = '[Shift A]'
+ SHIFT_B = '[Shift B]'
+ FNC_1 = '[FNC 1]'
+ FNC_2 = '[FNC 2]'
+ FNC_3 = '[FNC 3]'
+ FNC_4 = '[FNC 4]'
+ STOP = '[Stop]'
+
+ _start_codes = {'A': Special.START_A, 'B': Special.START_B, 'C': Special.START_C}
+ _char_codes = {'A': Special.CODE_A, 'B': Special.CODE_B, 'C': Special.CODE_C}
+
+ # Lists mapping symbol values to characters in each character set. This defines the alphabet and Code128._sym2val
+ # is derived from this structure.
+ _val2sym = {
+ # Code Set A includes ordinals 0 through 95 and 7 special characters. The ordinals include digits,
+ # upper case characters, punctuation and control characters.
+ 'A':
+ [chr(x) for x in range(32, 95 + 1)] +
+ [chr(x) for x in range(0, 31 + 1)] +
+ [
+ Special.FNC_3, Special.FNC_2, Special.SHIFT_B, Special.CODE_C,
+ Special.CODE_B, Special.FNC_4, Special.FNC_1,
+ Special.START_A, Special.START_B, Special.START_C, Special.STOP
+ ],
+ # Code Set B includes ordinals 32 through 127 and 7 special characters. The ordinals include digits,
+ # upper and lover case characters and punctuation.
+ 'B':
+ [chr(x) for x in range(32, 127 + 1)] +
+ [
+ Special.FNC_3, Special.FNC_2, Special.SHIFT_A, Special.CODE_C,
+ Special.FNC_4, Special.CODE_A, Special.FNC_1,
+ Special.START_A, Special.START_B, Special.START_C, Special.STOP
+ ],
+ # Code Set C includes all pairs of 2 digits and 3 special characters.
+ 'C':
+ ['%02d' % (x,) for x in range(0, 99 + 1)] +
+ [
+ Special.CODE_B, Special.CODE_A, Special.FNC_1,
+ Special.START_A, Special.START_B, Special.START_C, Special.STOP
+ ],
+ }
+
+ # Dicts mapping characters to symbol values in each character set.
+ _sym2val = {
+ 'A': {char: val for val, char in enumerate(_val2sym['A'])},
+ 'B': {char: val for val, char in enumerate(_val2sym['B'])},
+ 'C': {char: val for val, char in enumerate(_val2sym['C'])},
+ }
+
+ # How large the quiet zone is on either side of the barcode, when quiet zone is used.
+ quiet_zone = 10
+
+ def __init__(self, data, charset=None):
+ """Initialize a barcode with data as described by the character sets in charset.
+
+ :param data: The data to be encoded.
+ :param charset: A single character set (A, B or C), an iterable with a character set for each symbol or None.
+ - If a single character set is chosen, all characters will be encoded with that set, except for
+ incompatible characters which will be coded with one of the other character sets.
+ - If a sequence of character sets are given, incompatible characters will result in
+ Code128.IncompatibleCharsetError. Wrong size of the charset sequence in relation to data,
+ will result in Code128.CharsetLengthError.
+ - If None is given, the character set will be chosen as to minimize the length of the barcode.
+ """
+ self._validate_charset(data, charset)
+
+ if charset in ('A', 'B'):
+ charset *= len(data)
+ elif charset in ('C',):
+ charset *= (len(data) // 2)
+ if len(data) % 2 == 1:
+ # If there are an odd number of characters for charset C, encode the last character with charset B.
+ charset += 'B'
+
+ self.data = data
+ self.symbol_values = self._encode(data, charset)
+
+ def width(self, add_quiet_zone=False):
+ """Return the barcodes width in modules for a given data and character set combination.
+
+ :param add_quiet_zone: Whether quiet zone should be included in the width.
+
+ :return: Width of barcode in modules, which for images translates to pixels.
+ """
+ quiet_zone = self.quiet_zone if add_quiet_zone else 0
+ return len(self.modules) + 2 * quiet_zone
+
+ @staticmethod
+ def _validate_charset(data, charset):
+ """"Validate that the charset is correct and throw an error if it isn't."""
+ if len(charset) > 1:
+ charset_data_length = 0
+ for symbol_charset in charset:
+ if symbol_charset not in ('A', 'B', 'C'):
+ raise Code128.CharsetError
+ charset_data_length += 2 if symbol_charset is 'C' else 1
+ if charset_data_length != len(data):
+ raise Code128.CharsetLengthError
+ elif len(charset) == 1:
+ if charset not in ('A', 'B', 'C'):
+ raise Code128.CharsetError
+ elif charset is not None:
+ raise Code128.CharsetError
+
+ @classmethod
+ def _encode(cls, data, charsets):
+ """Encode the data using the character sets in charsets.
+
+ :param data: Data to be encoded.
+ :param charsets: Sequence of charsets that are used to encode the barcode.
+ Must be the exact amount of symbols needed to encode the data.
+ :return: List of the symbol values representing the barcode.
+ """
+ result = []
+
+ charset = charsets[0]
+ start_symbol = cls._start_codes[charset]
+ result.append(cls._sym2val[charset][start_symbol])
+
+ cur = 0
+ prev_charset = charsets[0]
+ for symbol_num in range(len(charsets)):
+ charset = charsets[symbol_num]
+
+ if charset is not prev_charset:
+ # Handle a special case of there being a single A in middle of two B's or the other way around, where
+ # using a single shift character is more efficient than using two character set switches.
+ next_charset = charsets[symbol_num + 1] if symbol_num + 1 < len(charsets) else None
+ if charset == 'A' and prev_charset == next_charset == 'B':
+ result.append(cls._sym2val[prev_charset][cls.Special.SHIFT_A])
+ elif charset == 'B' and prev_charset == next_charset == 'A':
+ result.append(cls._sym2val[prev_charset][cls.Special.SHIFT_B])
+ else:
+ # This is the normal case.
+ charset_symbol = cls._char_codes[charset]
+ result.append(cls._sym2val[prev_charset][charset_symbol])
+ prev_charset = charset
+
+ nxt = cur + (2 if charset == 'C' else 1)
+ symbol = data[cur:nxt]
+ cur = nxt
+ result.append(cls._sym2val[charset][symbol])
+
+ result.append(cls._calc_checksum(result))
+ result.append(cls._sym2val[charset][cls.Special.STOP])
+
+ return result
+
+ @property
+ def symbols(self):
+ """List of the coded symbols as strings, with special characters included."""
+ def _iter_symbols(symbol_values):
+ # The initial charset doesn't matter, as the start codes have the same symbol values in all charsets.
+ charset = 'A'
+
+ shift_charset = None
+ for symbol_value in symbol_values:
+ if shift_charset:
+ symbol = self._val2sym[shift_charset][symbol_value]
+ shift_charset = None
+ else:
+ symbol = self._val2sym[charset][symbol_value]
+
+ if symbol in (self.Special.START_A, self.Special.CODE_A):
+ charset = 'A'
+ elif symbol in (self.Special.START_B, self.Special.CODE_B):
+ charset = 'B'
+ elif symbol in (self.Special.START_C, self.Special.CODE_C):
+ charset = 'C'
+ elif symbol in (self.Special.SHIFT_A,):
+ shift_charset = 'A'
+ elif symbol in (self.Special.SHIFT_B,):
+ shift_charset = 'B'
+
+ yield symbol
+
+ return list(_iter_symbols(self.symbol_values))
+
+ @property
+ def bars(self):
+ """A string of the bar and space weights of the barcode. Starting with a bar and alternating.
+
+ >>> barcode = Code128("Hello!", charset='B')
+ >>> barcode.bars
+ '2112142311131122142211142211141341112221221212412331112'
+
+ :rtype: string
+ """
+ return ''.join(map((lambda val: self._val2bars[val]), self.symbol_values))
+
+ @property
+ def modules(self):
+ """A list of the modules, with 0 representing a bar and 1 representing a space.
+
+ >>> barcode = Code128("Hello!", charset='B')
+ >>> barcode.modules # doctest: +ELLIPSIS
+ [0, 0, 1, 0, 1, 1, 0, 1, ..., 0, 0, 0, 1, 0, 1, 0, 0]
+
+ :rtype: list[int]
+ """
+ def _iterate_modules(bars):
+ is_bar = True
+ for char in map(int, bars):
+ while char > 0:
+ char -= 1
+ yield 0 if is_bar else 1
+ is_bar = not is_bar
+
+ return list(_iterate_modules(self.bars))
+
+ @staticmethod
+ def _calc_checksum(values):
+ """Calculate the symbol check character."""
+ checksum = values[0]
+ for index, value in enumerate(values):
+ checksum += index * value
+ return checksum % 103
+
+ def image(self, height=1, module_width=1, add_quiet_zone=True):
+ """Get the barcode as PIL.Image.
+
+ By default the image is one pixel high and the number of modules pixels wide, with 10 empty modules added to
+ each side to act as the quiet zone. The size can be modified by setting height and module_width, but if used in
+ a web page it might be a good idea to do the scaling on client side.
+
+ :param height: Height of the image in number of pixels.
+ :param module_width: A multiplier for the width.
+ :param add_quiet_zone: Whether to add 10 empty modules to each side of the barcode.
+
+ :rtype: PIL.Image
+ :return: A monochromatic image containing the barcode as black bars on white background.
+ """
+ if Image is None:
+ raise Code128.MissingDependencyError("PIL module is required to use image method.")
+
+ modules = list(self.modules)
+ if add_quiet_zone:
+ # Add ten space modules to each side of the barcode.
+ modules = [1] * self.quiet_zone + modules + [1] * self.quiet_zone
+ width = len(modules)
+
+ img = Image.new(mode='1', size=(width, 1))
+ img.putdata(modules)
+
+ if height == 1 and module_width == 1:
+ return img
+ else:
+ new_size = (width * module_width, height)
+ return img.resize(new_size, resample=Image.NEAREST)
+
+ def data_url(self, image_format='png', add_quiet_zone=True):
+ """Get a data URL representing the barcode.
+
+ >>> barcode = Code128('Hello!', charset='B')
+ >>> barcode.data_url() # doctest: +ELLIPSIS
+ 'data:image/png;base64,...'
+
+ :param image_format: Either 'png' or 'bmp'.
+ :param add_quiet_zone: Add a 10 white pixels on either side of the barcode.
+
+ :raises: Code128.UnknownFormatError
+ :raises: Code128.MissingDependencyError
+
+ :rtype: str
+ :returns: A data URL with the barcode as an image.
+ """
+ memory_file = io.BytesIO()
+ pil_image = self.image(add_quiet_zone=add_quiet_zone)
+
+ # Using BMP can often result in smaller data URLs than PNG, but it isn't as widely supported by browsers as PNG.
+ # GIFs result in data URLs 10 times bigger than PNG or BMP, possibly due to lack of support for monochrome GIFs
+ # in Pillow, so they shouldn't be used.
+ if image_format == 'png':
+ # Unfortunately there is no way to avoid adding the zlib headers.
+ # Using compress_level=0 sometimes results in a slightly bigger data size (by a few bytes), but there
+ # doesn't appear to be a difference between levels 9 and 1, so let's just use 1.
+ pil_image.save(memory_file, format='png', compress_level=1)
+ elif image_format == 'bmp':
+ pil_image.save(memory_file, format='bmp')
+ else:
+ raise Code128.UnknownFormatError('Only png and bmp are supported.')
+
+ # Encode the data in the BytesIO object and convert the result into unicode.
+ base64_image = base64.b64encode(memory_file.getvalue()).decode('ascii')
+
+ data_url = 'data:image/{format};base64,{base64_data}'.format(
+ format=image_format,
+ base64_data=base64_image
+ )
+
+ return data_url