Commit c820a376 authored by Enrico Faulhaber's avatar Enrico Faulhaber
Browse files

fix and rework greisinger

- fix crc calculation to stay within the intended 16 bits
- provide special BinaryIO for parasitic power supply
- rework communication code, provide and use self._query

tested @nguidectrl.spodi

Change-Id: I8741e7a921c53f2bbed8f52888f813fe82528448
Reviewed-on: https://forge.frm2.tum.de/review/c/frm2/tango/entangle/+/23883

Tested-by: default avatarJenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Tested-by: default avatarEnrico Faulhaber <enrico.faulhaber@frm2.tum.de>
Reviewed-by: default avatarEnrico Faulhaber <enrico.faulhaber@frm2.tum.de>
parent 66876712
Greisinger device for sensor modules
------------------------------------
.. autodev:: greisinger.BinaryIO
.. autodev:: greisinger.Sensor
......@@ -17,174 +17,247 @@
#
# Module authors:
# Lea Kleesattel <lea.kleesattel@frm2.tum.de>
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
#
# *****************************************************************************
from entangle import base
from entangle.core import Prop, subdev, intrange, ON, FAULT, ALARM
from entangle.core import Prop, subdev, intrange, ON, FAULT, ALARM, CommunicationFailure
from entangle.lib.util import format_statusbits
from entangle.device.serial import BinaryIO as NormalBinaryIO, ReadBufferMixin, serial
PREFIX = 0xFF
VALUE = 0x00
VALUE_LENGTH = 3
STATUS = 0x30
STATUS_LENGTH = 3
DISPLAYUNIT = 0xF2
DISPLAYUNIT_LENGTH = 6
UNIT = {1: "C",
2: "F",
3: "K",
10: "% r.F.",
18: "inHg(0°C)",
19: "inHg(60°F)",
20: "bar",
21: "mbar",
22: "Pa",
23: "hPa",
24: "kPa",
25: "MPa",
26: "kg/cm^2",
27: "mmHg",
28: "PSI",
29: "mm H2O",
30: "S/cm",
31: "mS/cm",
32: "µS/cm",
40: "pH",
41: "rH",
45: "mg/l O2",
46: "% Sat O2",
47: "% O2",
50: "U/min",
53: "Hz",
55: "Impulse",
60: "m/s",
61: "km/h",
62: "mph",
63: "knot",
70: "mm",
71: "m",
72: "in",
73: "ft",
74: "cm",
75: "km",
79: "l/s",
80: "l/h",
81: "l/min",
82: "m^3/h",
83: "m^3/min",
84: "nm^3/h",
85: "ml/s",
86: "ml/min",
87: "ml/h",
88: "m^3/s",
90: "g",
91: "kg",
92: "N",
93: "Nm",
94: "t",
100: "A",
101: "mA",
102: "µA",
105: "V",
106: "mV",
107: "µV",
111: "W",
112: "kW",
115: "Wh",
116: "kWh",
119: "Wh/m2",
120: "mOhm",
121: "Ohm",
122: "kOhm",
123: "MOhm",
125: "kOhm*cm",
126: "MOhm*cm",
130: "cd",
131: "lx",
132: "lm",
150: "%",
151: "°",
152: "ppm",
153: "ppb",
160: "g/kg",
161: "g/m^3",
162: "mg/m^3",
163: "µg/m^3",
170: "kJ/kg",
171: "kcal/kg",
172: "mg/l",
173: "g/l",
175: "dB",
176: "dBm",
177: "dbA",
190: "sone",
191: "phon",
192: "µPa",
193: "dB(SPL)"}
STATUS_CODE = ['max. alarm',
'min. alarm',
'value too big',
'value too small',
'4 - reserved',
'5 - reserved',
'6 - reserved',
'7 - reserved',
'above range',
'below range',
'sensor error',
'11 - reserved',
'system error',
'calculation error',
'14 - reserved',
'battery low']
class BinaryIO(NormalBinaryIO):
"""hacked together binary IO JUST FOR GREISINGER GRS310x
which needs a permently activated DTR and a permanently deactivated RTS
"""
def init(self):
ReadBufferMixin._init_buffer(self)
try:
self._port = serial.Serial(
self.devfile,
baudrate=self.baudrate,
bytesize=8,
parity="N",
stopbits=1,
xonxoff=False,
rtscts=False,
dsrdtr=False,
timeout=0.2,
)
self._port.dtr = True
self._port.rts = False
except serial.SerialException as err:
raise CommunicationFailure("could not open port: %s" % err)
self._start_thread()
UNIT_MAP = {
1: "degC",
2: "degF",
3: "K",
10: "% r.F.",
18: "inHg(0°C)",
19: "inHg(60°F)",
20: "bar",
21: "mbar",
22: "Pa",
23: "hPa",
24: "kPa",
25: "MPa",
26: "kg/cm^2",
27: "mmHg",
28: "PSI",
29: "mm H2O",
30: "S/cm",
31: "mS/cm",
32: "µS/cm",
40: "pH",
41: "rH",
45: "mg/l O2",
46: "% Sat O2",
47: "% O2",
50: "U/min",
53: "Hz",
55: "Impulse",
60: "m/s",
61: "km/h",
62: "mph",
63: "knot",
70: "mm",
71: "m",
72: "in",
73: "ft",
74: "cm",
75: "km",
79: "l/s",
80: "l/h",
81: "l/min",
82: "m^3/h",
83: "m^3/min",
84: "nm^3/h",
85: "ml/s",
86: "ml/min",
87: "ml/h",
88: "m^3/s",
90: "g",
91: "kg",
92: "N",
93: "Nm",
94: "t",
100: "A",
101: "mA",
102: "µA",
105: "V",
106: "mV",
107: "µV",
111: "W",
112: "kW",
115: "Wh",
116: "kWh",
119: "Wh/m2",
120: "mOhm",
121: "Ohm",
122: "kOhm",
123: "MOhm",
125: "kOhm*cm",
126: "MOhm*cm",
130: "cd",
131: "lx",
132: "lm",
150: "%",
151: "°",
152: "ppm",
153: "ppb",
160: "g/kg",
161: "g/m^3",
162: "mg/m^3",
163: "µg/m^3",
170: "kJ/kg",
171: "kcal/kg",
172: "mg/l",
173: "g/l",
175: "dB",
176: "dBm",
177: "dbA",
190: "sone",
191: "phon",
192: "µPa",
193: "dB(SPL)",
}
STATUS_CODE = [
"max. alarm",
"min. alarm",
"value too big",
"value too small",
"4 - reserved",
"5 - reserved",
"6 - reserved",
"7 - reserved",
"above range",
"below range",
"sensor error",
"11 - reserved",
"system error",
"calculation error",
"14 - reserved",
"battery low",
]
ERROR_CODE = {
0x3FE0: 'above range',
0x3FE1: 'below range',
0x3FEA: 'calculation error',
0x3FEB: 'system error',
0x3FEC: 'empty battery',
0x3FED: 'no sensor',
0x3FEE: 'logging error - EEPROM',
0x3FEF: 'checksum error - EEPROM',
0x3FF0: 'logging error 6 - reboot system',
0x3FF1: 'logging error - data pointer',
0x3FF2: 'logging error - invalid data',
0x3FF3: 'invalid data',
0x3FE0: "above range",
0x3FE1: "below range",
0x3FEA: "calculation error",
0x3FEB: "system error",
0x3FEC: "empty battery",
0x3FED: "no sensor",
0x3FEE: "logging error - EEPROM",
0x3FEF: "checksum error - EEPROM",
0x3FF0: "logging error 6 - reboot system",
0x3FF1: "logging error - data pointer",
0x3FF2: "logging error - invalid data",
0x3FF3: "invalid data",
}
# defined commands
GET_VALUE = 0x00
GET_STATUS = 0x30
GET_MIN_VALUE = 0x60 # note: unimpl.
GET_MAX_VALUE = 0x70 # note: unimpl.
GET_SERIAL = 0xC0 # note: unimpl.
GET_EXT_DATA = 0xF2
EXT_DATA_DISPLAY_UNIT = 0xCA
EXT_DATA_CHANNELS = 0xD0 # note: unimpl.
REPLY_SIZE = {
GET_VALUE: 6,
GET_STATUS: 6,
GET_MIN_VALUE: 6,
GET_MAX_VALUE: 6,
GET_SERIAL: 3, # ??? hw responds with 3, should be 9. maybe unimplemented in hw?
GET_EXT_DATA: 9,
}
class Sensor(base.Sensor):
"""Readout of Greisinger - EASYBus Sensor module
and GMH portable measuring instruments."""
and GMH3692 portable measuring instruments."""
properties = {
'iodev': Prop(subdev, 'I/O device name.'),
'address': Prop(intrange(1, 4), 'Greisinger address.')
"iodev": Prop(subdev, "I/O device name."),
"address": Prop(intrange(1, 4), "Greisinger address."),
}
iodev_defaults = {
'class': 'BinaryIO',
'baudrate': '4800',
'parity': 'none',
'stopbits': 1,
'bytesize': 8,
"class": "BinaryIO",
"baudrate": "4800",
"parity": "none",
"stopbits": 1,
"bytesize": 8,
}
def _query(self, cmd, extcmd=None):
pkt = [self.address, cmd]
if extcmd is not None:
pkt.extend([extcmd & 0xFF, (extcmd >> 8) & 0xFF])
# invert the first byte of every 2 byte unit and append a crc
request = []
while len(pkt) > 1:
snippet, pkt = pkt[:2], pkt[2:]
request.extend([255 - snippet[0], snippet[1]])
request.append(self._crc(*request[-2:]))
reply = self._iodev.BinaryCommunicate([REPLY_SIZE[cmd]] + request)
if reply[0] != request[0]:
raise CommunicationFailure("wrong channel id received!")
# check + remove crc('s) and invert the first byte of each snippet
resp = []
while len(reply) > 2:
snippet, reply = reply[:3], reply[3:]
if not self._crc(*snippet[:2]) == snippet[2]:
raise CommunicationFailure(
"bad crc received, expected 0x%02x, got 0x%02x"
% (self._crc(*snippet[:2]), snippet[2])
)
resp.extend([255 - snippet[0], snippet[1]])
# decode data
if len(resp) > len(pkt):
data = resp[-1] + (resp[-2] << 8)
else:
data = None
return data
def init(self):
self._iodev = self.connect_client('iodev', self.iodev, 'BinaryIO')
self.channel = 0xFF - self.address
self._iodev = self.connect_client("iodev", self.iodev, "BinaryIO")
self.offset = 0.0
def state(self):
cmd = [STATUS_LENGTH, self.channel, STATUS]
cmd.append(self._crc(*cmd[-2:]))
resp = self._iodev.BinaryCommunicate(cmd)
st = self._decode(resp[3], resp[4])
msg = format_statusbits(st, STATUS_CODE)
st = self._query(GET_STATUS)
msg = ", ".join(format_statusbits(st, STATUS_CODE))
if st == 0:
return ON, msg
elif st & 0x3400:
......@@ -192,36 +265,24 @@ class Sensor(base.Sensor):
return ALARM, msg
def read_rawValue(self):
cmd = [VALUE_LENGTH, self.channel, VALUE]
cmd.append(self._crc(*cmd[-2:]))
resp = self._iodev.BinaryCommunicate(cmd)
return self._measurement_value(resp[3], resp[4])
code = self._query(GET_VALUE)
return self._measurement_value(code)
def get_rawValue_unit(self):
cmd = [DISPLAYUNIT_LENGTH, self.channel, DISPLAYUNIT]
cmd.append(self._crc(*cmd[-2:]))
cmd.extend([PREFIX - 202, 0])
cmd.append(self._crc(*cmd[-2:]))
resp = self._iodev.BinaryCommunicate(cmd)
return UNIT[resp]
def _crc(self, b1, b2):
code = self._query(GET_EXT_DATA, EXT_DATA_DISPLAY_UNIT)
return UNIT_MAP[code]
def _crc(self, b1=0, b2=0):
crc = b1 << 8 | b2
for _ in range(16):
if (crc & 0x8000) == 0x8000:
crc = (crc << 1) ^ 0x0700
else:
crc <<= 1
crc ^= 0x8380
crc <<= 1
return 0xFF - (crc >> 8)
def _decode(self, b1, b2):
return ((0x00FF - b1) << 8) | b2
def _measurement_value(self, b1, b2):
decoded = self._decode(b1, b2)
decimal_point_pos = (decoded & 0xC000) >> 14
decoded &= 0x3FFF
if decoded < 0x3FE0:
return (decoded - 0x800)/10**decimal_point_pos
raise ValueError(ERROR_CODE.get(decoded,
'unknown error code: %d' % decoded))
def _measurement_value(self, code):
decimal_point_pos = (code & 0xC000) >> 14
code &= 0x3FFF
if code < 0x3FE0:
return (code - 0x800) / 10.0 ** decimal_point_pos
raise ValueError(ERROR_CODE.get(code, "unknown error code: %d" % code))
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment