Skip to content

Instantly share code, notes, and snippets.

@luigifab
Last active January 1, 2024 11:44
Show Gist options
  • Select an option

  • Save luigifab/c3709a860754e3e67c8a2977b5ddfbea to your computer and use it in GitHub Desktop.

Select an option

Save luigifab/c3709a860754e3e67c8a2977b5ddfbea to your computer and use it in GitHub Desktop.

Revisions

  1. luigifab revised this gist Jan 1, 2024. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion __init__.py
    Original file line number Diff line number Diff line change
    @@ -220,7 +220,7 @@ def read(self, last=False):
    }

    if last:
    return dict({ max(values): values[max(values)] })
    return dict({ max(values): values[max(values)] }) if len(values) > 0 else values

    # sort by date
    return dict(sorted(values.items(), key=operator.itemgetter(0)))
  2. luigifab revised this gist Jan 1, 2024. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions __init__.py
    Original file line number Diff line number Diff line change
    @@ -279,3 +279,4 @@ def erase(self):
    self.hid_set_report((0x12, 0x12, 0x01, 0x03, 0, 0, 0, 0, 0, 0, 0, 0, 0x3c, 0x84))
    elif self.com == 'RD1212v1':
    self.hid_set_report((0x12, 0x12, 0x01, 0x03, 0, 0, 0, 0, 0, 0, 0, 0, 0x3c, 0x84))

  3. luigifab revised this gist Jan 1, 2024. 1 changed file with 281 additions and 1 deletion.
    282 changes: 281 additions & 1 deletion __init__.py
    Original file line number Diff line number Diff line change
    @@ -1 +1,281 @@
    # Go to https://github.com/luigifab/python-radexreader v1.2.3
    #!/usr/bin/python3
    # -*- coding: utf8 -*-
    # Created L/19/10/2020
    # Updated L/01/01/2024
    #
    # Copyright 2020-2024 | Fabrice Creuzot (luigifab) <code~luigifab~fr>
    # https://github.com/luigifab/python-radexreader
    #
    # This program is free software, you can redistribute it or modify
    # it under the terms of the GNU General Public License (GPL) as published
    # by the free software foundation, either version 2 of the license, or
    # (at your option) any later version.
    #
    # This program is distributed in the hope that it will be useful,
    # but without any warranty, without even the implied warranty of
    # merchantability or fitness for a particular purpose. See the
    # GNU General Public License (GPL) for more details.

    import sys
    import operator
    import time
    # pyusb
    import usb.core
    import usb.util
    import usb.backend.libusb1
    # pyserial
    import serial
    import serial.tools.list_ports

    __version__ = '1.2.4'

    class RadexReader():

    com = None
    usb = None
    serial = None
    keyA = None
    keyB = None
    keyC = None
    keyD = None

    def __init__(self):

    # backend only for alpine with docker?
    backend = usb.backend.libusb1.get_backend(find_library=lambda x: '/usr/lib/libusb-1.0.so.0')

    # RADEX ONE v1
    # search usb device (ABBA/A011)
    self.usb = usb.core.find(idVendor=0xabba, idProduct=0xa011, backend=backend)
    if self.usb is not None:
    self.com = 'ONEv1'
    # search usb serial device (https://stackoverflow.com/a/25112066/2980105)
    devices = serial.tools.list_ports.grep('RADEX')
    for device in devices:
    self.serial = serial.Serial(port=device.device, baudrate=9600, timeout=0.5)
    return # device found
    if self.serial is None:
    raise ValueError('Error: RADEX ONE (serial) not found')

    # RADEX RD1212 v1
    # search usb device (10C4/EA60 = Silicon labs USB to UART bridge or CP2102 USB to UART Bridge Controller)
    self.usb = usb.core.find(idVendor=0x10c4, idProduct=0xea60, backend=backend)
    if self.usb is not None:
    self.com = 'RD1212v1'
    # search usb serial device (https://stackoverflow.com/a/25112066/2980105)
    devices = serial.tools.list_ports.grep('RADEX')
    for device in devices:
    self.serial = serial.Serial(port=device.device, baudrate=115200, timeout=0.5)
    return # device found
    devices = serial.tools.list_ports.grep('CP2102')
    for device in devices:
    self.serial = serial.Serial(port=device.device, baudrate=115200, timeout=0.5)
    return # device found
    if self.serial is None:
    raise ValueError('Error: RADEX RD1212 (serial) not found')

    # RADEX RD1212 v2
    # search usb device (03EB/5603)
    self.usb = usb.core.find(idVendor=0x03eb, idProduct=0x5603, backend=backend)
    if self.usb is not None:
    self.com = 'RD1212v2'
    # usb reset
    self.usb.reset()
    if sys.platform != 'win32' and sys.platform != 'cygwin' and self.usb.is_kernel_driver_active(0):
    self.usb.detach_kernel_driver(0)
    self.usb.set_configuration()
    return # device found

    # no devices found
    if self.usb is None:
    raise ValueError('Error: RADEX RD1212 (usb) and RADEX ONE (usb) not plugged?')

    def get_device(self, getusb=False):
    if not getusb and self.serial is not None:
    return self.serial
    return self.usb

    def print_info(self):
    if self.com == 'RD1212v2':
    print('Manufacturer ' + hex(self.usb.idVendor).ljust(7, ' ') + ' ' + usb.util.get_string(self.usb, self.usb.iManufacturer))
    print('Product ' + hex(self.usb.idProduct).ljust(7, ' ') + ' ' + usb.util.get_string(self.usb, self.usb.iProduct))
    print()
    # https://www.criirad.org/laboratoire/radiametres/compteur-geiger.html
    print('[info] Sensor: Geiger-Müller tube SBM 20-1')
    print('[info] Measuring range: 0.05 - 999 µSv/h')
    print('[info] Uncertainty of the result: ±(15+6/D)%')
    print('[info] D is the value after a complete cycle of 100 seconds')
    print()
    print('[warn] The values shown are only approximations.')
    print('[warn] The actual value can only be measured with suitable professional device.')
    print('[warn] For now, not tested with measured values greater than 0.25 µSv/h.')
    print()
    elif self.com == 'RD1212v1':
    print('Manufacturer ' + hex(self.usb.idVendor).ljust(7, ' ') + ' ' + usb.util.get_string(self.usb, self.usb.iManufacturer))
    print('Product ' + hex(self.usb.idProduct).ljust(7, ' ') + ' ' + usb.util.get_string(self.usb, self.usb.iProduct))
    print('ComPort ' + self.serial.port)
    print()
    # source?
    print('[info] Sensor: Geiger-Müller tube SBM 20-1')
    print('[info] Measuring range: 0.05 - 999 µSv/h')
    print('[info] Uncertainty of the result: ±(15+6/D)%')
    print('[info] D is the value after a complete cycle of 100 seconds')
    print()
    print('[warn] The values shown are only approximations.')
    print('[warn] The actual value can only be measured with suitable professional device.')
    print('[warn] For now, not tested with measured values greater than 0.25 µSv/h.')
    print()
    elif self.com == 'ONEv1':
    if sys.platform != 'win32' and sys.platform != 'cygwin':
    print('Manufacturer ' + hex(self.usb.idVendor).ljust(7, ' ') + ' ' + usb.util.get_string(self.usb, self.usb.iManufacturer))
    print('Product ' + hex(self.usb.idProduct).ljust(7, ' ') + ' ' + usb.util.get_string(self.usb, self.usb.iProduct))
    else:
    print('Manufacturer -x---- QUARTA-RAD')
    print('Product -x---- RADEX ONE')
    print('ComPort ' + self.serial.port)
    print()
    # source?
    print('[info] Sensor: Geiger-Müller tube SBM 20-1')
    print('[info] Measuring range: 0.05 - 999 µSv/h')
    print('[info] Uncertainty of the result: ±(15+6/D)%')
    print('[info] D is the value after a complete cycle of 100? seconds')
    print()
    print('[warn] The values shown are only approximations.')
    print('[warn] The actual value can only be measured with suitable professional device.')
    print('[warn] For now, not tested with measured values greater than 0.25 µSv/h.')
    print()

    def hid_set_report(self, report):
    if self.com == 'RD1212v2':
    # https://stackoverflow.com/a/52368526/2980105
    self.usb.ctrl_transfer(
    0x21, # REQUEST_TYPE_CLASS | RECIPIENT_INTERFACE | ENDPOINT_OUT
    9, # SET_REPORT
    0x300, # Vendor Descriptor Type + 0 Descriptor Index
    0, # USB interface #0
    report # the HID payload as a byte array
    )
    elif self.com == 'RD1212v1':
    self.serial.write(report)
    elif self.com == 'ONEv1':
    self.serial.write(report)

    def hid_get_report(self):
    if self.com == 'RD1212v2':
    # https://stackoverflow.com/a/52368526/2980105
    return self.usb.ctrl_transfer(
    0xa1, # REQUEST_TYPE_CLASS | RECIPIENT_INTERFACE | ENDPOINT_IN
    1, # GET_REPORT
    0x300, # Vendor Descriptor Type + 0 Descriptor Index
    0, # USB interface #0
    64 # max reply size
    )
    elif self.com == 'RD1212v1':
    return self.serial.read(14)
    elif self.com == 'ONEv1':
    return self.serial.read(12 + 21 + 2 + 2 + 2 + 2 + 1)

    def read(self, last=False):

    if self.com == 'ONEv1':
    return self.readOne()

    values = {}
    # from 0x0 to 0xb3
    if last and self.com == 'RD1212v2':
    keys = [0x0]
    elif last and self.com == 'RD1212v1':
    keys = list(reversed(range(0x0, 0xb4)))
    else:
    keys = list(range(0x0, 0xb4))

    # 1 min, 5 min, 10 min, 30 min, 1 hour, 2 hours, 4 hours, 6 hours, 12 hours, 24 hours
    interval = {0: 1, 1: 5, 2: 10, 3: 30, 4: 60, 5: 120, 6: 240, 7: 360, 8: 720, 9: 1440}

    for key in keys:
    self.hid_set_report((0x12, 0x12, 0x01, 0x02, key, 0, 0, 0, 0, 0, 0, 0, 0x3c, 0x84))
    hexa = self.hid_get_report()
    if hexa != b'' and hexa[0] != 0:
    # timestamp = 01/01/2016 00:00:44 = 1451606444
    # timestamp = 01/01/2016 00:00:44 = 172 + 193 (×256) + 133 (×256×256) + 86 (×256×256×256)
    timestamp = (hexa[2] + hexa[3] * 256 + hexa[4] * 256 * 256 + hexa[5] * 256 * 256 * 256)
    # measure = 0.15 µSv/h = 15
    measure = (hexa[6] + hexa[7] * 256) / 100
    # uncertainty of the result
    percent = 15 + 6 / measure
    measure_min = measure * (1 - percent / 100)
    measure_max = measure * (1 + percent / 100)
    if measure_min < 0:
    measure_min = 0
    if percent > 99.9:
    percent = 99.9
    # memorize
    values[timestamp] = {
    'pct': percent,
    'min': measure_min,
    'val': measure,
    'max': measure_max,
    'time': timestamp,
    'interval': interval[hexa[8]]
    }

    if last:
    return dict({ max(values): values[max(values)] })

    # sort by date
    return dict(sorted(values.items(), key=operator.itemgetter(0)))

    def readOne(self):

    if self.keyA is None:
    self.keyA = 0x04 - 0x04
    self.keyB = 0x00
    self.keyC = 0x5a + 0x04
    self.keyD = 0x00

    self.keyA += 0x04
    self.keyC -= 0x04
    if self.keyA > 0xff:
    self.keyA -= 0xfe
    self.keyB += 0x01
    if self.keyB > 0xff:
    self.keyB = 0x00
    self.keyC -= 0x01
    elif self.keyC < 0x00:
    self.keyC += 0xff
    self.keyD -= 0x01
    if self.keyD < 0x00:
    self.keyD = 0xff

    self.hid_set_report((0x7b, 0xff, 0x20, 0, 0x06, 0, self.keyA, self.keyB, 0, 0, self.keyC, self.keyD, 0, 0x08, 0x0c, 0, 0xf3, 0xf7))
    hexa = self.hid_get_report()

    # measure = 0.15 µSv/h = 15 / 0.15 µSv accumulated = 15 / 15 CPM = 15
    measure = (hexa[20] + hexa[21] * 256 + hexa[22] * 256 * 256) / 100
    measure_acc = (hexa[24] + hexa[25] * 256 + hexa[26] * 256 * 256) / 100
    measure_cpm = hexa[28] + hexa[29] * 256 + hexa[30] * 256 * 256
    # uncertainty of the result
    percent = 15 + 6 / measure
    measure_min = measure * (1 - percent / 100)
    measure_max = measure * (1 + percent / 100)
    if measure_min < 0:
    measure_min = 0
    if percent > 99.9:
    percent = 99.9

    timestamp = int(time.time())
    return { timestamp: {
    'pct': percent,
    'min': measure_min,
    'val': measure,
    'max': measure_max,
    'acc': measure_acc,
    'cpm': measure_cpm,
    'time': timestamp
    } }

    def erase(self):
    if self.com == 'RD1212v2':
    self.hid_set_report((0x12, 0x12, 0x01, 0x03, 0, 0, 0, 0, 0, 0, 0, 0, 0x3c, 0x84))
    elif self.com == 'RD1212v1':
    self.hid_set_report((0x12, 0x12, 0x01, 0x03, 0, 0, 0, 0, 0, 0, 0, 0, 0x3c, 0x84))
  4. luigifab revised this gist Oct 14, 2023. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion __init__.py
    Original file line number Diff line number Diff line change
    @@ -1 +1 @@
    # Go to https://github.com/luigifab/python-radexreader v1.2.2
    # Go to https://github.com/luigifab/python-radexreader v1.2.3
  5. luigifab revised this gist Jun 6, 2023. 1 changed file with 1 addition and 281 deletions.
    282 changes: 1 addition & 281 deletions __init__.py
    Original file line number Diff line number Diff line change
    @@ -1,281 +1 @@
    #!/usr/bin/python3
    # -*- coding: utf8 -*-
    # Created L/19/10/2020
    # Updated S/04/03/2023
    #
    # Copyright 2020-2023 | Fabrice Creuzot (luigifab) <code~luigifab~fr>
    # https://github.com/luigifab/python-radexreader
    #
    # This program is free software, you can redistribute it or modify
    # it under the terms of the GNU General Public License (GPL) as published
    # by the free software foundation, either version 2 of the license, or
    # (at your option) any later version.
    #
    # This program is distributed in the hope that it will be useful,
    # but without any warranty, without even the implied warranty of
    # merchantability or fitness for a particular purpose. See the
    # GNU General Public License (GPL) for more details.

    import sys
    import operator
    import time
    # pyusb
    import usb.core
    import usb.util
    import usb.backend.libusb1
    # pyserial
    import serial
    import serial.tools.list_ports

    __version__ = '1.2.2'

    class RadexReader():

    com = None
    usb = None
    serial = None
    keyA = None
    keyB = None
    keyC = None
    keyD = None

    def __init__(self):

    # backend only for alpine with docker?
    backend = usb.backend.libusb1.get_backend(find_library=lambda x: '/usr/lib/libusb-1.0.so.0')

    # RADEX ONE v1
    # search usb device (ABBA/A011)
    self.usb = usb.core.find(idVendor=0xabba, idProduct=0xa011, backend=backend)
    if self.usb is not None:
    self.com = 'ONEv1'
    # search usb serial device (https://stackoverflow.com/a/25112066/2980105)
    devices = serial.tools.list_ports.grep('RADEX')
    for device in devices:
    self.serial = serial.Serial(port=device.device, baudrate=9600, timeout=0.5)
    return # device found
    if self.serial is None:
    raise ValueError('Error: RADEX ONE (serial) not found')

    # RADEX RD1212 v1
    # search usb device (10C4/EA60 = Silicon labs USB to UART bridge or CP2102 USB to UART Bridge Controller)
    self.usb = usb.core.find(idVendor=0x10c4, idProduct=0xea60, backend=backend)
    if self.usb is not None:
    self.com = 'RD1212v1'
    # search usb serial device (https://stackoverflow.com/a/25112066/2980105)
    devices = serial.tools.list_ports.grep('RADEX')
    for device in devices:
    self.serial = serial.Serial(port=device.device, baudrate=115200, timeout=0.5)
    return # device found
    devices = serial.tools.list_ports.grep('CP2102')
    for device in devices:
    self.serial = serial.Serial(port=device.device, baudrate=115200, timeout=0.5)
    return # device found
    if self.serial is None:
    raise ValueError('Error: RADEX RD1212 (serial) not found')

    # RADEX RD1212 v2
    # search usb device (03EB/5603)
    self.usb = usb.core.find(idVendor=0x03eb, idProduct=0x5603, backend=backend)
    if self.usb is not None:
    self.com = 'RD1212v2'
    # usb reset
    self.usb.reset()
    if sys.platform != 'win32' and sys.platform != 'cygwin' and self.usb.is_kernel_driver_active(0):
    self.usb.detach_kernel_driver(0)
    self.usb.set_configuration()
    return # device found

    # no devices found
    if self.usb is None:
    raise ValueError('Error: RADEX RD1212 (usb) and RADEX ONE (usb) not plugged?')

    def get_device(self, getusb=False):
    if not getusb and self.serial is not None:
    return self.serial
    return self.usb

    def print_info(self):
    if self.com == 'RD1212v2':
    print('Manufacturer ' + hex(self.usb.idVendor).ljust(7, ' ') + ' ' + usb.util.get_string(self.usb, self.usb.iManufacturer))
    print('Product ' + hex(self.usb.idProduct).ljust(7, ' ') + ' ' + usb.util.get_string(self.usb, self.usb.iProduct))
    print()
    # https://www.criirad.org/laboratoire/radiametres/compteur-geiger.html
    print('[info] Sensor: Geiger-Müller tube SBM 20-1')
    print('[info] Measuring range: 0.05 - 999 µSv/h')
    print('[info] Uncertainty of the result: ±(15+6/D)%')
    print('[info] D is the value after a complete cycle of 100 seconds')
    print()
    print('[warn] The values shown are only approximations.')
    print('[warn] The actual value can only be measured with suitable professional device.')
    print('[warn] For now, not tested with measured values greater than 0.25 µSv/h.')
    print()
    elif self.com == 'RD1212v1':
    print('Manufacturer ' + hex(self.usb.idVendor).ljust(7, ' ') + ' ' + usb.util.get_string(self.usb, self.usb.iManufacturer))
    print('Product ' + hex(self.usb.idProduct).ljust(7, ' ') + ' ' + usb.util.get_string(self.usb, self.usb.iProduct))
    print('ComPort ' + self.serial.port)
    print()
    # source?
    print('[info] Sensor: Geiger-Müller tube SBM 20-1')
    print('[info] Measuring range: 0.05 - 999 µSv/h')
    print('[info] Uncertainty of the result: ±(15+6/D)%')
    print('[info] D is the value after a complete cycle of 100 seconds')
    print()
    print('[warn] The values shown are only approximations.')
    print('[warn] The actual value can only be measured with suitable professional device.')
    print('[warn] For now, not tested with measured values greater than 0.25 µSv/h.')
    print()
    elif self.com == 'ONEv1':
    if sys.platform != 'win32' and sys.platform != 'cygwin':
    print('Manufacturer ' + hex(self.usb.idVendor).ljust(7, ' ') + ' ' + usb.util.get_string(self.usb, self.usb.iManufacturer))
    print('Product ' + hex(self.usb.idProduct).ljust(7, ' ') + ' ' + usb.util.get_string(self.usb, self.usb.iProduct))
    else:
    print('Manufacturer -x---- QUARTA-RAD')
    print('Product -x---- RADEX ONE')
    print('ComPort ' + self.serial.port)
    print()
    # source?
    print('[info] Sensor: Geiger-Müller tube SBM 20-1')
    print('[info] Measuring range: 0.05 - 999 µSv/h')
    print('[info] Uncertainty of the result: ±(15+6/D)%')
    print('[info] D is the value after a complete cycle of 100? seconds')
    print()
    print('[warn] The values shown are only approximations.')
    print('[warn] The actual value can only be measured with suitable professional device.')
    print('[warn] For now, not tested with measured values greater than 0.25 µSv/h.')
    print()

    def hid_set_report(self, report):
    if self.com == 'RD1212v2':
    # https://stackoverflow.com/a/52368526/2980105
    self.usb.ctrl_transfer(
    0x21, # REQUEST_TYPE_CLASS | RECIPIENT_INTERFACE | ENDPOINT_OUT
    9, # SET_REPORT
    0x300, # Vendor Descriptor Type + 0 Descriptor Index
    0, # USB interface #0
    report # the HID payload as a byte array
    )
    elif self.com == 'RD1212v1':
    self.serial.write(report)
    elif self.com == 'ONEv1':
    self.serial.write(report)

    def hid_get_report(self):
    if self.com == 'RD1212v2':
    # https://stackoverflow.com/a/52368526/2980105
    return self.usb.ctrl_transfer(
    0xa1, # REQUEST_TYPE_CLASS | RECIPIENT_INTERFACE | ENDPOINT_IN
    1, # GET_REPORT
    0x300, # Vendor Descriptor Type + 0 Descriptor Index
    0, # USB interface #0
    64 # max reply size
    )
    elif self.com == 'RD1212v1':
    return self.serial.read(14)
    elif self.com == 'ONEv1':
    return self.serial.read(12 + 21 + 2 + 2 + 2 + 2 + 1)

    def read(self, last=False):

    if self.com == 'ONEv1':
    return self.readOne()

    values = {}
    # from 0x0 to 0xb3
    if last:
    keys = [0x0]
    elif last and self.com == 'RD1212v1':
    keys = list(reversed(range(0x0, 0xb4)))
    else:
    keys = list(range(0x0, 0xb4))

    # 1 min, 5 min, 10 min, 30 min, 1 hour, 2 hours, 4 hours, 6 hours, 12 hours, 24 hours
    interval = {0: 1, 1: 5, 2: 10, 3: 30, 4: 60, 5: 120, 6: 240, 7: 360, 8: 720, 9: 1440}

    for key in keys:
    self.hid_set_report((0x12, 0x12, 0x01, 0x02, key, 0, 0, 0, 0, 0, 0, 0, 0x3c, 0x84))
    hexa = self.hid_get_report()
    if hexa != b'' and hexa[0] != 0:
    # timestamp = 01/01/2016 00:00:44 = 1451606444
    # timestamp = 01/01/2016 00:00:44 = 172 + 193 (×256) + 133 (×256×256) + 86 (×256×256×256)
    timestamp = (hexa[2] + hexa[3] * 256 + hexa[4] * 256 * 256 + hexa[5] * 256 * 256 * 256)
    # measure = 0.15 µSv/h = 15
    measure = (hexa[6] + hexa[7] * 256) / 100
    # uncertainty of the result
    percent = 15 + 6 / measure
    measure_min = measure * (1 - percent / 100)
    measure_max = measure * (1 + percent / 100)
    if measure_min < 0:
    measure_min = 0
    if percent > 99.9:
    percent = 99.9
    # memorize
    values[timestamp] = {
    'pct': percent,
    'min': measure_min,
    'val': measure,
    'max': measure_max,
    'time': timestamp,
    'interval': interval[hexa[8]]
    }
    # to get last measure with RD1212v1
    if last and self.com == 'RD1212v1':
    break

    # sort by date
    return dict(sorted(values.items(), key=operator.itemgetter(0)))

    def readOne(self):

    if self.keyA is None:
    self.keyA = 0x04 - 0x04
    self.keyB = 0x00
    self.keyC = 0x5a + 0x04
    self.keyD = 0x00

    self.keyA += 0x04
    self.keyC -= 0x04
    if self.keyA > 0xff:
    self.keyA -= 0xfe
    self.keyB += 0x01
    if self.keyB > 0xff:
    self.keyB = 0x00
    self.keyC -= 0x01
    elif self.keyC < 0x00:
    self.keyC += 0xff
    self.keyD -= 0x01
    if self.keyD < 0x00:
    self.keyD = 0xff

    self.hid_set_report((0x7b, 0xff, 0x20, 0, 0x06, 0, self.keyA, self.keyB, 0, 0, self.keyC, self.keyD, 0, 0x08, 0x0c, 0, 0xf3, 0xf7))
    hexa = self.hid_get_report()

    # measure = 0.15 µSv/h = 15 / 0.15 µSv accumulated = 15 / 15 CPM = 15
    measure = (hexa[20] + hexa[21] * 256 + hexa[22] * 256 * 256) / 100
    measure_acc = (hexa[24] + hexa[25] * 256 + hexa[26] * 256 * 256) / 100
    measure_cpm = hexa[28] + hexa[29] * 256 + hexa[30] * 256 * 256
    # uncertainty of the result
    percent = 15 + 6 / measure
    measure_min = measure * (1 - percent / 100)
    measure_max = measure * (1 + percent / 100)
    if measure_min < 0:
    measure_min = 0
    if percent > 99.9:
    percent = 99.9

    timestamp = int(time.time())
    return { timestamp: {
    'pct': percent,
    'min': measure_min,
    'val': measure,
    'max': measure_max,
    'acc': measure_acc,
    'cpm': measure_cpm,
    'time': timestamp
    } }

    def erase(self):
    if self.com == 'RD1212v2':
    self.hid_set_report((0x12, 0x12, 0x01, 0x03, 0, 0, 0, 0, 0, 0, 0, 0, 0x3c, 0x84))
    elif self.com == 'RD1212v1':
    self.hid_set_report((0x12, 0x12, 0x01, 0x03, 0, 0, 0, 0, 0, 0, 0, 0, 0x3c, 0x84))
    # Go to https://github.com/luigifab/python-radexreader v1.2.2
  6. luigifab revised this gist Jun 1, 2023. 1 changed file with 1 addition and 2 deletions.
    3 changes: 1 addition & 2 deletions __init__.py
    Original file line number Diff line number Diff line change
    @@ -247,8 +247,7 @@ def readOne(self):
    if self.keyD < 0x00:
    self.keyD = 0xff

    req = (0x7b, 0xff, 0x20, 0, 0x06, 0, self.keyA, self.keyB, 0, 0, self.keyC, self.keyD, 0, 0x08, 0x0c, 0, 0xf3, 0xf7)
    self.hid_set_report(req)
    self.hid_set_report((0x7b, 0xff, 0x20, 0, 0x06, 0, self.keyA, self.keyB, 0, 0, self.keyC, self.keyD, 0, 0x08, 0x0c, 0, 0xf3, 0xf7))
    hexa = self.hid_get_report()

    # measure = 0.15 µSv/h = 15 / 0.15 µSv accumulated = 15 / 15 CPM = 15
  7. luigifab revised this gist Mar 4, 2023. 1 changed file with 21 additions and 21 deletions.
    42 changes: 21 additions & 21 deletions __init__.py
    Original file line number Diff line number Diff line change
    @@ -44,17 +44,18 @@ def __init__(self):
    # backend only for alpine with docker?
    backend = usb.backend.libusb1.get_backend(find_library=lambda x: '/usr/lib/libusb-1.0.so.0')

    # RADEX RD1212 v2
    # search usb device (03EB/5603)
    self.usb = usb.core.find(idVendor=0x03eb, idProduct=0x5603, backend=backend)
    # RADEX ONE v1
    # search usb device (ABBA/A011)
    self.usb = usb.core.find(idVendor=0xabba, idProduct=0xa011, backend=backend)
    if self.usb is not None:
    self.com = 'RD1212v2'
    # usb reset
    self.usb.reset()
    if sys.platform != 'win32' and sys.platform != 'cygwin' and self.usb.is_kernel_driver_active(0):
    self.usb.detach_kernel_driver(0)
    self.usb.set_configuration()
    return # device found
    self.com = 'ONEv1'
    # search usb serial device (https://stackoverflow.com/a/25112066/2980105)
    devices = serial.tools.list_ports.grep('RADEX')
    for device in devices:
    self.serial = serial.Serial(port=device.device, baudrate=9600, timeout=0.5)
    return # device found
    if self.serial is None:
    raise ValueError('Error: RADEX ONE (serial) not found')

    # RADEX RD1212 v1
    # search usb device (10C4/EA60 = Silicon labs USB to UART bridge or CP2102 USB to UART Bridge Controller)
    @@ -73,18 +74,17 @@ def __init__(self):
    if self.serial is None:
    raise ValueError('Error: RADEX RD1212 (serial) not found')

    # RADEX ONE v1
    # search usb device (ABBA/A011)
    self.usb = usb.core.find(idVendor=0xabba, idProduct=0xa011, backend=backend)
    # RADEX RD1212 v2
    # search usb device (03EB/5603)
    self.usb = usb.core.find(idVendor=0x03eb, idProduct=0x5603, backend=backend)
    if self.usb is not None:
    self.com = 'ONEv1'
    # search usb serial device (https://stackoverflow.com/a/25112066/2980105)
    devices = serial.tools.list_ports.grep('RADEX')
    for device in devices:
    self.serial = serial.Serial(port=device.device, baudrate=9600, timeout=0.5)
    return # device found
    if self.serial is None:
    raise ValueError('Error: RADEX ONE (serial) not found')
    self.com = 'RD1212v2'
    # usb reset
    self.usb.reset()
    if sys.platform != 'win32' and sys.platform != 'cygwin' and self.usb.is_kernel_driver_active(0):
    self.usb.detach_kernel_driver(0)
    self.usb.set_configuration()
    return # device found

    # no devices found
    if self.usb is None:
  8. luigifab revised this gist Mar 4, 2023. 1 changed file with 2 additions and 7 deletions.
    9 changes: 2 additions & 7 deletions __init__.py
    Original file line number Diff line number Diff line change
    @@ -1,7 +1,7 @@
    #!/usr/bin/python3
    # -*- coding: utf8 -*-
    # Created L/19/10/2020
    # Updated D/01/01/2023
    # Updated S/04/03/2023
    #
    # Copyright 2020-2023 | Fabrice Creuzot (luigifab) <code~luigifab~fr>
    # https://github.com/luigifab/python-radexreader
    @@ -58,7 +58,6 @@ def __init__(self):

    # RADEX RD1212 v1
    # search usb device (10C4/EA60 = Silicon labs USB to UART bridge or CP2102 USB to UART Bridge Controller)
    self.usb.reset()
    self.usb = usb.core.find(idVendor=0x10c4, idProduct=0xea60, backend=backend)
    if self.usb is not None:
    self.com = 'RD1212v1'
    @@ -76,7 +75,6 @@ def __init__(self):

    # RADEX ONE v1
    # search usb device (ABBA/A011)
    self.usb.reset()
    self.usb = usb.core.find(idVendor=0xabba, idProduct=0xa011, backend=backend)
    if self.usb is not None:
    self.com = 'ONEv1'
    @@ -197,10 +195,7 @@ def read(self, last=False):
    for key in keys:
    self.hid_set_report((0x12, 0x12, 0x01, 0x02, key, 0, 0, 0, 0, 0, 0, 0, 0x3c, 0x84))
    hexa = self.hid_get_report()
    if hexa == b'':
    # quite often, somehow, nothing at all is returned, quietly ignore that state
    continue
    if hexa[0] != 0:
    if hexa != b'' and hexa[0] != 0:
    # timestamp = 01/01/2016 00:00:44 = 1451606444
    # timestamp = 01/01/2016 00:00:44 = 172 + 193 (×256) + 133 (×256×256) + 86 (×256×256×256)
    timestamp = (hexa[2] + hexa[3] * 256 + hexa[4] * 256 * 256 + hexa[5] * 256 * 256 * 256)
  9. luigifab revised this gist Jan 1, 2023. 1 changed file with 2 additions and 3 deletions.
    5 changes: 2 additions & 3 deletions __init__.py
    Original file line number Diff line number Diff line change
    @@ -58,6 +58,7 @@ def __init__(self):

    # RADEX RD1212 v1
    # search usb device (10C4/EA60 = Silicon labs USB to UART bridge or CP2102 USB to UART Bridge Controller)
    self.usb.reset()
    self.usb = usb.core.find(idVendor=0x10c4, idProduct=0xea60, backend=backend)
    if self.usb is not None:
    self.com = 'RD1212v1'
    @@ -66,17 +67,16 @@ def __init__(self):
    for device in devices:
    self.serial = serial.Serial(port=device.device, baudrate=115200, timeout=0.5)
    return # device found
    self.serial.close()
    devices = serial.tools.list_ports.grep('CP2102')
    for device in devices:
    self.serial = serial.Serial(port=device.device, baudrate=115200, timeout=0.5)
    return # device found
    self.serial.close()
    if self.serial is None:
    raise ValueError('Error: RADEX RD1212 (serial) not found')

    # RADEX ONE v1
    # search usb device (ABBA/A011)
    self.usb.reset()
    self.usb = usb.core.find(idVendor=0xabba, idProduct=0xa011, backend=backend)
    if self.usb is not None:
    self.com = 'ONEv1'
    @@ -85,7 +85,6 @@ def __init__(self):
    for device in devices:
    self.serial = serial.Serial(port=device.device, baudrate=9600, timeout=0.5)
    return # device found
    self.serial.close()
    if self.serial is None:
    raise ValueError('Error: RADEX ONE (serial) not found')

  10. luigifab revised this gist Jan 1, 2023. 1 changed file with 6 additions and 2 deletions.
    8 changes: 6 additions & 2 deletions __init__.py
    Original file line number Diff line number Diff line change
    @@ -1,7 +1,7 @@
    #!/usr/bin/python3
    # -*- coding: utf8 -*-
    # Created L/19/10/2020
    # Updated S/30/07/2022
    # Updated D/01/01/2023
    #
    # Copyright 2020-2023 | Fabrice Creuzot (luigifab) <code~luigifab~fr>
    # https://github.com/luigifab/python-radexreader
    @@ -66,10 +66,12 @@ def __init__(self):
    for device in devices:
    self.serial = serial.Serial(port=device.device, baudrate=115200, timeout=0.5)
    return # device found
    self.serial.close()
    devices = serial.tools.list_ports.grep('CP2102')
    for device in devices:
    self.serial = serial.Serial(port=device.device, baudrate=115200, timeout=0.5)
    return # device found
    self.serial.close()
    if self.serial is None:
    raise ValueError('Error: RADEX RD1212 (serial) not found')

    @@ -83,6 +85,7 @@ def __init__(self):
    for device in devices:
    self.serial = serial.Serial(port=device.device, baudrate=9600, timeout=0.5)
    return # device found
    self.serial.close()
    if self.serial is None:
    raise ValueError('Error: RADEX ONE (serial) not found')

    @@ -250,7 +253,8 @@ def readOne(self):
    if self.keyD < 0x00:
    self.keyD = 0xff

    self.hid_set_report((0x7b, 0xff, 0x20, 0, 0x06, 0, self.keyA, self.keyB, 0, 0, self.keyC, self.keyD, 0, 0x08, 0x0c, 0, 0xf3, 0xf7))
    req = (0x7b, 0xff, 0x20, 0, 0x06, 0, self.keyA, self.keyB, 0, 0, self.keyC, self.keyD, 0, 0x08, 0x0c, 0, 0xf3, 0xf7)
    self.hid_set_report(req)
    hexa = self.hid_get_report()

    # measure = 0.15 µSv/h = 15 / 0.15 µSv accumulated = 15 / 15 CPM = 15
  11. luigifab revised this gist Jan 1, 2023. 1 changed file with 1 addition and 6 deletions.
    7 changes: 1 addition & 6 deletions __init__.py
    Original file line number Diff line number Diff line change
    @@ -250,13 +250,8 @@ def readOne(self):
    if self.keyD < 0x00:
    self.keyD = 0xff

    # debug
    req = (0x7b, 0xff, 0x20, 0, 0x06, 0, 0x0a, 0, 0, 0, 0x54, 0, 0, 0x08, 0x0c, 0, 0xf3, 0xf7)
    req = (0x7b, 0xff, 0x20, 0, 0x06, 0, self.keyA, self.keyB, 0, 0, self.keyC, self.keyD, 0, 0x08, 0x0c, 0, 0xf3, 0xf7)
    self.hid_set_report(req)
    self.hid_set_report((0x7b, 0xff, 0x20, 0, 0x06, 0, self.keyA, self.keyB, 0, 0, self.keyC, self.keyD, 0, 0x08, 0x0c, 0, 0xf3, 0xf7))
    hexa = self.hid_get_report()
    print(req)
    print(hexa)

    # measure = 0.15 µSv/h = 15 / 0.15 µSv accumulated = 15 / 15 CPM = 15
    measure = (hexa[20] + hexa[21] * 256 + hexa[22] * 256 * 256) / 100
  12. luigifab revised this gist Jan 1, 2023. 1 changed file with 6 additions and 1 deletion.
    7 changes: 6 additions & 1 deletion __init__.py
    Original file line number Diff line number Diff line change
    @@ -250,8 +250,13 @@ def readOne(self):
    if self.keyD < 0x00:
    self.keyD = 0xff

    self.hid_set_report((0x7b, 0xff, 0x20, 0, 0x06, 0, self.keyA, self.keyB, 0, 0, self.keyC, self.keyD, 0, 0x08, 0x0c, 0, 0xf3, 0xf7))
    # debug
    req = (0x7b, 0xff, 0x20, 0, 0x06, 0, 0x0a, 0, 0, 0, 0x54, 0, 0, 0x08, 0x0c, 0, 0xf3, 0xf7)
    req = (0x7b, 0xff, 0x20, 0, 0x06, 0, self.keyA, self.keyB, 0, 0, self.keyC, self.keyD, 0, 0x08, 0x0c, 0, 0xf3, 0xf7)
    self.hid_set_report(req)
    hexa = self.hid_get_report()
    print(req)
    print(hexa)

    # measure = 0.15 µSv/h = 15 / 0.15 µSv accumulated = 15 / 15 CPM = 15
    measure = (hexa[20] + hexa[21] * 256 + hexa[22] * 256 * 256) / 100
  13. luigifab revised this gist Dec 23, 2022. 1 changed file with 1 addition and 3 deletions.
    4 changes: 1 addition & 3 deletions __init__.py
    Original file line number Diff line number Diff line change
    @@ -3,9 +3,8 @@
    # Created L/19/10/2020
    # Updated S/30/07/2022
    #
    # Copyright 2020-2022 | Fabrice Creuzot (luigifab) <code~luigifab~fr>
    # Copyright 2020-2023 | Fabrice Creuzot (luigifab) <code~luigifab~fr>
    # https://github.com/luigifab/python-radexreader
    # https://www.luigifab.fr/python/radexreader
    #
    # This program is free software, you can redistribute it or modify
    # it under the terms of the GNU General Public License (GPL) as published
    @@ -200,7 +199,6 @@ def read(self, last=False):
    # quite often, somehow, nothing at all is returned, quietly ignore that state
    continue
    if hexa[0] != 0:
    #print('%s minutes' % interval[hexa[8]])
    # timestamp = 01/01/2016 00:00:44 = 1451606444
    # timestamp = 01/01/2016 00:00:44 = 172 + 193 (×256) + 133 (×256×256) + 86 (×256×256×256)
    timestamp = (hexa[2] + hexa[3] * 256 + hexa[4] * 256 * 256 + hexa[5] * 256 * 256 * 256)
  14. luigifab revised this gist Jul 30, 2022. 1 changed file with 7 additions and 8 deletions.
    15 changes: 7 additions & 8 deletions __init__.py
    Original file line number Diff line number Diff line change
    @@ -1,7 +1,7 @@
    #!/usr/bin/python3
    # -*- coding: utf8 -*-
    # Created L/19/10/2020
    # Updated J/21/07/2022
    # Updated S/30/07/2022
    #
    # Copyright 2020-2022 | Fabrice Creuzot (luigifab) <code~luigifab~fr>
    # https://github.com/luigifab/python-radexreader
    @@ -185,8 +185,8 @@ def read(self, last=False):
    # from 0x0 to 0xb3
    if last:
    keys = [0x0]
    elif last and self.com == 'RD1212v1': # 0xb3 when history is full
    keys = list(range(0x0, 0xb4))
    elif last and self.com == 'RD1212v1':
    keys = list(reversed(range(0x0, 0xb4)))
    else:
    keys = list(range(0x0, 0xb4))

    @@ -223,13 +223,12 @@ def read(self, last=False):
    'time': timestamp,
    'interval': interval[hexa[8]]
    }
    # to get last measure with RD1212v1
    if last and self.com == 'RD1212v1':
    break

    # sort by date
    values = dict(sorted(values.items(), key=operator.itemgetter(0)))
    if last and len(values) > 0:
    return { list(values.keys())[-1]: list(values.values())[-1] }

    return values
    return dict(sorted(values.items(), key=operator.itemgetter(0)))

    def readOne(self):

  15. luigifab created this gist Jul 30, 2022.
    287 changes: 287 additions & 0 deletions __init__.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,287 @@
    #!/usr/bin/python3
    # -*- coding: utf8 -*-
    # Created L/19/10/2020
    # Updated J/21/07/2022
    #
    # Copyright 2020-2022 | Fabrice Creuzot (luigifab) <code~luigifab~fr>
    # https://github.com/luigifab/python-radexreader
    # https://www.luigifab.fr/python/radexreader
    #
    # This program is free software, you can redistribute it or modify
    # it under the terms of the GNU General Public License (GPL) as published
    # by the free software foundation, either version 2 of the license, or
    # (at your option) any later version.
    #
    # This program is distributed in the hope that it will be useful,
    # but without any warranty, without even the implied warranty of
    # merchantability or fitness for a particular purpose. See the
    # GNU General Public License (GPL) for more details.

    import sys
    import operator
    import time
    # pyusb
    import usb.core
    import usb.util
    import usb.backend.libusb1
    # pyserial
    import serial
    import serial.tools.list_ports

    __version__ = '1.2.2'

    class RadexReader():

    com = None
    usb = None
    serial = None
    keyA = None
    keyB = None
    keyC = None
    keyD = None

    def __init__(self):

    # backend only for alpine with docker?
    backend = usb.backend.libusb1.get_backend(find_library=lambda x: '/usr/lib/libusb-1.0.so.0')

    # RADEX RD1212 v2
    # search usb device (03EB/5603)
    self.usb = usb.core.find(idVendor=0x03eb, idProduct=0x5603, backend=backend)
    if self.usb is not None:
    self.com = 'RD1212v2'
    # usb reset
    self.usb.reset()
    if sys.platform != 'win32' and sys.platform != 'cygwin' and self.usb.is_kernel_driver_active(0):
    self.usb.detach_kernel_driver(0)
    self.usb.set_configuration()
    return # device found

    # RADEX RD1212 v1
    # search usb device (10C4/EA60 = Silicon labs USB to UART bridge or CP2102 USB to UART Bridge Controller)
    self.usb = usb.core.find(idVendor=0x10c4, idProduct=0xea60, backend=backend)
    if self.usb is not None:
    self.com = 'RD1212v1'
    # search usb serial device (https://stackoverflow.com/a/25112066/2980105)
    devices = serial.tools.list_ports.grep('RADEX')
    for device in devices:
    self.serial = serial.Serial(port=device.device, baudrate=115200, timeout=0.5)
    return # device found
    devices = serial.tools.list_ports.grep('CP2102')
    for device in devices:
    self.serial = serial.Serial(port=device.device, baudrate=115200, timeout=0.5)
    return # device found
    if self.serial is None:
    raise ValueError('Error: RADEX RD1212 (serial) not found')

    # RADEX ONE v1
    # search usb device (ABBA/A011)
    self.usb = usb.core.find(idVendor=0xabba, idProduct=0xa011, backend=backend)
    if self.usb is not None:
    self.com = 'ONEv1'
    # search usb serial device (https://stackoverflow.com/a/25112066/2980105)
    devices = serial.tools.list_ports.grep('RADEX')
    for device in devices:
    self.serial = serial.Serial(port=device.device, baudrate=9600, timeout=0.5)
    return # device found
    if self.serial is None:
    raise ValueError('Error: RADEX ONE (serial) not found')

    # no devices found
    if self.usb is None:
    raise ValueError('Error: RADEX RD1212 (usb) and RADEX ONE (usb) not plugged?')

    def get_device(self, getusb=False):
    if not getusb and self.serial is not None:
    return self.serial
    return self.usb

    def print_info(self):
    if self.com == 'RD1212v2':
    print('Manufacturer ' + hex(self.usb.idVendor).ljust(7, ' ') + ' ' + usb.util.get_string(self.usb, self.usb.iManufacturer))
    print('Product ' + hex(self.usb.idProduct).ljust(7, ' ') + ' ' + usb.util.get_string(self.usb, self.usb.iProduct))
    print()
    # https://www.criirad.org/laboratoire/radiametres/compteur-geiger.html
    print('[info] Sensor: Geiger-Müller tube SBM 20-1')
    print('[info] Measuring range: 0.05 - 999 µSv/h')
    print('[info] Uncertainty of the result: ±(15+6/D)%')
    print('[info] D is the value after a complete cycle of 100 seconds')
    print()
    print('[warn] The values shown are only approximations.')
    print('[warn] The actual value can only be measured with suitable professional device.')
    print('[warn] For now, not tested with measured values greater than 0.25 µSv/h.')
    print()
    elif self.com == 'RD1212v1':
    print('Manufacturer ' + hex(self.usb.idVendor).ljust(7, ' ') + ' ' + usb.util.get_string(self.usb, self.usb.iManufacturer))
    print('Product ' + hex(self.usb.idProduct).ljust(7, ' ') + ' ' + usb.util.get_string(self.usb, self.usb.iProduct))
    print('ComPort ' + self.serial.port)
    print()
    # source?
    print('[info] Sensor: Geiger-Müller tube SBM 20-1')
    print('[info] Measuring range: 0.05 - 999 µSv/h')
    print('[info] Uncertainty of the result: ±(15+6/D)%')
    print('[info] D is the value after a complete cycle of 100 seconds')
    print()
    print('[warn] The values shown are only approximations.')
    print('[warn] The actual value can only be measured with suitable professional device.')
    print('[warn] For now, not tested with measured values greater than 0.25 µSv/h.')
    print()
    elif self.com == 'ONEv1':
    if sys.platform != 'win32' and sys.platform != 'cygwin':
    print('Manufacturer ' + hex(self.usb.idVendor).ljust(7, ' ') + ' ' + usb.util.get_string(self.usb, self.usb.iManufacturer))
    print('Product ' + hex(self.usb.idProduct).ljust(7, ' ') + ' ' + usb.util.get_string(self.usb, self.usb.iProduct))
    else:
    print('Manufacturer -x---- QUARTA-RAD')
    print('Product -x---- RADEX ONE')
    print('ComPort ' + self.serial.port)
    print()
    # source?
    print('[info] Sensor: Geiger-Müller tube SBM 20-1')
    print('[info] Measuring range: 0.05 - 999 µSv/h')
    print('[info] Uncertainty of the result: ±(15+6/D)%')
    print('[info] D is the value after a complete cycle of 100? seconds')
    print()
    print('[warn] The values shown are only approximations.')
    print('[warn] The actual value can only be measured with suitable professional device.')
    print('[warn] For now, not tested with measured values greater than 0.25 µSv/h.')
    print()

    def hid_set_report(self, report):
    if self.com == 'RD1212v2':
    # https://stackoverflow.com/a/52368526/2980105
    self.usb.ctrl_transfer(
    0x21, # REQUEST_TYPE_CLASS | RECIPIENT_INTERFACE | ENDPOINT_OUT
    9, # SET_REPORT
    0x300, # Vendor Descriptor Type + 0 Descriptor Index
    0, # USB interface #0
    report # the HID payload as a byte array
    )
    elif self.com == 'RD1212v1':
    self.serial.write(report)
    elif self.com == 'ONEv1':
    self.serial.write(report)

    def hid_get_report(self):
    if self.com == 'RD1212v2':
    # https://stackoverflow.com/a/52368526/2980105
    return self.usb.ctrl_transfer(
    0xa1, # REQUEST_TYPE_CLASS | RECIPIENT_INTERFACE | ENDPOINT_IN
    1, # GET_REPORT
    0x300, # Vendor Descriptor Type + 0 Descriptor Index
    0, # USB interface #0
    64 # max reply size
    )
    elif self.com == 'RD1212v1':
    return self.serial.read(14)
    elif self.com == 'ONEv1':
    return self.serial.read(12 + 21 + 2 + 2 + 2 + 2 + 1)

    def read(self, last=False):

    if self.com == 'ONEv1':
    return self.readOne()

    values = {}
    # from 0x0 to 0xb3
    if last:
    keys = [0x0]
    elif last and self.com == 'RD1212v1': # 0xb3 when history is full
    keys = list(range(0x0, 0xb4))
    else:
    keys = list(range(0x0, 0xb4))

    # 1 min, 5 min, 10 min, 30 min, 1 hour, 2 hours, 4 hours, 6 hours, 12 hours, 24 hours
    interval = {0: 1, 1: 5, 2: 10, 3: 30, 4: 60, 5: 120, 6: 240, 7: 360, 8: 720, 9: 1440}

    for key in keys:
    self.hid_set_report((0x12, 0x12, 0x01, 0x02, key, 0, 0, 0, 0, 0, 0, 0, 0x3c, 0x84))
    hexa = self.hid_get_report()
    if hexa == b'':
    # quite often, somehow, nothing at all is returned, quietly ignore that state
    continue
    if hexa[0] != 0:
    #print('%s minutes' % interval[hexa[8]])
    # timestamp = 01/01/2016 00:00:44 = 1451606444
    # timestamp = 01/01/2016 00:00:44 = 172 + 193 (×256) + 133 (×256×256) + 86 (×256×256×256)
    timestamp = (hexa[2] + hexa[3] * 256 + hexa[4] * 256 * 256 + hexa[5] * 256 * 256 * 256)
    # measure = 0.15 µSv/h = 15
    measure = (hexa[6] + hexa[7] * 256) / 100
    # uncertainty of the result
    percent = 15 + 6 / measure
    measure_min = measure * (1 - percent / 100)
    measure_max = measure * (1 + percent / 100)
    if measure_min < 0:
    measure_min = 0
    if percent > 99.9:
    percent = 99.9
    # memorize
    values[timestamp] = {
    'pct': percent,
    'min': measure_min,
    'val': measure,
    'max': measure_max,
    'time': timestamp,
    'interval': interval[hexa[8]]
    }

    # sort by date
    values = dict(sorted(values.items(), key=operator.itemgetter(0)))
    if last and len(values) > 0:
    return { list(values.keys())[-1]: list(values.values())[-1] }

    return values

    def readOne(self):

    if self.keyA is None:
    self.keyA = 0x04 - 0x04
    self.keyB = 0x00
    self.keyC = 0x5a + 0x04
    self.keyD = 0x00

    self.keyA += 0x04
    self.keyC -= 0x04
    if self.keyA > 0xff:
    self.keyA -= 0xfe
    self.keyB += 0x01
    if self.keyB > 0xff:
    self.keyB = 0x00
    self.keyC -= 0x01
    elif self.keyC < 0x00:
    self.keyC += 0xff
    self.keyD -= 0x01
    if self.keyD < 0x00:
    self.keyD = 0xff

    self.hid_set_report((0x7b, 0xff, 0x20, 0, 0x06, 0, self.keyA, self.keyB, 0, 0, self.keyC, self.keyD, 0, 0x08, 0x0c, 0, 0xf3, 0xf7))
    hexa = self.hid_get_report()

    # measure = 0.15 µSv/h = 15 / 0.15 µSv accumulated = 15 / 15 CPM = 15
    measure = (hexa[20] + hexa[21] * 256 + hexa[22] * 256 * 256) / 100
    measure_acc = (hexa[24] + hexa[25] * 256 + hexa[26] * 256 * 256) / 100
    measure_cpm = hexa[28] + hexa[29] * 256 + hexa[30] * 256 * 256
    # uncertainty of the result
    percent = 15 + 6 / measure
    measure_min = measure * (1 - percent / 100)
    measure_max = measure * (1 + percent / 100)
    if measure_min < 0:
    measure_min = 0
    if percent > 99.9:
    percent = 99.9

    timestamp = int(time.time())
    return { timestamp: {
    'pct': percent,
    'min': measure_min,
    'val': measure,
    'max': measure_max,
    'acc': measure_acc,
    'cpm': measure_cpm,
    'time': timestamp
    } }

    def erase(self):
    if self.com == 'RD1212v2':
    self.hid_set_report((0x12, 0x12, 0x01, 0x03, 0, 0, 0, 0, 0, 0, 0, 0, 0x3c, 0x84))
    elif self.com == 'RD1212v1':
    self.hid_set_report((0x12, 0x12, 0x01, 0x03, 0, 0, 0, 0, 0, 0, 0, 0, 0x3c, 0x84))