2021-12-20 14:52:55 +08:00
import base64
import re
import zlib
import sys
import gc
from collections import Counter
from typing import Iterable
from time import time , sleep
from biopro.devlib.data import *
from json import loads as json_parse , dumps as _json_stringify
from typing import Dict , Optional , Any
from datetime import datetime
import threading
from biopro.recording.file import RecordingMetaFile , RecordingFile , RecordingMini , RecordingFileWriter
from biopro.devlib.data import DataDecodeFormat
from biopro.util.logger import logging_info , Logger
from biopro.util.json import JSON_OBJECT
from .socket import ServerThread
from multiprocessing import Process , Queue
from .mqtt import MqttDataMessageHandler , MqttThread
import biopro.server._identify
from concurrent.futures import ThreadPoolExecutor , as_completed
_RUNTIME_COMPILE = False
def json_stringify ( o : Any ) -> str :
return _json_stringify ( o , separators = ( ',' , ':' ))
# noinspection PyUnusedLocal
class RecordingProcess ( Process ):
"""
connect to DB and send sql command
"""
2022-04-13 14:08:57 +08:00
def __init__ ( self , client_id : str , log_verbose , device_id , queue_rec , queue_ds , queue_msg , meta_path ,
2021-12-20 14:52:55 +08:00
device_info , parameter , parent , recording_file_name , data_format ,
mqtt_ip , mqtt_port ,
name = 'Recording-Process' , database = None ):
super ( RecordingProcess , self ) . __init__ ( name = name )
self . _database = database
self . _device = device_id
self . _meta_file = RecordingMetaFile . load ( meta_path , self . _database )
self . _meta_file . _device = device_info
self . _meta_file . _filepath = meta_path
self . _meta_file . _parameter = parameter
self . _meta_file . _parent = parent
self . _meta_file . _recording_file_name = recording_file_name
self . _meta_file . _device_id = device_id
print ( self . _meta_file . _device , self . _meta_file . _parameter , self . _meta_file . _parent , self . _meta_file . _filepath )
self . _meta_file . write ( self . _database )
self . _data_format = data_format
self . _timer = None
self . sync_file_request = False
self . _sync_started = False
self . _writer = None
self . log_verbose = log_verbose
self . _mqtt_thread = MqttThread ( None ,
mqtt_ip ,
mqtt_port ,
'device-' + str ( self . _device ) + '-' + str ( self . _meta_file . _file_uuid ),
self . log_verbose ,
'MQTT-device-' + str ( self . _device ),
is_sub = False )
# self._mqtt_thread.start()
self . _mqtt_send_data_ch_level = {}
self . _queue_rec = queue_rec
self . _queue_ds = queue_ds
2022-04-13 14:08:57 +08:00
self . _queue_msg = queue_msg
2021-12-20 14:52:55 +08:00
self . queue_flag = True
self . _is_close = True
self . _prev_data = []
self . _prev_delta_time = []
self . _prev_time_stamp = []
self . _isTimeOut = False
# section use
self . _skip_ram_cnt = 0
self . _get_first_last_cnt = [ True , True , True , True , True , True , True , True ]
self . _last_cnt = [ - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 ]
self . _wrong_cnt = 0
2022-04-07 12:33:02 +08:00
# elite data length
self . _elite_data_len = 40
self . _mem_header_len = 3
self . _mem_tailer_len = 6
self . _single_data_len = self . _elite_data_len + self . _mem_header_len + self . _mem_tailer_len
2021-12-20 14:52:55 +08:00
def ensure_data_format ( self ) -> DataDecodeFormat :
if isinstance ( self . _data_format , ( str , bytes )):
self . _data_format = DataDecodeFormat . parse ( self . _data_format )
# add device information
if isinstance ( self . _data_format , RecDataDecoder ):
self . _data_format . device = self . _device
return self . _data_format
def data_format ( self ) -> DataDecodeFormat :
decoder = self . ensure_data_format ()
# transmit calibration gain level to decoder
if isinstance ( decoder , TDC4VAF2DataDecoder ):
# get amp_gain from meta file
decoder . amp_gain = self . _meta_file . configuration . amp_gain
elif isinstance ( decoder , TDC4VCDataDecoder ):
# get amp_gain from meta file
decoder . amp_gain = self . _meta_file . configuration . amp_gain
decoder . _channel = self . _meta_file . configuration . channel
decoder . _adc_clock = self . _meta_file . configuration . get_parameter ( 'ADC_CLOCK' )
# decoder._time_stamp_list = self._time_stamp_list
decoder . _axis_ch = self . _meta_file . configuration . get_parameter ( 'AXIS_CH' )
decoder . _prev_data = self . _prev_data
decoder . _prev_delta_time = self . _prev_delta_time
decoder . _prev_time_stamp = self . _prev_time_stamp
# elif isinstance(decoder, I4V4Z4T4DataDecoder):
# get cycle_time from meta file
# decoder._mode = self._meta_file.configuration.MODE
# decoder._cycle_start_time = self._cycle_start_time
elif isinstance ( decoder , NeuliveThreeOneDataDecoder ):
# get amp_gain from meta file
decoder . amp_gain = self . _meta_file . configuration . amp_gain
decoder . _channel = self . _meta_file . configuration . channel
decoder . _adc_clock = self . _meta_file . configuration . get_parameter ( 'ADC_CLOCK' )
# decoder._time_stamp_list = self._time_stamp_list
decoder . _axis_ch = self . _meta_file . configuration . get_parameter ( 'AXIS_CH' )
decoder . _accelerator_sensitivity = self . _meta_file . configuration . get_parameter ( 'ACCELERATOR_S' )
decoder . _prev_data = self . _prev_data
decoder . _prev_delta_time = self . _prev_delta_time
decoder . _prev_time_stamp = self . _prev_time_stamp
elif isinstance ( decoder , EISZeroOneDataDecoder ):
# get amp_gain from meta file
decoder . _ac_amp = self . _meta_file . configuration . get_parameter ( 'AC_AMP' )
decoder . _mode = self . _meta_file . configuration . get_parameter ( 'MODE' )
decoder . _freq_start = self . _meta_file . configuration . get_parameter ( 'FREQ_START' )
decoder . _freq_stop = self . _meta_file . configuration . get_parameter ( 'FREQ_STOP' )
return decoder
def recv_data_foreach_runtime ( self ) -> bool :
"""foreach data runtime to receive data. called by :class:`DataRuntimeThread`.
:return: has runtime received data
"""
q = None
try :
2022-04-07 12:29:18 +08:00
# now = time()
2021-12-20 14:52:55 +08:00
q = self . _queue_rec . get ()
2022-04-07 12:29:18 +08:00
# get_queue_time_cost = time() - now
# if get_queue_time_cost > 2:
# print('get queue cost time', get_queue_time_cost)
2021-12-20 14:52:55 +08:00
except :
return False
finally :
if self . _queue_rec . qsize () > 10 :
print ( 'qsize: ' , self . _device , self . _queue_rec . qsize ())
if q == 'close' :
self . log_verbose ( 'RecordingProcess file close:' , self . _device )
self . rec_update ()
self . final_write ()
self . is_closed = True
return False
else :
self . rec_update ()
self . sync_data ( q )
del q
return True
def sync_data ( self , data = None ):
self . _isTimeOut = False
current_time = time ()
if self . _timer is not None :
if current_time - self . _timer > 1.5 :
2022-03-25 11:34:30 +08:00
print ( 'time, sync_data routine_time' , 'device:' , self . _device , datetime . now (), current_time , current_time - self . _timer )
2021-12-20 14:52:55 +08:00
self . _isTimeOut = True
self . _timer = current_time
decoder = self . data_format ()
# print('sync_data')
# print('data',data)
# print('server/data', self._prev_delta_time,self._prev_time_stamp, self._prev_data)
if data is None or len ( data ) == 0 :
result = decoder . decode ( b '' )
if result is not None :
ret = result
else :
return None
else :
ret = []
# print('decoder data')
for offset , section in self . _foreach_data_section ( data ):
# for section in self._neu_foreach_data_section(data):
result = decoder . decode ( section )
# if self._isTimeOut:
# try:
# print('result: ', result.data_size)
# except:
# pass
try :
if isinstance ( decoder , I4V4Z4T4DataDecoder ) or isinstance ( decoder , EISZeroOneDataDecoder ):
if decoder . isFinishMode is not None and decoder . isFinishMode () == 1 :
2022-04-13 14:08:57 +08:00
self . _queue_msg . put ([ 'ds' , self . _device , 'interrupt' ])
# content = {}
# content['header'] = 'device_instruction/0'
# content['device'] = result.device
# content['instruction'] = 'interrupt'
# self._mqtt_thread.publish('device_instruction',json_stringify(content), True)
2021-12-20 14:52:55 +08:00
# self.server.stop_sync(self.device)
except RuntimeError as e :
print ( e )
if result is not None :
##
if len ( self . _mqtt_send_data_ch_level ) == 0 :
for ch in result . channels ():
self . _mqtt_send_data_ch_level [ ch ] = MqttDataMessageHandler ( self . _mqtt_thread , 'data_server/device_data_stream/' + str ( result . device ) + '/' + str ( ch ) )
ret . append ( result )
else :
continue
# write back
# ctime1 = time()
if self . _writer is not None and len ( ret ) > 0 :
if len ( self . _writer . channel_list ) == 0 :
self . _writer . channels_update ( ret [ 0 ] . channels ())
self . _writer . write ( ret , self . _mqtt_send_data_ch_level )
# print('write time: ', time() - ctime1)
# print(ret)
del ret
del data
return
def _check_cnt ( self , raw_data : bytes , device : int ):
# check memory board cnt, if jump cnt, don't save data
save = True
head = raw_data [ 0 ]
head_counter = raw_data [ 1 ]
device_id = raw_data [ 3 ]
try :
# assign last_data_cnt value and handle first last_data_cnt
if ( device == 4 ):
if ( self . _get_first_last_cnt [ 4 ] == True ):
self . _get_first_last_cnt [ 4 ] = False
self . _last_cnt [ 4 ] = head_counter - 1
last_data_cnt = self . _last_cnt [ 4 ]
elif ( device == 5 ):
if ( self . _get_first_last_cnt [ 5 ] == True ):
self . _get_first_last_cnt [ 5 ] = False
self . _last_cnt [ 5 ] = head_counter - 1
last_data_cnt = self . _last_cnt [ 5 ]
elif ( device == 6 ):
if ( self . _get_first_last_cnt [ 6 ] == True ):
self . _get_first_last_cnt [ 6 ] = False
self . _last_cnt [ 6 ] = head_counter - 1
last_data_cnt = self . _last_cnt [ 6 ]
elif ( device == 7 ):
if ( self . _get_first_last_cnt [ 7 ] == True ):
self . _get_first_last_cnt [ 7 ] = False
self . _last_cnt [ 7 ] = head_counter - 1
last_data_cnt = self . _last_cnt [ 7 ]
2022-04-07 12:33:02 +08:00
# dont save to section when head or id is wrong
2021-12-20 14:52:55 +08:00
if ( head != 255 or device_id != device ):
save = False
last_data_cnt = last_data_cnt + 1
return save
if ( last_data_cnt == 255 ):
if ( head_counter - last_data_cnt != - 255 ):
save = False
last_data_cnt = 0
else :
last_data_cnt = head_counter
else :
if ( head_counter - last_data_cnt != 1 ):
save = False
last_data_cnt = last_data_cnt + 1
else :
last_data_cnt = head_counter
finally :
if ( device == 4 ):
self . _last_cnt [ 4 ] = last_data_cnt
elif ( device == 5 ):
self . _last_cnt [ 5 ] = last_data_cnt
elif ( device == 6 ):
self . _last_cnt [ 6 ] = last_data_cnt
elif ( device == 7 ):
self . _last_cnt [ 7 ] = last_data_cnt
if ( save == False ):
self . _wrong_cnt += 1
2022-02-10 17:51:08 +08:00
print ( 'jump cnt' , self . _wrong_cnt , 'times, device:' , device , ',' , datetime . now ())
2021-12-20 14:52:55 +08:00
return save
2022-04-07 12:33:02 +08:00
def _check_jump_ram ( self , raw_data : bytes , colum_now : int , colum_total : int , save : bool , device : int , data_len : int ):
col = colum_now
save = False
if raw_data [ col ][ 0 ] != 255 or raw_data [ col ][ 3 ] != device :
return save
if colum_total == 1 :
print ( 'this ram data < 3 records, colum_total = ' , colum_total )
print ( 'raw_data[0]' , raw_data [ 0 ])
return save
elif colum_total == 2 :
print ( 'this ram data < 3 records, colum_total = ' , colum_total )
print ( 'raw_data[0]' , raw_data [ 0 ])
print ( 'raw_data[1]' , raw_data [ 1 ])
if ( raw_data [ col + 1 ][ 1 ] - raw_data [ col ][ 1 ] == 1 ) or ( raw_data [ col ][ 1 ] == 255 and raw_data [ col + 1 ][ 1 ] == 0 ):
save = True
elif colum_total >= 3 :
if ( raw_data [ col + 1 ][ 1 ] - raw_data [ col ][ 1 ] == 1 and raw_data [ col + 2 ][ 1 ] - raw_data [ col + 1 ][ 1 ] == 1 ) or \
( raw_data [ col ][ 1 ] == 255 and raw_data [ col + 1 ][ 1 ] == 0 and raw_data [ col + 2 ][ 1 ] == 1 ) or \
( raw_data [ col ][ 1 ] == 254 and raw_data [ col + 1 ][ 1 ] == 255 and raw_data [ col + 2 ][ 1 ] == 0 ):
save = True
if save == True :
self . _skip_ram_cnt += 1
last_data_cnt = raw_data [ col ][ 1 ]
if ( device == 4 ):
print ( 'self._last_cnt[4]:' , self . _last_cnt [ 4 ], ', last_data_cnt:' , last_data_cnt )
if ( device == 5 ):
print ( 'self._last_cnt[5]:' , self . _last_cnt [ 5 ], ', last_data_cnt:' , last_data_cnt )
if ( device == 6 ):
print ( 'self._last_cnt[6]:' , self . _last_cnt [ 6 ], ', last_data_cnt:' , last_data_cnt )
if ( device == 7 ):
print ( 'self._last_cnt[7]:' , self . _last_cnt [ 7 ], ', last_data_cnt:' , last_data_cnt )
print ( 'skip RAM, but data is right, solve data' , list ( raw_data [ col ][ 0 : 3 ]), '|' , list ( raw_data [ colum_total - 1 ][ 0 : 3 ]), 'skip Ram' , self . _skip_ram_cnt , 'times \n ' )
2022-02-14 14:29:52 +08:00
2022-04-07 12:33:02 +08:00
if ( device == 4 ):
self . _last_cnt [ 4 ] = last_data_cnt
2021-12-22 13:55:20 +08:00
2022-04-07 12:33:02 +08:00
elif ( device == 5 ):
self . _last_cnt [ 5 ] = last_data_cnt
2021-12-20 14:52:55 +08:00
2022-04-07 12:33:02 +08:00
elif ( device == 6 ):
self . _last_cnt [ 6 ] = last_data_cnt
2021-12-20 14:52:55 +08:00
2022-04-07 12:33:02 +08:00
elif ( device == 7 ):
self . _last_cnt [ 7 ] = last_data_cnt
2021-12-22 13:55:20 +08:00
return save
2021-12-20 14:52:55 +08:00
def _neu_foreach_data_section ( self , data : bytes ):
"""
:param data:
:return:
"""
_device = 6
offset = 0
ret = [ _device ]
total_sample = 0
ble_packet_end = 0
header = None
# packet header format
# - header (0xFF)
# - data counter
# - data content len (ble_packet_end)
# - chip id
2021-12-22 13:55:20 +08:00
def _discard_packet_header ( offset : int , data : bytes ) -> List [ int , int ]:
2021-12-20 14:52:55 +08:00
while offset < len ( data ):
data_header = data [ offset ]
if data_header != 0xFF :
offset += 1
continue
head_counter = data [ offset + 1 ]
ble_packet_len = data [ offset + 2 ]
device_id = data [ offset + 3 ]
if device_id != _device :
offset += 1
continue
else :
ble_packet_end = offset + ble_packet_len + 3
# print("head_counter", head_counter)
return offset + 4 , ble_packet_end
return len ( data ), 0
# decoder header format:
# - timestamp (LSB)
# - channnel data number
# - delta time
2021-12-22 13:55:20 +08:00
def _get_ret_header ( offset : int , data : bytes ) -> List [ int , int , Optional [ bytes ]]:
2021-12-20 14:52:55 +08:00
total_sample = data [ offset + 4 ]
decoder_header = data [ offset : offset + 7 ]
offset += 7
return [ offset , total_sample , decoder_header ]
def _is_ret_need_header ( ret : bytes ) -> bool :
if len ( ret ) < 7 :
return True
else :
return False
def _is_ret_complete ( ret : bytes ) -> bool :
if len ( ret ) < 8 :
return False
sample_length = 3
sample_number = int (( len ( ret ) - 8 ) / sample_length )
if sample_number >= total_sample :
return True
else :
return False
2021-12-22 13:55:20 +08:00
def _extend_ret ( ret : bytes , offset : int , data : bytes ) -> List [ int , bytes ]:
2021-12-20 14:52:55 +08:00
expect_ret_len = 8 + total_sample * 3 # header_len = 8
extend_len = expect_ret_len - len ( ret )
if ( offset + extend_len ) < ble_packet_end :
ret . extend ( data [ offset : offset + extend_len ])
offset += extend_len
else :
ret . extend ( data [ offset : ble_packet_end ])
offset = ble_packet_end
return offset , ret
# print("pre decode data len = ", len(data))
# print("pre decode data = ", list(data))
while offset < len ( data ):
if offset >= ble_packet_end :
offset , ble_packet_end = _discard_packet_header ( offset , data )
if _is_ret_need_header ( ret ):
# get ret header
offset , total_sample , header = _get_ret_header ( offset , data )
total_sample += 2 # acc data can be view as two sample
ret . extend ( header )
# extend ret
offset , ret = _extend_ret ( ret , offset , data )
# is ret complete?
if _is_ret_complete ( ret ):
# print("ret = ", ret)
# print("\n")
yield bytes ( ret )
ret = [ _device ]
def log_data_receive_statistics ( self , logger : Logger ):
c = self . _data_counter
def _foreach_data_section ( self , data : bytes ):
"""
:param data:
:return:
"""
raw_data = []
2022-04-07 12:33:02 +08:00
records = 0
2021-12-20 14:52:55 +08:00
print_flag = False
2022-04-07 12:33:02 +08:00
device = self . _device
single_data_len = self . _single_data_len
mem_header_len = self . _mem_header_len
2021-12-20 14:52:55 +08:00
# packet section data use two microarray
for i in range ( 0 , len ( data ), single_data_len ):
2022-04-07 12:33:02 +08:00
records = records + 1
raw_data . append ( data [ i : i + single_data_len ])
if len ( data [ i : i + 10 ]) < 10 :
print ( 'data dont be package:' , raw_data [ records - 1 ], 'index:' , records - 1 )
del raw_data [ records - 1 ]
records = records - 1
2021-12-20 14:52:55 +08:00
# check cnt
2022-04-07 12:33:02 +08:00
for i in range ( records ):
2021-12-20 14:52:55 +08:00
# if jump cnt, don't save data
save = self . _check_cnt ( raw_data [ i ], device )
# # if jump Ram, also can handle data
if save == False :
2022-04-07 12:33:02 +08:00
save = self . _check_jump_ram ( raw_data , i , records , save , device , single_data_len )
2021-12-20 14:52:55 +08:00
if ( save == True ):
section = raw_data [ i ]
2022-03-25 11:44:52 +08:00
check_sum = sum ( section [ 0 : single_data_len - 1 ]) & 0b11111111
if ( check_sum != section [ single_data_len - 1 ]):
print ( 'check number not match,' ,
list ( section [ 0 : mem_header_len ]), section [ - 1 ], '!=' , check_sum , 'device:' , device , ',' , datetime . now ())
2022-03-16 11:50:00 +08:00
if ( i - 1 >= 0 ):
2022-03-25 11:44:52 +08:00
print ( 'check number not match' , list ( raw_data [ i - 1 ]), 'raw:' , i - 1 )
2022-03-24 17:12:06 +08:00
2022-03-25 11:44:52 +08:00
print ( 'check number not match' , list ( raw_data [ i ]), 'raw:' , i )
2022-03-24 17:12:06 +08:00
2022-04-08 18:16:31 +08:00
if ( i + 1 <= records ):
2022-03-25 11:44:52 +08:00
print ( 'check number not match' , list ( raw_data [ i + 1 ]), 'raw:' , i + 1 , ' \n ' )
section = section + b ' \x01 '
2022-03-24 17:12:06 +08:00
print ( 'delete data:' , list ( raw_data [ i ][ 0 : 3 ]), 'raw:' , i , ' \n ' )
continue
2022-03-25 11:44:52 +08:00
else :
section = section + b ' \x00 '
# section = section[3:]
2021-12-20 14:52:55 +08:00
yield None , section
else :
print_flag = True
2022-03-24 17:12:06 +08:00
print ( 'delete data:' , list ( raw_data [ i ][ 0 : 3 ]), 'raw:' , i , ' \n ' )
2021-12-20 14:52:55 +08:00
2022-02-17 10:58:05 +08:00
# if print_flag:
# print('delete data context:')
# for i in range(0, len(data), single_data_len):
# print(list(data[i:i+single_data_len]))
# print()
2021-12-20 14:52:55 +08:00
return
def run ( self ) -> None :
self . setup ()
# self.routine()
def setup ( self ) -> None :
self . log_verbose ( 'RecordingProcess setup:' , self . _device )
self . _is_close = False
if self . _mqtt_thread is not None :
self . _mqtt_thread . start ()
self . _writer = RecordingFileWriter ( self . _meta_file , self . _device , self . _database )
self . routine ()
def routine ( self ) -> None :
self . log_verbose ( 'RecordingProcess routine:' , self . _device )
self . _sync_started = True
running = True
while running :
running = self . recv_data_foreach_runtime ()
self . log_verbose ( 'RecordingProcess routine done:' , self . _device )
if self . _mqtt_thread is not None :
self . _mqtt_thread . shutdown ()
return None
def shutdown ( self ) -> None :
self . log_verbose ( 'RecordingProcess shutdown:' , self . _device )
self . _sync_started = False
self . _queue_rec . put ( 'close' )
self . queue_flag = True
def final_write ( self ):
if self . _writer is not None :
return self . _writer . close ()
else :
return False
# def put_queue(self, request: list) -> None:
# self._queue.put(request)
# del request
# return
@property
def filepath ( self ):
return self . _meta_file . filepath
@property
def meta_file ( self ) -> RecordingMetaFile :
return self . _meta_file
@property
def file_writer ( self ) -> Optional [ RecordingFileWriter ]:
return self . _writer
@property
def device ( self ) -> int :
return self . _device
@property
def sync_started ( self ) -> bool :
return self . _sync_started
# @sync_started.setter
# def sync_started(self, value: bool):
# self._sync_started = value
# if value:
# self._open()
# else:
# self._close()
def put_rec_queue ( self , data ) -> None :
self . _queue_rec . put ( data )
return
def rec_update ( self ) -> bool :
while self . _queue_ds . qsize () > 0 :
try :
q = self . _queue_ds . get ( block = False )
except :
return False
if q is not None :
func_name = q [ 0 ]
args = q [ 1 :]
try :
self . solve_for ( func_name , args = args )
except RuntimeError as e :
print ( 'solve func error' , func_name , args )
return False
return True
def solve_for ( self , name : str , args : list ):
do = f " { name } "
if callable ( getattr ( self , do )):
try :
getattr ( self , do )( * args )
except :
pass
return None
def update_meta_id ( self , _uuid , _id ):
if self . _writer is not None :
self . _writer . update_meta_id ( _id )
return
def update_raw_dict_id ( self , _uuid , _channel , _id ):
if self . _writer is not None :
self . _writer . update_raw_dict_id ( _channel , _id )
return
def update_mini_dict_id ( self , _uuid , _scale , _channel , _id ):
if self . _writer is not None :
self . _writer . update_mini_dict_id ( _scale , _channel , _id )
return