Compare commits

...

49 Commits

Author SHA1 Message Date
peterlu14 6c232b3b07 [update] fix struct.pack type error support signed int & max number type 2022-06-22 15:23:44 +08:00
peterlu14 cfc2bf1d83 Merge remote-tracking branch 'origin/rel/v.1.6.4/read_ram_debug' into rel/v.1.6.5/restruct_data_format 2022-06-22 13:18:03 +08:00
Roy 2b60e68197 [update] update eis decoder code 2022-06-22 13:11:57 +08:00
peterlu14 efb5e9aeb6 [update] add list struct update in new way & fix list parameter not valid & collection nested error & fixed encoder struct bytes wrong (singed, unsigned) 2022-06-17 19:54:42 +08:00
peterlu14 b4d88a0e67 [update] change elite pulse sensing paramter in array 2022-06-17 19:51:05 +08:00
peterlu14 6029ff6fe1 [update] FREQ & DPV_curr_rec max_min to array and add update array format 2022-06-15 16:48:23 +08:00
Roy 6efc5e1097 [update] hide log message 2022-06-10 17:36:13 +08:00
Roy a23394d3d1 Merge branch 'dev/eis_parameter_bug' into rel/v.1.6.4/read_ram_debug 2022-06-10 17:31:45 +08:00
Roy c96f76f438 [update] read RAM data bug 2022-06-10 17:22:10 +08:00
Roy f0f1c9fc49 [update] update eis json 2022-06-10 17:20:58 +08:00
peterlu14 512c80af40 [update] add new info id,name,status,mac_address,memory_board 2022-06-06 18:52:12 +08:00
Roy 499964b287 [update] fix TIME_DURATION in eis devlib 2022-05-23 17:42:12 +08:00
Roy 16f12d40bf [update] improve cp and cc mode - speed and resistance value 2022-05-20 12:11:56 +08:00
peterlu14 63c1ba8efb [update] update decorder mode in I4V4Z4T4 2022-05-18 16:16:17 +08:00
Roy 4a28348db4 [update] fix cp mode instruction and decoder 2022-05-18 11:06:23 +08:00
peterlu14 64dfe87d05 [update] remove decorder rebuild problem & add new parameter to control time duration 2022-05-18 10:19:08 +08:00
peterlu14 f7a5a867a4 [update] fix memory counter 2022-05-06 13:50:09 +08:00
peterlu14 1accaa9b9f [update] delete logger 2022-05-06 11:25:30 +08:00
peterlu14 b810e35fb2 Merge branch 'feature/get-running-project-flow' into rel/v1.6.1/cp_mode 2022-05-06 10:39:51 +08:00
peterlu14 82c9d2b0e3 [update] add clean logger script 2022-05-06 10:39:47 +08:00
peterlu14 e5a3f33b9f [update] add cp mode 2022-05-06 10:36:17 +08:00
peterlu14 1dd4ae2ce4 [update] add stop project api & fix uuid problem & get all projects & as_json problem 2022-05-04 18:07:05 +08:00
peterlu14 515cc9f7a2 [update] fix stop close writer losing data problem 2022-04-29 17:18:48 +08:00
peterlu14 9b92249fdb Merge branch 'feature/project' into rel/v1.6.0/highz 2022-04-27 14:01:07 +08:00
peterlu14 adcc13b4de [update] idle device not sending instruction 2022-04-27 13:26:05 +08:00
peterlu14 0cd0fd7027 [update] project task name rename 2022-04-27 13:24:31 +08:00
Roy 5228290eac [update] fix 1.5re.json about highz 2022-04-27 13:16:29 +08:00
LuoYiTing 034b7976cd Update README.md 2022-04-25 03:55:34 +00:00
peterlu14 fa8c219f76 Merge branch 'rel/v1.5.1' into feature/project 2022-04-25 10:48:22 +08:00
peterlu14 f233c019fd [update] auto get eth0 mac address 2022-04-25 10:36:02 +08:00
peterlu14 ced466f3aa [update] add condition & first task issue & add boardcast 2022-04-18 01:52:52 +08:00
peterlu14 f36906c034 [update] restruct project 2022-04-17 23:05:21 +08:00
peterlu14 0b5c62e525 [update] temp save 2022-04-16 10:50:37 +08:00
peterlu14 abe86238ca remove print messages 2022-04-13 15:58:27 +08:00
peterlu14 f9ab0bc59b [update] remove duplicate code 2022-04-13 15:49:04 +08:00
peterlu14 5dbe231c73 [update] add cacheClient communicate with control server & add comment 2022-04-13 14:08:57 +08:00
peterlu14 c721950bf7 [update] add complete device status 2022-04-12 14:28:04 +08:00
peterlu14 70c0271771 [remove] duplicate code 2022-04-11 16:35:04 +08:00
peterlu14 13825fe9ff [update] hide print 2022-04-08 18:22:44 +08:00
peterlu14 dd2e31d01f [update] fix index problem 2022-04-08 18:16:31 +08:00
peterlu14 455fa1f86b [update] fix stop error & relative_time 2022-04-08 11:17:07 +08:00
Roy 71850b31a8 [update] fix recording error 2022-04-07 12:33:02 +08:00
peterlu14 8d52fef4c1 [update] hide print message 2022-04-07 12:29:18 +08:00
peterlu14 e091f220fa Merge branch 'test/update_data_server' into rel/v1.4.0_schedule/channel_lost_data 2022-03-28 18:19:29 +08:00
peterlu14 6049154fae [update] change check time to 1 2022-03-28 18:18:35 +08:00
peterlu14 3502472021 [update] recover update mini file 2022-03-28 17:27:04 +08:00
peterlu14 5e48c8ae0a [update] update_subfile change 2022-03-28 17:17:07 +08:00
peterlu14 cb2a4c9d56 Revert "[update] solve meta raw&mini id grwon up problem"
This reverts commit cd60e14cbe.
2022-03-28 16:14:23 +08:00
peterlu14 b253aca73f [update] meta_id & mini_id update 2022-03-28 16:12:15 +08:00
30 changed files with 1537 additions and 1224 deletions
+2
View File
@@ -1,5 +1,7 @@
.DS_Store
*.cpython-37.opt-2.pyc
*.cpython-39-arm-linux-gnueabihf.so
*.
*.pyc
*/__pycache__
/.vscode
+174 -38
View File
@@ -4,7 +4,7 @@ New goal on pi4 without kotlin and django, and with only one dataserver.
# Installation
### Get Raspbrain into SD card
###### Download Raspbian Buster Lite
###### Download Raspbian Bullseye Lite
>https://www.raspberrypi.org/downloads/raspberry-pi-os/
@@ -14,7 +14,7 @@ New goal on pi4 without kotlin and django, and with only one dataserver.
lsblk
# copy data to sd card
sudo dd bs=4M if=yyyy-mm-dd-raspbian-buster.img of=/dev/sdX conv=fsync status=progress
sudo dd bs=4M if=yyyy-mm-dd-raspios-bullseye-armhf-lite.img of=/dev/sdX conv=fsync status=progress
# enable ssh on first boot
add ssh.txt in boot folder
@@ -77,18 +77,22 @@ add ssh.txt in boot folder
###### Install dependencies
sudo apt-get install vim git wiringpi build-essential libi2c-dev i2c-tools python-dev libffi-dev python3-pip postgresql
sudo apt-get install vim git wiringpi build-essential libi2c-dev i2c-tools python-dev libffi-dev python3-pip postgresql npm
###### wiringpi conflict
need to install wiringpi: https://github.com/WiringPi/WiringPi
install instructions: https://github.com/WiringPi/WiringPi/blob/master/INSTALL
### Setup project
###### Clone project
###### Clone data server project
```
cd ~
git clone https://gitlab.com/bioproscientific/bioprorelife.git
git clone https://gitlab.com/wisetop/controller/wisetopdataserver.git
```
###### Install python dependencies
```
cd ~/bioprorelife
cd ~/wisetopdataserver
pip3 install -r requirements.txt
```
##### Packages version
@@ -112,14 +116,14 @@ psycopg2 2.8.5
###### Build C extension
```
cd ~/bioprorelife/python/biopro/ext/
cd ~/wisetopdataserver/python/biopro/ext/
rm -rf *.so build
python3 setup.py build_ext --inplace
```
###### Check setup-uart file
```
cd ~/bioprorelife
cd ~/wisetopdataserver/
sudo vim ./run-setup-uart.sh
```
##### Modify the code as following
@@ -149,44 +153,72 @@ sudo raspi-config
reboot
```
###### Set pi password
```
$cd ~
$passwd
Current password:raspberry
New password:5k6zj454778225
Retype new password:5k6zj454778225
```
###### Auto run program when device start
```
sudo vim /etc/rc.local
```
add the code following as:
###### add the code following as:
```
sudo -H -u pi nohup /home/pi/bioprorelife/run-data-server.sh > /home/pi/.data_server.out &
sudo -H -u pi nohup /home/pi/wisetopdataserver/run-data-server.sh > /home/pi/.data_server.out &
#sudo -H -u pi nohup /home/pi/wisetopdataserver/run-data-server.sh > /home/pi/.wisetop_data_$now.out &
```
##### /etc/rc.local
```
_IP=$(hostname -I) || true
if [ "$_IP" ]; then
printf "My IP address is %s\n" "$_IP"
fi
now=$(date +'%F_%T')
sudo nohup systemctl restart apache2
sudo nohup systemctl restart mosquitto
nohup node /home/pi/wisetopapiserver/dist/app.js > /home/pi/.api_server.out &
sleep 5
sudo -H -u pi nohup /home/pi/wisetopdataserver/run-data-server.sh > /home/pi/.data_server.out &
#sudo -H -u pi nohup /home/pi/wisetopdataserver/run-data-server.sh > /home/pi/.wisetop_data_$now.out &
exit 0
```
### MQTT broker install
1. Install mosquitto
```
sudo apt-get install mosquitto mosquitto-clients
```
###### 1.Install mosquitto
$sudo apt-get install mosquitto mosquitto-clients
###### 2.Enable Remote Access
$sudo vim /etc/mosquitto/conf.d/default.conf
2. Enable Remote Access
```
sudo vim /etc/mosquitto/conf.d/default.conf
```
add the code following as:
```
listener 1883
protocol mqtt
listener 8083
protocol websockets
allow_anonymous true
```
restart mqtt broker
```
sudo systemctl restart mosquitto
```
###### 3.restart mqtt broker
$sudo systemctl restart mosquitto
3. Robust MQTT
###### 4.Robust MQTT
create mosquitto_restart.sh
```
cd ~
sudo vim mosquitto_restart.sh
```
$cd ~
$sudo vim mosquitto_restart.sh
add the code following as:
```
if [ "`ps -aux | grep /usr/sbin/mosquitto | wc -l`" == "1" ]
@@ -200,25 +232,129 @@ fi
echo "$SERVICE is currently running" >> /home/ubuntu/cron.log
exit 0
```
add mosquitto_restart.sh to root routing
```
sudo chmod +x mosquitto_restart.sh
sudo -i
crontab -e
```
###### 5.add mosquitto_restart.sh to root routing
$sudo chmod +x mosquitto_restart.sh
$sudo -i
$crontab -e
add the code following as:
```
*/5 * * * * /home/pi/mosquitto_restart.sh
```
3. Auto run MQTT broker when when pi-start
```
sudo vim /etc/rc.local
```
###### 6.Auto run MQTT broker when when pi-start
$sudo vim /etc/rc.local
add the code following as:
```
sudo nohup systemctl restart mosquitto
```
### Setup routed wireless access point
### Setting up a Routed Wireless Access Point
###### 1.Install AP and Management Software
$sudo apt install hostapd
$sudo systemctl unmask hostapd
$sudo systemctl enable hostapd
$sudo apt install dnsmasq
$sudo DEBIAN_FRONTEND=noninteractive apt install -y netfilter-persistent iptables-persistent
###### 2.Set up the Network Router
$sudo nano /etc/dhcpcd.conf
```
interface wlan0
static ip_address=192.168.2.1/24
nohook wpa_supplicant
```
$sudo nano /etc/sysctl.d/routed-ap.conf
```
# Enable IPv4 routing
net.ipv4.ip_forward=1
```
$sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
$sudo netfilter-persistent save
$sudo mv /etc/dnsmasq.conf /etc/dnsmasq.conf.orig
$sudo nano /etc/dnsmasq.conf
```
interface=wlan0 # Listening interface
dhcp-range=192.168.2.2,192.168.2.255,255.255.255.0,24h
# Pool of IP addresses served via DHCP
domain=wlan # Local wireless DNS domain
address=/gw.wlan/192.168.2.1
# Alias for this router
```
###### 3.Ensure Wireless Operation
$sudo rfkill unblock wlan
###### 4.Configure the AP Software
$sudo nano /etc/hostapd/hostapd.conf
```
country_code=TW
interface=wlan0
ssid=WTP_NONE
hw_mode=g
channel=7
macaddr_acl=0
auth_algs=1
ignore_broadcast_ssid=0
wpa=2
wpa_passphrase=00000000
wpa_key_mgmt=WPA-PSK
wpa_pairwise=TKIP
rsn_pairwise=CCMP
# 802.11n support
ieee80211n=1
# make raspberry pi dongle fast
wme_enabled=1
# ht_capab: HT capabilities, enable 40MHz
ht_capab=[HT40+][SHORT-GI-40]
```
###### 5.Running the new Wireless AP
$sudo systemctl reboot
### Set web site
$sudo vim ./sites-available/000-default.conf
```
<VirtualHost *:8080> #80------>8080
# The ServerName directive sets the request scheme, hostname and port that
# the server uses to identify itself. This is used when creating
# redirection URLs. In the context of virtual hosts, the ServerName
# specifies what hostname must appear in the request's Host: header to
# match this virtual host. For the default virtual host (this file) this
# value is not decisive as it is used as a last resort host regardless.
# However, you must set it for any further virtual host explicitly.
#ServerName www.example.com
ServerAdmin webmaster@localhost
DocumentRoot /var/www/html
# Available loglevels: trace8, ..., trace1, debug, info, notice, warn,
# error, crit, alert, emerg.
# It is also possible to configure the loglevel for particular
# modules, e.g.
#LogLevel info ssl:warn
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
# For most configuration files from conf-available/, which are
# enabled or disabled at a global level, it is possible to
# include a line for only one particular virtual host. For example the
# following line enables the CGI configuration for this host only
# after it has been globally disabled with "a2disconf".
#Include conf-available/serve-cgi-bin.conf
</VirtualHost>
<VirtualHost *:80>
ServerAdmin bioprovue@localhost
DocumentRoot /var/www/bioprovuebuild
</VirtualHost>
```
##### raspberry website document:
> https://www.raspberrypi.org/documentation/configuration/wireless/access-point-routed.md
+6 -3
View File
@@ -1578,10 +1578,13 @@ class ControlAPI(metaclass=Router):
raise NotImplementedError()
def run_project(self, project) -> bool:
print('create_project', project)
self._project_manager.create(project)
return False
raise NotImplementedError()
def get_running_project(self) -> bool:
raise NotImplementedError()
def stop_project(self, project) -> bool:
raise NotImplementedError()
# noinspection PyAbstractClass
class ControlClient(SocketClient, ControlAPI, metaclass=SocketClientMacro(ControlAPI)):
+1
View File
@@ -2035,6 +2035,7 @@ class CC2650SingleMasterCentralDevice(CC2650MasterDevice, Synchronized):
# print("local_name = ", local_name)
# addr type is don't care in BMD380
print('scan_response:', list(scan_response))
local_addr_type = int(scan_response[index:index + 1][0])
# local_addr_type = 0xFF
index = index + 1
+8 -8
View File
@@ -253,6 +253,7 @@ class DeviceInternalCommandHandler:
return True
# why inherit MasterDevice ?
class DeviceManager(MasterDevice, Synchronized):
"""device manager. It manager the device libraries, found device, connected device
and the control of the master device.
@@ -267,7 +268,6 @@ class DeviceManager(MasterDevice, Synchronized):
options: DeviceManagerOptions,
handler: Optional[DeviceInternalCommandHandler] = None):
"""
:param options: manager options.
:param handler: device internal command handler
"""
@@ -277,6 +277,7 @@ class DeviceManager(MasterDevice, Synchronized):
if handler is None:
handler = DeviceInternalCommandHandler()
# control server api
self._handler: DeviceInternalCommandHandler = handler
self._options: DeviceManagerOptions = options
@@ -286,7 +287,9 @@ class DeviceManager(MasterDevice, Synchronized):
# hardware
self._interface = options.interface
self._master: Optional[MasterDevice] = None
# memory board
self._centralMaster: Optional[MasterDevice] = None
# connected device
self._device: List[CompletedDevice] = []
# demo device
@@ -336,6 +339,10 @@ class DeviceManager(MasterDevice, Synchronized):
def library_path(self) -> List[Path]:
return self._repository.library_path
def get_device_status(self, mac_address) -> Optional[DeviceLibrary]:
device = self.get_device(mac_address)
return device.status
@logging_info
def reload_library(self):
"""reload device library"""
@@ -562,7 +569,6 @@ class DeviceManager(MasterDevice, Synchronized):
@logging_verbose
def get_device(self, device: Union[int, DeviceInfo]) -> Optional[CompletedDevice]:
print('get_device', device)
"""get connected device
:param device: device ID or response info
@@ -1024,15 +1030,9 @@ class DeviceManager(MasterDevice, Synchronized):
raise RuntimeError(DEVICE_NOT_FOUND, device)
if DeviceCommonInstruction(instruction) is not None:
# print('deviceCommonInstr')
# print(instruction)
device.call_instruction(instruction)
elif instruction in device.library.instruction_table:
# print('instruction')
# print(instruction)
device.call_instruction(instruction)
else:
raise RuntimeError(INSTRUCTION_UNKNOWN, instruction)
+26 -27
View File
@@ -881,12 +881,17 @@ class I4V4Z4T4DataDecoder(RecDataDecoder):
def decode(self, data: bytes) -> Optional[RecordingData]:
if len(data) < 18:
return None
voltage = 0
mem_cnt = data[1]
time_stamp: float = struct.unpack('<I', data[4:8])[0] # unit: ms 0x18030000
current = struct.unpack('<i', data[8:12])[0] # unit: nA
voltage = struct.unpack('<i', data[12:16])[0] # unit: uV
ch2 = struct.unpack('<i', data[12:16])[0] # unit: uV
impedance = struct.unpack('<i', data[16:20])[0] # unit: mOm
if self._mode == 16:
voltage = impedance - ch2
else:
voltage = ch2
cycle_number = struct.unpack('<H', data[20:22])[0]
finish_mode_falg = data[22]
battery = struct.unpack('<i', data[23:27])[0]
@@ -945,18 +950,18 @@ class I4V4Z4T4DataDecoder(RecDataDecoder):
ret.append_data(1, voltage)
ret.append_data(2, impedance)
ret.append_data(3, cycle_number)
ret.append_data(4, battery)
ret.append_data(5, elite_notify_times)
ret.append_data(6, mem_cnt)
# 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))
# # 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))
@@ -1396,39 +1401,33 @@ class EISZeroOneDataDecoder(RecDataDecoder):
hsrtia_a.append(struct.unpack('>i', cali_coeff[index+1:index+5])[0]/1e8)
hsrtia_b.append(struct.unpack('>i', cali_coeff[index+5:index+9])[0]/1e8)
hsrtia_c.append(struct.unpack('>i', cali_coeff[index+9:index+13])[0]/1e4)
hsrtia_d.append(struct.unpack('>B', cali_coeff[index+13:index+14])[0])
#Lv[1] 20k
index = 80
hsrtia_a.append(struct.unpack('>i', cali_coeff[index+1:index+5])[0]/1e8)
hsrtia_b.append(struct.unpack('>i', cali_coeff[index+5:index+9])[0]/1e8)
hsrtia_c.append(struct.unpack('>i', cali_coeff[index+9:index+13])[0]/1e4)
hsrtia_d.append(struct.unpack('>B', cali_coeff[index+13:index+14])[0])
#Lv[2] 5k
index = 100
hsrtia_a.append(struct.unpack('>i', cali_coeff[index+1:index+5])[0]/1e8)
hsrtia_b.append(struct.unpack('>i', cali_coeff[index+5:index+9])[0]/1e8)
hsrtia_c.append(struct.unpack('>i', cali_coeff[index+9:index+13])[0]/1e4)
hsrtia_d.append(struct.unpack('>B', cali_coeff[index+13:index+14])[0])
#Lv[3] 200R
index = 120
hsrtia_a.append(struct.unpack('>i', cali_coeff[index+1:index+5])[0]/1e8)
hsrtia_b.append(struct.unpack('>i', cali_coeff[index+5:index+9])[0]/1e8)
hsrtia_c.append(struct.unpack('>i', cali_coeff[index+9:index+13])[0]/1e4)
hsrtia_d.append(struct.unpack('>B', cali_coeff[index+13:index+14])[0])
# hsrtia_a.append(struct.unpack('>I', cali_coeff[index+1:index+5])[0])
# hsrtia_b.append(struct.unpack('>I', cali_coeff[index+5:index+9])[0]/1e6)
# hsrtia_c.append(struct.unpack('>I', cali_coeff[index+9:index+13])[0]/1e5)
# hsrtia_d.append(struct.unpack('>I', cali_coeff[index+13:index+17])[0]/1e6)
# print('cutoff_freq', cutoff_freq)
# print('hsrtia_a', hsrtia_a)
# print('hsrtia_b', hsrtia_b)
# print('hsrtia_c', hsrtia_c)
# print('hsrtia_d', hsrtia_d)
# print('phase_para_a', phase_para_a)
# print('phase_para_b', phase_para_b)
@@ -1456,12 +1455,12 @@ class EISZeroOneDataDecoder(RecDataDecoder):
if len(data) < 18:
return None
ch1 = struct.unpack('>i', data[1:5])[0] # unit: 1/1000 nA
ch2 = struct.unpack('>i', data[5:9])[0] # unit: mV
ch3 = struct.unpack('>i', data[9:13])[0] # unit: kOm
time_stamp: float = struct.unpack('<I', data[13:17])[0] # unit: ms
cycle_number = struct.unpack('>H', data[17:19])[0]
d19 = data[19]
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 = (d19 & 0x0F)
finishMode = (d19 & 0x80) >> 7
@@ -1585,10 +1584,10 @@ class EISZeroOneDataDecoder(RecDataDecoder):
ret = RecordingData(self.device, int(time_stamp * 1000 / 2), 0)
if (self._mode == 0): #EIS Mode
ret.append_data(3, cycle_number)
ret.append_data(0, ch1) #Raw Imag
ret.append_data(1, ch2) #Raw Real
ret.append_data(2, ch3 * 10) #Frequency [mHz]
ret.append_data(3, cycle_number)
ret.append_data(4, round(imag_after_cal)) #Z_imag [Ohm]
ret.append_data(5, round(real_after_cal)) #Z_real [Ohm]
ret.append_data(6, round(impedance)) #Impedance [Ohm]
@@ -1597,10 +1596,10 @@ class EISZeroOneDataDecoder(RecDataDecoder):
ret.append_data(9, gain) #Gain Level
else: #CV Mode
ret.append_data(3, cycle_number)
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
-8
View File
@@ -653,15 +653,9 @@ class DeviceInstruction:
VIS_CC_ZERO = 0x40
VIS_ASK = 0x30
"""ask in virtual instruction"""
VIS_STI = 0xC0
"""stimulation on virtual instruction"""
VIS_FUH = 0x90
"""flush virtual instruction"""
VIS_INT = 0x60
"""interrupt virtual instruction"""
@@ -750,8 +744,6 @@ class DeviceCommonInstruction:
return '_sync(False)', '_notify(False)', 'VIS_INT'
elif instruction == cls.CLOSE:
return '_sync(False)', '_notify(False)', 'VIS_INT'
elif instruction == cls.FLUSH:
return 'VIS_FUH',
elif instruction == cls.CALL:
return 'VIS_CAL',
elif instruction == cls.VIS_DEVICE_DETECT:
+15 -2
View File
@@ -4,6 +4,7 @@ from biopro.util.text import part_suffix
from . import *
from .expression import *
import json
class DeviceParameterError(RuntimeError):
__slots__ = ()
@@ -309,6 +310,8 @@ class ParameterValueDomain(ParameterDomain, metaclass=abc.ABCMeta):
return f
def valid_para(self, value: Any) -> bool:
if isinstance(value, list):
return True
f, t = self.range
return f <= float(value) < t
@@ -489,8 +492,12 @@ class ParameterCollectionValueOperator(metaclass=abc.ABCMeta):
@classmethod
def parse(cls, expr: Union[int, str]) -> 'ParameterCollectionValueOperator':
# parse epression then create value_operator
if isinstance(expr, int):
return SingleValueOperator(None, expr)
elif isinstance(expr, list):
return CollectValueOperator((0, len(expr)), expr)
elif expr == '*':
return AddAllValueOperator
@@ -498,6 +505,10 @@ class ParameterCollectionValueOperator(metaclass=abc.ABCMeta):
elif expr == ':=':
return CollectValueOperator((None, None), [])
elif '[' in expr and ']' in expr:
v = json.loads(expr)
return CollectValueOperator((0, len(v)), v)
elif '=' in expr:
a, v = part_suffix(expr, '=')
@@ -508,6 +519,7 @@ class ParameterCollectionValueOperator(metaclass=abc.ABCMeta):
return CollectValueOperator((None, None), v)
else:
# get list length (old way)
a, b = part_suffix(a, ':')
a = int(a) if len(a) else None
b = int(b) if len(b) else None
@@ -602,8 +614,8 @@ class ParameterCollectionDomain(ParameterDomain, metaclass=abc.ABCMeta):
def __init__(self, domain: ParameterDomain):
super().__init__()
if isinstance(domain, ParameterCollectionDomain):
raise RuntimeError('cannot compose with parameter collection domain')
# if isinstance(domain, ParameterCollectionDomain):
# raise RuntimeError('cannot compose with parameter collection domain')
self._domain = domain
@@ -725,6 +737,7 @@ class ParameterListDomain(ParameterCollectionDomain):
if oper == AddAllValueOperator:
raise ValueError("list parameter cannot accept '*' (all) value")
# d: domain, sz: lsit length, i: slice(a, b), v: list value
d = self.element_domain
sz = len(target)
i = oper.index(sz)
+7 -3
View File
@@ -119,7 +119,10 @@ class DeviceConfigurationEncoder:
e.write(b'L\0')
else:
parameter_type = cls._value_type(value[0])
parameter_type = cls._value_type(max(value))
for v in value:
if isinstance(v, int) and v < 0:
parameter_type = parameter_type.lower()
e.write(b'L')
e.write(parameter_type.encode())
e.u8(sz)
@@ -153,7 +156,8 @@ class DeviceConfigurationEncoder:
if isinstance(value, bool):
e.u8(1 if value else 0)
elif isinstance(value, str):
elif isinstance(value, str) or isinstance(value, list):
value = str(value)
e.encode_string(value)
elif isinstance(value, int):
@@ -170,7 +174,7 @@ class DeviceConfigurationEncoder:
if isinstance(value, bool):
return '?'
elif isinstance(value, str):
elif isinstance(value, str) or isinstance(value, list):
return 'p'
elif isinstance(value, int):
+4 -2
View File
@@ -1268,14 +1268,15 @@ class InstructionTable(DictNode['Instruction']):
instruction: str):
"""call instruction. chain context with :class:`DeviceInternalInstructionScope`
:param master: master device
:param device: device
:param master: DeviceManager
:param device: CompletedDevice
:param context: scope
:param instruction: instruction name
:raises InstructionNotFound: If instruction not found.
"""
try:
# ex. start
ins = self[instruction]
except KeyError as e:
@@ -1284,6 +1285,7 @@ class InstructionTable(DictNode['Instruction']):
else:
context = context.chain(InstructionTableScope(self))
# ex. _data_format, _sync, _notify
for single in ins.eval_instruction(context):
if isinstance(single, InternalInstruction):
if not single.handle_internal_instruction(master, device, context):
+34 -6
View File
@@ -510,9 +510,10 @@ class DeviceParameter(JsonSerialize):
:param para: parameter name
:param oper: operator expression
"""
# parse operator return CollectValueOperator instance
if not isinstance(oper, ParameterCollectionValueOperator):
oper = ParameterCollectionValueOperator.parse(oper)
table = self._library.parameter_table
info = table[para]
domain = info.domain
@@ -528,6 +529,7 @@ class DeviceParameter(JsonSerialize):
old = list(target)
try:
# replace target with new oper
domain.oper_para(target, oper, self._context)
except RuntimeError as e:
raise IllegalParameterValueError(para, str(oper)) from e
@@ -966,7 +968,7 @@ class CompletedDevice(Device):
"""
__slots__ = ('_master', '_device_id', '_device', '_library', '_context',
'_parameter', '_configuration', '_lock', '_feature_mask',
'_cache_battery', '_cache_battery_timestamp', '_coeff')
'_cache_battery', '_cache_battery_timestamp', '_coeff', '_status')
def __init__(self, master: MasterDevice, library: DeviceLibrary, device_id: int, device: Device):
"""
@@ -976,11 +978,15 @@ class CompletedDevice(Device):
:param device_id: device id
:param device: slave device implementation
"""
# DeviceManager
self._master = master
self._device = device
self._device_id = device_id
self._library = library
# device status ( 0: idle, 1: working, 2: error, 3: power off)
self._status = 0
# context
s1 = library.constant
s2 = DeviceScope(self)
@@ -1008,6 +1014,10 @@ class CompletedDevice(Device):
def device_id(self) -> int:
return self._device_id
@property
def memory_board(self) -> int:
return self._device_id
@property
def device_name(self) -> str:
return self._device.device_name
@@ -1020,6 +1030,10 @@ class CompletedDevice(Device):
def mac_address(self) -> ADDRESS:
return self._device.mac_address
@property
def mac_address_in_str(self) -> str:
return ':'.join('{:02x}'.format(b) for b in self._device.mac_address)
@property
def device(self) -> Device:
return self._device
@@ -1056,11 +1070,18 @@ class CompletedDevice(Device):
return self._library
# device lock
@property
def lock(self) -> DeviceLock:
return self._lock
@property
def status(self) -> int:
return self._status
@status.setter
def status(self, new_status):
self._status = new_status
# device parameter getter/setter
def parameters(self) -> List[str]:
@@ -1115,7 +1136,8 @@ class CompletedDevice(Device):
self._master.log_warn('on_change instruction not found : ' + on_change)
else:
self.call_instruction(on_change)
if self._status == 1:
self.call_instruction(on_change)
# device instruction call
@@ -1258,14 +1280,20 @@ class CompletedDevice(Device):
def as_json(self) -> JSON_OBJECT:
return {
'id': self.device_id,
'name': self.device_name,
'status': self._status,
'device_id': self.device_id,
'device_name': self.device_name,
'device_address': list(self.mac_address),
'device_status': self._status,
'mac_address': self.mac_address_in_str,
'memory_board': self.memory_board,
'serial_number': self.serial_number.as_json(),
'version': self.device_version,
'battery': self.battery,
'parent': self.parent,
'recording_file_name': self.recording_file_name,
'device_version': self.device_version,
'serial_number': self.serial_number.as_json(),
'library_name': self._library.name,
'library_version': str(self._library.version),
'configuration': self.configuration.as_json(list_hide=True),
+6 -4
View File
@@ -76,6 +76,8 @@ class MultiExtMemSpiInterface(LowLevelHardwareInterface):
def set_pin_mem_req(self, value: bool):
channel = self.select
# if self._pin_mem_req_value[channel] == value:
# print('last_req_sig[channel] == value', channel, self._pin_mem_req_value[channel], value, datetime.now())
self.pin_mem_req.output(value)
self._pin_mem_req_value[channel] = value
@@ -287,8 +289,8 @@ class MultiExtMemSpiInterface(LowLevelHardwareInterface):
print("length < 12")
print("green data print:", data, device, datetime.now())
if length == 0:
self._print_ram_all_data()
# if length == 0:
# self._print_ram_all_data()
return None
@@ -305,7 +307,7 @@ class MultiExtMemSpiInterface(LowLevelHardwareInterface):
if (header != 255):
flag_print = True
print("green data: header is not 255: ", header)
self._print_ram_all_data()
# self._print_ram_all_data()
if (elite_data_len != self._elite_data_len):
flag_print = True
@@ -384,7 +386,7 @@ class MultiExtMemSpiInterface(LowLevelHardwareInterface):
# print(check_sum, rx[i + self._single_data_len - 1])
if (check_sum != rx[i + self._single_data_len - 1]):
print('check_sum wrong, origin value:', 'check_sum =', check_sum, rx[i : i + self._single_data_len])
# print('check_sum wrong, origin value:', 'check_sum =', check_sum, rx[i : i + self._single_data_len])
tx_d = [0] * (self._single_data_len + 3)
address = 12 + self._single_data_len * index
tx_d[0] = MEM_INS_READ
+4 -4
View File
@@ -118,13 +118,13 @@ class Selector:
self._p2.output(p[2])
# if (value == 4 and self._last_sel != 6):
# print('mem_sel is not use gray code, value:', value, ', last value:', self._last_sel)
# print('mem_sel is not use gray code, value:', value, ', last value:', self._last_sel, datetime.now())
# elif (value == 5 and self._last_sel != 4):
# print('mem_sel is not use gray code, value:', value, ', last value:', self._last_sel)
# print('mem_sel is not use gray code, value:', value, ', last value:', self._last_sel, datetime.now())
# elif (value == 7 and self._last_sel != 5):
# print('mem_sel is not use gray code, value:', value, ', last value:', self._last_sel)
# print('mem_sel is not use gray code, value:', value, ', last value:', self._last_sel, datetime.now())
# elif (value == 6 and self._last_sel != 7):
# print('mem_sel is not use gray code, value:', value, ', last value:', self._last_sel)
# print('mem_sel is not use gray code, value:', value, ', last value:', self._last_sel, datetime.now())
self._last_sel = value
+20 -59
View File
@@ -1,65 +1,26 @@
class Action():
def __init__(self, task, action_id, action):
self._task = task
self._id:str = action_id
self._type:str = action['type']
self._target:str = action['target']
self._condition_list:list[str] = action['condition']
self._duration = action.get('duration', None)
self._goto = action.get('goto', None)
self._cycle = action.get('cycle', None)
self._instruction = None
self.update_instruction()
def __init__(self, action_id, action):
self._id: str = action_id
self._type: str = action['type']
self._target: str = action['target']
self._condition : list[str] = action['condition']
# self._duration = action.get('duration', None)
# self._goto = action.get('goto', None)
# self._cycle = action.get('cycle', None)
@property
def id(self):
return self._id
@property
def type(self):
return self._type
def update_instruction(self):
self._instruction = {
"device_instruction": {
"header": "call_instruction",
"device": self._target,
"arguments": {
"instruction": self._type,
}
},
"device_parameter": {
"header": "set_multi_parameters",
"device": self._target,
"arguments": {
"parameter": self._task.get_parameter_set_by_device(self._target),
}
},
"device_recording_file_name": {
"header": "update_recording_file_name_info",
"device": self._target,
"arguments": {
"content": self._task.file_name,
}
},
"device_parent": {
"header": "update_parent_info",
"device": self._target,
"arguments": {
"content": self._task.parent
}
}
}
def get_condition_list(self):
return self._condition_list
def get_instruction(self, value):
return self._instruction[value]
def get_instruction_list(self):
instruction_set = {
'start': ['device_parent', 'device_recording_file_name', 'device_parameter', 'device_instruction'],
'stop': ['device_instruction'],
}
return map(self.get_instruction, instruction_set[self._type])
@property
def target(self):
return self._target
@property
def condition(self):
return self._condition
+48 -27
View File
@@ -3,70 +3,91 @@ from time import time
class Condition():
def __init__(self, id, condition):
print('condition', id, condition)
self._id = id
self._type = condition['type']
self._comparsion = condition['comparsion']
self._value = condition['value']
self._active = False
@property
def id(self):
return self._id
@property
def type(self):
return self._type
@property
def comparsion(self):
return self._comparsion
@property
def value(self):
return self._value
def set_comparsion(self, comparsion):
self._comparsion = comparsion
def set_type(self, type):
self._type = type
@id.setter
def id(self, new_id):
self._id = new_id
def set_value(self, value):
self._value = value
@type.setter
def type(self, new_type):
self._type = new_type
def compareWith(self, operator:str, x, y) -> bool:
@comparsion.setter
def comparsion(self, new_comparsion):
self._comparsion = new_comparsion
@value.setter
def value(self, new_value):
self._value = new_value
def compareWith(self, operator, x, y) -> bool:
# print(operator, x, y)
cases = {
"equal": lambda a, b: a == b,
"bigger": lambda a, b: a > b,
"smaller": lambda a, b: a < b,
}
# print('x','y',x, y,type(x),type(y))
return cases[operator](x, y)
def match_or_not(self, **kwargs):
return self.method_mapping(self.type)()
# print(self._type, self._comparsion, self._value)
return getattr(self, self.type)(**kwargs)
def absolute_time(self, **kwargs):
now = time()
time_condition = self.datetime_to_timestamp(self.str_to_datetime(self._value))
now = int(time())
time_condition = round(self.datetime_to_timestamp(self.str_to_datetime(self._value)))
return self.compareWith(self.comparsion, int(now), int(time_condition))
return self.compareWith(self.comparsion, now, time_condition)
def relative_time(self, **kwargs):
now = time()
def after_project_run(self, **kwargs):
project_start_time = kwargs['project_start_time']
delay_time = kwargs['delay_time']
time_diff = int(time() - project_start_time - delay_time)
return self.compareWith(self.comparsion, time_diff, int(self._value))
def after_task_run(self, **kwargs):
# print('relative_time_from_task', kwargs,kwargs['task_start_time'],kwargs['delay_time'])
if len(kwargs['task_start_time']) == 0:
return False
task_start_time = kwargs['task_start_time'][-1]
delay_time = kwargs['delay_time']
time_diff = int(time() - task_start_time - delay_time)
# print('time_diff', time_diff)
return self.compareWith(self.comparsion, time_diff, int(self._value))
def device(self, **kwargs):
print('device')
def task_status(self, **kwargs):
print('task_status')
def method_mapping(self, method_name):
methods = {
"relative_time": self.relative_time,
"absolute_time": self.absolute_time,
"device": self.device,
"task_status": self.task_status,
}
return methods[method_name]
def previous_task_done(self, **kwargs):
running_task = kwargs['running_task']
if running_task.status == 2 and self._active == False:
self._active = True
return True
return False
def str_to_datetime(self, time_str):
return datetime.strptime(time_str,'%Y-%m-%dT%H:%M')
+32
View File
@@ -0,0 +1,32 @@
class Instruction():
def __init__(self):
self._instruction_set = {
'set_file_name': {
'method': 'update_recording_file_name_info',
'arguments': ['file_name']
},
'set_parent': {
'method': 'update_parent_info',
'arguments': ['parent']
},
'set_parameter':{
'method': 'set_multi_parameters',
'arguments': ['parameter']
},
'call_instruction': {
'method': 'call_instruction',
'arguments': ['instruction']
},
}
self._start_instruction = list(map(lambda ins: self._instruction_set[ins] ,['set_file_name', 'set_parent', 'set_parameter', 'call_instruction']))
self._stop_instruction = list(map(lambda ins: self._instruction_set[ins] ,['call_instruction']))
@property
def start(self) -> list:
return self._start_instruction
@property
def stop(self) -> list:
return self._stop_instruction
+187 -41
View File
@@ -1,14 +1,23 @@
from .task import Task
from .task_manager import TaskManager
import sys
import json
import threading
import time
from time import time, sleep
from datetime import datetime
from collections import deque
from copy import copy
from uuid import uuid4
from .task import Task
from .task_manager import TaskManager
from .instruction import Instruction
from biopro.device.manager import DeviceManager
from biopro.text import *
key_list = {
'deviceList': 'device',
}
class Project(threading.Thread):
def __init__(self, project, device_manager: DeviceManager, mqttThread = None, name="project"):
super(Project, self).__init__(name = name)
@@ -17,62 +26,199 @@ class Project(threading.Thread):
self._mqtt_thread = mqttThread
self._time_interval = 1
self._start_time = None
self._end_time = None
self._task_manager = TaskManager(self._project['taskList'])
self._id = None
self._uuid = str(uuid4())
self._name = None
self._desc = None
self._device = None
self._complete_device = []
self._status = 0
self._instruction_set = Instruction()
self._stop_flag = False
self._task_manager = None
self.setup_project(project)
self.setup_device(self._device)
def setup_project(self, project):
for (key, value) in project.items():
if key in key_list.keys():
key = key_list[key]
if key == 'task':
self._task_manager = TaskManager(project['task'])
elif key == 'uuid':
pass
else:
setattr(self, key, value)
def setup_device(self, device_list):
for device in device_list:
complete_device = self._device_manager.get_device(device['connectDevice']['device_address'])
self._complete_device.append(complete_device)
@property
def id(self) -> int:
return self._id
@id.setter
def id(self, new_id):
self._id = new_id
@property
def uuid(self) -> str:
return self._uuid
@uuid.setter
def uuid(self, new_uuid):
self._uuid = new_uuid
@property
def name(self) -> str:
return self._name
@name.setter
def name(self, new_name):
self._name = new_name
self._start_time = None
@property
def desc(self) -> str:
return self._project['desc']
return self._desc
@desc.setter
def desc(self, new_desc):
self._desc = new_desc
@property
def status(self) -> str:
return self._project['status']
return self._status
@status.setter
def status(self, new_status):
self._status = new_status
@property
def device(self) -> list:
return self._project['deviceList']
return self._device
@device.setter
def device(self, new_device):
self._device = new_device
@property
def task_list(self):
return self._task_manager.export_task_list
@property
def task(self) -> list:
return self._task_manager.get()
@property
def get_current_task(self) -> str:
return self._task_manager.get_current_task()
def mqtt_thread(self):
return self._mqtt_thread
def run(self):
self._start_time = time.time()
self._status = 1
self._start_time = time()
self.mqtt_thread.broadcast_command('project:' + self._name + ' start at ' + datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3])
while not self._stop_flag :
# check running task first
delay_time = 0
check_list = copy(self._task_manager.check_list)
while True:
# current task
for task in self._task_manager.get_check_task_list():
# [<match_condition>, ...]
match_condition_list = task.check_condition('time')
# [[<match_action>, ...], ...]
match_action_list = task.get_match_action_list(match_condition_list)
for action_list in match_action_list:
for action in action_list:
if action.type == 'start':
self._task_manager.set_current_task(task)
task.do_action(action)
while len(task.instruction_list) > 0:
instruction = task.instruction_list.pop(0)
if instruction.get('device', None) != None:
device = self._device_manager.get_device(self.device[instruction['device']]['connectDevice']['device_address'])
for task in check_list:
if task != None:
now = time()
# print('round task', task.name)
# print('running task', self._task_manager.running_task.name, self._task_manager.running_task.status)
# if self._task_manager.prev_task != None:
# print('previous_task', self._task_manager.prev_task.name, self._task_manager.prev_task.status)
if device is None:
raise RuntimeError(DEVICE_NOT_FOUND, device)
match_condition_list = task.check_condition(
project_start_time = self._start_time,
delay_time = delay_time,
running_task= self._task_manager.running_task,
previous_task = self._task_manager.prev_task
)
# print('match_condition_list', match_condition_list)
for condition in match_condition_list:
match_action_list = task.get_match_action(condition.id)
for action in match_action_list:
# print('match_action', action.type, action.target)
if action.type == 'start' and task.status != 1:
self._task_manager.set_running_task(task)
self.mqtt_thread.broadcast_command('project:task ' + task.name + ' start at ' + datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3])
device = self._complete_device[action.target]
task_info = task.get_task_info(action)
instruction_set = getattr(self._instruction_set, action.type, None)
# print('instruction_set',instruction_set)
# print(instruction['header'], *instruction['arguments'].values())
getattr(device, instruction['header'])(*instruction['arguments'].values())
if instruction_set != None:
for instruction in instruction_set:
args = list(map(lambda arg: task_info[arg], instruction['arguments']))
threading.Thread(target=getattr(device, instruction['method'])(*args))
delay_time += (time() - now)
# check task not running then stop
if self.check_running_task_not_run() == True:
self.mqtt_thread.broadcast_command('project:task ' + str(self._task_manager.running_task.name) + ' stop at ' + datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3])
self._task_manager.running_task.stop()
# check project done then close project
if self.check_project_done() == True:
print('project stop at', datetime.now())
self.close()
if self._time_interval - delay_time > 0:
sleep(self._time_interval - delay_time)
def pause(self):
# TODO
pass
time.sleep(self._time_interval)
def stop(self):
return
self._task_manager.running_task.stop()
self.mqtt_thread.broadcast_command('project:task ' + str(self._task_manager.running_task.name) + ' stop at ' + datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3])
self._status = 2
self._end_time = time()
self._stop_flag = True
self.mqtt_thread.broadcast_command('project:project ' + str(self._name) + ' stop at ' + datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3])
def close(self):
return
self._status = 2
self._end_time = time()
self._stop_flag = True
self.mqtt_thread.broadcast_command('project:project ' + str(self._name) + ' stop at ' + datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3])
def check_project_done(self):
for task in self._task_manager.task_list:
# if task never start or still task is running then reject
if task.status == 1 or task.status == -1:
return False
return True
def check_running_task_not_run(self):
# if no running task
if self._task_manager.running_task == None:
return False
for device in self._task_manager.running_task.device:
if self._complete_device[device].status == 1:
return False
return True
def as_json(self):
data = {
'id': self._id,
'name': self._name,
'uuid': self._uuid,
'desc': self._desc,
'status': self._status,
'device': self._device,
'task': self.task_list,
'running_task': self._task_manager.running_task.as_json()
}
return data
+12 -6
View File
@@ -18,16 +18,22 @@ class ProjectManager():
new_project = Project(project, self._device_manager, self._mqtt_thread)
self._project_list.append(new_project)
return new_project
def remove(self, index):
self._project_list[index].stop()
del self._project_list[index]
def get(self):
return self._project_list
def get(self, project_uuid = None):
if project_uuid == None:
return self._project_list
else:
for project in self._project_list:
if project.uuid == project_uuid:
return project
def run(self, project):
def run_project(self, project):
project.start()
def stop(self, project):
project.stop()
def stop_project(self, project):
_project = self.get(project)
_project.stop()
+202 -118
View File
@@ -1,110 +1,187 @@
from typing import List
from .condition import Condition
from .action import Action
from time import time
from datetime import datetime
key_list = {
'deviceList': 'device',
'parameterSet': 'parameter_set'
}
class Task:
def __init__(self, task):
self._task = task
self._condition_list = []
self._action_list = []
self._start_time = time()
self._idle_time = []
self._period = None
self._next_time = None
self._last_time = None
self._id = None
self._name = None
self._parent = None
self._cycle = None
self._device = None
self._event = None
self._trigger = None
self._parameter_set = None
self._condition = None
self._action = None
# -1: initial 0: start(idle), 1: running, 2: close(idle)
self._status = 0
self._next = None
self._condition_list: List[Condition] = []
self._action_list: List[Action] = []
self._instruction_list = []
self._record_list = []
self.setup()
self._start_time = []
self._idle_time = []
self._end_time = []
self._period = None
self.load_task(task)
def setup(self) -> None:
# add condition
for key, condition in self._task['condition'].items():
new_condition = Condition(key, condition)
self._condition_list.append(new_condition)
def load_task(self, task) -> None:
# update task attribute
for key, value in task.items():
if key in key_list.keys():
key = key_list[key]
if key == 'condition':
self.update_condition_list(value)
if key == 'action':
self.update_action_list(value)
# print(key, value)
setattr(self, key, value)
# add action
for key, action in self._task['action'].items():
new_action = Action(self, key, action)
self._action_list.append(new_action)
def update_condition_list(self, condition_dict):
self._condition_list.clear()
for key, condition in condition_dict.items():
self._condition_list.append(Condition(key, condition))
def update_action_list(self, action_dict):
self._action_list.clear()
for key, action in action_dict.items():
self._action_list.append(Action(key, action))
@property
def id(self) -> str:
return self._task['id']
return self._id
@property
def file_name(self):
return self._task['name']
@property
def parent(self):
return self._task.get('parent', {"folder": [2]})
@property
def instruction_list(self):
return self._instruction_list
@property
def cycle(self) -> int:
return self._task['cycle']
@property
def order(self) -> int:
return self._task['order']
@property
def max_cycle(self) -> int:
return self._task['max_cycle']
@property
def devices(self) -> dict:
return self._task['devices']
@property
def devices_id(self) -> list:
return [device_id for device_id in self.devices]
@property
def events(self) -> list:
return self._task['events']
@property
def triggers(self) -> list:
return self._task['triggers']
@property
def parameter_set(self) -> list:
return self._task['parameterSet']
@property
def instructions(self) -> list:
return self._task['instructions']
@property
def conditions(self) -> list:
return self._task['conditions']
@property
def actions(self) -> list:
return self._task['actions']
@property
def condition_list(self) -> list:
return self._condition_list
@property
def status(self) -> str:
return self._task['status']
@id.setter
def id(self, new_id):
self._id = new_id
@property
def uuid(self) -> str:
return self._task['uuid']
return self._uuid
@uuid.setter
def uuid(self, new_uuid):
self._uuid = new_uuid
@property
def name(self):
return self._name
@name.setter
def name(self, new_name):
self._name = new_name
@property
def parent(self):
if self._parent == None:
return {"folder": [2]}
return self._parent
@parent.setter
def parent(self, new_parent):
self._parent = new_parent
@property
def cycle(self) -> int:
return self._cycle
@cycle.setter
def cycle(self, new_cycle):
self._cycle = new_cycle
@property
def device(self) -> dict:
return self._device
@device.setter
def device(self, new_device):
self._device = new_device
@property
def event(self) -> list:
return self._event
@event.setter
def event(self, new_event):
self._event = new_event
@property
def trigger(self) -> list:
return self._trigger
@trigger.setter
def trigger(self, new_trigger):
self._trigger = new_trigger
@property
def parameter_set(self) -> list:
return self._parameter_set
@parameter_set.setter
def parameter_set(self, new_parameter_set):
self._parameter_set = new_parameter_set
@property
def condition(self) -> list:
return self._condition
@condition.setter
def condition(self, new_condition):
self._condition = new_condition
@property
def action(self) -> list:
return self._action
@action.setter
def action(self, new_action):
self._action = new_action
@property
def status(self) -> str:
return self._status
@status.setter
def status(self, new_status):
self._status = new_status
@property
def next(self) -> str:
return self._task['next']
return self._next
@next.setter
def next(self, new_next):
self._next = new_next
@property
def instruction_list(self) -> List:
return self._instruction_list
@property
def start_time(self) -> List:
return self._start_time
@property
def end_time(self) -> List:
return self._end_time
def new_start_time(self):
self._start_time.append(time())
def new_end_time(self):
self._end_time.append(time())
def get_parameter_set_by_device(self, device):
return [parameter_set for parameter_set in self.parameter_set.values() if device == parameter_set['target']]
@@ -121,48 +198,55 @@ class Task:
return '|'.join(name_list) if name_or_value == 'name' else '|'.join(value_list)
def run(self):
self._status = 'running'
self._start_time = time()
def pause(self):
self._status = 'pause'
def set_cycle(self, cycle):
self._cycle = cycle
def set_status(self, status):
self._status = status
print(self._name, 'run at', datetime.now())
self._status = 1
self._start_time.append(time())
def start(self):
self.set_status('running')
self.status = 1
def stop(self):
if self.status == 'running':
self.do_action('stop')
self.set_status('idle')
def done(self):
self.set_status('done')
def end(self):
self.set_status('end')
self.status = 2
def get_match_action_list(self, match_condition_list):
return map(lambda condition: [x for x in self._action_list if condition.id in x.get_condition_list()], match_condition_list)
def get_match_action(self, condition_id):
return [action for action in self._action_list if condition_id in action.condition]
def do_action(self, action):
# # match_action = [x for x in self._action_list if condition_id in x.get_condition_list()]
# for action in match_action:
for instruction in action.get_instruction_list():
self._instruction_list.append(instruction)
def status_mapping(self, status):
statuses = {
"start": self.task_start,
"done": self.task_done,
}
return statuses[status]
def check_condition(self, **kwargs):
kwargs['task_start_time'] = self._start_time
return [condition for condition in self._condition_list if condition.match_or_not(**kwargs)]
def get_task_info(self, action):
_type = action.type
if _type == 'stop':
_type = 'interrupt'
def check_condition(self, type):
return [condition for condition in self._condition_list if condition.match_or_not()]
return {
'device': action.target,
'parameter': self.get_parameter_set_by_device(action.target),
'file_name': self._name + '-' + str(action.target),
'parent': self.parent,
'instruction': _type
}
def as_json(self):
return {
'id': self.id,
'name': self.name,
'parent': self.parent,
'cycle': self.cycle,
'device': self.device,
'event': self.event,
'trigger': self.trigger,
'parameter_set': self.parameter_set,
'condition': self.condition,
'action': self.action,
'status': self.status,
'next': self.next,
}
+66 -34
View File
@@ -1,5 +1,6 @@
from json import loads as json_parse, dumps as _json_stringify
from typing import Dict, Optional, Any
from socket import if_nametoindex
from typing import Dict, List, Optional, Any
from xml.dom.expatbuilder import parseString
import paho.mqtt.client as mqtt
from biopro.text import *
@@ -12,45 +13,76 @@ def json_stringify(o: Any) -> str:
class TaskManager():
def __init__(self, task_list):
self._current_task = None
self._task_list = []
self._need_to_check_task_list = []
self._next_task = []
self._prev_task = None
self._running_task = None
self.load_task_list(task_list)
# self.set_running_task(self._task_list[0])
# self._running_task.run()
self._next_task.append(self._task_list[0])
@property
def task_list(self):
return self._task_list
@property
def export_task_list(self):
return [d.as_json() for d in self._task_list]
@property
def prev_task(self):
return self._prev_task
@property
def running_task(self):
return self._running_task
@property
def next_task(self):
return self._next_task
@property
def check_list(self):
return [self._running_task, *self._next_task]
@property
def check_list(self):
return [self._running_task, *self._next_task]
def load_task_list(self, task_list):
for task in task_list:
task = Task(task)
self._task_list.append(task)
self.set_current_task(self._task_list[0])
self._current_task.run()
def remove(self, index):
pass
def set_running_task(self, task):
try:
# if there is task running & same task active ,then reject
if self._running_task != None and self._running_task.uuid == task.uuid:
return False
# save running task
self._prev_task = self._running_task
# clear next task list
self._next_task.clear()
self._running_task = task
self._running_task.run()
if self._prev_task != None:
# if previous task is still running, then need to close
if self._prev_task.status == 1:
self._prev_task.stop()
print('prev', 'run', self._prev_task.name, self._running_task.name)
for task_uuid in self._running_task.next:
_task = next((task for task in self._task_list if task.uuid == task_uuid), None)
if _task != None:
self._next_task.append(_task)
except RuntimeError as e:
print(e)
def get_task(self, task_id):
return self._task_list[task_id]
def get_check_task_list(self):
return self._need_to_check_task_list
def get_current_task(self) -> Task:
return self._current_task
def get_next_task(self):
return self._current_task.next
def set_current_task(self, task):
try:
if self._current_task != None and self._current_task.id != task.id:
self._current_task.stop()
self._current_task = task
self._need_to_check_task_list = []
# append current task
self._need_to_check_task_list.append(self._current_task)
# append next task
for task_uuid in self._current_task.next:
task = next((task for task in self._task_list if task.uuid == task_uuid), None)
self._need_to_check_task_list.append(task)
except Exception as e:
print(e)
+103 -21
View File
@@ -8,6 +8,7 @@ import gc
from biopro.util.json import JSON
from biopro.util.stack import print_exception
from biopro.util.logger import calculate_time
from .data import RecordingData
from .loader import *
@@ -115,8 +116,8 @@ class RecordingMetaFile(JsonSerialize):
__slots__ = ('_filename', '_filepath', '_id_db', '_create_time', '_file_version', '_file_uuid', '_data_format', '_last_time',
'_device', '_configuration', '_channel_mask', '_database',
'_dirty', '_last_modify_time', '_file_size_cache',
'_recording_file_ch', '_size',
'_parameter', '_parent', '_recording_file_name', '_recording_mini_ch',
'_recording_file_ch', '_recording_sub_file', '_size',
'_parameter', '_parent', '_recording_file_name', '_recording_sub_mini', '_recording_mini_ch',
'_raw_serial_number', '_mini_serial_number', '_device_id')
def __init__(self, filepath: Union[str, Path], data_format: bytes = None, id_db = 0, database = None):
@@ -167,6 +168,10 @@ class RecordingMetaFile(JsonSerialize):
self._raw_serial_number = {}
self._mini_serial_number = {}
self._recording_sub_file = {}
self._recording_sub_mini = {}
self._size = 0
self._dirty = True
@@ -304,6 +309,7 @@ class RecordingMetaFile(JsonSerialize):
if database is not None :
self._database = database
self._database.put_queue(['data_meta_write', meta_data, self._device_id, str(self._file_uuid), self._id_db])
# database.put_queue(['data_meta_write', self, meta_data, _path, self._id_db])
with self._filepath.open('wb') as _f:
f = FileEncoder(_f)
@@ -353,15 +359,58 @@ class RecordingMetaFile(JsonSerialize):
def update_subfile(self, database = None):
meta_data = {
'raw_data': self._recording_sub_file,
'mini_data': self._recording_sub_mini,
'channels': str(self._channel_mask.channels()),
'size': str(self._size),
'time_duration': str(self._last_time),
'uuid': str(self._file_uuid),
}
_path = str(self._filepath).replace('/', '^')
if database is not None :
self._database = database
self._database.put_queue(['data_meta_write', meta_data, self._device_id, str(self._file_uuid), self._id_db])
# database.put_queue(['data_meta_write', self, meta_data, _path, self._id_db])
return None
def update_subfile_time_size(self, database = None):
meta_data = {
'channels': str(self._channel_mask.channels()),
'size': str(self._size),
'time_duration': str(self._last_time),
'uuid': str(self._file_uuid),
}
_path = str(self._filepath).replace('/', '^')
if database is not None :
self._database = database
self._database.put_queue(['data_meta_write', meta_data, self._device_id, str(self._file_uuid), self._id_db])
# database.put_queue(['data_meta_write', self, meta_data, _path, self._id_db])
return None
def update_subfile_raw(self, database = None):
meta_data = {
'raw_data': self._recording_sub_file,
}
_path = str(self._filepath).replace('/', '^')
if database is not None :
self._database = database
self._database.put_queue(['data_meta_write', meta_data, self._device_id, str(self._file_uuid), self._id_db])
# database.put_queue(['data_meta_write', self, meta_data, _path, self._id_db])
return None
def update_subfile_mini(self, database = None):
meta_data = {
'mini_data': self._recording_sub_mini,
}
_path = str(self._filepath).replace('/', '^')
if database is not None :
self._database = database
self._database.put_queue(['data_meta_write', meta_data, self._device_id, str(self._file_uuid), self._id_db])
# database.put_queue(['data_meta_write', self, meta_data, _path, self._id_db])
return None
@property
@@ -965,7 +1014,8 @@ class RecordingFileWriter:
'_data_value_ch', '_close', '_data_mini_ch',
'_mini_scale_list', '_time_real_time', '_data_rl', '_data_db',
'_raw_save', '_mini_save', '_data_time_ch', '_data_value_ch_for_rl',
'_data_time_ch_for_rl', '_device_id', '_send_data', '_data_mqtt_ch', '_id_db_save')
'_data_time_ch_for_rl', '_device_id', '_send_data', '_data_mqtt_ch', '_id_db_save', '_raw_create_not_done',
'_mini_create_not_done')
def __init__(self, meta: RecordingMetaFile, device_id, database = None):
self._meta = meta
@@ -1046,6 +1096,9 @@ class RecordingFileWriter:
self._splitting_size = None
self._raw_create_not_done = True
self._mini_create_not_done = True
@property
def meta_file(self) -> RecordingMetaFile:
@@ -1066,7 +1119,6 @@ class RecordingFileWriter:
def channels_update(self, channels):
channels.sort()
self._database.put_queue(['data_meta_update_raw_empty_channel', self._meta._id_db, channels])
self._channel_list.extend(channels)
return self._channel_list
@@ -1080,6 +1132,9 @@ class RecordingFileWriter:
return None
def close(self):
# if self._recording_file is not None:
# self._recording_file.close()
# self._recording_file = None
print('close1')
self._close = True
if len(self._recording_file_dict) > 0:
@@ -1259,7 +1314,8 @@ class RecordingFileWriter:
self._data_db[c].append(str(v))
self._time_now = int(t)
return
# @calculate_time(1)
def write(self, data: Union[bytes, RecordingData, List[bytes], List[RecordingData]], mqtt_thread) -> int:
# check size
ths = self.splitting_threshold_size
@@ -1318,18 +1374,34 @@ class RecordingFileWriter:
self.get_data_iter(d, mqtt_thread)
for ch in self._recording_file_dict:
if self._recording_file_dict[ch]._id_db == 0:
return None
data_save = False
mini_save = False
if len(self._recording_file_dict) > 0:
for ch in self._data_db.keys():
if self._time_now - self._time_real_time[ch] > 1000000:
self._send_data[ch] = True
self._time_real_time[ch] = self._time_now
if self._time_now - self._time[ch] > 2000000:
for ch in self._recording_file_dict:
if self._recording_file_dict[ch]._id_db == 0:
return None
for ch in self._data_db.keys():
for scale in self._mini_scale_list:
if self._recording_mini_dict[str(scale)][ch]._id_db == 0:
return None
if self._raw_create_not_done :
self._raw_create_not_done = False
self._meta.update_subfile_raw(database = self._database)
if self._mini_create_not_done :
self._mini_create_not_done = False
self._meta.update_subfile_mini(database = self._database)
data_save = False
mini_save = False
if len(self._recording_file_dict) > 0:
for ch in self._data_db.keys():
if self._time_now - self._time[ch] > 5000000:
if self._recording_file_dict[ch]._status:
_data = ' '.join(self._data_db[ch])
write_sz = self._recording_file_dict[ch].write(_data, self._channel_list)
@@ -1345,9 +1417,11 @@ class RecordingFileWriter:
if len(self._data_mini_ch[ch]['1000']['mean']) >= 10:
mini_save = True
for scale in self._mini_scale_list:
str_mean = [str(int) for int in self._data_mini_ch[ch][str(scale)]['mean']]
data_mean = str(self._data_mini_ch[ch][str(scale)]['start_time']) + ' ' + ' '.join(str_mean) + '"***"'
self._mini_save[str(scale)]['id'][ch] = self._recording_mini_dict[str(scale)][ch]._id_db
self._mini_save[str(scale)]['start_time'][ch] = str(self._data_mini_ch[ch][str(scale)]['start_time'])
self._mini_save[str(scale)]['data_mean'][ch] = self._data_mini_ch[ch][str(scale)]['mean'].copy()
self._mini_save[str(scale)]['data_mean'][ch] = data_mean
# self._mini_save[str(scale)]['data_random'][ch] = self._data_mini_ch[ch][str(scale)]['random'].copy()
# self._mini_save[str(scale)]['data_bar'][ch] = self._data_mini_ch[ch][str(scale)]['bar'].copy()
self._recording_mini_dict[str(scale)][ch].write(self._mini_save[str(scale)]['data_mean'][ch])
@@ -1360,19 +1434,20 @@ class RecordingFileWriter:
if self._database is not None:
recording_input = ['data_raw_recording_new', copy(self._raw_save['id']), copy(self._channel_list), copy(self._raw_save['data']), copy(self._raw_save['end_time']), copy(self._raw_save['size'])]
self._database.put_queue(recording_input)
self._meta.update_subfile(database = self._database)
self._meta.update_subfile_time_size(database = self._database)
if mini_save is True:
if self._database is not None:
for scale in self._mini_scale_list:
self._database.put_queue(['data_mini_recording', self._mini_save[str(scale)]['id'], self._channel_list, self._mini_save[str(scale)]['start_time'], self._mini_save[str(scale)]['data_mean']])
self._meta.update_subfile(database = self._database)
self._database.put_queue(['data_mini_recording_new', copy(self._mini_save[str(scale)]['id']), self._channel_list, copy(self._mini_save[str(scale)]['data_mean'])])
self._meta.update_subfile_time_size(database = self._database)
del data
return None
def _switch_recording_file(self):
self._raw_create_not_done = True
if len(self._recording_file_dict) > 0:
for ch in self._recording_file_dict.keys():
self._id_db_save[ch] = self._recording_file_dict[ch]._id_db
@@ -1393,13 +1468,14 @@ class RecordingFileWriter:
self._database.put_queue(['data_raw_create', raw_data_dict, self._channel_list, self._device_id])
self._meta._last_time = self._time_now
self._meta.update_subfile(database = self._database)
# self._meta.update_subfile(database = self._database)
self._splitting_size = 0
return None
def _switch_recording_mini(self, scale):
self._mini_create_not_done = True
if len(self._recording_mini_dict[str(scale)]) > 0:
for ch in self._recording_mini_dict[str(scale)].keys():
@@ -1419,7 +1495,7 @@ class RecordingFileWriter:
self._database.put_queue(['data_mini_create', mini_data_dict, self._channel_list, scale, self._device_id])
self._meta._last_time = self._time_now
self._meta.update_subfile(database = self._database)
# self._meta.update_subfile(database = self._database)
return None
def update_meta_id(self, _id):
@@ -1428,10 +1504,16 @@ class RecordingFileWriter:
def update_raw_dict_id(self, _channel, _id):
self._recording_file_dict[int(_channel)]._id_db = _id
self._database.put_queue(['data_meta_update_channel_raw_id', self._meta._id_db, _channel, _id])
if int(_channel) not in self._meta._recording_sub_file:
self._meta._recording_sub_file[int(_channel)] = []
self._meta._recording_sub_file[int(_channel)].append(_id)
return None
def update_mini_dict_id(self, _scale, _channel, _id):
self._recording_mini_dict[str(_scale)][int(_channel)]._id_db = _id
self._database.put_queue(['data_meta_update_channel_mini_id', self._meta._id_db, _channel, _scale, _id])
if int(_channel) not in self._meta._recording_sub_mini:
self._meta._recording_sub_mini[int(_channel)] = {}
if int(_scale) not in self._meta._recording_sub_mini[int(_channel)]:
self._meta._recording_sub_mini[int(_channel)][int(_scale)] = []
self._meta._recording_sub_mini[int(_channel)][int(_scale)].append(_id)
return None
+3 -17
View File
@@ -9,23 +9,9 @@ _jwt = None
def controller_device_id() -> str:
### device ID
return 'b8:27:eb:52:ea:97'
ret = None
try:
with open('/etc/BPS/device_id') as f:
for line in f:
if line.startswith('#'):
continue
ret = line.strip()
break
except FileNotFoundError:
pass
if ret is None or len(ret) == 0:
return str(uuid.uuid1())
else:
return ret
f = open("/sys/class/net/eth0/address", "r")
mac = f.read()[:-1]
return mac
def get_api_jwt() -> str:
global _jwt
+41 -27
View File
@@ -104,18 +104,9 @@ class DataServer(SocketServer, DataAPI):
self.log_verbose,
'MQTT-data-server')
# api socket
self._queue_spi = queue.Queue()
# self._queue_spi_dict = {}
self._queue_ds_dict = {}
self._queue_db = Queue()
self._queue_db_error = Queue()
# self._queue_ds = Queue()
# if len(self._queue_spi_dict) == 0:
# self._queue_spi_dict[4] = Queue()
# self._queue_spi_dict[5] = Queue()
# self._queue_spi_dict[6] = Queue()
# self._queue_spi_dict[7] = Queue()
self._queue_msg = Queue()
if len(self._queue_ds_dict) == 0:
self._queue_ds_dict[4] = Queue()
@@ -123,9 +114,12 @@ class DataServer(SocketServer, DataAPI):
self._queue_ds_dict[6] = Queue()
self._queue_ds_dict[7] = Queue()
self.database_process = DataBaseProcess('data_server', self.log_verbose, self._queue_db, self._queue_ds_dict, self._queue_db_error)
self.database_process = DataBaseProcess('data_server', self.log_verbose, self._queue_db, self._queue_ds_dict, self._queue_msg)
self.database_process.start()
# self._no_data_total_duration = 0
# self._recv_memory_cost_time = 0
@logging_info
def setup(self):
self._available_channel.clear()
@@ -146,7 +140,6 @@ class DataServer(SocketServer, DataAPI):
try:
self._stop_runtime()
self.stop_sync()
self._queue_spi.queue.clear()
finally:
self._configurations.clear()
@@ -185,9 +178,6 @@ class DataServer(SocketServer, DataAPI):
self._configurations.clear()
with CacheClient() as client:
client.clear()
# reopen spi, which has close at close()
if spi_mode is None:
spi_mode = self._spi_mode
@@ -417,7 +407,7 @@ class DataServer(SocketServer, DataAPI):
c.join()
self._spi.set_wait_flag(c._device, True)
data_runtime_process = RecordingProcess('data_server', self.log_verbose, device_id,
Queue(), self._queue_ds_dict[int(device_id)], meta_file,
Queue(), self._queue_ds_dict[int(device_id)], self._queue_msg, meta_file,
json_stringify(_device), _parameter, _parent, _recording_file_name,
data_format, self.socket_mqtt_ip, self.socket_mqtt_port,
database = self.database_process )
@@ -618,8 +608,18 @@ class DataServer(SocketServer, DataAPI):
return
def open_cache_client(self):
self._cache_client = None
return
self.log_verbose('open cache client')
try:
client = CacheClient()
client.open_socket()
except RuntimeError as e:
self.log_warn(e)
else:
client.clear()
self._cache_client = client
def _ensure_cache_client(self) -> Optional[CacheClient]:
if self._cache_client is None:
@@ -669,20 +669,34 @@ class DataServer(SocketServer, DataAPI):
print("q size= ", self._configurations[device]._queue_rec.qsize())
self._configurations[device].put_rec_queue(data)
# self._no_data_total_duration = 0
ret = True
# else:
# time_diff = time() - self._recv_memory_cost_time
# self._no_data_total_duration += time_diff
# if self._no_data_total_duration > 1:
# print('data is None', device, datetime.now(), self._no_data_total_duration)
# print("sync.get_pin_mem_req() != sync.get_pin_ram_sel()")
# print("no~", sync.select, sync.get_pin_mem_req(), sync.get_pin_ram_sel(), datetime.now())
# print()
# self._recv_memory_cost_time = time()
return ret
def rec_update(self) -> bool:
device_id = self._queue_db_error.get()
self.mqtt_thread.broadcast_command('runtime error: ' + str(device_id))
content = {
'header': 'device_instruction/0',
'device': device_id,
'instruction': 'interrupt'
}
self.mqtt_thread.publish('device_instruction', json_stringify(content), inter = True)
ds_db_msg = self._queue_msg.get()
with CacheClient() as client:
if isinstance(ds_db_msg, List):
if ds_db_msg[0] == 'ds':
client.send_command('device_instruction', ds_db_msg[1], ds_db_msg[2])
else:
self.mqtt_thread.broadcast_command('runtime error: ' + str(ds_db_msg))
content = {
'header': 'device_instruction/0',
'device': ds_db_msg,
'instruction': 'interrupt'
}
self.mqtt_thread.publish('device_instruction', json_stringify(content), inter = True)
return True
+73 -27
View File
@@ -23,6 +23,7 @@ import biopro.server._identify
from time import time
from concurrent.futures import ThreadPoolExecutor, as_completed
from biopro.util.logger import calculate_time
_RUNTIME_COMPILE = False
@@ -68,7 +69,9 @@ class DataBaseProcess(Process):
self._data_raw_create_sql_str = None
self._data_raw_update_sql_str = None
self._data_raw_recording_sql_str = 'UPDATE "public"."%s_recording_data_raws" SET data = concat(data, %s), end_time=%s, size=%s where id = %s'
self._data_raw_recording_sql_str = 'UPDATE "public"."%s_recording_data_raws" SET data = concat(data, %s) where id = %s'
self._new_data_raw_recording_sql_str = 'UPDATE "public"."%s_recording_data_raws" SET data = concat(data, %s), end_time=%s, size=%s where id = %s'
self._data_mini_recording_sql_str = 'UPDATE "public"."%s_recording_data_minis" SET data_mean = concat(data_mean, %s) where id = %s'
@property
def db_host(self) -> str:
@@ -85,13 +88,6 @@ class DataBaseProcess(Process):
@property
def api_token(self) -> str:
return self._api_token
def calculate_time(func):
def warp(*args, **kwargs):
now = time()
func(*args, **kwargs)
print(func.__name__,'cost:', time() - now)
return warp
def run(self) -> None:
self.setup()
@@ -165,7 +161,7 @@ class DataBaseProcess(Process):
getattr(self, do)(*args)
return None
# @calculate_time
# @calculate_time(1)
def data_meta_write(self, _data: JSON_OBJECT, device_id, _uuid = None, _id = None):
if _id > 0:
sql_str = 'UPDATE "public"."recording_data_metas" SET '
@@ -268,6 +264,7 @@ class DataBaseProcess(Process):
# _conn.close()
return None
# @calculate_time()
def data_meta_get(self, _path = None, _id = None):
if _id is not None:
sql_str = 'SELECT * FROM "public"."recording_data_metas" WHERE id = %s'
@@ -302,6 +299,7 @@ class DataBaseProcess(Process):
# _conn.close()
return None
# @calculate_time()
def data_meta_id_get_by_path(self, _path = None):
ret = None
if _path is not None:
@@ -323,7 +321,7 @@ class DataBaseProcess(Process):
# _conn.close()
return None
# @calculate_time
# @calculate_time()
def data_raw_create(self, _data_dict, _channel_list, device_id):
if self._data_raw_create_sql_str == None:
sql_str_list = []
@@ -345,7 +343,7 @@ class DataBaseProcess(Process):
return True
# @calculate_time
# @calculate_time()
def data_raw_update(self, _id, _channel, _data: JSON_OBJECT):
sql_str = 'UPDATE "public"."' + str(_channel) + '_recording_data_raws" SET '
sql_set = []
@@ -365,7 +363,7 @@ class DataBaseProcess(Process):
sql_cursor.close()
return None
# @calculate_time
# @calculate_time()
def data_raw_recording(self, _id_dict, _channel_list, _data_dict, _id_db_save):
try:
with self._psql_conn as conn:
@@ -379,19 +377,20 @@ class DataBaseProcess(Process):
print('recording error', e)
return None
# @calculate_time
# @calculate_time(1)
def data_raw_recording_new(self, _id_dict, _channel_list, _data_dict, _end_time_dict, _size_dict):
try:
para_list = []
for _channel in _channel_list:
para_list.append([_channel, _data_dict[_channel] + '"***"', _end_time_dict[_channel], _size_dict[_channel], _id_dict[_channel]])
with self._psql_conn as conn:
with conn.cursor() as sql_cursor:
para_list = []
for _channel in _channel_list:
para_list.append([_channel, _data_dict[_channel] + '"***"', _end_time_dict[_channel], _size_dict[_channel], _id_dict[_channel]])
execute_batch(sql_cursor, self._data_raw_recording_sql_str, para_list)
execute_batch(sql_cursor, self._new_data_raw_recording_sql_str, para_list)
except psycopg2.Error as e:
print('recording error', e)
return None
# @calculate_time()
def data_mini_create(self, _data_dict, _channel_list, _scale, device_id):
for _channel in _channel_list:
sql_str = 'INSERT INTO "public"."' + str(_channel) + '_recording_data_minis" ('
@@ -425,6 +424,7 @@ class DataBaseProcess(Process):
# _conn.close()
return True
# @calculate_time()
def data_mini_recording(self, _id_dict, _channel_list, _start_time_dict, _data_mean_dict):
with self._psql_conn as conn:
with self._psql_conn.cursor() as sql_cursor:
@@ -444,9 +444,23 @@ class DataBaseProcess(Process):
for _channel in _channel_list:
del _start_time_dict[_channel], _data_mean_dict[_channel]
gc.collect()
return True
# @calculate_time(1)
def data_mini_recording_new(self, _id_dict, _channel_list, _data_mean_dict):
try:
para_list = []
for _channel in _channel_list:
para_list.append([_channel, _data_mean_dict[_channel], _id_dict[_channel]])
with self._psql_conn as conn:
with conn.cursor() as sql_cursor:
execute_batch(sql_cursor, self._data_mini_recording_sql_str, para_list)
except psycopg2.Error as e:
print('recording error', e)
return None
# @calculate_time()
def data_mini_update(self, _id, _channel, _data: JSON_OBJECT):
sql_str = 'UPDATE "public"."' + str(_channel) + '_recording_data_minis" SET '
sql_set = []
@@ -481,26 +495,58 @@ class DataBaseProcess(Process):
print('e', e)
return True
# @calculate_time()
def data_meta_update_channel_raw_id(self, meta_id, channel, raw_id):
try:
with self._psql_conn as conn:
with conn.cursor() as sql_cursor:
sql_str = "UPDATE recording_data_metas SET raw_data = jsonb_insert(raw_data, '{%s, -1}', '%s', TRUE) WHERE id = %s;"
sql_set = [channel, raw_id, 571]
sql_cursor.execute(sql_str, sql_set)
except psycopg2.Error as e:
print('e', e)
return True
# @calculate_time()
def data_meta_update_all_channel_raw_id(self, meta_id:str, raw_id_dict:dict):
try:
sql_str = "UPDATE recording_data_metas SET raw_data = jsonb_insert(raw_data, '{%s, -1}', '%s', TRUE) WHERE id = %s;"
sql_set = []
for ch in raw_id_dict.keys():
sql_set.append([ch, raw_id_dict[ch], meta_id])
with self._psql_conn as conn:
with conn.cursor() as sql_cursor:
sql_str = "UPDATE recording_data_metas SET raw_data = jsonb_insert(raw_data, '{%s, -1}', '%s', TRUE) WHERE id = %s;"
sql_set = [channel, raw_id, meta_id]
execute_batch(sql_cursor, sql_str, sql_set)
except psycopg2.Error as e:
print('e', e)
return True
def data_meta_update_channel_mini_id(self, meta_id, channel, scale, mini_id):
try:
with self._psql_conn as conn:
with conn.cursor() as sql_cursor:
sql_str = "UPDATE recording_data_metas SET mini_data = jsonb_insert(mini_data, '{%s, %s, -1}', '%s', TRUE) WHERE id = %s;"
sql_set = [channel, scale, mini_id, meta_id]
sql_cursor.execute(sql_str, sql_set)
except psycopg2.Error as e:
print('e', e)
return True
def data_meta_update_channel_mini_id(self, meta_id, channel, scale, raw_id):
# @calculate_time()
def data_meta_update_all_channel_mini_id(self, meta_id, mini_id_dict):
try:
sql_str = "UPDATE recording_data_metas SET mini_data = jsonb_insert(mini_data, '{%s, %s, -1}', '%s', TRUE) WHERE id = %s;"
sql_set = []
for ch in mini_id_dict.keys():
for scale in mini_id_dict[ch].keys():
sql_set.append([ch, scale, mini_id_dict[ch][scale], meta_id])
with self._psql_conn as conn:
with conn.cursor() as sql_cursor:
sql_str = "UPDATE recording_data_metas SET mini_data = jsonb_insert(mini_data, '{%s, %s, -1}', '%s', TRUE) WHERE id = %s;"
sql_set = [channel, scale, raw_id, meta_id]
sql_cursor.execute(sql_str, sql_set)
with conn.cursor() as sql_cursor:
execute_batch(sql_cursor, sql_str, sql_set)
except psycopg2.Error as e:
print('e', e)
+20 -533
View File
@@ -43,7 +43,7 @@ def _fc(c):
return c % len(FORE_COLOR) + FORE_COLOR[0]
class ControlServer(SocketServer, ControlServerAPI, CacheAPI, CacheNotifyHandler):
class ControlServer(SocketServer, ControlServerAPI):
"""Data Server"""
def __init__(self, *options):
@@ -60,7 +60,7 @@ class ControlServer(SocketServer, ControlServerAPI, CacheAPI, CacheNotifyHandler
:param options: server options
"""
super().__init__((ControlAPI, CacheAPI), 'Controller', self.ADDR,
super().__init__((ControlAPI), 'Controller', self.ADDR,
socket_listen=10,
thread_pool_count=16)
@@ -87,21 +87,6 @@ class ControlServer(SocketServer, ControlServerAPI, CacheAPI, CacheNotifyHandler
data_options.repository = device_option.repository
self.data_server = DataServerProcess(data_options)
# experimental protocol
# exp_options = ExpManagerOption.get_options(*options)
# self.exp_manager = ExpManager(self, exp_options)
# cache
self.cache_manager = CacheManager(self,
c.cache_server_cache_size,
# time unit from sec to ms
c.cache_server_cache_time * 1000)
# websocket server ###
### self.websocket_thread = WebsocketThread(self,
### c.websocket_address,
### c.websocket_port)
# remote server / MQTT
self.mqtt_thread = None
if not c.flag_disable_remote_server:
@@ -112,17 +97,6 @@ class ControlServer(SocketServer, ControlServerAPI, CacheAPI, CacheNotifyHandler
self.log_verbose,
'MQTT-control-server')
# routine trash thread
self._routine_trash_thread = None
# if not c.flag_disable_routine_trash:
# self._routine_trash_thread = RoutineTrashThread(self._file_manager,
# c.trash_routine_time,
# c.trash_expire_time,
# c.trash_remove_expire_time)
# routine data routine
self._data_stream_thread = None
# LED control
self.led_thread = LEDControlThread(disable=c.flag_disable_led_control,
led_count=c.led_count,
@@ -131,6 +105,8 @@ class ControlServer(SocketServer, ControlServerAPI, CacheAPI, CacheNotifyHandler
# device detect
self._detect_device_thread = RoutineConnectDeviceThread(self._device_manager, self.mqtt_thread, self.led_thread, 3)
self._project_manager = ProjectManager(self.device_manager, self.mqtt_thread)
if not c.flag_disable_led_control:
self.led_thread.set_state(LED.COLOR_RED)
@@ -144,8 +120,6 @@ class ControlServer(SocketServer, ControlServerAPI, CacheAPI, CacheNotifyHandler
self._session = {}
self._user_session_expire = c.user_session_expire_days * 24 * 60 * 60 # sec
# temp use variable
# indicate those device together
self._device_instruction_group = None
@@ -155,9 +129,6 @@ class ControlServer(SocketServer, ControlServerAPI, CacheAPI, CacheNotifyHandler
# _api request build
self._api = ApiRequests()
self._project_manager = ProjectManager(self.device_manager, self.mqtt_thread)
# init method
def _init_file_manager(self, option: ControlServerOptions) -> FileManager:
@@ -249,32 +220,16 @@ class ControlServer(SocketServer, ControlServerAPI, CacheAPI, CacheNotifyHandler
# device manager reset
self.submit_thread(self._setup_device)
# self.submit_thread(self._setup_controller)
self._setup_controller()
# self.run_thread(self._detect_device_thread)
# experimental protocol manager
# if not self._controller_options.flag_disable_exp_manager:
# self.exp_manager.setup()
# self.exp_manager.reload_protocol()
# websocket server
### self.log_verbose('setup websocket server', pc(self.websocket_thread.websocket_address, YELLOW))
# websocket server thread run ###
### self.run_thread(self.websocket_thread)
# broadcast greeting
self.broadcast_command(DataMessage.greeting())
# routine trashing
if self._routine_trash_thread is not None:
self.run_thread(self._routine_trash_thread)
# remote server
if self.mqtt_thread is not None:
self.mqtt_thread.start()
# self.run_thread(self.mqtt_thread)
def _setup_file_manager(self):
self.log_verbose('setup file manager')
@@ -449,90 +404,14 @@ class ControlServer(SocketServer, ControlServerAPI, CacheAPI, CacheNotifyHandler
self.broadcast_command('shutdown')
self.data_server.stop_process(external_interrupt)
self._stop_data_stream_thread()
self.device_manager.shutdown()
self.file_manager.shutdown(umount_external=True)
self.cache_manager.shutdown()
### self.websocket_thread.close()
self.led_thread.close()
if self.mqtt_thread is not None:
self.mqtt_thread.shutdown()
# self.mqtt_thread.close()
# user session
def new_user_session(self) -> str:
session = UserSession(None)
self._session[session.user_session] = session
self.log_info('new user session', session.user_session)
return session.user_session
@logging_info
def del_user_session(self, user_session: Optional[str] = None):
if user_session is None:
self._session.clear()
else:
try:
del self._session[user_session]
except KeyError:
pass
def get_user_session(self,
user_session: Optional[str],
new_session = False,
allow_empty = False) -> Optional[UserSession]:
"""
:param user_session: user session ID
:param new_session: new a user session if *user_session* not found.
:param allow_empty: consider empty *user_session* None
:return: user session
"""
if user_session is None:
session = None
elif len(user_session) == 0:
if allow_empty:
session = None
else:
raise None
else:
session = self._session.get(user_session, None)
if session is not None:
session.time()
elif new_session:
session = UserSession(user_session)
self._session[session.user_session] = session
self.log_info('new user session', session.user_session)
return session
def expire_user_session(self):
current_time = time()
for user_session in list(self._session.keys()):
session = self._session[user_session]
if session.time(update=False) - current_time > self._user_session_expire:
del self._session[user_session]
# general post action
def _post_device_list(self) -> List[CompletedDevice]:
"""general post action for getting list of connected devices.
@@ -869,12 +748,6 @@ class ControlServer(SocketServer, ControlServerAPI, CacheAPI, CacheNotifyHandler
def get_device_info(self, device: int) -> Optional[CompletedDevice]:
return super().get_device_info(device)
# @logging_info
# def device_call(self, device: int) -> bool:
# self.device_manager.call_device(device)
#
# return True
@logging_info
def device_instruction_all(self, device: int) -> List[str]:
return self.device_manager.list_device_instruction(device)
@@ -1048,8 +921,6 @@ class ControlServer(SocketServer, ControlServerAPI, CacheAPI, CacheNotifyHandler
@logging_info
def device_parameter(self, device: int, parameter: str, content: Optional[str] = None) -> Any:
# if content is not None:
# self.broadcast_command('parameter:' + str(device) + '/' + parameter + '/' + content)
return self.device_manager.handle_device_parameter_command(device, parameter, content)
@logging_info
@@ -1141,255 +1012,22 @@ class ControlServer(SocketServer, ControlServerAPI, CacheAPI, CacheNotifyHandler
def device_history_delete(self, user: str, device: Optional[int]) -> bool:
raise NotImplementedError()
# server provide method implement : file manager functions
@logging_info
def file_clean_storage(self) -> bool:
return super().file_clean_storage()
@logging_info
def file_index(self) -> RootInfo:
return super().file_index()
@logging_info
def file_list(self, path: str) -> DirectoryInfo:
return super().file_list(path)
@logging_info
def file_list_range(self, path: str, start: int, end: int) -> DirectoryInfo:
return super().file_list_range(path, start, end)
@logging_info
def file_rename(self, path: str, content: str) -> FileInfo:
return super().file_rename(path, content)
@logging_info
def file_mkdir(self, path: str) -> DirectoryInfo:
return super().file_mkdir(path)
@logging_info
def file_delete(self, path: str) -> bool:
return super().file_delete(path)
@logging_info
def file_info(self, path: str) -> FileInfo:
return super().file_info(path)
@logging_info
def file_meta_info(self, path: str) -> RecordingMetaFile:
return super().file_meta_info(path)
@logging_info
def file_export_info(self, path: str) -> Dict[str, Optional[Path]]:
return super().file_export_info(path)
@logging_info
def file_export_functions(self) -> Tuple[ExternalExportFunction, ...]:
return super().file_export_functions()
@logging_info
def file_export_parameters(self, ftype: str) -> Tuple[ExternalParameter, ...]:
return super().file_export_parameters(ftype)
@logging_info
def file_export(self, ftype: str, path: str, **options: str) -> str:
return super().file_export(ftype, path, **options)
@logging_info
def file_segment(self, method: str, path: str) -> Union[None, bool, FileSegment]:
return super().file_segment(method, path)
@logging_info
def file_segment_get(self, segment: int, path: str):
return super().file_segment_get(segment, path)
@logging_info
def file_handle_open(self, content: str) -> Optional[FileHandle]:
info = self.file_manager.info(content)
meta = info.meta_file
if meta is None:
raise RuntimeError('not a meta file')
handle = self.cache_manager.file_open(meta)
return self.file_handle_info(handle)
@logging_info
def file_handle_info(self, handle: int) -> Optional[FileHandle]:
info = self.cache_manager.file_info(handle)
if info is None:
return None
# modify file path
info.shadow_path(self.file_manager.shadow_path(Path(info.filepath)))
return info
@logging_info
def file_handle_close(self, handle: int):
self.cache_manager.file_close(handle)
@logging_info
def user_all(self) -> List[str]:
raise super().user_all()
@logging_info
def user_info(self, user: str, *, user_session: Optional[str] = None) -> Optional[AbstractUserSetting]:
return super().user_info(user, user_session=user_session)
@logging_info
def user_login(self, user: str, content: str, *,
user_session = None) -> ComplexResponse[AbstractUserSetting]:
raise super().user_login(user, content, user_session=user_session)
@logging_info
def user_logout(self, user_session: Optional[str] = None) -> None:
super().user_logout(user_session)
@logging_info
def user_add(self, user: str, content: str, *,
user_session = None) -> ComplexResponse[AbstractUserSetting]:
return super().user_add(user, content, user_session=user_session)
@logging_info
def user_del(self, user: str, content: str) -> bool:
return super().user_del(user, content)
@logging_info
def user_sync(self, user_session: Optional[str]) -> bool:
return super().user_sync(user_session)
@logging_info
def user_configuration_list(self, user_session: Optional[str], **options: str) -> List[str]:
return super().user_configuration_list(user_session, **options)
@logging_info
def user_configuration_get(self, name: str, user_session: Optional[str] = None) -> Optional[RecordingMetaFile]:
return super().user_configuration_get(name, user_session)
@logging_info
def user_configuration_add(self, name: str, content: str, user_session: Optional[str] = None) -> bool:
return super().user_configuration_add(name, content, user_session)
@logging_info
def user_configuration_del(self, name: str, user_session: Optional[str] = None) -> bool:
return super().user_configuration_del(name, user_session)
@logging_info
def user_configuration_clone(self, name: str, content: str, user_session: Optional[str] = None) -> bool:
return super().user_configuration_clone(name, content, user_session)
@logging_info
def user_setup_list(self, user_session: Optional[str] = None) -> List[str]:
return super().user_setup_list(user_session)
@logging_info
def user_setup_info(self, name: str, user_session: Optional[str] = None) -> Optional[UserSetup]:
return super().user_setup_info(name, user_session)
@logging_info
def user_setup_new(self, name: str, user_session: Optional[str] = None) -> Optional[UserSetup]:
return super().user_setup_new(name, user_session)
@logging_info
def user_setup_del(self, name: str, user_session: Optional[str] = None) -> bool:
return super().user_setup_del(name, user_session)
@logging_info
def user_alias_list(self, user_session: Optional[str] = None) -> List[UserDeviceAlias]:
return super().user_alias_list(user_session)
@logging_info
def user_alias_info(self, name: str, user_session: Optional[str] = None) -> Optional[UserDeviceAlias]:
return super().user_alias_info(name, user_session)
@logging_info
def user_alias_edit(self, name: str, content: str, user_session: Optional[str] = None) -> Optional[UserDeviceAlias]:
return super().user_alias_edit(name, content, user_session)
# @logging_info
# def exp_pro_list(self) -> List[ExpProtocolInfo]:
# return self.exp_manager.list_available_protocol()
@logging_info
def exp_pro_create(self, content: str, *,
user_session = None) -> int:
user_name = self._session_user_name(user_session)
if user_name is None:
raise RuntimeError('require user login')
self.file_manager.user_setting.user_info(user_name)
# p = self.exp_manager.create_protocol(content, user_name)
return
@logging_info
def run_project(self, project) -> bool:
project = self._project_manager.create(project)
self._project_manager.run(project)
return False
if project is not None:
project = self._project_manager.create(project)
self._project_manager.run_project(project)
return project.as_json()
@logging_info
def stop_project(self, project) -> bool:
if project is not None:
self._project_manager.stop_project(project)
return True
# @logging_info
# def exp_pro_options(self, protocol: int) -> List[ExpOption]:
# return self.exp_manager.protocol_options(protocol)
# @logging_info
# def exp_pro_option(self, protocol: int, option: str, content: Optional[str] = None) -> bool:
# if content == '':
# # option reset
# if option == '':
# # reset all options
# self.exp_manager.protocol_reset(protocol)
# else:
# self.exp_manager.protocol_reset(protocol, *option.split(','))
# else:
# # option get/set
# self.exp_manager.protocol_option(protocol, option, content)
# return True
# @logging_info
# def exp_pro_status_all(self) -> List[AbstractExpProtocolState]:
# ret = []
# for p in self.exp_manager.list_protocol():
# s = self.exp_manager.protocol_status(p)
# if s is not None:
# ret.append(s)
# else:
# ret.append(AbstractExpProtocolState(p))
# return ret
# @logging_info
# def exp_pro_status(self, protocol: int) -> Optional[ExpProtocolState]:
# return self.exp_manager.protocol_status(protocol)
# @logging_info
# def exp_pro_command(self, protocol: int, content: str) -> bool:
# if content == 'ensure':
# return self.exp_manager.ensure_protocol(protocol)
# elif content == 'start':
# self.exp_manager.submit_protocol(protocol)
# elif content == 'interrupt':
# self.exp_manager.interrupt_protocol(protocol)
# elif content == 'suspend':
# self.exp_manager.suspend_protocol(protocol)
# elif content == 'resume':
# self.exp_manager.resume_protocol(protocol)
# else:
# raise RuntimeError('unknown protocol command : ' + content)
# return True
@logging_info
def get_running_project(self) -> bool:
return self._project_manager.get()
@logging_info
def device_internal_command(self, device: int, oper: str, value: Any) -> bool:
@@ -1406,8 +1044,6 @@ class ControlServer(SocketServer, ControlServerAPI, CacheAPI, CacheNotifyHandler
return True
elif oper == InternalInstruction.PREDEFINED_DISABLE_CACHE:
if device is not None:
self._device_set_disable_cache(device, value)
return True
else:
@@ -1459,10 +1095,6 @@ class ControlServer(SocketServer, ControlServerAPI, CacheAPI, CacheNotifyHandler
def _device_set_sync(self, device: CompletedDevice, sync: bool):
if sync:
self.log_verbose('start sync', device.device_id)
self.cache_manager.drop_data(device)
client = self.data_server.client()
if client is not None:
info = self.file_manager.use(device)
@@ -1473,22 +1105,14 @@ class ControlServer(SocketServer, ControlServerAPI, CacheAPI, CacheNotifyHandler
filename = format_filename(self.TEMP_FILENAME_EXPR, device)
info = self.file_manager.save(device, filename)
client.update_device_configuration(device, info.meta_file, RawDataDecoder())
print('***456')
if self._device_instruction_group is None:
client.start_sync(device)
if self._device_instruction_group is None:
# broadcast
self.broadcast_command(DataMessage.start(device.device_id))
device.status = 1
else:
self.log_verbose('stop sync', device.device_id)
if self._device_instruction_group is None:
start_sync = False
client = self.data_server.client()
if client is not None:
with client:
@@ -1501,12 +1125,10 @@ class ControlServer(SocketServer, ControlServerAPI, CacheAPI, CacheNotifyHandler
if start_sync:
# unset file info, but keep file path cache
self.file_manager.unset(device.device_id)
device.status = 0
# broadcast
self.broadcast_command(DataMessage.stop(device.device_id))
# server provide method implement : websocket broadcast functions
@logging_info
def broadcast_command(self, command: str, device: Optional[int] = None):
self.log_verbose(pc('broadcast_command', GREEN),
@@ -1516,96 +1138,6 @@ class ControlServer(SocketServer, ControlServerAPI, CacheAPI, CacheNotifyHandler
if self.mqtt_thread is not None:
self.mqtt_thread.broadcast_command(command, device=device)
# data stream thread
def data_request_stream(self, start: bool, handler: DataMessageHandler):
if start:
t = self._start_data_stream_thread()
t.add_handle(handler)
else:
t = self._data_stream_thread
if t is not None:
t.del_handle(handler)
if not t.has_handle():
self._stop_data_stream_thread()
def _start_data_stream_thread(self) -> DataStreamThread:
t = self._data_stream_thread
if self._data_stream_thread is None:
self._data_stream_thread = t = self.run_thread(DataStreamThread(self))
return t
def _stop_data_stream_thread(self):
t = self._data_stream_thread
self._data_stream_thread = None
if t is not None:
t.close()
# noinspection PyShadowingNames
@logging_info
def websocket_command(self, command: str) -> Union[None, str, bytes, List[str]]:
if command == DataMessage.GREETING:
return DataMessage.greeting()
elif command == DataMessage.SYNC:
return self._websocket_command_sync(map(lambda d: d.device_id, self._post_device_list()))
elif command.startswith(DataMessage.SYNC):
_, device_list = part_suffix(command, ':')
if ',' in device_list:
return self._websocket_command_sync(map(int, device_list.split(',')))
else:
return self._websocket_command_sync([int(device_list)])
elif command.startswith(DataMessage.BROADCAST):
_, message = part_suffix(command, ':')
if message is not None:
self.broadcast_command(DataMessage.broadcast(message))
return None
# noinspection PyShadowingNames
def _websocket_command_sync(self, device_list: Iterable[int]) -> Union[str, List[str]]:
client = self.data_server.client()
if client is None:
return DataMessage.sync(False)
else:
result_start = []
result_stop = []
with client:
for device in device_list:
if client.is_sync(device):
result_start.append(device)
else:
result_stop.append(device)
return [DataMessage.start(result_start), DataMessage.stop(result_stop)]
# noinspection PyShadowingNames
def websocket_data_request(self, handler: DataMessageHandler, command: Optional[str]):
try:
self._handle_command_response(handler, command)
except (ValueError, IndexError) as e:
self.log_warn(e)
handler.on_error(DataMessage.warn(str(e)))
except BaseException as e:
self.log_warn(e)
handler.on_error(DataMessage.error(str(e)))
# noinspection PyShadowingNames
def _handle_command_response(self, handler: DataMessageHandler, command: Optional[str]):
"""
@@ -1627,29 +1159,7 @@ class ControlServer(SocketServer, ControlServerAPI, CacheAPI, CacheNotifyHandler
else:
self.cache_manager.get_data_message(handler, response)
# server provide method implement : cache API
@logging_verbose
def clear(self):
self.cache_manager.clear()
def push_data(self, data: Union[bytes, List[bytes]]):
self.cache_manager.push_data(data)
@logging_verbose
def drop_data(self, *device: int):
self.cache_manager.drop_data(*device)
def stop_data(self, device: int):
self.cache_manager.stop_data(device)
# noinspection PyShadowingNames
@logging_verbose
def notify_data(self, device: int, message: str):
self.broadcast_command(message, device=device)
# led function
def get_led_state(self) -> str:
return self.led_thread.state
@@ -1657,7 +1167,6 @@ class ControlServer(SocketServer, ControlServerAPI, CacheAPI, CacheNotifyHandler
self.led_thread.set_state(state)
# hardware send test
if not _RUNTIME_COMPILE:
def _hardware_send_test(self, options: Dict[str, str]):
@@ -1700,30 +1209,9 @@ class ControlServer(SocketServer, ControlServerAPI, CacheAPI, CacheNotifyHandler
'export_root': str(self.file_manager.export_root)
}
if section is None or 'cache' in section:
ret['cache'] = {
'cache_size': self.cache_manager.cache_size,
'cache_time': self.cache_manager.cache_time,
}
# if section is None or 'websocket' in section:
# ret['websocket'] = {
# 'address': self.websocket_thread.websocket_address,
# 'port': self.websocket_thread.websocket_port,
# }
if section is None or 'mqtt' in section:
ret['mqtt'] = None if self.mqtt_thread is None else self.mqtt_thread.hardware_test()
if section is None or 'trash' in section:
trash_manager = self._routine_trash_thread
ret['trash'] = None if trash_manager is None else {
'routine_interval': trash_manager.routine_interval,
'trash_expire': trash_manager.trash_expire,
'remove_expire': trash_manager.remove_expire,
}
if section is None or 'led' in section:
ret['led'] = {
'state_all': list(LED.COLOR.keys()),
@@ -1852,7 +1340,6 @@ class ControlServer(SocketServer, ControlServerAPI, CacheAPI, CacheNotifyHandler
# TODO write options files
return False
class _RandomCrashThread(ServerThread):
def __init__(self):
super().__init__('Crash')
+91 -73
View File
@@ -41,7 +41,7 @@ class RecordingProcess(Process):
connect to DB and send sql command
"""
def __init__(self, client_id: str, log_verbose, device_id, queue_rec, queue_ds, meta_path,
def __init__(self, client_id: str, log_verbose, device_id, queue_rec, queue_ds, queue_msg, meta_path,
device_info, parameter, parent, recording_file_name, data_format,
mqtt_ip, mqtt_port,
name = 'Recording-Process', database = None):
@@ -82,6 +82,7 @@ class RecordingProcess(Process):
self._queue_rec = queue_rec
self._queue_ds = queue_ds
self._queue_msg = queue_msg
self.queue_flag = True
self._is_close = True
@@ -98,6 +99,14 @@ class RecordingProcess(Process):
self._last_cnt = [-1, -1, -1, -1, -1, -1, -1, -1]
self._wrong_cnt = 0
# 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
self._decoder = self.data_format()
self._start_time = time()
def ensure_data_format(self) -> DataDecodeFormat:
if isinstance(self._data_format, (str, bytes)):
@@ -127,9 +136,9 @@ class RecordingProcess(Process):
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):
elif isinstance(decoder, I4V4Z4T4DataDecoder):
# get cycle_time from meta file
# decoder._mode = self._meta_file.configuration.MODE
decoder._mode = self._meta_file.configuration.get_parameter('MODE')
# decoder._cycle_start_time = self._cycle_start_time
elif isinstance(decoder, NeuliveThreeOneDataDecoder):
# get amp_gain from meta file
@@ -145,9 +154,8 @@ class RecordingProcess(Process):
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._ac_amp = self._meta_file.configuration.get_parameter('AC_AMP')
decoder._freq_start = self._meta_file.configuration.get_parameter('FREQ_START')
decoder._freq_stop = self._meta_file.configuration.get_parameter('FREQ_STOP')
@@ -160,7 +168,11 @@ class RecordingProcess(Process):
"""
q = None
try:
# now = time()
q = self._queue_rec.get()
# get_queue_time_cost = time() - now
# if get_queue_time_cost > 2:
# print('get queue cost time', get_queue_time_cost)
except:
return False
finally:
@@ -190,14 +202,13 @@ class RecordingProcess(Process):
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'')
result = self._decoder.decode(b'')
if result is not None:
ret = result
@@ -211,7 +222,7 @@ class RecordingProcess(Process):
for offset, section in self._foreach_data_section(data):
# for section in self._neu_foreach_data_section(data):
result = decoder.decode(section)
result = self._decoder.decode(section)
# if self._isTimeOut:
# try:
# print('result: ', result.data_size)
@@ -219,13 +230,20 @@ class RecordingProcess(Process):
# pass
try:
if isinstance(decoder, I4V4Z4T4DataDecoder) or isinstance(decoder, EISZeroOneDataDecoder):
if decoder.isFinishMode is not None and decoder.isFinishMode() == 1:
content = {}
content['header'] = 'device_instruction/0'
content['device'] = result.device
content['instruction'] = 'interrupt'
self._mqtt_thread.publish('device_instruction',json_stringify(content), True)
time_duration = self._meta_file.configuration.get_parameter('TIME_DURATION')
if time_duration and time_duration is not 0 and time() - self._start_time >= time_duration:
self._queue_msg.put(['ds', self._device, 'interrupt'])
return None
if isinstance(self._decoder, I4V4Z4T4DataDecoder) or isinstance(self._decoder, EISZeroOneDataDecoder):
if self._decoder.isFinishMode is not None and self._decoder.isFinishMode() == 1:
self._queue_msg.put(['ds', self._device, 'interrupt'])
return None
# content = {}
# content['header'] = 'device_instruction/0'
# content['device'] = result.device
# content['instruction'] = 'interrupt'
# self._mqtt_thread.publish('device_instruction',json_stringify(content), True)
# self.server.stop_sync(self.device)
except RuntimeError as e:
print(e)
@@ -286,7 +304,8 @@ class RecordingProcess(Process):
self._last_cnt[7] = head_counter - 1
last_data_cnt = self._last_cnt[7]
if (head != 255 or device_id != device):
# dont save to section when head or id is wrong
if (head != 255 or device_id != device or head_counter > 255 or head_counter < 0):
save = False
last_data_cnt = last_data_cnt + 1
@@ -328,60 +347,58 @@ class RecordingProcess(Process):
return save
def _check_jump_ram(self, raw_data: bytes, colum_now: int, colum_total: int, save: bool, device: int):
i = colum_now
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[i][0] == 255 and raw_data[i][3] == device):
if colum_total < 3:
print('this ram data < 3 records, need to fix, colum_total = ', colum_total)
if colum_total == 1:
print('raw_data[0]', raw_data[0])
if raw_data[col][0] != 255 or raw_data[col][3] != device:
return save
elif colum_total == 2:
print('raw_data[0]', raw_data[0])
print('raw_data[1]', raw_data[1])
if colum_total == 1:
print('this ram data < 3 records, colum_total = ', colum_total)
print('raw_data[0]', list(raw_data[0]))
save = True
# return save
else:
elif colum_total == 2:
print('this ram data < 3 records, colum_total = ', colum_total)
print('raw_data[0]', list(raw_data[0]))
print('raw_data[1]', list(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
if((raw_data[i + 1][1] - raw_data[i][1] == 1 and raw_data[i + 2][1] - raw_data[i + 1][1] == 1) or
(raw_data[i][1] == 255 and raw_data[i + 1][1] == 0 and raw_data[i + 2][1] == 1) or
(raw_data[i][1] == 254 and raw_data[i + 1][1] == 255 and raw_data[i + 2][1] == 0)):
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 ((raw_data[i][1] == 255 and raw_data[i + 1][1] == 0 and raw_data[i + 2][1] == 1) or
(raw_data[i][1] == 254 and raw_data[i + 1][1] == 255 and raw_data[i + 2][1] == 0)):
print('cnt = [254, 255, 0] or [255, 0, 1]')
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')
save = True
self._skip_ram_cnt += 1
last_data_cnt = raw_data[i][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[i][0:3]),'|', list(raw_data[colum_total-1][0:3]), 'skip Ram', self._skip_ram_cnt, 'times \n')
# print('data:')
# print('|', list(raw_data[0]))
# print('|', list(raw_data[1]))
# print('| ... ... ...')
# print('| ... ... ...')
# print('|', list(raw_data[colum_total-1]))
if (device == 4):
self._last_cnt[4] = last_data_cnt
if (device == 4):
self._last_cnt[4] = last_data_cnt
elif (device == 5):
self._last_cnt[5] = 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 == 6):
self._last_cnt[6] = last_data_cnt
elif (device == 7):
self._last_cnt[7] = last_data_cnt
elif (device == 7):
self._last_cnt[7] = last_data_cnt
return save
def _neu_foreach_data_section(self, data: bytes):
@@ -496,29 +513,30 @@ class RecordingProcess(Process):
:return:
"""
raw_data = []
index = 0
elite_data_len = 40
mem_header_len = 3
mem_tailer_len = 6
single_data_len = elite_data_len + mem_header_len + mem_tailer_len
device = self._device
records = 0
print_flag = False
device = self._device
single_data_len = self._single_data_len
mem_header_len = self._mem_header_len
# packet section data use two microarray
for i in range(0, len(data), single_data_len):
index = index + 1
for j in range(index, index + 1):
raw_data.append(data[i : i + single_data_len])
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
# check cnt
for i in range(index):
for i in range(records):
# 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:
save = self._check_jump_ram(raw_data, i, index, save, device)
save = self._check_jump_ram(raw_data, i, records, save, device, single_data_len)
if (save == True):
section = raw_data[i]
@@ -533,7 +551,7 @@ class RecordingProcess(Process):
print('check number not match', list(raw_data[i]), 'raw:', i)
if (i+1 <= index):
if (i+1 <= records):
print('check number not match', list(raw_data[i+1]), 'raw:', i+1, '\n')
section = section + b'\x01'
+15
View File
@@ -3,6 +3,7 @@ import inspect
import os
import sys
import threading as _threading
from time import time
from datetime import datetime
from functools import wraps
from typing import Any, Optional, Tuple, Union
@@ -520,3 +521,17 @@ def logging_error(f):
return f(self, *args, **kwargs)
return _logger
def calculate_time(time_limit=None):
def actual_calculate_time(func):
def warp(*args, **kwargs):
now = time()
func(*args, **kwargs)
if time_limit == None:
print(func.__name__,'cost:', time() - now)
else:
if time() - now > time_limit:
print(func.__name__,'cost:', time() - now)
return warp
return actual_calculate_time
+60 -34
View File
@@ -11,9 +11,21 @@
"constant": {
"FREQ_MAX": 4294967296,
"VOLT_MAX": 65536,
"BLE_WRITE_MAX": 255
"BLE_WRITE_MAX": 255,
"TIME_MAX": 100000
},
"parameters": {
"TIME_DURATION": {
"description": "Run duration",
"record_meta": true,
"initial": 0,
"domain": [
"TIME_MAX"
],
"value": {
"expression": "VALUE"
}
},
"CHANNEL": {
"derzteription": "record channels",
"record_meta": true,
@@ -48,20 +60,34 @@
"description": "working mode",
"record_meta": true,
"value": [
"EIS CURVE",
"Cyclic Voltammetry",
"Dev Mode"
"EIS CURVE",
"Cyclic Voltammetry",
"Dev Mode"
]
},
"FREQ": {
"description": "DPV current recording period start",
"record_meta": true,
"initial": [
13422819,
7
],
"domain": {
"list": [
"FREQ_MAX"
]
},
"value": "VALUE"
},
"FREQ_START": {
"description": "Start of Freq Scan",
"record_meta": true,
"initial": 13422819,
"domain": [
"FREQ_MAX"
"FREQ_MAX"
],
"value": {
"expression": "VALUE"
"expression": "VALUE"
}
},
"FREQ_STOP": {
@@ -69,10 +95,10 @@
"record_meta": true,
"initial": 7,
"domain": [
"FREQ_MAX"
"FREQ_MAX"
],
"value": {
"expression": "VALUE"
"expression": "VALUE"
}
},
"DELAY": {
@@ -91,21 +117,21 @@
"record_meta": true,
"initial": 8,
"value": [
2,
4,
8,
16
2,
4,
8,
16
]
},
"DC_BIAS": {
"description": "DC voltage bias in mV",
"record_meta": true,
"initial": 15000,
"initial": 25000,
"domain": [
35001
"VOLT_MAX"
],
"value": {
"expression": "VALUE"
"expression": "VALUE"
}
},
"AC_AMP": {
@@ -113,7 +139,7 @@
"record_meta": true,
"initial": 25,
"domain": [
2048
2048
]
},
"SCALE": {
@@ -121,8 +147,8 @@
"record_meta": true,
"initial": 0,
"value": [
0,
1
0,
1
]
},
"PPD": {
@@ -130,7 +156,7 @@
"record_meta": true,
"initial": 10,
"domain": [
11
11
]
},
"RTIA": {
@@ -138,11 +164,11 @@
"record_meta": true,
"initial": 4,
"value": [
0,
1,
2,
3,
4
0,
1,
2,
3,
4
]
},
"ADC_VALUE_I": {
@@ -154,10 +180,10 @@
"record_meta": true,
"initial": 10000,
"domain": [
100001
100001
],
"value": {
"expression": "VALUE"
"expression": "VALUE"
}
},
"CTRL_HIGH_Z_15": {
@@ -242,9 +268,9 @@
"BLE_WRITE": {
"description": "send msg to elite",
"domain": {
"list": [
"list": [
"BLE_WRITE_MAX"
]
]
},
"initial": "[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]",
"value": "VALUE"
@@ -259,7 +285,7 @@
{
"expression": "MODE",
"when": {
"*": "start_data"
"*": "start_data"
}
}
],
@@ -339,8 +365,8 @@
"curve_eis": {
"type": "RIS",
"parameter": {
"fa": "FREQ_START",
"fb": "FREQ_STOP",
"fa": "FREQ[0]",
"fb": "FREQ[1]",
"dp": "DELAY",
"dc": "DC_BIAS",
"am": "AC_AMP",
@@ -356,8 +382,8 @@
"curve_eis_para2": {
"type": "RIS",
"parameter": {
"fa": "FREQ_START",
"fb": "FREQ_STOP",
"fa": "FREQ[0]",
"fb": "FREQ[1]",
"dp": "DELAY",
"dc": "DC_BIAS",
"am": "AC_AMP",
@@ -430,4 +456,4 @@
"_cdr('20X>ADC_VALUE_I')"
]
}
}
}
+274 -102
View File
@@ -9,6 +9,7 @@
"minor_version_number": 6
},
"constant": {
"TIME_MAX": 100000,
"VOLT_MAX": 65536,
"Const_Current_Range": 1500001,
"BLE_WRITE_MAX": 255
@@ -17,7 +18,7 @@
"DPV_e_init": {
"description": "DPV initial voltage ",
"record_meta": true,
"initial":20000,
"initial": 20000,
"domain": [
50001
],
@@ -28,7 +29,7 @@
"DPV_e_final": {
"description": "DPV final voltage",
"record_meta": true,
"initial":30000,
"initial": 30000,
"domain": [
50001
],
@@ -39,7 +40,7 @@
"DPV_e_1": {
"description": "DPV voltage return 1",
"record_meta": true,
"initial":35000,
"initial": 35000,
"domain": [
50001
],
@@ -50,7 +51,7 @@
"DPV_e_2": {
"description": "DPV voltage return 2",
"record_meta": true,
"initial":15000,
"initial": 15000,
"domain": [
50001
],
@@ -61,7 +62,7 @@
"DPV_amp": {
"description": "DPV pulse amplitude",
"record_meta": true,
"initial":25125,
"initial": 25125,
"domain": [
50001
],
@@ -72,7 +73,7 @@
"DPV_pul_width": {
"description": "DPV pulse width",
"record_meta": true,
"initial":50,
"initial": 50,
"domain": [
1000001
],
@@ -83,7 +84,7 @@
"DPV_increment": {
"description": "DPV increment",
"record_meta": true,
"initial":25025,
"initial": 25025,
"domain": [
50001
],
@@ -94,7 +95,7 @@
"DPV_step_time": {
"description": "DPV step time = time of one period",
"record_meta": true,
"initial":500,
"initial": 500,
"domain": [
1000001
],
@@ -105,7 +106,7 @@
"DPV_notify_rate": {
"description": "DPV sample rate",
"record_meta": true,
"initial":1000,
"initial": 1000,
"domain": [
1001
],
@@ -113,10 +114,24 @@
"expression": "VALUE"
}
},
"DPV_curr_rec": {
"description": "DPV current recording period start",
"record_meta": true,
"initial": [
25,
100
],
"domain": {
"list": [
101
]
},
"value": "VALUE"
},
"DPV_curr_rec_min": {
"description": "DPV current recording period start",
"record_meta": true,
"initial":25,
"initial": 25,
"domain": [
101
],
@@ -127,7 +142,7 @@
"DPV_curr_rec_max": {
"description": "DPV current recording period end",
"record_meta": true,
"initial":100,
"initial": 100,
"domain": [
101
],
@@ -138,11 +153,8 @@
"DPV_pulse_option": {
"description": "Invert of pulse option",
"record_meta": true,
"initial": 0,
"value": [
0,
1
]
"domain": "bool",
"initial": false
},
"DPV_mode": {
"description": "AUTO: 0 / ADVANCED: 1/ ENGINEERING: 2",
@@ -157,16 +169,29 @@
"DPV_engineering_enable": {
"description": "Engineering mode switch",
"record_meta": true,
"initial": 0,
"value": [
0,
1
]
"domain": "bool",
"initial": false
},
"V_initial": {
"description": "Pulse Mode Segment Voltage",
"record_meta": true,
"initial": [
50000,
25000,
25000,
25000
],
"domain": {
"list": [
50001
]
},
"value": "VALUE"
},
"V_initial_0": {
"description": "Pulse Mode Segment Voltage 0",
"record_meta": true,
"initial":50000,
"initial": 50000,
"domain": [
50001
],
@@ -177,7 +202,7 @@
"V_initial_1": {
"description": "Pulse Mode Segment Voltage 1",
"record_meta": true,
"initial":25000,
"initial": 25000,
"domain": [
50001
],
@@ -188,7 +213,7 @@
"V_initial_2": {
"description": "Pulse Mode Segment Voltage 2",
"record_meta": true,
"initial":25000,
"initial": 25000,
"domain": [
50001
],
@@ -199,7 +224,7 @@
"V_initial_3": {
"description": "Pulse Mode Segment Voltage 3",
"record_meta": true,
"initial":25000,
"initial": 25000,
"domain": [
50001
],
@@ -207,10 +232,26 @@
"expression": "VALUE"
}
},
"t_pulse": {
"description": "Pulse Mode Segment Duration",
"record_meta": true,
"initial": [
750,
9250,
1000,
1000
],
"domain": {
"list": [
1000001
]
},
"value": "VALUE"
},
"t_pulse_0": {
"description": "Pulse Mode Segment Duration 0",
"record_meta": true,
"initial":750,
"initial": 750,
"domain": [
1000001
],
@@ -221,7 +262,7 @@
"t_pulse_1": {
"description": "Pulse Mode Segment Duration 1",
"record_meta": true,
"initial":9250,
"initial": 9250,
"domain": [
1000001
],
@@ -232,7 +273,7 @@
"t_pulse_2": {
"description": "Pulse Mode Segment Duration 2",
"record_meta": true,
"initial":1000,
"initial": 1000,
"domain": [
1000001
],
@@ -243,7 +284,7 @@
"t_pulse_3": {
"description": "Pulse Mode Segment Duration 3",
"record_meta": true,
"initial":1000,
"initial": 1000,
"domain": [
1000001
],
@@ -251,10 +292,40 @@
"expression": "VALUE"
}
},
"CURR_REC": {
"description": "Pulse Mode Current Recording Period",
"record_meta": true,
"initial": [
[
35,
90
],
[
35,
99
],
[
35,
95
],
[
35,
95
]
],
"domain": {
"list": {
"list": [
100
]
}
},
"value": "VALUE"
},
"CURR_REC_START_0": {
"description": "Pulse Mode Current Recording Period Start",
"record_meta": true,
"initial":35,
"initial": 35,
"domain": [
100
],
@@ -265,7 +336,7 @@
"CURR_REC_START_1": {
"description": "Pulse Mode Current Recording Period Start",
"record_meta": true,
"initial":35,
"initial": 35,
"domain": [
100
],
@@ -276,7 +347,7 @@
"CURR_REC_START_2": {
"description": "Pulse Mode Current Recording Period Start",
"record_meta": true,
"initial":35,
"initial": 35,
"domain": [
100
],
@@ -287,7 +358,7 @@
"CURR_REC_START_3": {
"description": "Pulse Mode Current Recording Period Start",
"record_meta": true,
"initial":35,
"initial": 35,
"domain": [
100
],
@@ -298,7 +369,7 @@
"CURR_REC_END_0": {
"description": "Pulse Mode Current Recording Period End",
"record_meta": true,
"initial":90,
"initial": 90,
"domain": [
100
],
@@ -309,7 +380,7 @@
"CURR_REC_END_1": {
"description": "Pulse Mode Current Recording Period End",
"record_meta": true,
"initial":99,
"initial": 99,
"domain": [
100
],
@@ -320,7 +391,7 @@
"CURR_REC_END_2": {
"description": "Pulse Mode Current Recording Period End",
"record_meta": true,
"initial":95,
"initial": 95,
"domain": [
100
],
@@ -331,7 +402,7 @@
"CURR_REC_END_3": {
"description": "Pulse Mode Current Recording Period End",
"record_meta": true,
"initial":95,
"initial": 95,
"domain": [
100
],
@@ -339,6 +410,40 @@
"expression": "VALUE"
}
},
"segment_ui_order": {
"description": "Pulse Mode Segment Order",
"record_meta": true,
"initial": [
0,
1,
-1,
-1
],
"domain": {
"list": [
-1,
6
]
},
"value": "VALUE"
},
"segment_order": {
"description": "Pulse Mode Segment Order",
"record_meta": true,
"initial": [
0,
1,
-1,
-1
],
"domain": {
"list": [
-1,
6
]
},
"value": "VALUE"
},
"segment_ui_order_0": {
"description": "Pulse Mode Segment UI Order 0, value: segment ID + 1",
"record_meta": true,
@@ -478,13 +583,14 @@
"Constant Current",
"Cyclic Voltammetry",
"Linear Sweep Voltammetry",
"Chronoamperometric Graph",
"Chronoamperometric",
"Cali DAC - test",
"Cali ADC - test",
"Dev Mode",
"Open Circuit Potential",
"Pulse Sensing",
"Differential Pulse Voltammetry (DPV)"
"Differential Pulse Voltammetry (DPV)",
"Chronopotentiometry"
]
},
"VOLT_ORIGIN": {
@@ -644,7 +750,7 @@
},
"CURRNET_LIMIT_VALUE": {
"description": "Current value Setting",
"initial":1500000,
"initial": 1500000,
"domain": [
"Const_Current_Range"
],
@@ -685,6 +791,15 @@
"expression": "VALUE"
}
},
"CC_CP_SPEED": {
"description": "charge speed for CC and CP mode",
"initial": 1,
"value": [
"low",
"normal",
"high"
]
},
"ADC_LEVEL_I_15": {
"description": "ADC level",
"record_meta": true,
@@ -762,6 +877,17 @@
"s",
"ms"
]
},
"TIME_DURATION": {
"description": "Run duration",
"record_meta": true,
"initial": 0,
"domain": [
"TIME_MAX"
],
"value": {
"expression": "VALUE"
}
}
},
"instruction": {
@@ -812,7 +938,8 @@
"7": "set_adc_gain_I",
"8": "set_adc_gain_I",
"9": "set_adc_gain_I",
"13": "set_adc_gain_I"
"13": "set_adc_gain_I",
"16": "set_adc_gain_I"
}
},
{
@@ -827,7 +954,8 @@
"7": "set_adc_gain_Vin",
"8": "set_adc_gain_Vin",
"9": "set_adc_gain_Vin",
"13": "set_adc_gain_Vin"
"13": "set_adc_gain_Vin",
"16": "set_adc_gain_Vin"
}
},
{
@@ -845,7 +973,8 @@
"9": "curve_const_vscan",
"13": "curve_ocp",
"14": "curve_pulse_sensing",
"15": "curve_dpv"
"15": "curve_dpv",
"16": "curve_cp"
}
},
{
@@ -1043,6 +1172,7 @@
"vb": "Const_Current_value",
"vc": "VOLTSTOP_MAX",
"vd": "VOLTSTOP_MIN",
"ve": "CC_CP_SPEED",
"pa": "ADC_LEVEL_I_15",
"pb": "ADC_LEVEL_V_IN_15",
"pc": "DAC_LEVEL_V_OUT_15",
@@ -1054,7 +1184,31 @@
"B>va;4B>vb;2B>vc;2B>vd;",
"4b>pa;4b>pb;",
"4b>pc;4b>pd;",
"2B>pe"
"2B>pe;",
"4b>0b0001;4b>ve"
]
},
"curve_cp": {
"type": "RIS",
"parameter": {
"va": "Charge",
"vb": "Const_Current_value",
"vc": "VOLTSTOP_MAX",
"vd": "VOLTSTOP_MIN",
"ve": "CC_CP_SPEED",
"pa": "ADC_LEVEL_I_15",
"pb": "ADC_LEVEL_V_IN_15",
"pc": "DAC_LEVEL_V_OUT_15",
"pd": "CTRL_HIGH_Z_15",
"pe": "SAMPLE_RATE"
},
"data": [
"X07;",
"B>va;4B>vb;2B>vc;2B>vd;",
"4b>pa;4b>pb;",
"4b>pc;4b>pd;",
"2B>pe;",
"4b>0b0000;4b>ve"
]
},
"curve_cv3": {
@@ -1180,58 +1334,67 @@
},
"curve_pulse_sensing": [
{
"expression": "segment_ui_order_0",
"expression": "segment_ui_order[0]",
"when": {
"1": "pul_sen_segment_0",
"2": "pul_sen_segment_1",
"3": "pul_sen_segment_2",
"4": "pul_sen_segment_3"
"0": "pul_sen_segment_0",
"1": "pul_sen_segment_1",
"2": "pul_sen_segment_2",
"3": "pul_sen_segment_3"
}
},
{
"expression": "segment_ui_order_1",
"expression": "segment_ui_order[1]",
"when": {
"1": "pul_sen_segment_0",
"2": "pul_sen_segment_1",
"3": "pul_sen_segment_2",
"4": "pul_sen_segment_3"
"0": "pul_sen_segment_0",
"1": "pul_sen_segment_1",
"2": "pul_sen_segment_2",
"3": "pul_sen_segment_3"
}
},
{
"expression": "segment_ui_order_2",
"expression": "segment_ui_order[2]",
"when": {
"1": "pul_sen_segment_0",
"2": "pul_sen_segment_1",
"3": "pul_sen_segment_2",
"4": "pul_sen_segment_3"
"0": "pul_sen_segment_0",
"1": "pul_sen_segment_1",
"2": "pul_sen_segment_2",
"3": "pul_sen_segment_3"
}
},
{
"expression": "segment_ui_order_3",
"expression": "segment_ui_order[3]",
"when": {
"1": "pul_sen_segment_0",
"2": "pul_sen_segment_1",
"3": "pul_sen_segment_2",
"4": "pul_sen_segment_3"
"0": "pul_sen_segment_0",
"1": "pul_sen_segment_1",
"2": "pul_sen_segment_2",
"3": "pul_sen_segment_3"
}
},
"pul_sen_start"
],
"pul_sen_start": {
"type": "RIS",
"parameter": {
"pa": "ADC_LEVEL_I_15",
"pb": "ADC_LEVEL_V_IN_15",
"pc": "DAC_LEVEL_V_OUT_15",
"pd": "CTRL_HIGH_Z_15",
"pe": "SAMPLE_RATE"
},
"data": [
"1X0D;",
"1XFF"
"1XFF",
"4b>pa;4b>pb;",
"4b>pc;4b>pd"
]
},
"pul_sen_segment_0": {
"type": "RIS",
"parameter": {
"va": "V_initial_0",
"vb": "t_pulse_0",
"vc": "CURR_REC_START_0",
"vd": "CURR_REC_END_0",
"ve": "segment_order_0 - 1",
"va": "V_initial[0]",
"vb": "t_pulse[0]",
"vc": "CURR_REC[0][0]",
"vd": "CURR_REC[0][1]",
"ve": "segment_order.index(segment_order[0])",
"pa": "ADC_LEVEL_I_15",
"pb": "ADC_LEVEL_V_IN_15",
"pc": "DAC_LEVEL_V_OUT_15",
@@ -1247,11 +1410,11 @@
"pul_sen_segment_1": {
"type": "RIS",
"parameter": {
"va": "V_initial_1",
"vb": "t_pulse_1",
"vc": "CURR_REC_START_1",
"vd": "CURR_REC_END_1",
"ve": "segment_order_1 - 1",
"va": "V_initial[1]",
"vb": "t_pulse[1]",
"vc": "CURR_REC[1][0]",
"vd": "CURR_REC[1][1]",
"ve": "segment_order.index(segment_order[1])",
"pa": "ADC_LEVEL_I_15",
"pb": "ADC_LEVEL_V_IN_15",
"pc": "DAC_LEVEL_V_OUT_15",
@@ -1267,11 +1430,11 @@
"pul_sen_segment_2": {
"type": "RIS",
"parameter": {
"va": "V_initial_2",
"vb": "t_pulse_2",
"vc": "CURR_REC_START_2",
"vd": "CURR_REC_END_2",
"ve": "segment_order_2 - 1",
"va": "V_initial[2]",
"vb": "t_pulse[2]",
"vc": "CURR_REC[2][0]",
"vd": "CURR_REC[2][1]",
"ve": "segment_order.index(segment_order[2])",
"pa": "ADC_LEVEL_I_15",
"pb": "ADC_LEVEL_V_IN_15",
"pc": "DAC_LEVEL_V_OUT_15",
@@ -1287,11 +1450,11 @@
"pul_sen_segment_3": {
"type": "RIS",
"parameter": {
"va": "V_initial_3",
"vb": "t_pulse_3",
"vc": "CURR_REC_START_3",
"vd": "CURR_REC_END_3",
"ve": "segment_order_3 - 1",
"va": "V_initial[3]",
"vb": "t_pulse[3]",
"vc": "CURR_REC[3][0]",
"vd": "CURR_REC[3][1]",
"ve": "segment_order.index(segment_order[3])",
"pa": "ADC_LEVEL_I_15",
"pb": "ADC_LEVEL_V_IN_15",
"pc": "DAC_LEVEL_V_OUT_15",
@@ -1304,9 +1467,7 @@
"2B>va;4B>vb;1B>vc;1B>vd;1B>ve"
]
},
"curve_dpv":[
"curve_dpv": [
{
"expression": "DPV_mode",
"when": {
@@ -1354,8 +1515,8 @@
"parameter": {
"va": "DPV_step_time",
"vb": "DPV_notify_rate",
"vc": "DPV_curr_rec_min",
"vd": "DPV_curr_rec_max"
"vc": "DPV_curr_rec[0]",
"vd": "DPV_curr_rec[1]"
},
"data": [
"1X0E;",
@@ -1365,14 +1526,20 @@
},
"dpv_auto_mode_ins_final": {
"type": "RIS",
"parameter": {
"pa": "ADC_LEVEL_I_15",
"pb": "ADC_LEVEL_V_IN_15",
"pc": "DAC_LEVEL_V_OUT_15",
"pd": "CTRL_HIGH_Z_15",
"pe": "SAMPLE_RATE"
},
"data": [
"1X0E;",
"1XFF"
"1XFF;",
"4b>pa;4b>pb;",
"4b>pc;4b>pd"
]
},
"dpv_advanced_mode": [
"dpv_advanced_mode_ins_1",
"dpv_advanced_mode_ins_2",
@@ -1412,8 +1579,8 @@
"parameter": {
"va": "DPV_step_time",
"vb": "DPV_notify_rate",
"vc": "DPV_curr_rec_min",
"vd": "DPV_curr_rec_max"
"vc": "DPV_curr_rec[0]",
"vd": "DPV_curr_rec[1]"
},
"data": [
"1X10;",
@@ -1437,9 +1604,18 @@
},
"dpv_advanced_mode_ins_final": {
"type": "RIS",
"parameter": {
"pa": "ADC_LEVEL_I_15",
"pb": "ADC_LEVEL_V_IN_15",
"pc": "DAC_LEVEL_V_OUT_15",
"pd": "CTRL_HIGH_Z_15",
"pe": "SAMPLE_RATE"
},
"data": [
"1X10;",
"1XFF"
"1XFF;",
"4b>pa;4b>pb;",
"4b>pc;4b>pd"
]
},
"dpv_engineering_mode": [
@@ -1457,8 +1633,6 @@
"dpv_engineering_mode_advanced": [
"dpv_advanced_mode"
],
"VIS_CC_ZERO": [
"_data_format('I4V4Z4T4')",
"_disable_cache(False)",
@@ -1499,6 +1673,4 @@
]
}
}
}
}
+3
View File
@@ -0,0 +1,3 @@
#!/bin/bash
find /home/pi -maxdepth 1 -type f -iname '*.out' -mtime +30 -delete