Files
controller-wisetopdataserver/python/biopro/devlib/data.py
T

1680 lines
65 KiB
Python

import abc
import struct
import math
import numpy
from typing import Optional, TypeVar, Generic, Tuple, Dict, List, AnyStr
from datetime import datetime
from biopro.recording import RecordingData, RecordingFileDataFormat
# from biopro.util.console import hex_line
T = TypeVar('T')
class EODInterrupt(RuntimeError):
"""End of Data interrupt.
"""
__slots__ = ()
class DataDecodeFormat(Generic[T], metaclass=abc.ABCMeta):
"""data decoder.
do not share this instance between data decoding handle, except you make sure it is safe.
"""
__slots__ = ()
@property
@abc.abstractmethod
def name(self) -> AnyStr:
"""decoder name
make sure that decoder and ``DataDecodeFormat.parse(decoder.name)`` are
functional equally.
:return: decoder name.
"""
pass
@property
@abc.abstractmethod
def format(self) -> bytes:
"""recording meta file format
:return:
"""
pass
def message(self) -> Optional[str]:
"""send message to front-end to indicate that data has changed something.
:return: message content
"""
return None
@abc.abstractmethod
def decode(self, data: bytes) -> Optional[T]:
"""
:param data: raw data
:return: decoded data
"""
pass
@classmethod
def parse(cls, expr: AnyStr) -> 'DataDecodeFormat':
if expr == NulDataDecoder.NAME:
return NulDataDecoder.INSTANCE
elif expr == RawDataDecoder.NAME:
return RawDataDecoder.INSTANCE
elif expr == TC4VAF2DataDecoder.NAME:
return TC4VAF2DataDecoder()
elif expr == TDC4VAF2DataDecoder.NAME:
return TDC4VAF2DataDecoder()
elif expr == TDC4VCDataDecoder.NAME:
return TDC4VCDataDecoder()
elif expr == I4V4Z4T4DataDecoder.NAME:
return I4V4Z4T4DataDecoder()
elif expr == PEL_FMT1_DataDecoder.NAME:
return PEL_FMT1_DataDecoder()
elif isinstance(expr, str):
raise RuntimeError('current not support custom data decode format : ' + expr)
elif isinstance(expr, bytes):
if expr.startswith(b'TDC4VAF2:'):
return TDC4VAF2DataDecoder(expr[9:])
elif expr.startswith(b'TDC4VC:'):
return TDC4VCDataDecoder(expr[7:])
elif expr.startswith(b'EISZeroOne:'):
return EISZeroOneDataDecoder(expr[11:])
raise RuntimeError('current not support custom data decode format : ' + str(expr))
def __str__(self):
return self.__class__.__name__
__repr__ = __str__
class NulDataDecoder(DataDecodeFormat[None]):
"""drop all data, decode always return None."""
NAME = 'NUL'
__slots__ = ()
@property
def name(self) -> str:
return self.NAME
@property
def format(self) -> bytes:
return RecordingFileDataFormat.RAW_DATA
def decode(self, data: bytes) -> None:
"""
:param data: raw data
:return: always None
"""
return None
NulDataDecoder.INSTANCE = NulDataDecoder()
class RawDataDecoder(DataDecodeFormat[bytes]):
NAME = 'RAW'
__slots__ = ()
@property
def name(self) -> str:
return self.NAME
@property
def format(self) -> bytes:
return RecordingFileDataFormat.RAW_DATA
def decode(self, data: bytes) -> bytes:
"""
:param data: raw data
:return: *data*
"""
return data
RawDataDecoder.INSTANCE = RawDataDecoder()
class Timer:
CC2650_TIMESTAMP_OVERFLOW = (int)(0xFFFF_FFFF / 31)
BMD380_TIMESTAMP_OVERFLOW = (0xFFFF_FFFF)
__slots__ = '_start_time', '_prev_time', '_prev_prev_time', '_overflow'
def __init__(self):
self._start_time: Dict[int, float] = {}
self._prev_time: Dict[int, float] = {}
self._prev_prev_time: Dict[int, float] = {}
self._overflow: Dict[int, float] = {}
for dev_num in range(8):
for d in [self._start_time, self._prev_time, self._prev_prev_time, self._overflow]:
d[dev_num] = None
def eis_get_time_stamp(self, device: int, time_stamp: float) -> Tuple[Optional[float], Optional[float]]:
if self._start_time[device] is None:
self._start_time[device] = time_stamp
self._prev_time[device] = time_stamp
self._prev_prev_time[device] = time_stamp
self._overflow[device] = 0
return 0, 0, False
prev_time = self._prev_time.get(device, None)
prev_prev_time = self._prev_prev_time.get(device, None)
overflow = self._overflow.get(device, 0)
start_time = self._start_time[device]
if prev_time is None:
self._prev_time[device] = time_stamp
self._overflow[device] = 0
ret = time_stamp - start_time
print("ignore prev_time is None; ", "time:", time_stamp, "; prev_time:", prev_time, "; prev_prev_time:", prev_prev_time, ",", datetime.now())
return ret, 0, False
# data duplicate
elif time_stamp == prev_time:
print("ignore time_stamp == prev_time; ", "time:", time_stamp, "; prev_time:", prev_time, "; prev_prev_time:", prev_prev_time, ",", datetime.now())
return None, None, True
# time stamp overflow
if time_stamp < 0x0100_0000 and 0x0800_0000 < prev_time:
overflow += 1
self._overflow[device] = overflow
self._prev_time[device] = time_stamp
delta = time_stamp + (self.CC2650_TIMESTAMP_OVERFLOW + 1) - prev_time
ret = (self.CC2650_TIMESTAMP_OVERFLOW + 1) * overflow + time_stamp - start_time
print("time stamp overflow;", "time:", time_stamp, "; prev_time:", prev_time, "; prev_prev_time:", prev_prev_time, "; delta:", delta, ",", datetime.now())
print("overflow:", overflow, "; device:", device)
return ret, delta, False
# ignore negative delta
elif time_stamp < prev_time and time_stamp != 0:
print("ignore negative delta; ", "time:", time_stamp, "; prev_time:", prev_time, "; prev_prev_time:", prev_prev_time, ",", datetime.now())
return None, None, True
else:
delta = time_stamp - prev_time
self._prev_time[device] = time_stamp
overflow = self._overflow[device]
ret = time_stamp + (self.CC2650_TIMESTAMP_OVERFLOW + 1) * overflow - start_time
self._prev_prev_time[device] = prev_time
return ret, delta, False
def get_time_stamp(self, device: int, time_stamp: float) -> Tuple[Optional[float], Optional[float]]:
if self._start_time[device] is None:
self._start_time[device] = time_stamp
self._prev_time[device] = time_stamp
self._prev_prev_time[device] = time_stamp
self._overflow[device] = 0
return 0, 0, False
prev_time = self._prev_time.get(device, None)
prev_prev_time = self._prev_prev_time.get(device, None)
overflow = self._overflow.get(device, 0)
start_time = self._start_time[device]
if prev_time is None:
self._prev_time[device] = time_stamp
self._overflow[device] = 0
ret = time_stamp - start_time
print("ignore prev_time is None; ", "time:", time_stamp, "; prev_time:", prev_time, "; prev_prev_time:", prev_prev_time, ",", datetime.now())
return ret, 0, False
# data duplicate
elif time_stamp == prev_time:
print("ignore time_stamp == prev_time; ", "time:", time_stamp, "; prev_time:", prev_time, "; prev_prev_time:", prev_prev_time, ",", datetime.now())
return None, None, True
# time stamp overflow
if time_stamp < 0x0100_0000 and 0x0800_0000 < prev_time:
overflow += 1
self._overflow[device] = overflow
self._prev_time[device] = time_stamp
delta = time_stamp + (self.CC2650_TIMESTAMP_OVERFLOW + 1) - prev_time
ret = (self.CC2650_TIMESTAMP_OVERFLOW + 1) * overflow + time_stamp - start_time
print("time stamp overflow;", "time:", time_stamp, "; prev_time:", prev_time, "; prev_prev_time:", prev_prev_time, "; delta:", delta, ",", datetime.now())
print("overflow:", overflow, "; device:", device)
return ret, delta, False
# ignore negative delta
elif time_stamp < prev_time and time_stamp != 0:
print("ignore negative delta; ", "time:", time_stamp, "; prev_time:", prev_time, "; prev_prev_time:", prev_prev_time, ",", datetime.now())
return None, None, True
# ignore big jump, if delta t > 30s, ignore
elif time_stamp > prev_time and (time_stamp - prev_time > 60000) and prev_time != 0:
print("ignore big jump;", "time:", time_stamp, "; prev_time:", prev_time, "; prev_prev_time:", prev_prev_time, ",", datetime.now())
return None, None, True
else:
delta = time_stamp - prev_time
self._prev_time[device] = time_stamp
overflow = self._overflow[device]
ret = time_stamp + (self.CC2650_TIMESTAMP_OVERFLOW + 1) * overflow - start_time
self._prev_prev_time[device] = prev_time
return ret, delta, False
def get_bmd380_time_stamp(self, device: int, time_stamp: float) -> Tuple[Optional[float], Optional[float]]:
if self._start_time[device] is None:
self._start_time[device] = time_stamp
self._prev_time[device] = time_stamp
self._prev_prev_time[device] = time_stamp
self._overflow[device] = 0
return 0, 0, False
start_time = self._start_time[device]
prev_time = self._prev_time[device]
prev_prev_time = self._prev_prev_time[device]
overflow = self._overflow[device]
# timestamp overflow
if time_stamp < 0x0100_0000 and 0x0F00_0000 < prev_time:
overflow += 1
delta = time_stamp + (self.BMD380_TIMESTAMP_OVERFLOW + 1) - prev_time
ret = (self.BMD380_TIMESTAMP_OVERFLOW + 1) * overflow + time_stamp - start_time
print("time_stamp overflow:", "time_stamp:", time_stamp, "; prev_time:", prev_time, "; prev_prev_time:", prev_prev_time, "; delta:", delta, ",", datetime.now())
print("time_stamp overflow:", "overflow_times:", overflow, "; device:", device)
else:
delta = time_stamp - prev_time
ret = (self.BMD380_TIMESTAMP_OVERFLOW + 1) * overflow + time_stamp - start_time
self._overflow[device] = overflow
self._prev_prev_time[device] = prev_time
self._prev_time[device] = time_stamp
return ret, delta, False
class RecDataDecoder(DataDecodeFormat[RecordingData], metaclass=abc.ABCMeta):
__slots__ = '_device', 'timer'
def __init__(self):
self._device: int = 0
self.timer = Timer()
@property
def device(self) -> int:
return self._device
@device.setter
def device(self, value: int):
if not (0 <= value):
raise ValueError('illegal device value : ' + str(value))
self._device = value
@property
def format(self) -> bytes:
return RecordingFileDataFormat.REC_DATA
def get_time_stamp(self, time_stamp: float) -> Tuple[Optional[float], Optional[float]]:
return self.timer.get_time_stamp(self.device, time_stamp)
def eis_get_time_stamp(self, time_stamp: float) -> Tuple[Optional[float], Optional[float]]:
return self.timer.eis_get_time_stamp(self.device, time_stamp)
def get_bmd380_time_stamp(self, time_stamp: float) -> Tuple[Optional[float], Optional[float]]:
return self.timer.get_bmd380_time_stamp(self.device, time_stamp)
@abc.abstractmethod
def decode(self, data: bytes) -> Optional[RecordingData]:
pass
class TC4VAF2DataDecoder(RecDataDecoder):
"""
::
| | 1 | 2 | 3 |
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2
-----------------------------------------------------------------
| header | length |
| timestamp |
| data [0] | data[1] |
| ............................ | data[N] |
*data*
::
| | 1 |
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6
---------------------------------
|channel| value | F |
"""
NAME = 'TC4VAF2'
__slots__ = ('_prev_data',)
def __init__(self):
super().__init__()
self._prev_data: Optional[bytes] = None
@property
def name(self) -> str:
return self.NAME
def decode(self, data: bytes) -> Optional[RecordingData]:
if len(data) < 6:
return None
# hex_line(data)
data_length = int(data[1])
if data_length == 0:
return None
time_stamp: float = struct.unpack('<I', data[2:6])[0] # unit: 1/32ms
time_stamp, delta, ret_get_time_stamp = self.get_time_stamp(time_stamp)
if time_stamp is None:
return None
if time_stamp == 0:
self._prev_data = data
return None
prev_data = self._prev_data
self._prev_data = data
assert prev_data is not None
time_stamp /= 32 # unit: ms
data_length = int(prev_data[1])
sample_rate = 1000 * ((data_length - 4) / 2) / delta
ret = RecordingData(self.device, int(time_stamp), int(sample_rate))
for i in range(6, data_length + 2, 2):
d1 = data[i]
d2 = data[i + 1]
channel = (d1 & 0xF0) >> 4
value = ((d1 & 0x0F) << 6) | ((d2 & 0xFC) >> 2)
flag = d2 & 0b0011
if flag == 0:
ret.append_data(channel, value - 512)
else:
ret.append_data(channel, None)
return ret
class TDC4VAF2DataDecoder(RecDataDecoder):
"""
data format
-----------
::
| | 1 | 2 | 3 |
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2
-----------------------------------------------------------------
| header | length = N |
| timestamp |
| time delta |
| data [0] | data[1] |
| ............................ | data[N] |
*data*
::
| | 1 |
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6
---------------------------------
|channel| value | F |
cali coeff table format
-----------------------
TODO cali coeff table format
::
| | 1 | 2 | 3 | 4 | 5 | 6 |
01234567890123456789012345678901234567890123456789012345678901234
-----------------------------------------------------------------
| | c0 g0 amp | c0 g1 amp | c0 g2..
.. amp | | c0 g0 off | c0 g1..
.. off | c0 g2 off | | c1 g0..
| ............................................................. |
| cN g0 amp | cN g1 amp | cN g2 amp |.. (4 bytes) ..|
| cN g0 off | cN g1 off | cN g2 off |.. (4 bytes) ..|
"""
NAME = 'TDC4VAF2'
AMP_GAIN = (35, 75, 325)
__slots__ = 'amp_gain', 'data_counter', 'cali_coeff', '_cali_coeff'
def __init__(self, cali_coeff: bytes = None):
super().__init__()
self.amp_gain: int = 0
self.data_counter: int = 0
self._cali_coeff: Optional[bytes] = None
self.cali_coeff: Optional[List[Tuple[int, int]]] = None
if cali_coeff is not None:
self._cali_coeff = cali_coeff
self.cali_coeff = self._decode_cali_coeff(cali_coeff)
@staticmethod
def _decode_cali_coeff(self, cali_coeff: bytes) -> Optional[List[Tuple[int, int]]]:
if cali_coeff != b'':
cali_table = []
for i in range(0, 12): # foreach channel
for j in range(20 * i + 3, 20 * i + 9, 2):
amp = struct.unpack('>h', cali_coeff[j:j + 2])[0]
offset = struct.unpack('>h', cali_coeff[j + 10:j + 12])[0]
cali_table.append((amp, offset))
return cali_table
else:
return None
@property
def name(self) -> AnyStr:
if self._cali_coeff is None:
return self.NAME
else:
return self.NAME.encode() + b':' + self._cali_coeff
def decode(self, data: bytes) -> Optional[RecordingData]:
"""
data_counter is used for excluding data which were send from the beginning ( < 15 )with the wrong timestamp,
and these data will cause error while recording.
"""
if self.data_counter < 15:
self.data_counter += 1
else:
if len(data) < 8:
return None
# print(hex_line(data))
data_length = int(data[1])
if data_length == 0:
return None
time_stamp: float = struct.unpack('<I', data[2:6])[0] # unit: 1/32K sec, 1/32 ms
time_delta: float = struct.unpack('<H', data[6:8])[0] # unit: as above
time_stamp, _, ret_get_time_stamp = self.get_time_stamp(time_stamp)
if time_stamp is None:
return None
if data_length == 1 or int(time_delta) == 0:
sample_rate = 0
else:
# recalculate sample rate
sample_rate = 32769 * (data_length - 1) / time_delta # unit 1/s
ret = RecordingData(self.device, time_stamp / 32.769, int(sample_rate))
amp_gain = self.AMP_GAIN.index(self.amp_gain)
stop = len(data)
for i in range(data_length):
# avoid index out of range
offset = 8 + 2 * i
if offset + 1 < stop:
d1 = data[offset]
d2 = data[offset + 1]
channel = (d1 & 0xF0) >> 4
value = ((d1 & 0x0F) << 6) | ((d2 & 0xFC) >> 2)
flag = d2 & 0b0011
if flag != 0:
ret.append_data(channel, None)
else:
if channel < 12 and self.cali_coeff is not None:
gain, offset = self.cali_coeff[channel * 3 + amp_gain]
gain /= 2
if gain == 0:
cali_value = value - 512
else:
cali_value = int(1000 * ((value - 512 - offset) / gain))
else:
cali_value = value - 512
ret.append_data(channel, cali_value)
return ret
class TDC4VCDataDecoder(RecDataDecoder):
"""
::
| | 1 | 2 | 3 |
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2
-----------------------------------------------------------------
| header | length = N |
| timestamp |
| data [0] | data[1] |
| ............................ | data[15] |
*data*
::
| | 1 |
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6
---------------------------------
|channel| value | F |
"""
NAME = 'TDC4VC'
AMP_GAIN = (100, 400, 800, 25)
CH_NUMBER = 8
GAIN_LEVEL_NUMBER = 4
__slots__ = "_start_return_data", "_time_stamp", "_time_stamp_list", "_discard_data_counter", "_channel", 'amp_gain', 'data_counter', 'cali_coeff', '_cali_coeff', "_cycle_start_time", "_adc_clock", "_axis_ch", "_prev_delta_time","_prev_data", "_prev_time_stamp"
def __init__(self, cali_coeff: bytes = None):
super().__init__()
self._start_return_data = False
self._time_stamp = 0
self._time_stamp_list = []
self._discard_data_counter = 0
self._channel = 0
self._adc_clock = 0
self._axis_ch = 0
self.amp_gain: int = 0
self.data_counter: int = 0
self._cali_coeff: Optional[bytes] = None
self.cali_coeff: Optional[List[Tuple[int, int]]] = None
self._cycle_start_time = []
if cali_coeff is not None:
self._cali_coeff = cali_coeff
self.cali_coeff = self._decode_cali_coeff(cali_coeff)
self._prev_data = None
self._prev_delta_time = None
self._prev_time_stamp = None
@staticmethod
def _decode_cali_coeff(cali_coeff: bytes) -> Optional[List[Tuple[int, int]]]:
if cali_coeff != b'':
cali_table = []
max_length = 4
size_of_cali_data_per_amp_gain_level = 33
for i in range(0, max_length):
for j in range(1, size_of_cali_data_per_amp_gain_level, 4):
offset = i * size_of_cali_data_per_amp_gain_level + j
amp = struct.unpack('>h', cali_coeff[offset: offset + 2])[0]
offset = struct.unpack('>h', cali_coeff[offset + 2: offset + 4])[0]
cali_table.append((amp, offset))
# print('decode cali', i , j , amp , offset)
# print('cali_table', cali_table)
return cali_table
else:
print()
return None
@property
def name(self) -> AnyStr:
if self._cali_coeff is None:
return self.NAME
else:
return self.NAME.encode() + b':' + self._cali_coeff
@property
def real_sample_rate(self) -> int:
result = 0
if self._adc_clock != 0:
result = (200 / self._adc_clock) * 1000
return result
def decode(self, data: bytes) -> Optional[RecordingData]:
if len(data) < 20:
return None
# data_packet contains (data_length) pair of datas
data_length = int(data[1])
# axis data start in index (axis_start_index)
axis_start_index = 8 + data_length * 2
# data_packet contains (axis_data_length) pair of datas
axis_data_length = int((len(data) - axis_start_index) / 6)
if data_length == 0:
return None
time_stamp: float = struct.unpack('>I', data[2:6])[0]
time_delta: float = struct.unpack('>H', data[6:8])[0]
# print('absolute time_stamp', time_stamp * 1000 / 32)
"""
why multiple first then divide at last
"""
# time_stamp = time_stamp * 32 # unit: 1/32K sec, 1/32 ms
# time_delta = time_delta * 32 # unit: as above
''' garbage -> 0 -> real data'''
''' False -> True -> True '''
''' Some device will start early before enter decoder, so start flag will never enable'''
if time_stamp == 0 or (self._time_stamp != 0 and self._time_stamp < time_stamp):
self._start_return_data = True
if time_stamp is None or time_stamp == 0 or not self._start_return_data:
self._time_stamp = time_stamp
return None
if self._discard_data_counter < 10:
self._discard_data_counter += 1
return None
stop = len(data)
if time_stamp != 0 and self._start_return_data:
time_stamp, _ , ret_get_time_stamp = self.get_time_stamp(time_stamp)
if data_length == 1 or int(time_delta) == 0:
sample_rate = 0
if len(self._prev_time_stamp) > 0 and time_stamp is not None:
# print('prev_time_stamp, time_stamp', self._prev_time_stamp[0], time_stamp)
data_length_for_sample_rate = data_length
data_packets_time_delta = time_stamp - self._prev_time_stamp[0]
if self._prev_time_stamp[0] > time_stamp:
return None
if int(data_packets_time_delta / self._prev_delta_time[0]) >= 2 or self._prev_time_stamp[0] is None:
data_packets_time_delta = self._prev_delta_time[0]
# unit 1/s
sample_rate = 1000 * (data_length_for_sample_rate) / data_packets_time_delta
sample_rate_channel_upper_256 = sample_rate * axis_data_length / data_length
# print('sample_rate', sample_rate)
ret = RecordingData(self.device, (self._prev_time_stamp[0] * 1000), int(sample_rate), int(sample_rate_channel_upper_256), 4)
ret.cycle = self._cycle_start_time
# get user setting gain level
# and set amp_gain=0~3 => gain=25,100,400,800
amp_gain = self.amp_gain
amp_gain = amp_gain + 1
if amp_gain == 4:
amp_gain = 0
# decode recording data
for i in range(data_length):
# avoid index out of range
offset = 8 + 2 * i
if offset + 1 < stop:
d1 = (data[offset] & 0xF0) >> 4
d2 = (data[offset] & 0x0F)
d3 = data[offset + 1]
channel = d1
value = d2 << 8 | d3
# print("\nNeulive 3.x")
# print("channel", channel + 1)
# print("value", value - 2048)
# print("amp_gain = ", amp_gain)
# print("cali:", self.cali_coeff)
if channel < 8 and self._prev_data[0] is not None:
gain, offset = self._prev_data[0][8 * amp_gain + channel]
# print('cali_gain', gain)
# print('cali_offset', offset)
if gain == 0:
cali_value = value - 2048
elif gain == 1 and offset == 0:
cali_value = value - 2048
else:
cali_value = int(1000 * ((value - 2048 - offset) / gain))
else:
cali_value = value - 2048
# print('c,v', channel, cali_value)
ret.append_data(channel, cali_value)
# print('\n')
# decode 3-axis acc data
x_axis_acc_ch = 256
y_axis_acc_ch = 257
z_axis_acc_ch = 258
axis_acc_ch = 259
if self._axis_ch > 0:
axis_channel_allow = bin(self._axis_ch)[2:]
axis_channel_allow_list = [ch for ch in axis_channel_allow]
axis_channel_allow_list.reverse()
for i in range(axis_data_length):
x_axis_index: int = i*6 + axis_start_index
y_axis_index: int = i*6 + axis_start_index + 2
z_axis_index: int = i*6 + axis_start_index + 4
x_axis_data = data[x_axis_index] << 8 | data[x_axis_index + 1]
y_axis_data = data[y_axis_index] << 8 | data[y_axis_index + 1]
z_axis_data = data[z_axis_index] << 8 | data[z_axis_index + 1]
# using 2's complement
if x_axis_data >= 0x8000:
x_axis_data = x_axis_data - 0xFFFF
if y_axis_data >= 0x8000:
y_axis_data = y_axis_data - 0xFFFF
if z_axis_data >= 0x8000:
z_axis_data = z_axis_data - 0xFFFF
axis_data = int(math.sqrt(x_axis_data ** 2 + y_axis_data ** 2 + z_axis_data ** 2))
if len(axis_channel_allow_list) >= 1:
if int(axis_channel_allow_list[0]) == 1:
ret.append_data(x_axis_acc_ch, int(x_axis_data / 100))
if len(axis_channel_allow_list) >= 2:
if int(axis_channel_allow_list[1]) == 1:
ret.append_data(y_axis_acc_ch, int(y_axis_data / 100))
if len(axis_channel_allow_list) >= 3:
if int(axis_channel_allow_list[2]) == 1:
ret.append_data(z_axis_acc_ch, int(z_axis_data / 100))
if len(axis_channel_allow_list) >= 4:
if int(axis_channel_allow_list[3]) == 1:
ret.append_data(axis_acc_ch, int(axis_data / 100))
self._prev_data.pop()
self._prev_time_stamp.pop()
self._prev_delta_time.pop()
self._prev_data.append(self.cali_coeff)
self._prev_time_stamp.append(time_stamp)
self._prev_delta_time.append(time_delta)
return ret
else:
if time_stamp is not None:
self._prev_data.append(self.cali_coeff)
self._prev_time_stamp.append(time_stamp)
self._prev_delta_time.append(time_delta)
return None
class I4V4Z4T4DataDecoder(RecDataDecoder):
"""
::
| | 1 | 2 | 3 |
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2
-----------------------------------------------------------------
| header |
| current |
| voltage |
| impedance |
| time stamp |
| cycle number |
cycle number
for cyclic voltammetry use, we save it as channel number.
0xFF
"""
NAME = 'I4V4Z4T4'
MESSAGE_CYCLE_COMPLETE = 'CycleComplete'
__slots__ = ('_message', '_cycle_number', '_start_return_data', '_time_stamp',
'_total_time_stamp', '_mode', '_cycle_start_time',
'_mode_stop', '_show_data',
'_last_mem_wrong_information', '_last_mem_cnt', '_last_elite_notify_times')
def __init__(self):
super().__init__()
self._cycle_number = 0
self._message: Optional[str] = None
self._start_return_data = False
self._time_stamp = 0
self._total_time_stamp = 0
self._mode_stop = 0
self._mode = 0
self._cycle_start_time = []
self._show_data = False
self._last_mem_wrong_information = -1
self._last_mem_cnt = -1
self._last_elite_notify_times = -1
@property
def name(self) -> str:
return self.NAME
def message(self) -> Optional[str]:
ret = self._message
self._message = None
return ret
def decode(self, data: bytes) -> Optional[RecordingData]:
if len(data) < 18:
return None
#/* Elite Notify data:
# * +--------+----------+---------+---------+---------+-----------+-----------------+
# * | id(1B) | time(4B) | ch1(4B) | ch2(4B) | ch3(4B) | cycle(2B) | finish_flag(1B) |
# * | bat(4B) | notify#(1B) | ch4(4B) | ch5(4B) | ch6(4B) | __(3B) |
# * +---------+-------------+---------+---------+---------+--------+
# */
#/*
# * EliteADCControl(): use ADC plot, and send what data to controller
# * +---------------------------+-----------+-----------+-----------+-----------+-----------+
# * | MODE | ch1 | ch2 | ch3 | cycle | ch4 |
# * +---------------------------+-----------+-----------+-----------+-----------+-----------+
# * | CURVE_IV | Iin | Vout | Vin | | Vmon |
# * | CURVE_IV_CY | Iin | Vout | Vin | v | Vmon |
# * | CURVE_VO | Iin | Vout | Vin | | Vmon |
# * | CURVE_RT | Iin | Vout | R | | Vmon |
# * | CURVE_VT | Iin | Vin | | | |
# * | CURVE_IT | Iin | Vin | Vout | | Vmon |
# * | CURVE_CC | Iin | Vin | Vout | | Vmon |
# * | CURVE_CP | Iin | Vout-Vin | Vout | | Vmon |
# * | CURVE_CV | Iin | Vout-Vin | Vout | v | Vmon |
# * | CURVE_LSV | Iin | Vout-Vin | Vout | | Vmon |
# * | CURVE_CA | Iin | Vout-Vin | Vout | | Vmon |
# * | CURVE_OCP | Iin | Vmon-Vin | Vin | | Vmon |
# * | CURVE_UNI_PULSE | pul1_Iin | pul2_Iin | | | |
# * | CURVE_DPV | c1&c2_avg | Vout-Vin | Vout | | Vmon |
# * | CURVE_DPV_SMPRATE | Iin | Vout-Vin | Vout | | Vmon |
# * | CURVE_DPV_ADVANCE | c1&c2_avg | Vout-Vin | Vout | | Vmon |
# * | CURVE_DPV_ADVANCE_SMPRATE | Iin | Vout-Vin | Vout | | Vmon |
# * +---------------------------+-----------+-----------+-----------+-----------+-----------+
# *
# * ps. c1_avg = pul1_Iin
# * ps. c2_avg = pul2_Iin
# */
mem_cnt = data[1]
time_stamp: float = struct.unpack('<I', data[1+3:5+3])[0]
ch1 = struct.unpack('<i', data[5+3:9+3])[0] # unit: nA
ch2 = struct.unpack('<i', data[9+3:13+3])[0] # unit: uV
ch3 = struct.unpack('<i', data[13+3:17+3])[0] # unit: mOm
cycle_number = struct.unpack('<H', data[17+3:19+3])[0]
finish_mode_falg = data[19+3]
battery = struct.unpack('<i', data[20+3:24+3])[0]
elite_notify_times = data[24+3]
ch4 = struct.unpack('<i', data[25+3:29+3])[0]
ch5 = struct.unpack('<i', data[29+3:33+3])[0]
ch6 = struct.unpack('<i', data[33+3:37+3])[0]
# self._show_data = True
mem_wrong_information = struct.unpack('<i', data[43:47])[0] # mem_wrong_information = green retry, green wrong, red retry, red wrong
if mem_wrong_information != self._last_mem_wrong_information:
print(datetime.now(), 'device', str(self.device), 'mem_wrong_information[43:47]:', data[43:47], mem_wrong_information, self._last_mem_wrong_information, flush = True)
if mem_cnt != self._last_mem_cnt+1:
if not (mem_cnt == 0 and self._last_mem_cnt == 255):
print(datetime.now(), 'device', str(self.device), 'mem_cnt:', mem_cnt, 'self._last_mem_cnt:', self._last_mem_cnt, flush = True)
if (elite_notify_times != self._last_elite_notify_times+1) and not (elite_notify_times == 0 and self._last_elite_notify_times == 0):
if not (elite_notify_times == 0 and self._last_elite_notify_times == 255):
print(datetime.now(), 'device', str(self.device), 'elite_notify_times:', elite_notify_times, 'self._elite_notify_times:', self._last_elite_notify_times, flush = True)
self._last_mem_wrong_information = mem_wrong_information
self._last_mem_cnt = mem_cnt
self._last_elite_notify_times = elite_notify_times
ram_num = data[47]
broken_flag = data[-1]
if (finish_mode_falg & 0b11110000 == 0b10100000):
finishMode = True
else:
finishMode = False
if time_stamp == 0:
self._start_return_data = True
if time_stamp is None or time_stamp == 0 or not self._start_return_data:
return None
if time_stamp != 0 and self._start_return_data == True:
time_stamp, delta, ret_get_time_stamp = self.get_time_stamp(time_stamp)
if ret_get_time_stamp == True:
print("error timeStamp full data:", list(data), datetime.now(), '\n')
return None
else:
if self._show_data:
print('|', time_stamp, '|', delta, '|', int(time_stamp * 1000 / 2),
'|', ch1, '|', ch2, '|', ch3, '|', cycle_number,
'|', ch4, '|', ch5, '|', ch6,
'|', finishMode, '@', str(self.device), flush = True)
# print('|', '{:10}'.format(time_stamp),
# '|', '{:4}'.format(delta),
# '|', '{:10}'.format(int(time_stamp * 1000 / 2)), #[usec]
# '|', '{:10}'.format(ch1), #[nA]
# '|', '{:10}'.format(ch2), #[uV]
# '|', '{:10}'.format(ch3),
# '|', '{:5}'.format(cycle_number),
# '|', '{:10}'.format(ch4), #Voutin[uV]
# '|', '{:10}'.format(ch5),
# '|', '{:10}'.format(ch6),
# '|', '{:5}'.format(battery), #[mV]
# '|', '{:4}'.format(elite_notify_times),
# '|', '{:1}'.format(finishMode),
# '@', str(self.device), '|', flush = True)
# print('|', '{:5}'.format(mem_wrong_information),
# '|', '{:2}'.format(ram_num),
# '|', '{:2}'.format(broken_flag),
# '@', str(self.device), '|')
if finishMode == True:
print("finishMode full data:", list(data), datetime.now())
self._mode_stop = 1
else:
self._mode_stop = 0
ret = RecordingData(self.device, int(time_stamp * 1000 / 2), 0)
ret.append_data(0, ch1)
ret.append_data(1, ch2)
ret.append_data(2, ch3)
ret.append_data(3, cycle_number)
ret.append_data(4, ch4)
ret.append_data(5, ch5)
ret.append_data(6, ch6)
# ret.append_data(4, battery)
# ret.append_data(5, elite_notify_times)
# ret.append_data(6, mem_cnt)
# # memoryboard information
# ret.append_data(7, ram_num)
# ret.append_data(8, broken_flag)
# try:
# ret.append_data(9, mem_wrong_information)
# # print('append_data success, mem_wrong_information:', mem_wrong_information, hex(mem_wrong_information))
# except:
# print('append_data fail, mem_wrong_information:', mem_wrong_information, hex(mem_wrong_information))
if cycle_number != self._cycle_number:
# notify cycle_number change
self._message = self.MESSAGE_CYCLE_COMPLETE + '=%d' % cycle_number
self._cycle_number = cycle_number
return ret
def isFinishMode(self) -> int:
return self._mode_stop
class PEL_FMT1_DataDecoder(RecDataDecoder):
NAME = 'PEL_FMT1_'
MESSAGE_CYCLE_COMPLETE = 'CycleComplete'
__slots__ = ('_message', '_mode', '_mode_stop', '_show_data')
def __init__(self):
super().__init__()
self._message: Optional[str] = None
self._mode_stop = 0
self._mode = 0
self._show_data = False
@property
def name(self) -> str:
return self.NAME
def message(self) -> Optional[str]:
ret = self._message
self._message = None
return ret
def decode(self, data: bytes) -> Optional[RecordingData]:
mem_board_id: int = struct.unpack('<B', data[0+3:1+3])[0]
packet_sequence: int = struct.unpack('<B', data[1+3:2+3])[0]
raw_time_stamp: float = struct.unpack('<I', data[2+3:6+3])[0]
if all(b == 0 for b in data[0+3:42+3]):
hex_string = ' '.join(format(i, '02X') for i in data)
print(hex_string, flush=True)
return None
timestamp, delta, ret_get_time_stamp = self.get_bmd380_time_stamp(raw_time_stamp)
output_vo = struct.unpack('<i', data[6+3:10+3])[0]
output_vc = struct.unpack('<i', data[10+3:14+3])[0]
output_ve = struct.unpack('<i', data[14+3:18+3])[0]
hold_OUT = struct.unpack('<f', data[18+3:22+3])[0]
hold_VCC = struct.unpack('<f', data[22+3:26+3])[0]
hold_VEE = struct.unpack('<f', data[26+3:30+3])[0]
resis_pattern_id = struct.unpack('<H', data[30+3:32+3])[0]
resis_bitsmask = struct.unpack('<H', data[32+3:34+3])[0]
resis_g_val = struct.unpack('<f', data[34+3:38+3])[0]
mode_flag = struct.unpack('<I', data[38+3:42+3])[0]
flag_mode_complete = (mode_flag & 0x0000000F)
val1 = 0
val2 = 0
val3 = 0
values = [val1, val2, output_vo, output_vc, output_ve, val3, resis_g_val, hold_OUT, hold_VCC, hold_VEE, resis_pattern_id, resis_bitsmask]
channels = [0,1,2,3,4,5,6,7,8,9,10,11]
if self._mode == 3:
VO_VEE = hold_OUT - hold_VEE
current = VO_VEE * resis_g_val
VO_VCC = hold_OUT - hold_VCC
values.extend([VO_VEE, current, VO_VCC])
channels.extend([12,13,14])
if self._mode == 4:
VCC_VO = hold_VCC - hold_OUT
current = VCC_VO * resis_g_val
VO_VEE = hold_OUT - hold_VEE
values.extend([VCC_VO, current, VO_VEE])
channels.extend([12,13,14])
if self._mode == 5:
VO_VEE = hold_OUT - hold_VEE
current = VO_VEE * resis_g_val
VO_VCC = hold_OUT - hold_VCC
values.extend([VO_VEE, current, VO_VCC])
channels.extend([12,13,14])
print(mem_board_id, packet_sequence, raw_time_stamp, timestamp, delta, output_vo, output_vc, output_ve, hold_OUT, hold_VCC, hold_VEE, resis_pattern_id, resis_bitsmask, resis_g_val, mode_flag, flush=True)
result = {
"time": timestamp,
"channels": channels,
"data": values,
"device": self.device
}
if (flag_mode_complete == 0x0A):
print("!!!!!!finish Mode", datetime.now(), flush = True)
self._mode_stop = 1
else:
self._mode_stop = 0
# Use a list comprehension to unpack all values
# values = [struct.unpack(fmt, data[start:end])[0] for start, end, fmt in data_specs]
return result
def isFinishMode(self) -> int:
return self._mode_stop
class TDBC4VCTHDataDecoder(RecDataDecoder):
"""
::
| | 1 | 2 | 3 |
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2
-----------------------------------------------------------------
| header | length |
| timestamp |
| | battery voltage |
|channel| channel voltage |
| stimulation remind times |
|channel| channel voltage |
| stimulation remind times |
"""
NAME = 'TDBC4VCTH'
__slots__ = ('_remind',)
def __init__(self):
super().__init__()
self._remind: int = 1
@property
def name(self) -> str:
return self.NAME
def decode(self, data: bytes) -> Optional[RecordingData]:
if self._remind == 0:
raise EODInterrupt()
if len(data) < 6:
return None
data_length = int(data[1])
if data_length == 0:
return None
time_stamp: float = struct.unpack('<I', data[2:6])[0]
time_stamp, _, ret_get_time_stamp = self.get_time_stamp(time_stamp)
ret = RecordingData(self.device, int(time_stamp), 0)
if data_length >= 8:
d1 = data[6]
d2 = data[7]
voltage = ((d1 & 0xF0) << 8) | (d2 & 0xFF)
voltage = 4300 * voltage / 4096
ret.append_data(0, int(voltage))
remind = 0
for i in range(8, data_length, 4):
d1 = data[i]
d2 = data[i + 1]
d3 = data[i + 2]
d4 = data[i + 3]
channel = (d1 >> 4) & 0x0F
value = ((d1 & 0x0F) << 8) | (d2 & 0xFF)
remind += ((d3 & 0xFF) << 8) | (d4 & 0xFF)
value = 26 * value - 25 * voltage
ret.append_data(channel, int(value))
self._remind = remind
return ret
class EISZeroOneDataDecoder(RecDataDecoder):
"""
::S
| | 1 | 2 | 3 |
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
-----------------------------------------------------------------
| id |
| ch1 |
| ch2 |
| ch3 |
| timestamp |
| None |
| finishMode |
"""
NAME = 'EISZeroOne'
MESSAGE_CYCLE_COMPLETE = 'CycleComplete'
__slots__ = ('_message', '_cycle_number', '_start_return_data', '_time_stamp',
'_total_time_stamp', '_mode', '_cycle_start_time',
'_mode_stop', '_last_time_stamp', '_last_delta',
'_cali_package', 'cali_coeff', '_ac_amp', '_mode',
'_last_phase', '_first_phase_flag', '_show_data')
def __init__(self, cali_coeff: bytes = None):
super().__init__()
self._cycle_number = 0
self._message: Optional[str] = None
self._start_return_data = False
self._time_stamp = 0
self._total_time_stamp = 0
self._mode_stop = 0
self._cycle_start_time = []
self._mode: int = 0
self._last_phase = 0
self._first_phase_flag = 1
self._cali_package: Optional[bytes] = None
self.cali_coeff: Optional[List[Tuple[int, int]]] = None
self._show_data = False
if self._cali_package is None:
self._cali_package = cali_coeff
self.cali_coeff = self._decode_cali_coeff(self._cali_package)
@staticmethod
def _decode_cali_coeff(cali_coeff: bytes) -> Optional[List[Tuple[int, int]]]:
if cali_coeff != b'':
cis_data_len = 20
cali_table = []
hsrtia_a = []
hsrtia_b = []
rolloff = []
phase_coeff = []
phase_offset = []
phase_coeff = numpy.zeros([8, 4], dtype = int)
phase_offset = numpy.zeros([8, 4], dtype = int)
########################################
# phase_coeff
# [[freq0, freq1, freq2, freq3] ----->gain0
# [freq0, freq1, freq2, freq3] ----->gain1
# [freq0, freq1, freq2, freq3] ----->gain2
# [freq0, freq1, freq2, freq3] ----->gain3
# ]
#######################################
#hstia=0
cis_cali_packet = 1
index = (cis_cali_packet - 1) * cis_data_len
hsrtia_a.append(struct.unpack('>i', cali_coeff[index+2:index+6])[0])
hsrtia_b.append(struct.unpack('>q', cali_coeff[index+6:index+14])[0])
rolloff.append(struct.unpack('>i', cali_coeff[index+14:index+18])[0])
cis_cali_packet = 2
index = (cis_cali_packet - 1) * cis_data_len
g = 0
phase_coeff[g][0] = struct.unpack('>i', cali_coeff[index+2:index+6])[0]
phase_offset[g][0] = struct.unpack('>i', cali_coeff[index+6:index+10])[0]
phase_coeff[g][1] = struct.unpack('>i', cali_coeff[index+10:index+14])[0]
phase_offset[g][1] = struct.unpack('>i', cali_coeff[index+14:index+18])[0]
cis_cali_packet = 3
index = (cis_cali_packet - 1) * cis_data_len
g = 0
phase_coeff[g][2] = struct.unpack('>i', cali_coeff[index+2:index+6])[0]
phase_offset[g][2] = struct.unpack('>i', cali_coeff[index+6:index+10])[0]
phase_coeff[g][3] = struct.unpack('>i', cali_coeff[index+10:index+14])[0]
phase_offset[g][3] = struct.unpack('>i', cali_coeff[index+14:index+18])[0]
#hstia=1
cis_cali_packet = 4
index = (cis_cali_packet - 1) * cis_data_len
hsrtia_a.append(struct.unpack('>i', cali_coeff[index+2:index+6])[0])
hsrtia_b.append(struct.unpack('>q', cali_coeff[index+6:index+14])[0])
rolloff.append(struct.unpack('>i', cali_coeff[index+14:index+18])[0])
cis_cali_packet = 5
index = (cis_cali_packet - 1) * cis_data_len
g = 1
phase_coeff[g][0] = struct.unpack('>i', cali_coeff[index+2:index+6])[0]
phase_offset[g][0] = struct.unpack('>i', cali_coeff[index+6:index+10])[0]
phase_coeff[g][1] = struct.unpack('>i', cali_coeff[index+10:index+14])[0]
phase_offset[g][1] = struct.unpack('>i', cali_coeff[index+14:index+18])[0]
cis_cali_packet = 6
index = (cis_cali_packet - 1) * cis_data_len
g = 1
phase_coeff[g][2] = struct.unpack('>i', cali_coeff[index+2:index+6])[0]
phase_offset[g][2] = struct.unpack('>i', cali_coeff[index+6:index+10])[0]
phase_coeff[g][3] = struct.unpack('>i', cali_coeff[index+10:index+14])[0]
phase_offset[g][3] = struct.unpack('>i', cali_coeff[index+14:index+18])[0]
#hstia=2
cis_cali_packet = 7
index = (cis_cali_packet - 1) * cis_data_len
hsrtia_a.append(struct.unpack('>i', cali_coeff[index+2:index+6])[0])
hsrtia_b.append(struct.unpack('>q', cali_coeff[index+6:index+14])[0])
rolloff.append(struct.unpack('>i', cali_coeff[index+14:index+18])[0])
cis_cali_packet = 8
index = (cis_cali_packet - 1) * cis_data_len
g = 2
phase_coeff[g][0] = struct.unpack('>i', cali_coeff[index+2:index+6])[0]
phase_offset[g][0] = struct.unpack('>i', cali_coeff[index+6:index+10])[0]
phase_coeff[g][1] = struct.unpack('>i', cali_coeff[index+10:index+14])[0]
phase_offset[g][1] = struct.unpack('>i', cali_coeff[index+14:index+18])[0]
cis_cali_packet = 9
index = (cis_cali_packet - 1) * cis_data_len
g = 2
phase_coeff[g][2] = struct.unpack('>i', cali_coeff[index+2:index+6])[0]
phase_offset[g][2] = struct.unpack('>i', cali_coeff[index+6:index+10])[0]
phase_coeff[g][3] = struct.unpack('>i', cali_coeff[index+10:index+14])[0]
phase_offset[g][3] = struct.unpack('>i', cali_coeff[index+14:index+18])[0]
#hstia=3
cis_cali_packet = 10
index = (cis_cali_packet - 1) * cis_data_len
hsrtia_a.append(struct.unpack('>i', cali_coeff[index+2:index+6])[0])
hsrtia_b.append(struct.unpack('>q', cali_coeff[index+6:index+14])[0])
rolloff.append(struct.unpack('>i', cali_coeff[index+14:index+18])[0])
cis_cali_packet = 11
index = (cis_cali_packet - 1) * cis_data_len
g = 3
phase_coeff[g][0] = struct.unpack('>i', cali_coeff[index+2:index+6])[0]
phase_offset[g][0] = struct.unpack('>i', cali_coeff[index+6:index+10])[0]
phase_coeff[g][1] = struct.unpack('>i', cali_coeff[index+10:index+14])[0]
phase_offset[g][1] = struct.unpack('>i', cali_coeff[index+14:index+18])[0]
cis_cali_packet = 12
index = (cis_cali_packet - 1) * cis_data_len
g = 3
phase_coeff[g][2] = struct.unpack('>i', cali_coeff[index+2:index+6])[0]
phase_offset[g][2] = struct.unpack('>i', cali_coeff[index+6:index+10])[0]
phase_coeff[g][3] = struct.unpack('>i', cali_coeff[index+10:index+14])[0]
phase_offset[g][3] = struct.unpack('>i', cali_coeff[index+14:index+18])[0]
#hstia=4
cis_cali_packet = 13
index = (cis_cali_packet - 1) * cis_data_len
hsrtia_a.append(struct.unpack('>i', cali_coeff[index+2:index+6])[0])
hsrtia_b.append(struct.unpack('>q', cali_coeff[index+6:index+14])[0])
rolloff.append(struct.unpack('>i', cali_coeff[index+14:index+18])[0])
cis_cali_packet = 14
index = (cis_cali_packet - 1) * cis_data_len
g = 4
phase_coeff[g][0] = struct.unpack('>i', cali_coeff[index+2:index+6])[0]
phase_offset[g][0] = struct.unpack('>i', cali_coeff[index+6:index+10])[0]
phase_coeff[g][1] = struct.unpack('>i', cali_coeff[index+10:index+14])[0]
phase_offset[g][1] = struct.unpack('>i', cali_coeff[index+14:index+18])[0]
cis_cali_packet = 15
index = (cis_cali_packet - 1) * cis_data_len
g = 4
phase_coeff[g][2] = struct.unpack('>i', cali_coeff[index+2:index+6])[0]
phase_offset[g][2] = struct.unpack('>i', cali_coeff[index+6:index+10])[0]
phase_coeff[g][3] = struct.unpack('>i', cali_coeff[index+10:index+14])[0]
phase_offset[g][3] = struct.unpack('>i', cali_coeff[index+14:index+18])[0]
#hstia=5
cis_cali_packet = 16
index = (cis_cali_packet - 1) * cis_data_len
hsrtia_a.append(struct.unpack('>i', cali_coeff[index+2:index+6])[0])
hsrtia_b.append(struct.unpack('>q', cali_coeff[index+6:index+14])[0])
rolloff.append(struct.unpack('>i', cali_coeff[index+14:index+18])[0])
cis_cali_packet = 17
index = (cis_cali_packet - 1) * cis_data_len
g = 5
phase_coeff[g][0] = struct.unpack('>i', cali_coeff[index+2:index+6])[0]
phase_offset[g][0] = struct.unpack('>i', cali_coeff[index+6:index+10])[0]
phase_coeff[g][1] = struct.unpack('>i', cali_coeff[index+10:index+14])[0]
phase_offset[g][1] = struct.unpack('>i', cali_coeff[index+14:index+18])[0]
cis_cali_packet = 18
index = (cis_cali_packet - 1) * cis_data_len
g = 5
phase_coeff[g][2] = struct.unpack('>i', cali_coeff[index+2:index+6])[0]
phase_offset[g][2] = struct.unpack('>i', cali_coeff[index+6:index+10])[0]
phase_coeff[g][3] = struct.unpack('>i', cali_coeff[index+10:index+14])[0]
phase_offset[g][3] = struct.unpack('>i', cali_coeff[index+14:index+18])[0]
#hstia=6
cis_cali_packet = 19
index = (cis_cali_packet - 1) * cis_data_len
hsrtia_a.append(struct.unpack('>i', cali_coeff[index+2:index+6])[0])
hsrtia_b.append(struct.unpack('>q', cali_coeff[index+6:index+14])[0])
rolloff.append(struct.unpack('>i', cali_coeff[index+14:index+18])[0])
cis_cali_packet = 20
index = (cis_cali_packet - 1) * cis_data_len
g = 6
phase_coeff[g][0] = struct.unpack('>i', cali_coeff[index+2:index+6])[0]
phase_offset[g][0] = struct.unpack('>i', cali_coeff[index+6:index+10])[0]
phase_coeff[g][1] = struct.unpack('>i', cali_coeff[index+10:index+14])[0]
phase_offset[g][1] = struct.unpack('>i', cali_coeff[index+14:index+18])[0]
cis_cali_packet = 21
index = (cis_cali_packet - 1) * cis_data_len
g = 6
phase_coeff[g][2] = struct.unpack('>i', cali_coeff[index+2:index+6])[0]
phase_offset[g][2] = struct.unpack('>i', cali_coeff[index+6:index+10])[0]
phase_coeff[g][3] = struct.unpack('>i', cali_coeff[index+10:index+14])[0]
phase_offset[g][3] = struct.unpack('>i', cali_coeff[index+14:index+18])[0]
#hstia=7
cis_cali_packet = 22
index = (cis_cali_packet - 1) * cis_data_len
hsrtia_a.append(struct.unpack('>i', cali_coeff[index+2:index+6])[0])
hsrtia_b.append(struct.unpack('>q', cali_coeff[index+6:index+14])[0])
rolloff.append(struct.unpack('>i', cali_coeff[index+14:index+18])[0])
cis_cali_packet = 23
index = (cis_cali_packet - 1) * cis_data_len
g = 7
phase_coeff[g][0] = struct.unpack('>i', cali_coeff[index+2:index+6])[0]
phase_offset[g][0] = struct.unpack('>i', cali_coeff[index+6:index+10])[0]
phase_coeff[g][1] = struct.unpack('>i', cali_coeff[index+10:index+14])[0]
phase_offset[g][1] = struct.unpack('>i', cali_coeff[index+14:index+18])[0]
cis_cali_packet = 24
index = (cis_cali_packet - 1) * cis_data_len
g = 7
phase_coeff[g][2] = struct.unpack('>i', cali_coeff[index+2:index+6])[0]
phase_offset[g][2] = struct.unpack('>i', cali_coeff[index+6:index+10])[0]
phase_coeff[g][3] = struct.unpack('>i', cali_coeff[index+10:index+14])[0]
phase_offset[g][3] = struct.unpack('>i', cali_coeff[index+14:index+18])[0]
print('hsrtia_a', hsrtia_a)
print('hsrtia_b', hsrtia_b)
print('rolloff', rolloff)
print('phase_coeff')
print(phase_coeff)
print('phase_offset')
print(phase_offset)
cali_table.append((phase_coeff, phase_offset, hsrtia_a, hsrtia_b, rolloff))
return cali_table
else:
print()
return None
@property
def name(self) -> AnyStr:
if self._cali_package is None:
return self.NAME
else:
return self.NAME.encode() + b':' + self._cali_package
def message(self) -> Optional[str]:
ret = self._message
self._message = None
return ret
def decode(self, data: bytes) -> Optional[RecordingData]:
if len(data) < 18:
return None
ch1 = struct.unpack('>i', data[1+3:5+3])[0] # unit: 1/1000 nA
ch2 = struct.unpack('>i', data[5+3:9+3])[0] # unit: mV
ch3 = struct.unpack('>i', data[9+3:13+3])[0] # unit: kOm
time_stamp: float = struct.unpack('<I', data[13+3:17+3])[0] # unit: ms
cycle_number = struct.unpack('>H', data[17+3:19+3])[0]
d19 = data[19+3]
gain = data[20+3]
finishMode = (d19 & 0x80) >> 7
ch4 = struct.unpack('<i', data[21+3:25+3])[0] # Amp[uV]
notify_one = struct.unpack('<i', data[25+3:29+3])[0]
notify_two = struct.unpack('<i', data[29+3:33+3])[0]
notify_three = struct.unpack('<i', data[33+3:37+3])[0]
if time_stamp == 0:
self._start_return_data = True
if time_stamp is None or time_stamp == 0 or not self._start_return_data:
return None
if time_stamp != 0 and self._start_return_data == True:
if (self._mode == 0 or self._mode == 5):
time_stamp, delta, ret_get_time_stamp = self.eis_get_time_stamp(time_stamp)
else:
time_stamp, delta, ret_get_time_stamp = self.get_time_stamp(time_stamp)
if time_stamp is None or time_stamp < 0 or delta < 0:
print("error timeStamp full data:", list(data), datetime.now(), '\n')
return None
else:
if self.cali_coeff is not None and (self._mode == 0 or self._mode == 5):
hsrtia_a = []
hsrtia_b = []
rolloff = []
phase_coeff, phase_offset, hsrtia_a, hsrtia_b, rolloff = self.cali_coeff[0]
if (self._mode == 0 or self._mode == 5):
img = ch1
real = ch2
freq = ch3
fre_idx = 0
voltage_amp = round(ch4 / 1000) # Amp[mV]
rolloff_cali = rolloff[gain]
voltage_mag = math.sqrt(img ** 2 + real ** 2) * (1 + freq ** 2 / rolloff_cali ** 2 / 1e4)
current = (voltage_mag ** 2 * hsrtia_a[gain] + voltage_mag * hsrtia_b[gain]) / 1e8 #[nA]
if (current != 0):
# impedance[mOhm] = voltage_amp[mv] * 1000000 / 1.414213 / current[nA] #RMS=amp*SQRT(2), SQRT(2)=1.414213
impedance = voltage_amp * 707106.78 / current
else:
impedance = 0
raw_phase = math.atan2(img , real) * 180 / math.pi
if (freq >= 1000000): # 10000 Hz
fre_idx = 0
elif (freq >= 10000): # 100 Hz
fre_idx = 1
elif (freq >= 1000): # 10 Hz
fre_idx = 2
elif (freq >= 1): # 0.01 Hz
fre_idx = 3
ideal_raw_phase = phase_coeff[gain][fre_idx] /1e10 * freq + phase_offset[gain][fre_idx] / 1e6
phase = raw_phase - ideal_raw_phase
phase = phase % 180 if phase % 180<=90 else phase % 180-180
imag_after_cal = round(impedance * math.sin(phase * math.pi / 180))
real_after_cal = round(impedance * math.cos(phase * math.pi / 180))
if self._show_data:
if (self._mode == 0 or self._mode == 5):
print('|', '{:10}'.format(time_stamp),
'|', '{:5}'.format(delta),
'|', '{:5}'.format(ch1), #raw_img
'|', '{:5}'.format(ch2), #raw_real
'|', '{:8}'.format(ch3 * 10), '[mHz]', #Frequency [mHz]
'|', '{:5}'.format(cycle_number), #cycle
'|', '{:5}'.format(round(imag_after_cal)), '[Ohm]', #Z_imag [Ohm]
'|', '{:5}'.format(round(real_after_cal)), '[Ohm]', #Z_real [Ohm]
'|', '{:5}'.format(round(impedance)), '[Ohm]', #Impedance [Ohm]
'|', '{:5}'.format(round(phase*1000)), '[mdegree]', #Phase [millidegree]
'|', '{:5}'.format(round(current)), '[nA]', #Current [nA]
'|', '{:1}'.format(gain), #gain
'|', '{:1}'.format(finishMode), #finishMode
'@', str(self.device), '|', flush = True)
print('|', '{:10}'.format(time_stamp),
'|', '{:5}'.format(delta),
'|', '{:5}'.format(notify_one),
'|', '{:5}'.format(notify_two),
'|', '{:5}'.format(notify_three),
'|', '{:5}'.format(voltage_amp), #amp[mV]
'|', flush = True)
pass
else:
print('|', '{:10}'.format(time_stamp),
'|', '{:5}'.format(delta),
'|', '{:5}'.format(ch1),
'|', '{:5}'.format(ch2),
'|', '{:5}'.format(ch3),
'|', '{:5}'.format(cycle_number),
'|', '{:1}'.format(gain),
'|', '{:1}'.format(finishMode),
'@', str(self.device), '|', flush = True)
pass
if finishMode == True:
print("finishMode full data:", list(data), datetime.now())
self._mode_stop = 1
else:
self._mode_stop = 0
ret = RecordingData(self.device, int(time_stamp * 1000 / 2), 0)
if self._mode == 0 or self._mode == 5:
ret.append_data(0, ch1) #raw_img
ret.append_data(1, ch2) #raw_real
ret.append_data(2, ch3 * 10) #Frequency [mHz]
ret.append_data(3, cycle_number) #cycle
ret.append_data(4, imag_after_cal) #Z_imag [Ohm]
ret.append_data(5, real_after_cal) #Z_real [Ohm]
ret.append_data(6, round(impedance)) #Impedance [Ohm]
ret.append_data(7, round(phase*1000)) #Phase [millidegree]
ret.append_data(8, round(current)) #Current [nA]
ret.append_data(9, gain) #gain
#debug data
ret.append_data(10, notify_one)
ret.append_data(11, notify_two)
ret.append_data(12, notify_three)
ret.append_data(13, voltage_amp) #amp[mV]
else: #CV Mode
ret.append_data(0, ch1) #Iin [nA]
ret.append_data(1, ch2) #Vset [nV]
ret.append_data(2, ch3) #Vout [nV]
ret.append_data(3, cycle_number)
if cycle_number != self._cycle_number:
# notify cycle_number change
self._message = self.MESSAGE_CYCLE_COMPLETE + '=%d' % cycle_number
self._cycle_number = cycle_number
# print("$$$ real_time_stamp $$$")
# print(time_stamp)
return ret
def isFinishMode(self) -> int:
return self._mode_stop