Compare commits

...

148 Commits

Author SHA1 Message Date
warren30815 73c11c9c49 ui wish and bug fix 2022-03-31 11:30:05 +08:00
warren30815 6b16f3e8b4 spike ui typo 2022-03-29 17:20:43 +08:00
warren30815 6758d98e03 spike ui bug fix 2022-03-29 16:57:30 +08:00
warren30815 6514626c02 [remove] filetable upload multiple sd 2022-03-29 11:35:29 +08:00
warren30815 82c154d360 filetable upload multiple sd (to be tested) 2022-03-28 18:08:18 +08:00
warren30815 76de87368b [add] spike ble data can also set start / end time 2022-03-28 15:53:44 +08:00
warren30815 ae6646273a [add] spike can set start / end time 2022-03-28 13:58:39 +08:00
warren30815 f30720cf36 fix spike bug 2022-03-28 10:25:09 +08:00
warren30815 5f8a47215c fix sorting bug 2022-03-23 18:18:12 +08:00
warren30815 a8018c84b8 ble replay move up / down bug fix 2022-03-23 14:49:24 +08:00
warren30815 c028814a1c [fix] offline download bug 2022-03-22 16:38:41 +08:00
warren30815 0ae013521c [add] record / stimulate all button 2022-03-22 15:11:20 +08:00
warren30815 2008b086f7 [modify] unified ble replay move up / down 2022-03-22 14:40:47 +08:00
warren30815 81fe11c7b6 [add] ble replay move right / left 2022-03-22 14:35:57 +08:00
warren30815 595c9bcb91 fill ble replay data loss with nan 2022-03-22 13:57:47 +08:00
warren30815 32b3000d59 reset replay download to open another page 2022-03-21 11:46:29 +08:00
warren30815 de76e098db reset download to open another page 2022-03-21 11:27:50 +08:00
warren30815 ab75887da2 delete 16k 2022-03-21 11:03:19 +08:00
warren30815 26f3d2abfc [add] neulive 3.1 16k sample rate 2022-03-21 10:40:37 +08:00
warren30815 3ffe0463c6 bug fix 2022-03-18 13:46:56 +08:00
warren30815 6536267b1c [add] sd timestamp offset 2022-03-18 13:43:43 +08:00
warren30815 60eb85d977 refresh spike overview window when reloading 2022-03-17 22:22:42 +08:00
warren30815 cbd5ad79a7 sd file size estimation adjustment 2022-03-17 16:59:53 +08:00
warren30815 b4483c9bc1 ui wish and bug fix 2022-03-17 16:39:21 +08:00
warren30815 29af33de31 [modify] replay filter ui flow wish 2022-03-17 11:35:54 +08:00
warren30815 0ea3731e71 ui wish 2022-03-17 10:51:15 +08:00
warren30815 c28ecabf65 Merge branch 'feature/spike_with_sd_data' into feature/testing_20220317 2022-03-17 10:27:57 +08:00
warren30815 d3b6530347 timestamp bug fix 2022-03-17 10:26:06 +08:00
warren30815 9aff6225d1 [add] download / replay consider multiple chip timestamp 2022-03-16 15:47:48 +08:00
warren30815 45bf01d178 replay filter ui wish 2022-03-16 15:11:09 +08:00
warren30815 edc6592f31 3.5ex ui wish 2022-03-16 15:05:16 +08:00
warren30815 60f3fe3089 [add] spike front-end 2022-03-16 14:11:01 +08:00
warren30815 c4a6de8235 filter consider scale rate (10, 100 or 1000:1) 2022-03-15 14:28:20 +08:00
warren30815 a140c9983b [fix] download timestamp float precision bug / sd status button disable rule 2022-03-15 13:34:23 +08:00
warren30815 5e0499d739 [modify] replay filter ui flow 2022-03-15 13:11:00 +08:00
warren30815 ef6d518377 sd download export bug fix 2022-03-15 10:42:47 +08:00
warren30815 27aff31a45 replay downsampling 2022-03-14 18:10:12 +08:00
warren30815 172ab999fe replay bug fix 2022-03-14 17:38:18 +08:00
warren30815 3723df9bf8 free unused memory 2022-03-14 16:47:19 +08:00
warren30815 bdbfeab245 Merge branch 'feature/replay_filter' into release/20220314_CHEN-PO-HSI 2022-03-14 10:45:55 +08:00
warren30815 99fcdc9b4b [add] change name function also apply to new meta table 2022-03-11 17:03:40 +08:00
warren30815 5bdc7cd67f [add] replay init / downloading status 2022-03-11 15:53:15 +08:00
warren30815 623196521b [init] sd data can be used (but need to optimize display performance) 2022-03-10 18:46:43 +08:00
warren30815 9be35f5541 Merge remote-tracking branch 'origin/feature/replay_filter' into feature/replay_filter 2022-03-09 10:25:37 +08:00
warren30815 bc6971926d modify filter ui flow 2022-03-09 10:24:30 +08:00
warren30815 b47836bc62 [add] replay global filter 2022-03-09 10:24:30 +08:00
warren30815 f3d55099a6 fix filetable bug 2022-03-08 17:38:51 +08:00
warren30815 4ac8dfb12f fix filetable bug 2022-03-08 17:38:18 +08:00
warren30815 876add4789 modify filter ui flow 2022-03-08 17:37:44 +08:00
warren30815 64bf6fe313 fix file table display bug 2022-03-08 13:45:11 +08:00
warren30815 1d6dbbaa66 [add] replay global filter 2022-03-07 22:50:28 +08:00
warren30815 9743290e69 ui wish 2022-03-07 15:57:10 +08:00
warren30815 bb7e8287fd ui wish 2022-03-07 14:44:30 +08:00
Jordan Hsu 2ccca9b389 sd enable param 2022-03-03 09:43:15 +08:00
Jordan Hsu 62a2bad4a6 fix download filename bug 2022-03-02 18:39:48 +08:00
Jordan Hsu f8ed7c7ffd fix sd replay bug 2022-03-02 18:27:14 +08:00
Jordan Hsu 600abf40e6 fix replay draw bug 2022-03-02 17:43:46 +08:00
Jordan Hsu ae70d6d6d2 [fix] replay download file bug 2022-03-02 16:18:17 +08:00
Jordan Hsu dfad50aad7 3.5 ui wish 2022-03-02 14:56:11 +08:00
Jordan Hsu ea7a284b65 [fix] info button bug 2022-03-02 12:21:16 +08:00
Jordan Hsu 5b994f982a [add] workaround for sd raw data not start from 0s 2022-03-02 11:27:48 +08:00
Jordan Hsu 763016b105 hide gimer route 2022-03-01 16:20:34 +08:00
Jordan Hsu d7eca79154 [modify] trigger / sdcard reader icon 2022-03-01 16:17:57 +08:00
Jordan Hsu 26e0a82aa8 download file table not to open another page for avoiding window os bug 2022-03-01 12:04:53 +08:00
Jordan Hsu 7e9775bd34 3.5ex get storage bug fix 2022-02-25 16:09:23 +08:00
Jordan Hsu d067b0da6b [modify] more friendly progress bar 2022-02-25 15:18:59 +08:00
Jordan Hsu d5dd7c2321 clear unused code 2022-02-25 14:22:36 +08:00
Jordan Hsu 2158f19310 3.5ex log 2022-02-25 13:43:55 +08:00
Jordan Hsu 1b68eaef71 [add] 3.1/3.5 chip sample rate 2022-02-25 13:41:06 +08:00
Jordan Hsu 201f8f80cf [add] 3.5ex chip sample rate 2022-02-25 13:22:24 +08:00
Jordan Hsu ba487da5f4 remove tedious code 2022-02-24 17:51:58 +08:00
Jordan Hsu 6ce33876a3 fix sd card reader ui bug 2022-02-24 17:27:03 +08:00
Jordan Hsu f2c18924ae [fix] progress bar estimated time bug 2022-02-24 16:05:43 +08:00
Jordan Hsu d80f1dc996 [modify] odd code 2022-02-24 14:54:56 +08:00
Jordan Hsu 1c90566e64 [add] get sd storage and estimate remaining time 2022-02-24 14:46:20 +08:00
Jordan Hsu 4b2caf0964 to be tested 2022-02-24 10:37:54 +08:00
Jordan Hsu f87ef1fb54 [modify] sd upload progress bar estimated time 2022-02-23 17:22:22 +08:00
Jordan Hsu bf83da0649 [add] sd mount / enable api integration 2022-02-23 16:39:42 +08:00
Jordan Hsu 5cb772a18a [fix] sd upload check bug 2022-02-23 10:12:47 +08:00
Jordan Hsu 77901c3667 replay param table ready for testing 2022-02-22 18:28:33 +08:00
Jordan Hsu 98aacadec9 3.5ex param table 2022-02-22 17:23:44 +08:00
Jordan Hsu fa572f3117 [add] neulive replay param table 2022-02-22 00:14:08 +08:00
Jordan Hsu 6f538bea59 tmp 2022-02-21 22:50:30 +08:00
Jordan Hsu 87908e086c [add] sd replay up / down click event 2022-02-21 16:14:15 +08:00
Jordan Hsu c45faf8ce1 [modify] replay layout 2022-02-18 12:01:38 +08:00
Jordan Hsu 8b0cd8c7af [modify] sd replay when move datazoom reset to closest data chunk 2022-02-17 17:18:52 +08:00
Jordan Hsu e17cdd1dea [modify] chunk index start from 1 2022-02-16 17:02:48 +08:00
Jordan Hsu 14a5474094 fix replay sd button bug by reset param 2022-02-15 17:55:18 +08:00
Jordan Hsu a7297e2e85 [add] trigger file table init condition 2022-02-15 15:26:25 +08:00
Jordan Hsu 787aaa2bf8 [fix] trigger download 2022-02-15 11:37:48 +08:00
Jordan Hsu 53bcef0f60 fix real-time tooltip bug 2022-02-14 18:01:23 +08:00
Jordan Hsu ea2b3a0472 replay bug fix 2022-02-14 16:13:46 +08:00
Jordan Hsu 7987f26021 replay bug fix 2022-02-14 15:00:30 +08:00
Jordan Hsu ea2e8b23d8 data time step bug fix 2022-02-14 14:50:10 +08:00
Jordan Hsu 9ceee6b3d1 remove redundant code 2022-02-14 14:30:13 +08:00
Jordan Hsu bc3b466312 bug fix 2022-02-14 14:04:43 +08:00
Jordan Hsu 0519f04acc [add] missing code 2022-02-13 16:21:58 +08:00
Jordan Hsu eff22d1494 [add] sample rate per chip info 2022-02-13 15:48:50 +08:00
Jordan Hsu 54c8d2ac48 [modify] sd timestamp calculated by sample rate and channel amount 2022-02-13 15:08:22 +08:00
Jordan Hsu 4836ffcde9 [fix] configuration refresh bug 2022-02-12 17:16:53 +08:00
Jordan Hsu 1ac306c419 Merge branch 'feature/configuration_1.0' into release/AS_20220112 2022-02-12 16:28:50 +08:00
Jordan Hsu 7c93cf6620 [add] sd card reader front-end 2022-02-12 16:20:29 +08:00
Jordan Hsu 64936c0cf1 [modify] sd replay show 1/10 data 2022-02-10 16:51:46 +08:00
Jordan Hsu 5868caaec2 [modify] trigger mqtt data format 2022-02-10 11:04:25 +08:00
Jordan Hsu c938a9b001 sd card log 2022-02-10 10:31:46 +08:00
Jordan Hsu 801babed35 [add] sd card download whole pipeline 2022-02-09 19:53:34 +08:00
Jordan Hsu 1459128428 front-end for new db format 2022-02-09 17:23:46 +08:00
Jordan Hsu d01a772a67 sd replay data ready 2022-02-08 16:56:19 +08:00
Jordan Hsu ef6e7197a8 sd download online/offline ready for testing 2022-02-08 14:16:42 +08:00
Jordan Hsu 34e8294288 Merge branch 'feature/trigger_download' into release/AS_20220112 2022-02-08 11:09:42 +08:00
Jordan Hsu 0a7b679aa9 tmp 2022-02-07 21:08:47 +08:00
Jordan Hsu f53d88f2cf remove unreasonable code 2022-01-29 11:19:32 +08:00
Jordan Hsu 1dc09f4afa remove unused code 2022-01-28 18:55:01 +08:00
Jordan Hsu e4e995e694 sd replay and download ready for real data testing 2022-01-28 18:51:36 +08:00
Jordan Hsu 9d6f99e96b [add] trigger download page 2022-01-26 16:38:45 +08:00
Jordan Hsu ff0dc2622b [add] fake replay sd card data (wait for api) 2022-01-22 18:38:01 +08:00
Jordan Hsu bae437c26b [modify] ble download button color to 'success' 2022-01-21 16:57:29 +08:00
Jordan Hsu 78b2d947fe replay code cleaning... 2022-01-21 16:45:43 +08:00
Jordan Hsu f61f6f178d [modify] download csv button color 2022-01-18 12:01:46 +08:00
Jordan Hsu 4295fc02e2 [modify] filetable sd card icon 2022-01-17 16:47:08 +08:00
Jordan Hsu 2ef4b4b393 [fix] sd card icon bug 2022-01-17 15:03:37 +08:00
Jordan Hsu 93c4692dc0 [add] 3.5ex missing code 2022-01-17 12:25:38 +08:00
Jordan Hsu f92f4ebb6c [add] 3.5ex sample rate & sti constraint 2022-01-17 11:42:23 +08:00
Jordan Hsu 65ca86b38d Merge branch 'feature/3.5EX_download_bug_fix' into release/AS_20220112 2022-01-14 12:21:55 +08:00
Jordan Hsu 5d3da784cb [fix] 3.5ex not to download empty file, will have bug 2022-01-14 12:21:30 +08:00
Jordan Hsu 8d05136dcc Merge branch 'feature/3.5EX_download_bug_fix' into release/AS_20220112 2022-01-14 11:47:08 +08:00
Jordan Hsu 8c2c33fdd0 fix 3.5ex bug 2022-01-14 11:46:32 +08:00
Jordan Hsu 3c5596144f Merge branch 'feature/3.5EX_download_bug_fix' into release/AS_20220112 2022-01-13 17:17:58 +08:00
Jordan Hsu 02eae10c82 [add] 3.5 ex download filename 2022-01-13 17:16:57 +08:00
Jordan Hsu 9976903789 Merge branch 'testing/trigger' into release/AS_20220112 2022-01-13 16:58:41 +08:00
Jordan Hsu 7ce9b2408b Merge branch 'feature/3.5EX_download_bug_fix' into release/AS_20220112 2022-01-13 16:58:12 +08:00
Jordan Hsu 764d62dc9c [add] 3.5 extension board download format 2022-01-13 16:46:31 +08:00
Jordan Hsu dba3e118f4 Merge branch 'testing/security' into feature/AS_wish 2022-01-12 09:53:43 +08:00
Jordan Hsu 8c53573857 [add] trigger will save param when start 2022-01-06 16:42:18 +08:00
Jordan Hsu c8644f320a [fix] display bug when F5 2022-01-05 16:42:35 +08:00
Jordan Hsu 8b6348e77e bug fix and modify trigger mode option default to output 2022-01-05 16:04:37 +08:00
Jordan Hsu 48555dd793 [modify] trigger will automatically open chart when starting 2022-01-05 12:51:48 +08:00
Jordan Hsu 02617bbb86 [fix] trigger display order bug 2022-01-05 12:50:19 +08:00
Jordan Hsu e4a8f1e8d3 bug fix 2021-12-27 17:41:29 +08:00
Jordan Hsu 48ce6e6342 [add] chart hide initially and adjust x axis 2021-12-24 16:39:28 +08:00
TommyXin 7f0564a3ca Merge remote-tracking branch 'origin/neulive_hide_gimer' into testing/trigger 2021-12-24 12:05:05 +08:00
Jordan Hsu aa9c8c21b7 [modify] trigger mqtt route 2021-12-24 11:40:23 +08:00
TommyXin 0924c20e9e Merge remote-tracking branch 'origin/neulive_hide_gimer' into testing/trigger 2021-12-22 18:21:59 +08:00
Jordan Hsu a239223361 [add] trigger chart 2021-12-22 17:33:03 +08:00
Jordan Hsu e0341356c8 [add] trigger list ui with mqtt 2021-12-21 16:26:45 +08:00
Jordan Hsu b56c753063 [add] trigger list ui layout 2021-12-21 10:12:39 +08:00
Jordan Hsu 04741d8a77 [modify] triggerList to vuex 2021-12-21 10:10:34 +08:00
Jordan Hsu 3721e4a515 [add] device overview with trigger 2021-12-20 16:11:01 +08:00
133 changed files with 6761 additions and 1769 deletions
+8
View File
@@ -17237,6 +17237,14 @@
"mqtt": "^2.18.3"
}
},
"vue-multiple-progress": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/vue-multiple-progress/-/vue-multiple-progress-1.6.0.tgz",
"integrity": "sha512-TPk8B/vh9YrzuNQN9PcnMsMiNTaItsVia75+j23V7/VJuPCsRz+Omkwb/83p5CzQ97QT9ApEvdG7hupG+xb3aA==",
"requires": {
"vue": "^2.3.3"
}
},
"vue-resize": {
"version": "0.4.5",
"resolved": "https://registry.npmjs.org/vue-resize/-/vue-resize-0.4.5.tgz",
+1
View File
@@ -57,6 +57,7 @@
"vue-epic-bus": "^0.1.2",
"vue-gtm": "^2.2.0",
"vue-mqtt": "^2.0.3",
"vue-multiple-progress": "^1.6.0",
"vue-router": "^3.1.3",
"vue-slicksort": "^1.2.0",
"vue-toastification": "^1.7.14",
+1
View File
@@ -8,6 +8,7 @@ burn_to_raspberry_util.sh : 給burn_to_raspberry.sh用的,裡面的指令為
# Usage
burn_to_raspberry.sh
```shell
要先安裝sshpass: https://www.codeleading.com/article/75275615628/
sh ./burn_to_raspberry.sh <HOST> <PASSWORD>
e.g. sh ./burn_to_raspberry.sh pi@192.168.2.1 raspberry
+2
View File
@@ -34,6 +34,7 @@ import VueAxios from 'vue-axios'
import 'nprogress/nprogress.css'
import Toast from 'vue-toastification'
import Progress from 'vue-multiple-progress'
import 'vue-toastification/dist/index.css'
import localforage from 'localforage'
@@ -60,6 +61,7 @@ Vue.component('chart', ECharts)
Vue.use(VueWorker)
Vue.use(VueAxios, axios)
Vue.use(Toast)
Vue.use(Progress)
// Vue.use(VueMqtt, 'ws://52.194.17.114:8083')
Vue.use(VueMqtt, 'ws://' + location.href.split('/')[2] + ':8083')
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 569 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 473 B

@@ -54,6 +54,20 @@ export const navigationRoutes = {
iconClass: 'vuestic-iconset vuestic-iconset-files',
},
},
{
name: 'trigger-file',
displayName: 'TriggerFile',
meta: {
iconClass: 'vuestic-iconset vuestic-iconset-tables',
},
},
{
name: 'sdcard-reader',
displayName: 'SDcardReader',
meta: {
iconClass: 'vuestic-iconset vuestic-iconset-ui-elements',
},
},
{
name: 'settings',
displayName: 'Settings',
@@ -23,6 +23,30 @@
src="@/assets/img/gimer_gray.png"
>
</template>
<template v-else-if="title == 'TriggerFile'">
<img
v-if="this.isHovered || this.isActive"
class="app-sidebar-link__item-icon"
src="@/assets/img/trigger_white.png"
>
<img
v-else
class="app-sidebar-link__item-icon"
src="@/assets/img/trigger_gray.png"
>
</template>
<template v-else-if="title == 'SDcardReader'">
<img
v-if="this.isHovered || this.isActive"
class="app-sidebar-link__item-icon"
src="@/assets/img/sdcard_reader_white.png"
>
<img
v-else
class="app-sidebar-link__item-icon"
src="@/assets/img/sdcard_reader_gray.png"
>
</template>
<template v-else>
<va-icon
v-if="icon"
@@ -510,7 +510,7 @@ export default {
}
this.drawDetailData() // reDraw
},
...mapActions('replay', [types.chart.init, types.chart.register, types.chart.clear, types.chart.updateGridList, types.chart.updateDataZoom, types.chart.setAxisDisplay]),
...mapActions('replay', [types.chart.init, types.chart.register, types.chart.clear, types.chart.updateGridList, types.chart.updateDataZoom]),
...mapActions('replay', [types.meta.init]),
...mapActions('replay', [types.rec.init, types.rec.intoData, types.rec.initDisplaySetting, types.rec.updateDisplaySetting]),
@@ -530,7 +530,7 @@ export default {
console.log(this.metaList)
console.log(this.dataInfo)
await this.register()
this.setOverviewDataZoomStartAndEndChart()
this.setOverviewDataZoomStartAndEndChart({ start: 0, end: 100 })
this.$refs.top_control_panel.init() // will call changeAxis() here
this.$refs.set_min_max.init()
this.turnOnAxisEventHandler('click', 'yAxis.axisLabel', this.showMinMaxPanel, { axis: 'y' })
@@ -6,10 +6,15 @@
<div class="flex sm12 xl12 md12 xs12">
<va-accordion>
<va-collapse class="mb-2" :isOpenDefault="true">
<span slot="header">Control Panel</span>
<span slot="header">CONTROL PANEL</span>
<div slot="body">
<div>
<top-control-panel :ref="'top_control_panel'" @refreshChart="setOption" @changeOverviewDataZoom="changeOverviewDataZoom"/>
<top-control-panel
:ref="'top_control_panel'"
@changeOverviewDataZoom="changeOverviewDataZoom"
@redrawAllChart="drawInitData()"
@refreshChart="drawDetailData()"
/>
</div>
</div>
</va-collapse>
@@ -21,9 +26,8 @@
<div :class="chartAreaClass">
<div class="row" >
<div class="flex sm12 xl12 md12 xs12">
<neulive-replay-overview-chart :ref="'chart_overview'" class="mb-1" :cardheight="'7vh'" :data="chartOverviewData" @dataZoomEvent="dataZoomEvent($event)"/>
<neulive-replay-chart :ref="'chart_detail'" @dblclick.native="distribute()" :cardheight="'80vh'" :data="chartDetailData" @dataZoomEvent="updateMarkArea($event)" />
<change-channel-value-min-max :ref="'set_min_max'"/>
<neulive-replay-overview-chart :ref="'chart_overview'" class="my-1 py-0" :cardheight="'7vh'" :data="chartOverviewData" @dataZoomEvent="dataZoomEvent($event)"/>
<neulive-replay-chart :ref="'chart_detail'" class="my-1 py-0" @dblclick.native="distribute()" :cardheight="'84vh'" :data="chartDetailData" @dataZoomEvent="updateMarkArea($event)" />
</div>
</div>
</div>
@@ -36,11 +40,12 @@ import { mapActions, mapGetters, mapMutations } from 'vuex'
import NeuliveReplayChart from './NeuliveReplayChart.vue'
import NeuliveReplayOverviewChart from './NeuliveReplayOverviewChart.vue'
import TopControlPanel from './TopControlPanel.vue'
import ChangeChannelValueMinMax from '@/components/analysis/replay/toolbox/ChangeChannelValueMinMax.vue'
// import ChangeChannelValueMinMax from '@/components/analysis/replay/toolbox/ChangeChannelValueMinMax.vue'
import api from '@/data/api/index'
import { download } from '@/data/download/Download'
import types from '@/store/modules/analysis/replay/types'
import { mapFields } from 'vuex-map-fields'
import { download } from '@/data/download/Download'
import { IIRFilter } from '@/data/task/filter/IIRFilter.js'
export default {
name: 'neulive-data-replay',
@@ -48,7 +53,7 @@ export default {
NeuliveReplayChart,
NeuliveReplayOverviewChart,
TopControlPanel,
ChangeChannelValueMinMax,
// ChangeChannelValueMinMax,
},
computed: {
displayType () {
@@ -60,6 +65,7 @@ export default {
...mapFields('replay', [
'chartData',
'chartKey',
'chartOverviewData',
'chartDetailData',
'currentScale',
@@ -72,6 +78,12 @@ export default {
'endTimeCurrent',
'sampleMethod',
'useSdCardData',
'currentChunkDataSplitIndex',
'maxChunkDataSplitNum',
'sdDataChunkDict',
'useFilter',
'isInitDone',
'isDownloading',
]),
},
props: {
@@ -104,6 +116,7 @@ export default {
dataZoomStart: 0,
dataZoomEnd: 100,
inDistributeMode: false,
userClickDirection: '',
}
},
mqtt: {
@@ -112,12 +125,6 @@ export default {
turnOnAxisEventHandler (eventName, query, handler, attr) {
this.$refs.chart_detail.turnOnAxisEventHandler(eventName, query, handler, attr)
},
showMinMaxPanel (params) {
this.$refs.set_min_max.showPanel(params)
},
setOption (options) {
this.$refs.chart_detail.mergeOptions(options)
},
showChartLoading: function (chart) {
const loadStyle = {
text: '',
@@ -142,22 +149,6 @@ export default {
this.$refs.chart_overview.hideLoading()
this.$refs.chart_detail.hideLoading()
},
getData: async function (scale, sampleMethod, id, channel, meta) {
if (scale === 1) {
// [TODO] 0: assume first meta temporarily
if (this.useSdCardData && (this.metaList[0].sd_data_uploaded === true)) {
return await download.rawSdDataByIDChannels(id, channel, meta)
} else {
return await download.rawByIDChannels(id, channel, meta)
}
} else {
if (sampleMethod === 'mean') {
return await download.miniMeanByIDChannel(id, channel, meta)
} else if (sampleMethod === 'random') {
return await download.miniRandomByIDChannel(id, channel, meta)
}
}
},
register: async function () {
let chNumber = 0
this.metaList.forEach(meta => {
@@ -173,12 +164,12 @@ export default {
const register = this.metaList.map(meta => {
this.metaList.map(meta => {
JSON.parse(meta.channels).map(ch => {
JSON.parse(meta.channels).sort((a, b) => a > b ? 1 : -1).map(ch => {
this.registerChart({
chartAllData: this.chartData,
chartID: this.chartDetailData.chartID,
gridIndex: _chNumber,
deviceNameX: 'Time',
deviceNameX: 'Category',
deviceNameY: 'Neulive',
deviceIDY: meta.name,
channelY: ch,
@@ -201,10 +192,7 @@ export default {
}
})
await Promise.all(register)
this.chartDetailData.series.forEach((series, index) => {
this.chartDetailData.xAxis[series.xAxisIndex].min = 0
this.chartDetailData.xAxis[series.xAxisIndex].max = this.maxTime
})
this.chartOverviewData.series.forEach((series, index) => {
this.chartOverviewData.xAxis[series.xAxisIndex].min = 0
this.chartOverviewData.xAxis[series.xAxisIndex].max = this.maxTime
@@ -221,30 +209,30 @@ export default {
},
updateMarkArea: function (e) {
// console.log(e)
if (e.batch != null) {
// console.log(e.batch)
e.batch.forEach(dataZoom => {
const id = parseInt(dataZoom.dataZoomId.substr(dataZoom.dataZoomId.length - 3, dataZoom.dataZoomId.length - 1))
if (id === 1) {
this.dataZoomStart = dataZoom.start
this.dataZoomEnd = dataZoom.end
const startTime = (this.endTime - this.startTime) * dataZoom.start / 100
const endTime = (this.endTime - this.startTime) * dataZoom.end / 100
if (!this.useSdCardData) {
if (e.batch != null) {
// console.log(e.batch)
e.batch.forEach(dataZoom => {
const id = parseInt(dataZoom.dataZoomId.substr(dataZoom.dataZoomId.length - 3, dataZoom.dataZoomId.length - 1))
if (id === 1) {
this.dataZoomStart = dataZoom.start
this.dataZoomEnd = dataZoom.end
const startTime = (this.endTime - this.startTime) * dataZoom.start / 100
const endTime = (this.endTime - this.startTime) * dataZoom.end / 100
this.chartOverviewData.series[0].markArea.data[0][0].xAxis = this.startTime + startTime
this.chartOverviewData.series[0].markArea.data[0][1].xAxis = this.startTime + endTime
}
})
} else {
const id = parseInt(e.dataZoomId.substr(e.dataZoomId.length - 3, e.dataZoomId.length - 1))
if (id === 0) {
this.dataZoomStart = e.start
this.dataZoomEnd = e.end
const startTime = (this.endTime - this.startTime) * e.start / 100
const endTime = (this.endTime - this.startTime) * e.end / 100
this.chartOverviewData.series[0].markArea.data[0][0].xAxis = this.startTime + startTime
this.chartOverviewData.series[0].markArea.data[0][1].xAxis = this.startTime + endTime
// console.log(this.startTime, this.endTime)
}
})
} else {
const id = parseInt(e.dataZoomId.substr(e.dataZoomId.length - 3, e.dataZoomId.length - 1))
if (id === 0) {
this.dataZoomStart = e.start
this.dataZoomEnd = e.end
const startTime = (this.endTime - this.startTime) * e.start / 100
const endTime = (this.endTime - this.startTime) * e.end / 100
this.chartOverviewData.series[0].markArea.data[0][0].xAxis = this.startTime + startTime
this.chartOverviewData.series[0].markArea.data[0][1].xAxis = this.startTime + endTime
// console.log(this.startTime, this.endTime)
}
}
},
@@ -260,7 +248,6 @@ export default {
this.windowLength = this.endTime - this.startTime
this.chartOverviewData.dataZoom[0].start = dataZoom.start
this.chartOverviewData.dataZoom[0].end = dataZoom.end
// console.log(this.startTime, this.endTime)
}
})
} else {
@@ -271,7 +258,6 @@ export default {
this.windowLength = this.endTime - this.startTime
this.chartOverviewData.dataZoom[0].start = e.start
this.chartOverviewData.dataZoom[0].end = e.end
// console.log(this.startTime, this.endTime)
}
}
this.chartOverviewData.series[0].markArea.data[0][0].xAxis = this.startTime + ((this.endTime - this.startTime) * (this.dataZoomStart / 100))
@@ -279,138 +265,378 @@ export default {
this.zoomTime = 0
},
changeOverviewDataZoom () {
this.chartOverviewData.dataZoom[0].start = this.startTime * 100 / this.maxTime
this.chartOverviewData.dataZoom[0].end = this.endTime * 100 / this.maxTime
this.setOverviewDataZoomStartAndEndChart({
start: this.startTime * 100 / this.maxTime,
end: this.endTime * 100 / this.maxTime,
})
this.chartOverviewData.series[0].markArea.data[0][0].xAxis = this.startTime + ((this.endTime - this.startTime) * (this.dataZoomStart / 100))
this.chartOverviewData.series[0].markArea.data[0][1].xAxis = this.startTime + ((this.endTime - this.startTime) * (this.dataZoomEnd / 100))
this.$refs.chart_overview.mergeOptions(this.chartOverviewData)
this.chartKey++
},
computeResampleScale (meta, duration) {
const sampleRate = (meta.parameter_set.SAMPLE_RATE === 0) ? parseInt(8e5 / meta.parameter_set.ADC_CLOCK) : meta.parameter_set.SAMPLE_RATE // Hz
const dataPointNum = (parseInt(duration) / 1e6) * sampleRate // us -> s: /1e6
let scale
if (dataPointNum > 2e6) { // 2000 (points) * 1000 (resample) = 2e6
scale = '1000'
} else if (dataPointNum > 2e5) { // 2000 (points) * 100 (resample) = 2e5
scale = '100'
} else if (dataPointNum > 2e4) { // 2000 (points) * 10 (resample) = 2e4
scale = '10'
} else {
scale = '1'
}
return scale
},
drawInitData: async function () {
this.showChartLoading('overview')
this.setOverviewDataZoomStartAndEndChart({ start: 0, end: 100 })
this.chartOverviewData.series[0].markArea.data[0][0].xAxis = 0
this.chartOverviewData.series[0].markArea.data[0][1].xAxis = this.metaList[0].time_duration
let metaIndex = 0
for (const meta in this.dataInfo) {
let metaInfo
this.metaList.forEach(_meta => {
if (parseInt(_meta.id) === parseInt(meta)) {
metaInfo = _meta
}
})
let scale = '1000'
const sampleRate = (metaInfo.parameter_set.SAMPLE_RATE === 0) ? parseInt(8e5 / metaInfo.parameter_set.ADC_CLOCK) : metaInfo.parameter_set.SAMPLE_RATE // Hz
const dataPointNum = (parseInt(metaInfo.time_duration) / 1e6) * sampleRate // us -> s: /1e6
if (dataPointNum > 2e6) { // 2000 (points) * 1000 (resample) = 2e6
scale = '1000'
} else if (dataPointNum > 2e5) { // 2000 (points) * 100 (resample) = 2e5
scale = '100'
} else if (dataPointNum > 2e4) { // 2000 (points) * 10 (resample) = 2e4
scale = '10'
} else {
scale = '1'
}
const metaInfo = this.metaList[metaIndex]
const scale = this.computeResampleScale(metaInfo, metaInfo.time_duration)
const allChannels = JSON.parse(metaInfo.channels)
allChannels.sort((a, b) => a > b ? 1 : -1)
this.currentScale = scale
console.log('overview scale:', scale)
const getAllData = async function (_this) {
let seriesIndex = 0
for (const ch in _this.dataInfo[meta]) {
for (const ch of allChannels) {
let _channels
if (metaInfo.device.library_name === 'Neulive3.5ex') {
if (ch >= 0 && ch <= 31) {
_channels = allChannels.filter(c => c >= 0 && c <= 31)
} else if (ch >= 32 && ch <= 63) {
_channels = allChannels.filter(c => c >= 32 && c <= 63)
}
} else {
_channels = allChannels
}
const idealTimeStep = parseFloat((1e6 / metaInfo.parameter_set.SAMPLE_RATE) * _channels.length)
_this.chartOverviewData.series[seriesIndex].data.length = 0
_this.chartDetailData.xAxis[seriesIndex].data.length = 0
_this.chartDetailData.series[seriesIndex].data.length = 0
const dataList = _this.dataInfo[meta][ch][scale]
for (let i = 0; i < dataList.length; i++) {
_this.intoDataRec({
index: i,
metaID: meta,
const data = await _this.replayChartGetData({
scale: parseInt(scale),
sampleMethod: _this.sampleMethod,
id: dataList[i].id,
channel: ch,
scale: scale,
data: await _this.getData(parseInt(scale), _this.sampleMethod, dataList[i].id, ch, metaInfo),
meta: metaInfo,
})
_this.chartOverviewData.series[seriesIndex].data.push(...dataList[i].data)
_this.chartDetailData.series[seriesIndex].data.push(...dataList[i].data)
_this.chartOverviewData.series[seriesIndex].data.push(...data)
for (let j = 0, len = data.length; j < len; j++) {
const previousTime = _this.chartDetailData.xAxis[seriesIndex].data[_this.chartDetailData.xAxis[seriesIndex].data.length - 1]
const dataTime = parseInt(data[j][0])
if (_this.currentScale === '1' && previousTime + idealTimeStep < dataTime) {
for (let lossTime = previousTime; lossTime < dataTime; lossTime += idealTimeStep) {
_this.chartDetailData.xAxis[seriesIndex].data.push(lossTime)
_this.chartDetailData.series[seriesIndex].data.push(null)
}
}
_this.chartDetailData.xAxis[seriesIndex].data.push(dataTime)
_this.chartDetailData.series[seriesIndex].data.push(data[j][1])
}
}
_this.updateChartSamplingChart({ chartID: _this.chartOverviewData.chartID, seriesIndex: seriesIndex, method: function (frame) { return frame[0] } })
_this.updateChartSamplingChart({ chartID: _this.chartDetailData.chartID, seriesIndex: seriesIndex, method: function (frame) { return frame[0] } })
seriesIndex++
}
}
await getAllData(this)
metaIndex++
}
this.hideChartLoading()
},
getDataListIndex (dataList, pos) {
let dataListIndex
// [REGION]
// because current chunk start time >= previous chunk end time
// so the if-condition is for = condition
for (let i = 0; i < dataList.length; i++) {
const chunkStartTime = parseInt(dataList[i].start_time_sec) * 1e6 + parseInt(dataList[i].start_time_micro)
if (this.startTime === chunkStartTime) {
dataListIndex = i
break
}
}
if (dataListIndex === undefined) {
for (let i = 0; i < dataList.length; i++) {
const chunkStartTime = parseInt(dataList[i].start_time_sec) * 1e6 + parseInt(dataList[i].start_time_micro)
const chunkEndTime = parseInt(dataList[i].end_time_sec) * 1e6 + parseInt(dataList[i].end_time_micro)
if (this.startTime >= chunkStartTime && this.startTime <= chunkEndTime) {
dataListIndex = i
break // only download 1 chunk data
}
}
}
if (dataListIndex === undefined) { // for the case start time not from 0s
dataListIndex = 0
}
// [ENDREGION]
if (pos === 'prev' && this.currentChunkDataSplitIndex === this.maxChunkDataSplitNum) {
dataListIndex = Math.max(0, dataListIndex - 1)
} else if (pos === 'next' && this.currentChunkDataSplitIndex === 1) {
dataListIndex = Math.min(dataList.length - 1, dataListIndex + 1)
} else if (pos === 'up') {
dataListIndex = Math.max(0, dataListIndex - 1)
} else if (pos === 'down') {
dataListIndex = Math.min(dataList.length - 1, dataListIndex + 1)
}
return dataListIndex
},
async downloadSdDataChunk (scale, uuid, channel, startTime, endTime) {
// only download 1 chunk data for efficiency
return await this.replayChartGetData({
scale: parseInt(scale),
sampleMethod: null,
id: null,
channel: channel,
meta: null,
uuid: uuid,
startTime: startTime,
endTime: endTime,
})
},
updateCurrentChunkDataSplitIndex (pos) {
this.currentChunkDataSplitIndex = parseInt(this.currentChunkDataSplitIndex)
if (pos === 'next') {
this.currentChunkDataSplitIndex++
if (this.currentChunkDataSplitIndex === this.maxChunkDataSplitNum + 1) {
this.currentChunkDataSplitIndex = 1
}
} else if (pos === 'prev') {
this.currentChunkDataSplitIndex--
if (this.currentChunkDataSplitIndex < 1) {
this.currentChunkDataSplitIndex = this.maxChunkDataSplitNum
}
} else if (pos === 'up' || pos === 'down') {
this.currentChunkDataSplitIndex = 1
}
},
async drawSdChart (metaIndex, pos) {
this.showChartLoading('detail')
const scale = '1'
this.currentScale = scale
const metaID = this.metaList[metaIndex].id
const uuid = this.metaList[metaIndex].uuid
const channelList = Object.keys(this.dataInfo[metaID])
const ret = await download.rawSdDataAllTimeslotByUUID(uuid, channelList[0])
// channelList[0]: first channel
// cuz all channels data chunk will have same start time / end time
// so use first channel info is enough, and can avoid keyboard input move forward multiple time
const dataList = ret.data
const dataListIndex = this.getDataListIndex(dataList, pos)
let seriesIndex = 0
for (const channel of channelList) {
this.chartDetailData.xAxis[seriesIndex].data.length = 0
this.chartDetailData.series[seriesIndex].data.length = 0
if (pos === 'next' || pos === 'prev' || pos === 'up' || pos === 'down') {
this.startTimeCurrent = this.startTime = parseInt(dataList[dataListIndex].start_time_sec) * 1e6 + parseInt(dataList[dataListIndex].start_time_micro)
}
this.endTimeCurrent = this.endTime = parseInt(dataList[dataListIndex].end_time_sec) * 1e6 + parseInt(dataList[dataListIndex].end_time_micro)
this.windowLength = this.endTime - this.startTime
this.setOverviewDataZoomStartAndEndChart({
start: this.startTime * 100 / parseInt(this.metaList[metaIndex].time_duration),
end: this.endTime * 100 / parseInt(this.metaList[metaIndex].time_duration),
})
this.chartOverviewData.series[0].markArea.data[0][0].xAxis = this.startTime + (this.endTime - this.startTime) * ((this.currentChunkDataSplitIndex - 1) / this.maxChunkDataSplitNum)
this.chartOverviewData.series[0].markArea.data[0][1].xAxis = this.startTime + (this.endTime - this.startTime) * (this.currentChunkDataSplitIndex / this.maxChunkDataSplitNum)
if (this.currentChunkDataSplitIndex === 1 || this.currentChunkDataSplitIndex === this.maxChunkDataSplitNum) {
this.isDownloading = true
if (this.sdDataChunkDict[metaID] === undefined) {
this.sdDataChunkDict[metaID] = {}
}
this.sdDataChunkDict[metaID][channel] = await this.downloadSdDataChunk(scale, uuid, channel, this.startTime, this.endTime)
}
const sdDataChunk = this.sdDataChunkDict[metaID][channel]
const sdDataChunkSplit = sdDataChunk.slice(sdDataChunk.length * ((this.currentChunkDataSplitIndex - 1) / this.maxChunkDataSplitNum), sdDataChunk.length * (this.currentChunkDataSplitIndex / this.maxChunkDataSplitNum))
const timeArr = sdDataChunkSplit.map(x => x[0])
const valArr = sdDataChunkSplit.map(x => x[1])
for (let i = 0, len = sdDataChunkSplit.length; i < len; i++) {
this.chartDetailData.xAxis[seriesIndex].data.push(timeArr[i])
let filterData = valArr[i]
if (this.useFilter) {
this.chartDetailData.series[seriesIndex].filter.forEach(filter => {
if (filter.filter != null && filterData) {
filterData = IIRFilter.filter(filter.filter, filterData)
}
})
}
this.chartDetailData.series[seriesIndex].data.push(filterData)
}
seriesIndex++
}
this.chartKey++
this.hideChartLoading()
this.isDownloading = false
},
async moveToNextDataSubRegion () {
if (this.useSdCardData) {
this.updateCurrentChunkDataSplitIndex('next')
await this.drawSdChart(0, 'next')
}
},
async moveToPreviousDataSubRegion () {
if (this.useSdCardData) {
this.updateCurrentChunkDataSplitIndex('prev')
await this.drawSdChart(0, 'prev')
}
},
async moveToNextDataChunk () {
if (this.useSdCardData) {
this.updateCurrentChunkDataSplitIndex('down')
await this.drawSdChart(0, 'down')
} else {
if (this.endTime + this.windowLength > this.metaList[0].time_duration) {
this.endTime = this.metaList[0].time_duration
this.startTime = this.endTime - this.windowLength
} else {
this.startTime = this.endTime
this.endTime += this.windowLength
}
this.changeOverviewDataZoom()
}
},
async moveToPreviousDataChunk () {
if (this.useSdCardData) {
this.updateCurrentChunkDataSplitIndex('up')
await this.drawSdChart(0, 'up')
} else {
if (this.startTime - this.windowLength < 0) {
this.startTime = 0
this.endTime = this.windowLength
} else {
this.endTime = this.startTime
this.startTime -= this.windowLength
}
this.changeOverviewDataZoom()
}
},
drawDetailData: async function () {
this.showChartLoading('detail')
const timeDur = this.endTimeCurrent - this.startTimeCurrent
let _metaIndex = 0
for (const meta in this.dataInfo) {
let metaInfo
this.metaList.forEach(_meta => {
if (parseInt(_meta.id) === parseInt(meta)) {
metaInfo = _meta
}
})
let scale = '1000'
const sampleRate = (metaInfo.parameter_set.SAMPLE_RATE === 0) ? parseInt(8e5 / metaInfo.parameter_set.ADC_CLOCK) : metaInfo.parameter_set.SAMPLE_RATE // Hz
const dataPointNum = (timeDur / 1e6) * sampleRate // us -> s: /1e6
if (dataPointNum > 2e6) { // 2000 (points) * 1000 (resample) = 2e6
scale = '1000'
} else if (dataPointNum > 2e5) { // 2000 (points) * 100 (resample) = 2e5
scale = '100'
} else if (dataPointNum > 2e4) { // 2000 (points) * 10 (resample) = 2e4
scale = '10'
} else {
scale = '1'
}
const metaInfo = this.metaList[_metaIndex]
const allChannels = JSON.parse(metaInfo.channels)
allChannels.sort((a, b) => a > b ? 1 : -1)
const scale = this.computeResampleScale(metaInfo, timeDur)
this.currentScale = scale
let seriesIndex = 0
this.updateChartSamplingChart({ chartID: this.chartDetailData.chartID, seriesIndex: seriesIndex, method: function (frame) { return frame[0] } })
for (const ch in this.dataInfo[meta]) {
this.chartDetailData.series[seriesIndex].data.length = 0
const dataList = this.dataInfo[meta][ch][scale]
let startIndex = 0
let endIndex = 0
// get the subfile index
for (let i = 0; i < dataList.length; i++) {
if (this.startTimeCurrent >= parseInt(dataList[i].start_time) && this.startTimeCurrent <= parseInt(dataList[i].end_time)) {
startIndex = i
if (this.useSdCardData) {
this.updateCurrentChunkDataSplitIndex('curr')
await this.drawSdChart(_metaIndex, 'curr')
} else { // ble data
for (const ch of allChannels) {
let _channels
if (metaInfo.device.library_name === 'Neulive3.5ex') {
if (ch >= 0 && ch <= 31) {
_channels = allChannels.filter(c => c >= 0 && c <= 31)
} else if (ch >= 32 && ch <= 63) {
_channels = allChannels.filter(c => c >= 32 && c <= 63)
}
} else {
_channels = allChannels
}
if (this.endTimeCurrent >= parseInt(dataList[i].start_time) && this.endTimeCurrent <= parseInt(dataList[i].end_time)) {
endIndex = i
}
}
// download and push into chart
let endFlag = false // because data is sorted by time, so if current processed data piece exceeds endTimeCurrent, can early stop
for (let i = startIndex; i <= endIndex; i++) {
const data = await this.getData(parseInt(scale), this.sampleMethod, dataList[i].id, ch, metaInfo)
for (let j = 0, len = data.length; j < len; j++) {
if (parseInt(data[j][0]) >= this.startTimeCurrent && parseInt(data[j][0]) <= this.endTimeCurrent) {
const processedData = data[j]
if (this.displayType.x === 'log') {
processedData[0] = Math.abs(processedData[0])
}
if (this.displayType.y === 'log') {
processedData[1] = Math.abs(processedData[1])
}
const idealTimeStep = parseFloat((1e6 / metaInfo.parameter_set.SAMPLE_RATE) * _channels.length)
this.chartDetailData.series[seriesIndex].data.push(processedData)
this.chartDetailData.xAxis[seriesIndex].data.length = 0
this.chartDetailData.series[seriesIndex].data.length = 0
const dataList = this.dataInfo[meta][ch][scale]
let startIndex = 0
let endIndex = 0
// get the subfile index
for (let i = 0; i < dataList.length; i++) {
if (this.startTimeCurrent >= parseInt(dataList[i].start_time) && this.startTimeCurrent <= parseInt(dataList[i].end_time)) {
startIndex = i
}
if (this.endTimeCurrent >= parseInt(dataList[i].start_time) && this.endTimeCurrent <= parseInt(dataList[i].end_time)) {
endIndex = i
}
}
// download and push into chart
let endFlag = false // because data is sorted by time, so if current processed data piece exceeds endTimeCurrent, can early stop
this.isDownloading = true
for (let i = startIndex; i <= endIndex; i++) {
const data = await this.replayChartGetData({
scale: parseInt(scale),
sampleMethod: this.sampleMethod,
id: dataList[i].id,
channel: ch,
meta: metaInfo,
})
for (let j = 0, len = data.length; j < len; j++) {
const previousTime = this.chartDetailData.xAxis[seriesIndex].data[this.chartDetailData.xAxis[seriesIndex].data.length - 1]
const dataTime = parseInt(data[j][0])
if (dataTime >= this.startTimeCurrent && dataTime <= this.endTimeCurrent) {
if (this.currentScale === '1' && previousTime + idealTimeStep < dataTime) {
for (let lossTime = previousTime; lossTime < dataTime; lossTime += idealTimeStep) {
this.chartDetailData.xAxis[seriesIndex].data.push(lossTime)
this.chartDetailData.series[seriesIndex].data.push(null)
}
}
this.chartDetailData.xAxis[seriesIndex].data.push(dataTime)
let filterData = data[j][1]
if (this.useFilter) {
this.chartDetailData.series[seriesIndex].filter.forEach(filter => {
if (filter.filter != null && filterData) {
filterData = IIRFilter.filter(filter.filter, filterData)
}
})
}
this.chartDetailData.series[seriesIndex].data.push(filterData)
}
// early stop
if (dataTime > this.endTimeCurrent) {
endFlag = true
break
}
}
// early stop
if (parseInt(data[j][0]) > this.endTimeCurrent) {
endFlag = true
if (endFlag === true) {
break
}
}
// early stop
if (endFlag === true) {
break
}
seriesIndex++
}
for (let i = 0, len = this.chartDetailData.xAxis.length; i < len; i++) {
this.chartDetailData.xAxis[i].min = this.startTimeCurrent
this.chartDetailData.xAxis[i].max = this.endTimeCurrent
}
seriesIndex++
}
_metaIndex++
}
this.chartKey++
this.hideChartLoading()
this.isDownloading = false
},
checkDataZoom: function () {
checkDataZoom: async function () {
this.zoomTime++
if (this.zoomTime >= 2) {
if (this.startTimeCurrent !== this.startTime || this.endTimeCurrent !== this.endTime) {
this.startTimeCurrent = this.startTime
this.endTimeCurrent = this.endTime
if (this.useSdCardData) {
const metaID = this.metaList[0].id
const uuid = this.metaList[0].uuid
const channelList = Object.keys(this.dataInfo[metaID])
const ret = await download.rawSdDataAllTimeslotByUUID(uuid, channelList[0])
// channelList[0]: first channel
// cuz all channels data chunk will have same start time / end time
// so use first channel info is enough, and can avoid keyboard input move forward multiple time
const dataList = ret.data
const dataListIndex = this.getDataListIndex(dataList, 'curr')
this.startTimeCurrent = this.startTime = parseInt(dataList[dataListIndex].start_time_sec) * 1e6 + parseInt(dataList[dataListIndex].start_time_micro)
this.endTimeCurrent = this.endTime = parseInt(dataList[dataListIndex].end_time_sec) * 1e6 + parseInt(dataList[dataListIndex].end_time_micro)
} else {
this.startTimeCurrent = this.startTime
this.endTimeCurrent = this.endTime
}
this.windowLength = this.endTime - this.startTime
this.currentChunkDataSplitIndex = 1
this.drawDetailData()
}
}
@@ -480,7 +706,30 @@ export default {
this.inDistributeMode = true
}
},
...mapActions('replay', [types.chart.init, types.chart.register, types.chart.clear, types.chart.updateGridList, types.chart.updateDataZoom]),
async handleKeyboardInputEvent (event) {
if (event.key === 'ArrowRight') {
this.userClickDirection = 'next'
await this.moveToNextDataSubRegion()
} else if (event.key === 'ArrowLeft') {
this.userClickDirection = 'prev'
await this.moveToPreviousDataSubRegion()
} else if (event.key === 'ArrowDown') {
this.userClickDirection = 'next'
await this.moveToNextDataChunk()
} else if (event.key === 'ArrowUp') {
this.userClickDirection = 'prev'
await this.moveToPreviousDataChunk()
}
},
async eventHandler (event) {
if (this.isInitDone && !this.isDownloading) {
await this.handleKeyboardInputEvent(event)
}
},
...mapActions('replay', [types.chart.init, types.chart.register, types.chart.clear, types.chart.updateGridList, types.chart.updateDataZoom, types.chart.getData]),
...mapActions('replay', [types.meta.init]),
...mapActions('replay', [types.rec.init, types.rec.intoData, types.rec.initDisplaySetting, types.rec.updateDisplaySetting]),
@@ -490,9 +739,6 @@ export default {
...mapGetters('replay', [types.meta.getAll]),
...mapGetters('replay', [types.rec.getAll]),
},
// use watch because va-select with @change will have bug
watch: {
},
async mounted () {
this.showChartLoading()
const metaIDList = this.$route.params.metaList.split('-')
@@ -503,16 +749,19 @@ export default {
console.log(this.metaList)
console.log(this.dataInfo)
await this.register()
this.setOverviewDataZoomStartAndEndChart()
await this.drawInitData()
this.dataZoomTimer = setInterval(this.checkDataZoom, 1000)
this.$refs.top_control_panel.init()
this.$refs.set_min_max.init()
this.dataZoomTimer = setInterval(this.checkDataZoom, 1000)
this.turnOnAxisEventHandler('click', 'yAxis.axisLabel', this.showMinMaxPanel, { axis: 'y' })
// [TODO] change the global event listener to chart-specific event listener
window.addEventListener('keyup', this.eventHandler)
await this.drawInitData()
this.isInitDone = true
console.log(this.chartOverviewData)
console.log(this.chartDetailData)
},
beforeDestroy () {
this.currentChunkDataSplitIndex = 1
this.$refs.chart_overview.clear()
this.$refs.chart_detail.clear()
for (const id in this.chartData) {
@@ -522,8 +771,13 @@ export default {
delete this.chartData[id]
}
this.clearChart({ chartAllData: this.chartData })
this.startTime = 0
this.endTime = 0
this.startTimeCurrent = 0
this.endTimeCurrent = 0
},
destroyed () {
window.removeEventListener('keyup', this.eventHandler)
clearInterval(this.dataZoomTimer)
},
}
@@ -1,7 +1,15 @@
<template>
<div class="flex" :class="cardclass">
<va-card :style="'height: ' + cardheight + ';'">
<chart :ref="'chart_ref'" :style="'width: '+ cardwidth + '; height: ' + cardheight + ';'" :options="data" @dataZoom="updateZoom" :auto-resize="true"></chart>
<div>
<va-button small class="mb-3" @click="selectAllSeries">
SELECT ALL
</va-button>
<va-button small class="mb-3" @click="inverseSelectAllSeries">
INVERSE SELECT
</va-button>
</div>
<chart :ref="'chart_ref'" :key="chartKey" :style="'width: '+ cardwidth + '; height: ' + cardheight + ';'" :options="data" @dataZoom="updateZoom" :auto-resize="true"></chart>
</va-card>
</div>
</template>
@@ -9,6 +17,7 @@
<script>
import { mapMutations } from 'vuex'
import types from '@/store/modules/analysis/replay/types'
import { mapFields } from 'vuex-map-fields'
export default {
name: 'neulive-replay-chart',
@@ -40,28 +49,34 @@ export default {
},
data () {
return {
chartID: 1,
}
},
computed: {
...mapFields('replay', [
'chartKey',
'chartData',
]),
},
watch: {
},
methods: {
resize: function () {
chartEvent: function () {
if (this.$refs.chart_ref != null) {
this.$refs.chart_ref.resize()
}
if (this.$refs.chart_ref.chart != null) {
this.$refs.chart_ref.chart.on('legendselectchanged', this.legendChange)
this.$refs.chart_ref.chart.on('legendselectall', this.legendChange)
this.$refs.chart_ref.chart.on('legendinverseselect', this.legendChange)
}
},
updateZoom: function (e) {
this.$emit('dataZoomEvent', e)
},
mergeOptions (options) {
this.$refs.chart_ref.mergeOptions(options)
},
turnOnAxisEventHandler (eventName, query, handler, attr) {
const chartID = 1
this.updateAxisEventHandlerChart({
chartID: chartID,
chartID: this.chartID,
axis: attr.axis,
status: 'on',
})
@@ -69,9 +84,8 @@ export default {
this.$refs.chart_ref.chart._zr.on(eventName, query, handler)
},
turnOffAxisEventHandler (eventName, handler, attr) {
const chartID = 1
this.updateAxisEventHandlerChart({
chartID: chartID,
chartID: this.chartID,
axis: attr.axis,
status: 'off',
})
@@ -87,11 +101,36 @@ export default {
clear () {
this.$refs.chart_ref.clear()
},
legendChange (params) {
console.log('legendChange', params)
const activeLegendLen = Object.values(params.selected).filter(val => val === true).length
let index = 0
for (const [gridIndex, value] of Object.values(params.selected).entries()) { // [TODO] 技術債,目前real time chart一個grid只對應一條series
if (value === true) {
this.chartData[this.chartID].grid[gridIndex].height = (82 / activeLegendLen) - 2 + '%'
this.chartData[this.chartID].grid[gridIndex].top = index * (82 / activeLegendLen) + 7 + '%'
index++
} else {
this.chartData[this.chartID].grid[gridIndex].height = '0%'
this.chartData[this.chartID].grid[gridIndex].top = '0%'
}
}
},
selectAllSeries () {
this.$refs.chart_ref.dispatchAction({
type: 'legendAllSelect',
})
},
inverseSelectAllSeries () {
this.$refs.chart_ref.dispatchAction({
type: 'legendInverseSelect',
})
},
...mapMutations('replay', [types.chart.updateAxisEventHandler]),
},
async mounted () {
this.resizeTimer = setInterval(this.resize, 1000)
mounted () {
this.resizeTimer = setInterval(this.chartEvent, 1000)
},
destroyed () {
clearInterval(this.resizeTimer)
@@ -1,12 +1,13 @@
<template>
<div class="flex" :class="cardclass">
<va-card :style="'height: 8vh;'">
<chart :ref="'chart_ref'" :style="'width: '+ cardwidth + '; height: ' + cardheight + ';'" :options="data" @dataZoom="updateZoom" :auto-resize="true"></chart>
<chart :ref="'chart_ref'" :key="chartKey" :style="'width: '+ cardwidth + '; height: ' + cardheight + ';'" :options="data" @dataZoom="updateZoom" :auto-resize="true"></chart>
</va-card>
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields'
export default {
name: 'neulive-replay-overview-chart',
@@ -41,6 +42,9 @@ export default {
}
},
computed: {
...mapFields('replay', [
'chartKey',
]),
},
watch: {
},
@@ -53,9 +57,6 @@ export default {
updateZoom: function (e) {
this.$emit('dataZoomEvent', e)
},
mergeOptions (options) {
this.$refs.chart_ref.mergeOptions(options)
},
showLoading (options) {
this.$refs.chart_ref.showLoading(options)
},
@@ -1,23 +1,33 @@
<template>
<div>
<va-card>
<div class="flex-container">
<edit-file-name class="flex-child" :ref="'edit_file_name'"/>
<download-file class="flex-child2"/>
<delete-file class="flex-child2"/>
<edit-file-name :ref="'edit_file_name'"/>
<div class="row py-2">
<div class="col">
<download-file />
</div>
<div class="col">
<delete-file />
</div>
<div class="col">
<va-button flat small color="gray" icon="fa fa-filter" @click="openFilterPanel"/>
</div>
</div>
<show-resample-rate/>
<div class="flex-container">
<change-window-start-and-size class="flex-child" :ref="'change_window_start_and_size'" @changeOverviewDataZoom="changeOverviewDataZoom"/>
<lock-data-zoom class="flex-child2" :chartID="0"/>
<div>
<show-parameter v-if="metaList[0] && metaList[0].device.library_name !== 'Neulive3.5-REC'" class="mb-2" :ref="'show_param'"/>
<show-resample-rate/>
<div class="flex-container">
<change-window-start-and-size class="flex-child" :ref="'change_window_start_and_size'" @changeOverviewDataZoom="changeOverviewDataZoom"/>
<lock-data-zoom class="flex-child2" :chartID="0"/>
</div>
<change-axis v-show="showXAxis" :ref="'change_axis_x'" axis="x"/>
<change-axis v-show="showYAxis" :ref="'change_axis_y'" axis="y"/>
<change-unit v-show="showXUnit" :ref="'change_unit_x'" axis="x"/>
<change-unit :ref="'change_unit_y'" class="box_inline-block" axis="y"/>
<sd-card-toggle :ref="'sd_card_toggle'" :metaIndex="0" @redrawAllChart="redrawAllChart" @refreshChart="refreshChart"/>
<change-current-chunk-index @refreshChart="refreshChart"/>
</div>
<change-axis v-show="showXAxis" :ref="'change_axis_x'" axis="x"/>
<change-axis v-show="showYAxis" :ref="'change_axis_y'" axis="y"/>
<change-unit v-show="showXUnit" :ref="'change_unit_x'" axis="x" @refreshChart="refreshChart"/>
<change-unit :ref="'change_unit_y'" class="box_inline-block" axis="y" @refreshChart="refreshChart"/>
<change-scale :ref="'change_scale_y'" class="box_inline-block" axis="y"/>
<sd-card-toggle :ref="'sd_card_toggle'" :metaIndex="0"/>
<show-parameter :ref="'show_param'"/>
<filter-toggle :ref="'filter_toggle'" @refreshChart="refreshChart"/>
</va-card>
</div>
</template>
@@ -26,7 +36,7 @@
import ShowResampleRate from '@/components/analysis/replay/toolbox/ShowResampleRate.vue'
import ChangeAxis from '@/components/analysis/replay/toolbox/ChangeAxis.vue'
import ChangeUnit from '@/components/analysis/replay/toolbox/ChangeUnit.vue'
import ChangeScale from '@/components/analysis/replay/toolbox/ChangeScale.vue'
import ChangeCurrentChunkIndex from '@/components/analysis/replay/toolbox/ChangeCurrentChunkIndex.vue'
import DownloadFile from '@/components/analysis/replay/toolbox/DownloadFile.vue'
import DeleteFile from '@/components/analysis/replay/toolbox/DeleteFile.vue'
import EditFileName from '@/components/analysis/replay/toolbox/EditFileName.vue'
@@ -34,6 +44,7 @@ import ShowParameter from '@/components/analysis/replay/toolbox/ShowParameter.vu
import SdCardToggle from '@/components/analysis/replay/toolbox/SdCardToggle.vue'
import LockDataZoom from '@/components/analysis/replay/toolbox/LockDataZoom.vue'
import ChangeWindowStartAndSize from '@/components/analysis/replay/toolbox/ChangeWindowStartAndSize.vue'
import FilterToggle from '@/components/analysis/replay/toolbox/FilterToggle.vue'
import { mapFields } from 'vuex-map-fields'
export default {
@@ -42,7 +53,7 @@ export default {
ShowResampleRate,
ChangeAxis,
ChangeUnit,
ChangeScale,
ChangeCurrentChunkIndex,
DownloadFile,
DeleteFile,
EditFileName,
@@ -50,11 +61,15 @@ export default {
SdCardToggle,
LockDataZoom,
ChangeWindowStartAndSize,
FilterToggle,
},
computed: {
...mapFields('replay', [
'metaList',
'showCyclePanel',
'useSdCardData',
'startTime',
'endTime',
]),
},
async created () {
@@ -76,18 +91,29 @@ export default {
this.$refs.change_axis_y.init(this.deviceName)
this.$refs.change_unit_x.init(this.deviceName)
this.$refs.change_unit_y.init(this.deviceName)
this.$refs.change_scale_y.init(this.deviceName)
this.$refs.edit_file_name.init()
this.$refs.show_param.init(this.deviceName)
if (this.deviceName !== 'Neulive3.5-REC') {
this.$refs.show_param.init(this.deviceName)
}
this.$refs.change_window_start_and_size.init()
this.$refs.sd_card_toggle.init()
this.$refs.filter_toggle.init()
},
changeOverviewDataZoom () {
this.$emit('changeOverviewDataZoom')
},
refreshChart (options) {
this.$emit('refreshChart', options)
redrawAllChart () {
this.$emit('redrawAllChart')
},
refreshChart () {
this.$emit('refreshChart')
},
openFilterPanel () {
const sd = this.useSdCardData ? 'sd' : 'ble'
const href = location.href.split('/')[2]
window.open(`http://${href}/#/admin/data-spike/${this.metaList[0].id}/${this.startTime}/${this.endTime}/${sd}`)
},
// startTime
},
// use watch because va-select with @change will have bug
watch: {
@@ -14,7 +14,7 @@
<script>
import { mapActions, mapMutations } from 'vuex'
import { mapMutations } from 'vuex'
import { mapFields } from 'vuex-map-fields'
import types from '@/store/modules/analysis/replay/types'
@@ -99,7 +99,6 @@ export default {
}
},
...mapActions('replay', [types.chart.setAxisDisplay]),
...mapMutations('replay', [types.chart.updateSeries]),
},
async mounted () {
@@ -52,6 +52,7 @@ export default {
computed: {
...mapFields('replay', [
'chartData',
'chartKey',
]),
},
watch: {
@@ -85,7 +86,7 @@ export default {
minVal: this.minMaxInfo[this.gridIndex].min,
maxVal: this.minMaxInfo[this.gridIndex].max,
})
this.$emit('refreshChart', this.chartData[this.chartID])
this.chartKey++
},
...mapMutations('replay', [types.chart.updateAxisValMinMax]),
},
@@ -0,0 +1,58 @@
<template>
<div class="row flex sm12 xl12 md12 xs12 px-0 py-0 mx-0 my-0">
<div class="flex sm6 xl6 md6 xs6 px-0 mx-0" v-if="useSdCardData">
<p class="display-6"> Chunk Index </p>
<va-input
@keyup.enter="refreshChart()"
v-model="currentChunkDataSplitIndex"
class="mb-0"
:readonly="!isInitDone || isDownloading"
>
<p slot="append" style="margin-right: 0;">
/{{ maxChunkDataSplitNum }}
</p>
</va-input>
</div>
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields'
export default {
name: 'ChangeWindowSize',
components: {
},
computed: {
...mapFields('replay', [
'useSdCardData',
'currentChunkDataSplitIndex',
'maxChunkDataSplitNum',
'isInitDone',
'isDownloading',
]),
},
props: {
},
created () {
},
data () {
return {
}
},
mqtt: {
},
methods: {
refreshChart () {
this.$emit('refreshChart')
},
},
async mounted () {
},
destroyed () {
},
watch: {
},
}
</script>
@@ -177,7 +177,6 @@ export default {
this.chartData[this.chartID].xAxis[this.metaIndex].max = endTime
counter++
}
// this.$emit('refreshChart', this.chartData[this.chartID])
}
}
}
@@ -1,25 +1,30 @@
<template>
<div>
<va-select
@input="changeUnit()"
:label="''"
v-if="deviceName!=='EliteEIS'"
v-model="valueFormat"
textBy="unit"
class="va-select-without-margin"
:options="valueFormatOptions[dataType]"
:noClear='true'
/>
<va-select
@input="changeUnit()"
:label="''"
v-if="deviceName==='EliteEIS'"
v-model="valueFormat"
textBy="unit"
class="va-select-without-margin"
:options="eisValueFormatOptions[dataType]"
:noClear='true'
/>
<div class="row flex sm12 xl12 md12 xs12 px-0 py-0 mx-0 my-0">
<div class="flex sm12 xl12 md12 xs12 px-0 mx-0">
<p class="display-6"> UNIT </p>
<va-select
@input="changeUnit()"
:label="''"
v-if="deviceName!=='EliteEIS'"
v-model="valueFormat"
textBy="unit"
class="va-select-without-margin"
:options="valueFormatOptions[dataType]"
:noClear='true'
:disabled="!isInitDone || isDownloading"
/>
<va-select
@input="changeUnit()"
:label="''"
v-if="deviceName==='EliteEIS'"
v-model="valueFormat"
textBy="unit"
class="va-select-without-margin"
:options="eisValueFormatOptions[dataType]"
:noClear='true'
:disabled="!isInitDone || isDownloading"
/>
</div>
</div>
</template>
@@ -46,11 +51,14 @@ export default {
computed: {
...mapFields('replay', [
'chartData',
'chartKey',
'metaList',
'valueFormatX',
'valueFormatY',
'axisXDataType',
'axisYDataType',
'isInitDone',
'isDownloading',
]),
},
methods: {
@@ -187,7 +195,6 @@ export default {
channel: -1, // any num < 256, cuz channel only use for determining whether add unit text
})
}
this.$emit('refreshChart', this.chartData[chartID])
},
changeUnitByAxis () {
if (this.axis === 'x') {
@@ -1,11 +1,11 @@
<template>
<div class="row flex sm12 xl12 md12 xs12 px-0 py-0 mx-0 my-0">
<div class="flex sm6 xl6 md6 xs6 px-0 mx-0">
<p class="display-6"> Window Start <p/>
<p class="display-6"> WINDOW START <p/>
<va-input
@keyup.enter="startItemChange(startTimeInput)"
@blur="startItemChange(startTimeInput)"
v-model="startTimeInput"
:readonly="useSdCardData || !isInitDone || isDownloading"
class="mb-0 flex-child"
>
<p slot="append" style="margin-right: 0;" small>
@@ -14,11 +14,11 @@
</va-input>
</div>
<div class="flex sm6 xl6 md6 xs6 px-0 mx-0">
<p class="display-6"> Window Size <p/>
<p class="display-6"> WINDOW SIZE <p/>
<va-input
@keyup.enter="lenItemChange(windowLengthInput)"
@blur="lenItemChange(windowLengthInput)"
v-model="windowLengthInput"
:readonly="useSdCardData || !isInitDone || isDownloading"
class="mb-0 flex-child"
>
<p slot="append" style="margin-right: 0;" small>
@@ -63,6 +63,9 @@ export default {
'startTime',
'endTime',
'windowLength',
'useSdCardData',
'isInitDone',
'isDownloading',
]),
},
created () {
@@ -1,12 +1,18 @@
<template>
<div>
<va-button flat small color="gray" icon="fa fa-download" @click.stop="downloadCSV()"/>
<va-popover :message="`SD card data`" placement="top">
<div v-if="metaList[0]">
<va-button v-if="metaList[0].sd_data_uploaded" flat small color="primary" icon="fa fa-download" @click.stop="downloadSdCard()"/>
</div>
</va-popover>
<va-popover :message="`BLE data`" placement="top">
<va-button flat small color="success" icon="fa fa-download" @click.stop="downloadCsv()"/>
</va-popover>
</div>
</template>
<script>
// import { download } from '@/data/download/Download'
// import { exportData } from '@/data/export/export'
import api from '@/data/api/index'
import { mapFields } from 'vuex-map-fields'
export default {
@@ -29,14 +35,24 @@ export default {
watch: {
},
methods: {
downloadCSV: async function () {
for (let i = 0; i < this.metaList.length; i++) {
const meta = this.metaList[i]
if (meta.device.library_name.indexOf('Elite') >= 0) {
window.open('http://' + location.href.split('/')[2].split(':')[0] + '/#/download/excel/' + meta.id)
} else if (meta.device.library_name.indexOf('Neulive') >= 0) {
window.open('http://' + location.href.split('/')[2].split(':')[0] + '/#/download/csv/' + meta.id)
}
async downloadCsv () {
const href = location.href.split('/')[2]
const meta = this.metaList[0]
const connectCheck = await api.connection.check()
if (connectCheck.status === 200) {
window.open('http://' + href + '/#/download/online/csv/' + meta.id)
} else {
window.open('http://' + href + '/#/download/offline/csv/' + meta.id)
}
},
async downloadSdCard () {
const href = location.href.split('/')[2]
const meta = this.metaList[0]
const connectCheck = await api.connection.check()
if (connectCheck.status === 200) {
window.open('http://' + href + '/#/download/online/csv/' + meta.id + '/sd')
} else {
window.open('http://' + href + '/#/download/offline/csv/' + meta.id + '/sd')
}
},
},
@@ -1,6 +1,7 @@
<template>
<div>
<va-button flat small color="gray" icon="fa fa-file-text-o" @click.stop="editFileConfirm()"> {{ filename }} </va-button>
<span class="display-6"> {{ filename }} </span>
<va-button flat small color="gray" icon="fa fa-pencil" @click.stop="editFileConfirm()" />
<va-modal
class="flex sm12 xl12 md12 xs12"
v-model="showModal.editFile"
@@ -91,6 +92,7 @@ export default {
editFile: async function () {
if (this.editInfo.success === true) {
await api.meta.update(this.editInfo.info.id, { name: this.editInfo.changedName })
await api.meta.update30(this.editInfo.info.uuid, { name: this.editInfo.changedName })
this.editInfo.name = this.editInfo.changedName
this.filename = this.editInfo.changedName
}
@@ -0,0 +1,135 @@
<template>
<div>
<span class="box_inline-block display-6"> RUN FILTER </span>
<va-toggle
class="box_inline-block"
flat
small
v-model="useFilter"
@input="refreshChart()"
:disable="!isInitDone || isDownloading"
/>
<div class="row mt-2" v-if="initDone">
<div v-for="(filter, filterIndex) in filterList" :key="filter.id" :index="filterIndex">
<global-filter :ref="'filter_ref'" :chartID="chartID" :filterIndex="filterIndex" />
</div>
</div>
<div style="text-align: center;">
<va-button color="success" small :disabled="useFilter" @click="openAllFilter()">
Activate All Filters
</va-button>
<va-button color="danger" small :disabled="useFilter" @click="closeAllFilter()">
Deactivate All Filters
</va-button>
</div>
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields'
import GlobalFilter from '@/components/analysis/replay/toolbox/GlobalFilter.vue'
export default {
name: 'FilterToggle',
components: {
GlobalFilter,
},
computed: {
...mapFields('replay', [
'chartData',
'useFilter',
'isInitDone',
'isDownloading',
]),
},
props: {
},
created () {
},
data () {
return {
initDone: false,
filterList: [],
chartID: 1,
maxFilterNum: 3,
}
},
mqtt: {
},
methods: {
init () {
this.useFilter = false
this.initDone = true
},
getFilterStatus () {
let isAllClose = true
let isReady = true
this.chartData[this.chartID].series.forEach(_series => {
if (_series.filter.some(_filter => _filter.filter !== null)) {
isAllClose = false
}
})
if (this.$refs.filter_ref.some(_ref => _ref.isLoading === true)) {
isReady = false
}
return { isAllClose: isAllClose, isReady: isReady }
},
refreshChart () {
const { isAllClose, isReady } = this.getFilterStatus()
if (!isAllClose && isReady) {
this.$emit('refreshChart')
} else {
this.useFilter = false
this.pageToast('Please open either filter or wait for filter loading done')
}
},
addFilter () {
let availableID
for (let i = 0; i < this.maxFilterNum; i++) {
if (this.filterList.some(block => block.id === i) === false) { // ref: https://www.tutorialrepublic.com/faq/how-to-check-if-an-array-includes-an-object-in-javascript.php
availableID = i
break
}
}
this.filterList.push({ id: availableID })
},
async openAllFilter () {
this.closeAllFilter()
for (let i = 0; i < this.maxFilterNum; i++) {
await this.$refs.filter_ref[i].updateFilterInfo(i)
}
},
closeAllFilter () {
this.chartData[this.chartID].series.forEach(_series => {
_series.filter.forEach(_filter => {
_filter.filter = null
})
})
},
pageToast: function (mes) {
this.showToast(
mes,
{
icon: 'fa-bell',
position: 'bottom-right',
duration: 4000,
},
)
},
},
mounted () {
for (let i = 0; i < this.maxFilterNum; i++) {
this.addFilter()
}
},
}
</script>
<style lang="scss">
.box_inline-block {
padding: 1px;
margin: 1px;
display: inline-block;
}
</style>
@@ -0,0 +1,249 @@
<template>
<div class="row pl-2 ma-0">
<va-checkbox
v-if="!isLoading"
:disabled="useFilter"
:key="filterKey"
:value="isActive"
@input="updateFilterInfo(filterIndex)"
/>
<fulfilling-bouncing-circle-spinner
v-if="isLoading"
:animation-duration="3000"
:color="'#6c7fee'"
:size="20"
class="mx-2 mt-3"
/>
<div class="filter-input my-0 pa-0 mx-1">
<va-select
small
noClear
:label="'FILTER ' + (filterIndex + 1)"
:disabled="useFilter"
v-model="selectFilter"
textBy="name"
:options="optionFilter"
@input="clearThenReload()"
/>
</div>
<va-input
:label="'order'"
:disabled="useFilter"
class="mx-1 order-input"
:class="inputDisabledStyle"
v-model.lazy="order"
@change="clearThenReload()"
:error="showError"
/>
<va-input
v-if="[1, 2, 3].indexOf(selectFilter.id) >= 0"
:label="'FREQ'"
:disabled="useFilter"
class="mx-1 freq-input"
:class="inputDisabledStyle"
v-model.lazy="freq2"
@change="clearThenReload()"
:error="showError"
>
<p slot="append" style="margin-right: 0;" small>
Hz
</p>
</va-input>
<va-input
v-if="selectFilter.id > 3"
:label="'FREQ MIN'"
:disabled="useFilter"
class="mx-1 freq-input"
:class="inputDisabledStyle"
v-model.lazy="freq0"
@change="clearThenReload()"
:error="showError"
>
<p slot="append" style="margin-right: 0;" small>
Hz
</p>
</va-input>
<va-input
v-if="selectFilter.id > 3"
:label="'FREQ MAX'"
:disabled="useFilter"
class="mx-1 freq-input"
:class="inputDisabledStyle"
v-model.lazy="freq1"
@change="clearThenReload()"
:error="showError"
>
<p slot="append" style="margin-right: 0;" small>
Hz
</p>
</va-input>
</div>
</template>
<script>
import api from '@/data/api'
import { mapFields } from 'vuex-map-fields'
import { IIRFilter } from '@/data/task/filter/IIRFilter.js'
import { FulfillingBouncingCircleSpinner } from 'epic-spinners'
export default {
name: 'global-filter',
props: {
chartID: {
type: Number,
required: true,
},
filterIndex: {
type: Number,
required: true,
},
},
components: {
FulfillingBouncingCircleSpinner,
},
computed: {
...mapFields('replay', [
'chartData',
'metaList',
'useFilter',
'currentScale',
]),
isActive () {
if (this.data && this.data.series.length > 0 && this.data.series.every(item => item != null)) {
return !!this.data.series.every(item => item.filter[this.filterIndex].filter != null)
}
return false
},
inputDisabledStyle () {
return this.useFilter ? 'va-select--disabled' : ''
},
},
data () {
return {
filterKey: 0,
data: null,
selectFilter: {
id: 1,
name: 'notch',
},
order: 20,
freq0: 60,
freq1: 60,
freq2: 60,
optionFilter: [
{
id: 1,
name: 'notch',
},
{
id: 2,
name: 'lowpass',
},
{
id: 3,
name: 'highpass',
},
{
id: 4,
name: 'bandstop',
},
{
id: 5,
name: 'bandpass',
},
],
showError: false,
isLoading: false,
}
},
methods: {
clearFilter () {
for (let i = 0; i < this.data.series.length; i++) {
if (this.data.series[i].filter[this.filterIndex].filter != null) {
this.data.series[i].filter[this.filterIndex].filter = null
}
}
},
clearThenReload () {
this.clearFilter()
this.updateFilterInfo(this.filterIndex)
},
async updateFilterInfo (index) {
if (this.isActive) {
this.clearFilter()
return
}
this.isLoading = true
let fs = 0
let sos = []
for (let i = 0; i < this.data.series.length; i++) {
const _fs = this.metaList[0].parameter_set.SAMPLE_RATE / parseInt(this.currentScale) // currentScale: for the case 10, 100, 1000:1
const _chNum = Object.keys(this.metaList[0].raw_data).length
if (fs !== _fs) {
fs = _fs
const ret = await api.analysis.IIRFilter({
type: this.selectFilter.name,
order: this.order,
fs: parseFloat(_fs / _chNum),
f0: parseFloat(this.freq0),
f1: parseFloat(this.freq1),
f2: parseFloat(this.freq2),
})
if (ret.status !== 200) {
this.showError = true
this.isLoading = false
return false
} else {
this.showError = false
}
const _sosRet = ret.data.split('/')
const _sos = []
_sosRet.forEach((v, vi) => {
const _tmp = v.split(',')
const _arr = []
_tmp.forEach(s => {
_arr.push(parseFloat(s))
})
_sos.push(_arr)
})
sos = _sos
}
this.data.series[i].filter[index].type = this.selectFilter.name
this.data.series[i].filter[index].order = this.order
// sampling rate changed by channel number
this.data.series[i].filter[index].fs = parseFloat(_fs / _chNum)
this.data.series[i].filter[index].f0 = parseFloat(this.freq0)
this.data.series[i].filter[index].f1 = parseFloat(this.freq1)
this.data.series[i].filter[index].f2 = parseFloat(this.freq2)
this.data.series[i].filter[index].sos = sos
this.data.series[i].filter[index].filter = IIRFilter.init(sos)
}
this.filterKey++
this.isLoading = false
},
},
mounted () {
this.data = this.chartData[this.chartID]
},
}
</script>
<style lang='scss'>
.filter-input {
max-width: 100px;
}
.freq-input {
max-width: 100px;
}
.order-input {
max-width: 50px;
}
.filter-apply-button {
max-width: 25px;
}
</style>
@@ -1,7 +1,14 @@
<template>
<div>
<span class="box_inline-block"> use SD card data </span>
<va-toggle :disable="disableToggle" flat small class="box_inline-block" v-model="useSdCardData"/>
<div v-if="showToggle">
<span class="box_inline-block display-6"> USE SD CARD DATA </span>
<va-toggle
class="box_inline-block"
flat
small
v-model="useSdCardData"
@input="refreshChart()"
:disable="!isInitDone || isDownloading"
/>
</div>
</template>
@@ -17,6 +24,13 @@ export default {
...mapFields('replay', [
'metaList',
'useSdCardData',
'currentChunkDataSplitIndex',
'startTime',
'endTime',
'startTimeCurrent',
'endTimeCurrent',
'isInitDone',
'isDownloading',
]),
},
props: {
@@ -29,18 +43,26 @@ export default {
},
data () {
return {
disableToggle: true,
showToggle: false,
}
},
mqtt: {
},
methods: {
init () {
if (this.metaList[this.metaIndex].sd_data_uploaded === true) {
this.disableToggle = false
this.useSdCardData = false
if (this.metaList[this.metaIndex].sd_data_uploaded) {
this.showToggle = true
}
},
refreshChart () {
this.startTime = this.startTimeCurrent = 0
this.currentChunkDataSplitIndex = 1
if (!this.useSdCardData) {
this.endTime = this.endTimeCurrent = this.metaList[this.metaIndex].time_duration
this.$emit('redrawAllChart')
} else {
this.disableToggle = true
this.useSdCardData = false
this.$emit('refreshChart')
}
},
},
@@ -1,9 +1,9 @@
<template>
<va-tree-root>
<va-tree-category label="Experiment Setting" :isOpen="false">
<pre><div style="text-transform: none;" class="title mb-3" :style="{color: this.$themes.primary}" v-html="content"></div></pre>
</va-tree-category>
</va-tree-root>
<div>
<span class="display-6"> EXPERIMENT SETTING </span>
<va-button flat color="gray" icon="fa fa-info-circle" style="margin: 0.001rem; padding: 0.001rem;" @click.stop="isOpen = !isOpen"/>
<pre><div v-if="isOpen" class="mb-3" :style="{color: this.$themes.dark}" v-html="content"></div></pre>
</div>
</template>
<script>
@@ -24,211 +24,16 @@ export default {
},
data () {
return {
isOpen: false,
deviceName: '',
content: '',
params: {},
showDict: {},
// [region] for neulive
ampGainOptions: [
{
id: 0,
description: '25',
},
{
id: 1,
description: '100',
},
{
id: 2,
description: '400',
},
{
id: 3,
description: '800',
},
],
stimulateElectrodeOptions: [
{
label: 'Monopolar',
value: 0,
},
{
label: 'Bipolar',
value: 1,
},
],
cycleNumberOptions: [
{
label: 'limite',
value: 0,
},
{
label: 'Unlimite',
value: 1,
},
],
patternModeOptions: [
{
label: 'Monophasic',
value: 0,
},
{
label: 'Biphasic',
value: 1,
},
],
unitOptions: [
{
label: 's',
value: 0,
},
{
label: 'ms',
value: 1,
},
{
label: 'us',
value: 2,
},
],
// [Endregion]
// [region] for elite
workingModeOptions: [
{
id: 0,
description: 'I-V Curve',
},
{
id: 1,
description: 'Cycle I-V',
},
{
id: 2,
description: 'Function Generator',
},
{
id: 3,
description: 'R-T Graph',
},
{
id: 4,
description: 'V-T Graph',
},
{
id: 5,
description: 'I-T Graph',
},
{
id: 6,
description: 'ADC_Test',
},
{
id: 7,
description: 'Constant Current',
},
{
id: 8,
description: 'Cyclic Voltammetry',
},
{
id: 9,
description: 'Cyclic Voltammetry-hi-cycles',
},
{
id: 10,
description: 'Linear Sweep Voltammetry',
},
{
id: 11,
description: 'Chronoamperometric Graph',
},
],
currentRangeOptions: [
{
value: 0,
label: '<40 μA',
},
{
value: 1,
label: '30-1350 μA',
},
{
value: 2,
label: '>1000 μA',
},
{
value: 3,
label: 'Auto',
},
],
stepTimeOptions: [
{
value: 0,
label: '0.5s',
},
{
value: 1,
label: '1.0s',
},
{
value: 2,
label: '2.0s',
},
],
resisterRangeOptions: [
{
value: 0,
label: '>50 kΩ',
},
{
value: 1,
label: '2-100 kΩ',
},
{
value: 2,
label: '<10 kΩ',
},
{
value: 3,
label: 'Auto',
},
],
eliteSampleRateOptions: [
{
value: 0,
label: '100',
},
{
value: 1,
label: '10',
},
{
value: 2,
label: '5',
},
{
value: 3,
label: '1',
},
{
value: 4,
label: '0.1',
},
{
value: 5,
label: '0.01',
},
],
chargeOptions: [
{
value: 0,
label: 'Charge',
},
{
value: 1,
label: 'Discharge',
},
],
// [Endregion]
chipChannelDict: {}, // for 3.5ex
channelList: [], // total selected channels
sampleRateList: [], // each chip per channel sample rate
preprocessKey: [],
ignoreKey: [],
}
},
computed: {
@@ -239,185 +44,105 @@ export default {
watch: {
},
methods: {
init (deviceName) {
this.deviceName = deviceName
init (_deviceName) {
this.deviceName = _deviceName.replace(/[.]/g, '') // remove all '.'
const meta = this.metaList[this.metaIndex]
const params = meta.parameter_set
this.content += 'Duration: ' + parseInt(parseInt(meta.time_duration / 1e6) / 60) + ' m ' + parseInt((meta.time_duration % 6e7) / 1e6) + ' s<br/>'
this.content += '<br/>Parameter: <br/>'
this.params = meta.parameter_set
this.content += 'DURATION: ' + parseInt(parseInt(meta.time_duration / 1e6) / 60) + ' m ' + parseInt((meta.time_duration % 6e7) / 1e6) + ' s<br/>'
if (this.deviceName.startsWith('Neulive')) {
const recordingParam = paramTable[this.deviceName].showParameterRec
const stimulationParam = paramTable[this.deviceName].showParameterSti
// recording param
this.content += '<br/> Recording: <br/>'
for (const [key, value] of Object.entries(params)) {
if (!(key.includes('sti') || key.includes('STI'))) {
let showKey = ''
let showValue = value
if (key === 'RECORDING_CH' || key === 'AXIS_CH' || key === 'AMP_GAIN' || key === 'SAMPLE_RATE') {
if (key === 'RECORDING_CH') {
showKey = 'Channels'
// convert decimal to binary
const binaryChannelValue = showValue.toString(2)
const channelValue = binaryChannelValue.split('')
showValue = ''
channelValue.forEach(function (value, i) {
if (value === '1') {
showValue += `${i + 1} `
}
})
if (showValue === '') {
showValue = 'None'
}
} else if (key === 'AXIS_CH') {
showKey = '3-AXIS'
// convert decimal to binary
const binaryChannelValue = showValue.toString(2)
const channelValue = binaryChannelValue.split('').reverse()
showValue = ''
channelValue.forEach(function (value, i) {
if (value === '1') {
if (i === 0) {
showValue += 'x '
} else if (i === 1) {
showValue += 'y '
} else if (i === 2) {
showValue += 'z '
} else if (i === 3) {
showValue += 'mag '
}
}
})
if (showValue === '') {
showValue = 'None'
}
} else if (key === 'AMP_GAIN') {
showKey = 'Amp gain'
const targetID = (value + 1) % 4
for (let i = 0; i < this.ampGainOptions.length; i++) {
if (this.ampGainOptions[i].id === targetID) {
showValue = this.ampGainOptions[i].description
}
}
} else if (key === 'SAMPLE_RATE') {
showKey = 'Sample Rate'
showValue = value
}
this.content += ' ' + showKey + ': ' + showValue + '<br/>'
}
}
}
// stimulation param
this.content += '<br/> Stimulation: <br/>'
for (const [key, value] of Object.entries(params)) {
if (key.includes('sti') || key.includes('STI')) {
let showKey = ''
let showValue = value
if (key === 'STI_H_BRIDGE' || key === 'STI_CHANNEL' || key === 'STI_POLARITY' || key === 'CONTINUOUS_STI' ||
key === 'STI_AMPLITUDE' || key === 'STI_PATTERN' || key === 'STI_T1' || key === 'STI_T2' || key === 'STI_T3' || key === 'STI_T4') {
if (key === 'STI_H_BRIDGE') {
showKey = 'ELECTRODE'
if (value === true) {
showValue = this.stimulateElectrodeOptions[1].label
} else {
showValue = this.stimulateElectrodeOptions[0].label
}
} else if (key === 'STI_CHANNEL' || key === 'STI_POLARITY') {
if (key === 'STI_CHANNEL') {
showKey = 'Channels'
} else {
showKey = 'Polarity'
}
// convert decimal to binary
const binaryChannelValue = showValue.toString(2)
const channelValue = binaryChannelValue.split('')
showValue = ''
channelValue.forEach(function (value, i) {
if (value === '1') {
showValue += `${i + 1} `
}
})
if (showValue === '') {
showValue = 'None'
}
} else if (key === 'CONTINUOUS_STI') {
showKey = 'CYCLE NUMBER'
if (value === true) {
showValue = this.cycleNumberOptions[1].label
} else {
showValue = this.cycleNumberOptions[0].label
}
} else if (key === 'STI_AMPLITUDE') {
showKey = 'AMPLITUDE (uA)'
showValue = value * 5
} else if (key === 'STI_PATTERN') {
showKey = 'PATTERN'
if (value === true) {
showValue = this.patternModeOptions[1].label
} else {
showValue = this.patternModeOptions[0].label
}
} else if (key === 'STI_T1') {
showKey = 'T1 Range'
showValue = `${value[0] * 10 / 1000} ms` // 10 is cuz 1 value = 10us, [0] is cuz now all values in array are same
} else if (key === 'STI_T2') {
showKey = 'T2 Range'
showValue = `${value[0] * 10 / 1000} ms` // 10 is cuz 1 value = 10us
} else if (key === 'STI_T3') {
showKey = 'T3 Range'
showValue = `${value[0] * 10 / 1000} ms` // 10 is cuz 1 value = 10us
} else if (key === 'STI_T4') {
showKey = 'T4 Range'
showValue = `${value[0] * 10 / 1000} ms` // 10 is cuz 1 value = 10us
}
this.content += ' ' + showKey + ': ' + showValue + '<br/>'
}
}
}
if ('STI_T1' in params && 'STI_T2' in params && 'STI_T3' in params && 'STI_T4' in params) {
const period = (params.STI_T1[0] + params.STI_T2[0] + params.STI_T3[0] + params.STI_T4[0]) * 10 / 1000 // 10 is cuz 1 value = 10us
this.content += ' Frequency: ' + String(1000 / period) + ' Hz' + '<br/>' // 1000 is cuz 1s = 1000ms
this.content += ' Period: ' + String(period) + ' ms' + '<br/>'
}
} else if (this.deviceName === 'EliteZM15' || this.deviceName === 'EliteEIS') {
const mode = params.MODE
const modeName = paramTable[this.deviceName].MODE[mode].name
const showParams = paramTable[this.deviceName].MODE[mode].showParameter
this.showDict.MODE = modeName
if (modeName === 'Pulse Sensing') {
const duplicateParam = ['V_initial', 't_pulse', 'CURR_REC_START', 'CURR_REC_END']
for (let i = 0; i < 4; i++) {
const key = 'segment_order_' + String(i)
const order = paramTable[this.deviceName][key].rev_func(params[key])
if (parseInt(order) !== 0) { // if === 0, 代表沒有使用這個block
for (let key of duplicateParam) {
key = key + '_' + String(i)
this.fillShowDict(key, params[key])
}
}
}
const remainParams = showParams.filter((e) => { // 差集
return duplicateParam.indexOf(e) === -1
})
for (const key of remainParams) {
this.fillShowDict(key, params[key])
this.content += '<br/>RECORDING: <br/><br/>'
this.preprocessKey.push('SAMPLE_RATE')
const totalSampleRate = paramTable[this.deviceName].SAMPLE_RATE.rev_func(this.params.SAMPLE_RATE)
let chipAmount = 1
if (_deviceName === 'Neulive3.5ex') {
this.preprocessKey.push(...['REC_CH_DEV00', 'REC_CH_DEV01'])
chipAmount = 2
for (let i = 1, len = chipAmount; i <= len; i++) {
this.chipChannelDict['chip' + String(i)] = []
}
this.chipChannelDict.chip1 = paramTable[this.deviceName].REC_CH_DEV00.rev_func(this.params.REC_CH_DEV00)
this.chipChannelDict.chip2 = paramTable[this.deviceName].REC_CH_DEV01.rev_func(this.params.REC_CH_DEV01)
this.channelList.push(...this.chipChannelDict.chip1)
this.channelList.push(...this.chipChannelDict.chip2)
this.sampleRateList.push(parseInt(totalSampleRate / this.chipChannelDict.chip1.length))
this.sampleRateList.push(parseInt(totalSampleRate / this.chipChannelDict.chip2.length))
} else if (_deviceName === 'Neulive3.5') {
this.preprocessKey.push(...['RECORDING_CH_L', 'RECORDING_CH_H'])
this.channelList.push(...paramTable[this.deviceName].RECORDING_CH_L.rev_func(this.params.RECORDING_CH_L))
this.channelList.push(...paramTable[this.deviceName].RECORDING_CH_H.rev_func(this.params.RECORDING_CH_H))
this.sampleRateList.push(parseInt(totalSampleRate / this.channelList.length))
} else {
for (const key of showParams) {
this.fillShowDict(key, params[key])
this.preprocessKey.push(...['RECORDING_CH'])
this.channelList.push(...paramTable[this.deviceName].RECORDING_CH.rev_func(this.params.RECORDING_CH))
this.sampleRateList.push(parseInt(totalSampleRate / this.channelList.length))
}
const chipChannelDictKeys = Object.keys(this.chipChannelDict)
if (chipAmount === 1) {
this.content += ' CHANNELS: ' + this.channelList.toString() + '<br/>'
this.content += ' SAMPLING RATE PER CHANNEL: ' + (this.sampleRateList[0] / 1e3).toFixed(1) + 'k<br/>'
} else if (chipAmount > 1) { // e.g. 3.5ex
for (const [chipNumber, channels] of Object.entries(this.chipChannelDict)) {
this.content += ' CHANNELS (' + chipNumber + '): ' + channels.toString() + '<br/>'
}
for (let i = 0, len = chipAmount; i < len; i++) {
this.content += ' SAMPLING RATE PER CHANNEL (' + chipChannelDictKeys[i] + '): ' + (this.sampleRateList[i] / 1e3).toFixed(1) + 'k<br/>'
}
}
for (const key of recordingParam) {
if (!this.preprocessKey.includes(key)) {
this.fillShowDict(key)
}
}
this.dumpDict()
// stimulation param
this.content += '<br/>STIMULATION: <br/><br/>'
if (+this.params.CONTINUOUS_STI === 1) { // +: bool to int, ref: https://stackoverflow.com/questions/7820683/convert-boolean-result-into-number-integer
this.ignoreKey.push(...['STI_CYCLE_LIST', 'STI_CYCLE_CH0', 'REPEAT_TIME_littleEndian', 'REPEAT_INTERVAL_littleEndian'])
}
for (const key of stimulationParam) {
if (!this.ignoreKey.includes(key)) {
this.fillShowDict(key)
}
}
this.dumpDict()
}
this.dump()
this.reset()
},
fillShowDict (key, value) {
const expVal = paramTable[this.deviceName][key].rev_func(value)
fillShowDict (key) {
const value = this.params[key]
let expVal
if (key === 'STI_CHANNEL') {
expVal = paramTable[this.deviceName][key].rev_func(value, this.params.STI_POLARITY)
} else if (key === 'STI_POLARITY') {
expVal = paramTable[this.deviceName][key].rev_func(value, this.params.STI_CHANNEL)
} else {
expVal = paramTable[this.deviceName][key].rev_func(value)
}
const name = paramTable[this.deviceName][key].showName
const unit = paramTable[this.deviceName][key].defaultUnit
this.showDict[name] = unit !== undefined ? expVal.toString() + unit : expVal.toString()
const defaultUnit = paramTable[this.deviceName][key].defaultUnit
const unitScale = defaultUnit !== undefined ? paramTable[this.deviceName][key].unit[defaultUnit] : 1
this.showDict[name] = defaultUnit !== undefined ? (expVal / unitScale).toString() + defaultUnit : expVal.toString()
},
dump () {
dumpDict () {
for (const [showKey, showValue] of Object.entries(this.showDict)) {
this.content += ' ' + showKey + ': ' + showValue + '<br/>'
}
this.showDict = {}
},
reset () {
this.params = {}
this.showDict = {}
this.chipChannelDict = {}
this.channelList.length = 0
this.sampleRateList.length = 0
this.preprocessKey.length = 0
this.ignoreKey.length = 0
},
},
}
@@ -1,6 +1,6 @@
<template>
<div>
<p class="display-6"> Resample: {{ currentScale }} to 1 <p/>
<p class="display-6"> RESAMPLE: {{ currentScale }} to 1 <p/>
</div>
</template>
+18 -37
View File
@@ -3,18 +3,19 @@
<va-card>
<p style="font-size: 125%; text-decoration: underline;"> Channel </p>
<channels/>
<p style="font-size: 125%; text-decoration: underline;"> Detect </p>
<p :style="{color: this.$themes.primary}" class="display-6"> High pass filter </p>
<cutoff-freq/>
<filter-order/>
<sd-card-toggle ref="sd_card_toggle" :metaIndex="0"/>
<set-time-interval ref="set_time_interval" @setTimeInterval="setTimeInterval"/>
<p class="display-6"> High pass filter </p>
<cutoff-freq class="pl-3 ml-1"/>
<filter-order class="pl-3 ml-1"/>
<filter-button ref="filterButtonRef" @loadFilterData="loadFilterData"/>
<p :style="{color: this.$themes.danger}" class="display-6"> Threshold </p>
<p class="display-6"> Threshold </p>
<threshold/>
<br/>
<p class="display-6"> Waveform </p>
<wave-length/>
<prethreshold/>
<refractory-period/>
<wave-length class="pl-3 ml-1" />
<prethreshold class="pl-3 ml-1" />
<refractory-period class="pl-3 ml-1" />
<detect-button ref="detectButtonRef" @loadThresholdData="loadThresholdData"/>
</va-card>
</div>
@@ -22,6 +23,8 @@
<script>
import Channels from '@/components/analysis/spike/item/Channels.vue'
import SdCardToggle from '@/components/analysis/spike/item/SdCardToggle.vue'
import SetTimeInterval from '@/components/analysis/spike/item/SetTimeInterval.vue'
import CutoffFreq from '@/components/analysis/spike/item/CutoffFreq.vue'
import FilterOrder from '@/components/analysis/spike/item/FilterOrder.vue'
import Prethreshold from '@/components/analysis/spike/item/Prethreshold.vue'
@@ -35,6 +38,8 @@ export default {
name: 'control-panel',
components: {
Channels,
SdCardToggle,
SetTimeInterval,
CutoffFreq,
FilterOrder,
FilterButton,
@@ -55,11 +60,13 @@ export default {
mqtt: {
},
methods: {
init () {
init ({ startTime, endTime }) {
this.$refs.sd_card_toggle.init()
this.$refs.set_time_interval.init({ startTime, endTime })
this.enableFilterButton()
},
refreshChart (options) {
this.$emit('refreshChart', options)
setTimeInterval ({ startTime, endTime }) {
this.$emit('setTimeInterval', { startTime: startTime, endTime: endTime })
},
loadThresholdData () {
this.$emit('loadThresholdData')
@@ -87,29 +94,3 @@ export default {
},
}
</script>
<style lang="scss">
.flex-container {
display: flex;
}
.flex-child {
flex: 2;
}
.flex-child1 {
flex: 1;
}
.flex-child2 {
flex: 0;
}
.flex-child:first-child {
margin-right: 1px;
}
.right {
float: right;
}
</style>
+94 -25
View File
@@ -13,6 +13,7 @@
:ref="'control_panel'"
@loadFilterData="loadFilterData"
@loadThresholdData="loadThresholdData"
@setTimeInterval="setTimeInterval"
/>
</div>
</div>
@@ -55,6 +56,7 @@ export default {
computed: {
...mapFields('spike', [
'chartData',
'chartKey',
'chartOverviewData',
'chartDetailData',
'metaList',
@@ -67,10 +69,10 @@ export default {
'waveLength',
'preThreshold',
'refractoryPeriod',
'useSdCardData',
'showError',
]),
},
props: {
},
async created () {
this.chartOverviewData = await this.initChart({
chartAllData: this.chartData,
@@ -96,6 +98,8 @@ export default {
chartAreaClass: ['flex', 'xs12', 'sm12', 'md12', 'xl9', 'pa-0'],
randomColorArray: [],
controllerList: [],
startTime: 0,
endTime: -1,
}
},
mqtt: {
@@ -107,7 +111,7 @@ export default {
const dataStr = utf8decoder.decode(data)
const dataArray = JSON.parse(dataStr)
this.chartOverviewData.series[0].data.push(...dataArray)
dataArray.forEach(d => this.chartOverviewData.series[0].data.push(d))
} else if (topicSplitArray[3] === 'time_mark') {
const utf8decoder = new TextDecoder()
const timeMarkStr = JSON.parse(utf8decoder.decode(data))
@@ -237,9 +241,12 @@ export default {
})
}
// this.updateChartSamplingChart({ chartID: this.chartOverviewData.chartID, seriesIndex: 0, method: function (frame) { return frame[0] } })
this.chartOverviewData.xAxis[0].min = 0
this.chartOverviewData.xAxis[0].max = this.maxTime
this.updateChartSamplingChart({ chartID: this.chartOverviewData.chartID, seriesIndex: 0, method: function (frame) { return frame[0] } })
this.chartOverviewData.xAxis[0].min = this.startTime
this.chartOverviewData.xAxis[0].max = this.endTime
},
getSuffix () {
return this.useSdCardData ? 'sd' : 'ble'
},
loadFilterData: async function () {
this.showChartLoading('overview')
@@ -247,41 +254,49 @@ export default {
this.$refs.control_panel.disableDetectButton()
for (const meta in this.dataInfo) {
// reset overview chart
this.resetOverviewChart()
this.chartOverviewData.series[0].data.length = 0
this.chartOverviewData.series[0].markArea.data.length = 0
this.chartOverviewData.color = ['#000000'] // black
await api.analysis.spikeDetect({
const ret = await api.analysis.spikeDetect({
controller_id: this.controllerList[0].id,
meta_id: meta,
channel: this.selectedChannel,
filter_order: this.filterOrder,
cut_off_freq: this.cutoffFreq,
data_type: 'filter_data',
data_type: 'filter_data_' + this.getSuffix(),
start_time: this.startTime,
end_time: this.endTime,
})
if (ret.status === 200) {
this.showError.freq = false
this.$refs.control_panel.enableDetectButton()
console.log(this.chartOverviewData)
} else {
console.error(ret.data)
if (ret.status === 400) {
this.showError.freq = true
} else if (ret.status === 500) {
this.showError.freq = false
}
const msgList = ret.data.split('\n')
this.pageToast(msgList[msgList.length - 2])
}
this.hideChartLoading('overview')
this.$refs.control_panel.enableDetectButton()
console.log(this.chartOverviewData)
}
},
loadThresholdData: async function () {
this.showChartLoading('detail')
this.chartOverviewData.series[0].markArea.data.length = 0
this.chartOverviewData.color = ['#000000'] // black
for (const meta in this.dataInfo) {
let thresholdValue
if (this.selectedThreshold === 'over') {
thresholdValue = this.upperThreshold
} else if (this.selectedThreshold === 'bellow') {
} else if (this.selectedThreshold === 'below') {
thresholdValue = this.lowerThreshold
}
for (let i = 0; i < this.chartDetailData.series.length; i++) {
this.chartDetailData.series[i].name = ''
this.chartDetailData.series[i].data.length = 0
}
this.resetDetailChart()
await api.analysis.spikeDetect({
controller_id: this.controllerList[0].id,
@@ -294,9 +309,12 @@ export default {
thresholdValue,
],
waveForm: [this.waveLength, this.preThreshold, this.refractoryPeriod],
ret_data_type: 'threshold_data',
data_type: 'threshold_data_' + this.getSuffix(),
start_time: this.startTime,
end_time: this.endTime,
})
}
this.chartKey++
this.hideChartLoading('detail')
},
drawThresholdData (detailSignalArray) {
@@ -329,6 +347,7 @@ export default {
// push legendName
this.chartDetailData.legend.data.push(legendName)
this.chartDetailData.series[i].name = legendName
this.updateChartSamplingChart({ chartID: this.chartDetailData.chartID, seriesIndex: i, method: function (frame) { return frame[0] } })
// set legend and series color
this.chartDetailData.color.push(this.randomColorArray[i])
}
@@ -336,7 +355,7 @@ export default {
this.chartDetailData.xAxis[0].max = parseInt(this.waveLength)
this.chartDetailData.xAxis[0].axisLabel.show = true
},
drawTimeMark (timeMarkArray, period) {
drawTimeMark (timeMarkArray) {
// reset overview
this.chartOverviewData.series[0].markArea.data.length = 0
@@ -348,7 +367,6 @@ export default {
this.randomColorArray.push(randomColor)
if (Number.isInteger(parseInt(timeMarkArray[i]))) { // cuz maybe is '' if no any match after filtering
const timeMark = parseInt(timeMarkArray[i])
let startTime = timeMark - parseInt(this.preThreshold)
let endTime = timeMark + parseInt(this.refractoryPeriod)
if (startTime < 0) {
@@ -393,6 +411,38 @@ export default {
}
}
},
setTimeInterval ({ startTime, endTime }) {
this.startTime = startTime
this.endTime = endTime
this.chartOverviewData.xAxis[0].min = startTime
this.chartOverviewData.xAxis[0].max = endTime
this.resetOverviewChart()
this.resetDetailChart()
this.$refs.control_panel.disableDetectButton()
this.chartKey++
},
resetOverviewChart () {
this.chartOverviewData.series[0].data.length = 0
this.chartOverviewData.series[0].markArea.data.length = 0
this.chartOverviewData.color = ['#000000'] // black
},
resetDetailChart () {
for (let i = 0; i < this.chartDetailData.series.length; i++) {
this.chartDetailData.series[i].data.length = 0
}
this.chartDetailData.series.length = 0
this.chartDetailData.legend.data.length = 0
},
pageToast: function (mes) {
this.showToast(
mes,
{
icon: 'fa-bell',
position: 'bottom-right',
duration: 7000,
},
)
},
...mapActions('spike', [types.chart.init, types.chart.initSelectedChannel, types.chart.register, types.chart.clear, types.chart.updateGridList, types.chart.updateDataZoom]),
...mapActions('spike', [types.meta.init]),
...mapActions('spike', [types.rec.init, types.rec.intoData, types.rec.initDisplaySetting, types.rec.updateDisplaySetting]),
@@ -413,17 +463,36 @@ export default {
const metaIDList = this.$route.params.metaList.split('-')
await this.initMeta({ metaIDList: metaIDList, api: api })
this.metaList = this.getAllMeta()
if (this.$route.params.startTime) {
this.startTime = parseInt(this.$route.params.startTime)
}
if (this.$route.params.endTime) {
this.endTime = parseInt(this.$route.params.endTime)
} else {
this.endTime = parseInt(this.metaList[0].time_duration)
}
if (this.$route.params.sd === undefined || this.$route.params.sd === 'sd') {
this.useSdCardData = true
} else if (this.$route.params.sd === 'ble') {
this.useSdCardData = false
}
this.initSelectedChannelChart({ metaIndex: 0 })
await this.initRec({ api: api })
this.dataInfo = this.getAllRec()
console.log(this.metaList)
console.log(this.dataInfo)
await this.register()
this.$refs.control_panel.init()
this.$refs.control_panel.init({
startTime: this.startTime,
endTime: this.endTime,
})
},
beforeDestroy () {
this.pageMqttUnSub(this.controllerList[0].id)
this.$refs.chart_overview.clear()
this.$refs.chart_detail.clear()
this.resetOverviewChart()
this.resetDetailChart()
for (const id in this.chartData) {
for (const _series of this.chartData[id].series) {
_series.length = 0
+5 -4
View File
@@ -1,7 +1,7 @@
<template>
<div class="flex" :class="cardclass">
<va-card :style="'height: ' + cardheight + ';'">
<chart :ref="'chart_ref'" :style="'width: '+ cardwidth + '; height: ' + cardheight + ';'" :options="data" @dataZoom="updateZoom" :auto-resize="true"></chart>
<chart :ref="'chart_ref'" :key="chartKey" :style="'width: '+ cardwidth + '; height: ' + cardheight + ';'" :options="data" @dataZoom="updateZoom" :auto-resize="true"></chart>
</va-card>
</div>
</template>
@@ -9,6 +9,7 @@
<script>
import { mapMutations } from 'vuex'
import types from '@/store/modules/analysis/spike/types'
import { mapFields } from 'vuex-map-fields'
export default {
name: 'spike-replay-chart',
@@ -43,6 +44,9 @@ export default {
}
},
computed: {
...mapFields('spike', [
'chartKey',
]),
},
watch: {
},
@@ -55,9 +59,6 @@ export default {
updateZoom: function (e) {
this.$emit('dataZoomEvent', e)
},
mergeOptions (options) {
this.$refs.chart_ref.mergeOptions(options)
},
turnOnAxisEventHandler (eventName, query, handler, attr) {
const chartID = 1
this.updateAxisEventHandlerChart({
@@ -1,13 +1,15 @@
<template>
<div class="flex" :class="cardclass">
<va-card :style="'height: ' + cardheight + ';'">
<chart :ref="'chart_ref'" :style="'width: '+ cardwidth + '; height: ' + cardheight + ';'" :options="data" @dataZoom="updateZoom" :auto-resize="true"></chart>
<chart :ref="'chart_ref'" :key="chartKey" :style="'width: '+ cardwidth + '; height: ' + cardheight + ';'" :options="data" @dataZoom="updateZoom" :auto-resize="true"></chart>
</va-card>
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields'
export default {
name: 'spike-overview-chart',
components: {
@@ -41,6 +43,9 @@ export default {
}
},
computed: {
...mapFields('spike', [
'chartKey',
]),
},
watch: {
},
@@ -50,9 +55,6 @@ export default {
this.$refs.chart_ref.resize()
}
},
mergeOptions (options) {
this.$refs.chart_ref.mergeOptions(options)
},
updateZoom: function (e) {
this.$emit('dataZoomEvent', e)
},
@@ -10,6 +10,7 @@
@blur="itemChange(cutoffFreq)"
v-model="cutoffFreq"
class="mb-0"
:error="showError.freq"
>
<p slot="append" style="margin-right: 0;" small>
Hz
@@ -30,6 +31,7 @@ export default {
computed: {
...mapFields('spike', [
'cutoffFreq',
'showError',
]),
},
props: {
@@ -1,6 +1,6 @@
<template>
<div>
<va-button class="right" small @click="startDetect" :disabled="isDisable">
<va-button small @click="startDetect" :disabled="isDisable">
Start Detect
</va-button>
</div>
@@ -21,6 +21,7 @@ export default {
'chartDetailData',
'selectedChannel',
'waveLength',
'refractoryPeriod',
]),
},
props: {
@@ -36,7 +37,11 @@ export default {
},
methods: {
startDetect () {
this.$emit('loadThresholdData')
if (this.refractoryPeriod <= 0) {
this.pageToast('Invalid value')
} else {
this.$emit('loadThresholdData')
}
},
enableButton () {
this.isDisable = false
@@ -44,6 +49,16 @@ export default {
disableButton () {
this.isDisable = true
},
pageToast: function (mes) {
this.showToast(
mes,
{
icon: 'fa-bell',
position: 'bottom-right',
duration: 4000,
},
)
},
...mapMutations('spike', [types.chart.updateAxisLabel]),
},
async mounted () {
@@ -1,6 +1,6 @@
<template>
<div class="flex xl12 md12 sm12 xs12 mb-4">
<va-button class="right" small @click="startFilter" :disabled="isDisable">
<va-button small @click="startFilter" :disabled="isDisable">
Start Filter
</va-button>
</div>
@@ -29,7 +29,9 @@ export default {
},
computed: {
...mapFields('spike', [
'waveLength',
'preThreshold',
'refractoryPeriod',
]),
},
props: {
@@ -44,7 +46,14 @@ export default {
},
methods: {
itemChange (val) {
this.preThreshold = val
let inputVal
if (val > 0) {
inputVal = val
} else {
inputVal = 800
}
this.preThreshold = inputVal
this.refractoryPeriod = this.waveLength - inputVal
},
},
async mounted () {
@@ -5,11 +5,10 @@
</div>
<div class="flex sm5 xl5 md5 xs5 px-0 mx-0">
<va-input
@mouseleave.native="itemChange(refractoryPeriod)"
@keyup.enter="itemChange(refractoryPeriod)"
@blur="itemChange(refractoryPeriod)"
readonly
v-model="refractoryPeriod"
class="mb-0"
:error="refractoryPeriod <= 0"
>
<p slot="append" style="margin-right: 0;" small>
µs
@@ -0,0 +1,64 @@
<template>
<div v-if="showToggle">
<span class="box_inline-block display-6"> Use SD Card Data </span>
<va-toggle
class="box_inline-block"
flat
small
v-model="useSdCardData"
/>
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields'
export default {
name: 'SdCardToggle',
components: {
},
computed: {
...mapFields('spike', [
'metaList',
'useSdCardData',
]),
},
props: {
metaIndex: {
type: Number,
default: 0,
},
},
created () {
},
data () {
return {
showToggle: false,
}
},
mqtt: {
},
methods: {
init () {
if (this.metaList[this.metaIndex].sd_data_uploaded) {
this.showToggle = true
}
},
},
async mounted () {
},
destroyed () {
},
watch: {
},
}
</script>
<style lang="scss">
.box_inline-block {
padding: 1px;
margin: 1px;
display: inline-block;
}
</style>
@@ -0,0 +1,107 @@
<template>
<div class="row flex sm12 xl12 md12 xs12 px-0 py-0 mx-0 my-0">
<div class="flex sm6 xl6 md6 xs6 px-0 mx-0">
<p class="display-6"> START TIME <p/>
<va-input
@change="startTimeChange(startTimeInput)"
v-model.lazy="startTimeInput"
class="mb-0 flex-child"
>
<p slot="append" style="margin-right: 0;" small>
ms
</p>
</va-input>
</div>
<div class="flex sm6 xl6 md6 xs6 px-0 mx-0">
<p class="display-6"> END TIME <p/>
<va-input
@change="endTimeChange(endTimeInput)"
v-model.lazy="endTimeInput"
class="mb-0 flex-child"
>
<p slot="append" style="margin-right: 0;" small>
ms
</p>
</va-input>
</div>
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields'
export default {
name: 'set-start-end-time',
components: {
},
props: {
},
computed: {
...mapFields('spike', [
'metaList',
'useSdCardData',
]),
},
created () {
},
data () {
return {
startTimeInput: 'start',
endTimeInput: 'end',
startTime: 0,
endTime: -1,
}
},
mqtt: {
},
methods: {
init ({ startTime, endTime }) {
this.startTime = startTime
this.endTime = endTime
this.startTimeInput = String(startTime / 1e3)
this.endTimeInput = String(endTime / 1e3)
},
translator (val) {
if (val === 'start') {
return 0
} else if (val === 'end') {
return parseInt(this.metaList[0].time_duration)
}
return parseInt(val * 1e3) // us
},
startTimeChange (val) {
this.startTime = this.translator(val) // us
this.$emit('setTimeInterval', { startTime: this.startTime, endTime: this.endTime })
},
endTimeChange (val) {
this.endTime = this.translator(val) // us
this.$emit('setTimeInterval', { startTime: this.startTime, endTime: this.endTime })
},
},
async mounted () {
},
destroyed () {
},
watch: {
},
}
</script>
<style lang="scss">
.flex-container {
display: flex;
}
.flex-child {
flex: 2;
}
.flex-child2 {
flex: 0;
}
.flex-child:first-child {
margin-right: 1px;
}
</style>
@@ -6,26 +6,27 @@
v-model="selectedThreshold"
:option="option"
:label="`${option}`"
@click.native.stop="validateThreshold()"
/>
<va-input
v-if="selectedThreshold === 'over'"
@mouseleave.native="itemChange(upperThreshold, 'upper')"
@keyup.enter="itemChange(upperThreshold, 'upper')"
@blur="itemChange(upperThreshold, 'upper')"
v-model="upperThreshold"
@change="validateThreshold()"
v-model.lazy="upperThreshold"
class="mb-0"
:error="showError"
:error-messages="errorMsg"
>
<p slot="append" style="margin-right: 0;" small>
µV
</p>
</va-input>
<va-input
v-if="selectedThreshold === 'bellow'"
@mouseleave.native="itemChange(lowerThreshold, 'lower')"
@keyup.enter="itemChange(lowerThreshold, 'lower')"
@blur="itemChange(lowerThreshold, 'lower')"
v-model="lowerThreshold"
v-if="selectedThreshold === 'below'"
@change="validateThreshold()"
v-model.lazy="lowerThreshold"
class="mb-0"
:error="showError"
:error-messages="errorMsg"
>
<p slot="append" style="margin-right: 0;" small>
µV
@@ -55,22 +56,36 @@ export default {
},
data () {
return {
showError: false,
errorMsg: [''],
options: [
// 'Auto',
'over',
'bellow',
'below',
],
}
},
mqtt: {
},
methods: {
itemChange (val, type) {
if (type === 'upper') {
this.upperThreshold = val
} else if (type === 'lower') {
this.lowerThreshold = val
validateThreshold () {
if (this.selectedThreshold === 'over') {
if (this.upperThreshold < 0) {
this.upperThreshold = 0
this.showError = true
this.errorMsg[0] = 'need >= 0'
return
}
} else if (this.selectedThreshold === 'below') {
if (this.lowerThreshold > 0) {
this.lowerThreshold = 0
this.showError = true
this.errorMsg[0] = 'need <= 0'
return
}
}
this.showError = false
this.errorMsg[0] = ''
},
},
async mounted () {
@@ -30,6 +30,8 @@ export default {
computed: {
...mapFields('spike', [
'waveLength',
'preThreshold',
'refractoryPeriod',
]),
},
props: {
@@ -44,7 +46,14 @@ export default {
},
methods: {
itemChange (val) {
this.waveLength = val
let inputVal
if (val > 0) {
inputVal = val
} else {
inputVal = 1600
}
this.waveLength = inputVal
this.refractoryPeriod = inputVal - this.preThreshold
},
},
async mounted () {
@@ -15,6 +15,7 @@
</template>
<script>
export default {
name: 'progress-bar',
data () {
@@ -24,28 +25,57 @@ export default {
}
},
props: {
// unit: sec
time: {
type: String,
meta: { // control system 2.0 meta
type: Object,
required: true,
},
},
methods: {
animateValues () {
async estimateTime () {
let sampleRate
if (this.meta.device.library_name !== 'Neulive3.5ex') {
sampleRate = this.meta.parameter_set.SAMPLE_RATE
} else {
const channelList = JSON.parse(this.meta.channels)
const chip1Channels = channelList.filter(c => c >= 0 && c <= 31)
const chip2Channels = channelList.filter(c => c >= 32 && c <= 63)
if (chip1Channels.length !== 0 && chip2Channels.length !== 0) {
sampleRate = this.meta.parameter_set.SAMPLE_RATE * 2
} else {
sampleRate = this.meta.parameter_set.SAMPLE_RATE
}
}
return parseFloat(this.meta.time_duration / 1e6) * parseFloat(sampleRate / 1e3) * 0.006 * 4
// 經驗公式
// time_duration/1e6: us -> s
// sampleRate/1e3: sample rate unit to (k)
// *4: control system 2.0 upload rate is about 250kb/s
},
animateValues (estimatedTime) { // estimatedTime unit: seconds
let currentTime = this.progressValue = 0
this.timeInterval = setInterval(() => {
this.progressValue += 1
if (this.progressValue === 99) {
if (estimatedTime < 1) {
this.progressValue = 99
clearInterval(this.timeInterval)
} else if (this.progressValue < 90) {
currentTime += (1 / estimatedTime)
this.progressValue = parseInt(currentTime * 100)
} else if (this.progressValue >= 90 && this.progressValue < 99) {
currentTime += (1 / (estimatedTime * 10)) // *10: buffer
this.progressValue = parseInt(currentTime * 100)
} else {
this.progressValue = 99
clearInterval(this.timeInterval)
}
}, parseInt(this.time * 10))
}, 1000)
},
progressDone () {
this.progressValue = 100
},
},
mounted () {
this.animateValues()
async mounted () {
const estimatedTime = await this.estimateTime()
this.animateValues(estimatedTime)
},
destroyed () {
clearInterval(this.timeInterval)
@@ -8,11 +8,11 @@
hide-default-actions
>
<progressBar
:time="time"
:meta="meta"
:ref="'progressBarRef'"
/>
<div class="flex-center sm12 xl12 md12 xs12">
<va-button @click="hide">Cancel</va-button>
<va-button @click="hide">Close</va-button>
</div>
</va-modal>
</template>
@@ -29,9 +29,8 @@ export default {
type: String,
default: '',
},
// unit: sec
time: {
type: String,
meta: {
type: Object,
required: true,
},
},
+80 -5
View File
@@ -38,7 +38,7 @@
<div class="row flex">
<div
class="flex sm12 xl6 md12 xs12 ma-0"
class="flex sm12 xl12 md12 xs12 ma-0"
v-for="device in controller.devices" :key="device.addr"
>
<div v-if="!controller.isScan || device.status == 'connected'">
@@ -94,6 +94,35 @@
</va-card>
</div>
</div>
<div
class="flex sm12 xl6 md12 xs12 ma-0"
v-for="trigger in triggerList" :key="trigger.mac"
>
<div v-if="!controller.isScan">
<va-card
style="background-color: #f8f8f8; box-shadow: 5px 5px 0 0 #f1f1f1;"
class="ma-0 pa-0"
>
<va-item :key="trigger.mac" class="pa-0 ma-0"
>
<!-- <va-item-section avatar class="ml-1">
<va-avatar style="border-radius: 0;">
<img :src="trigger.pic" :alt="trigger.name">
</va-avatar>
</va-item-section> -->
<va-item-section class="ml-3">
<va-item-label>
{{ trigger.name }}
<va-badge class="px-1 py-0 ml-2 mt-0" color="success">connected</va-badge>
</va-item-label>
<va-item-label caption>
{{ trigger.mac }}
</va-item-label>
</va-item-section>
</va-item>
</va-card>
</div>
</div>
</div>
</div>
@@ -140,12 +169,18 @@
import api from '@/data/api/index'
import { taskInfo } from '@/data/task/TaskInfo'
import { FulfillingBouncingCircleSpinner } from 'epic-spinners'
import { mapFields } from 'vuex-map-fields'
export default {
name: 'devices-overview',
components: {
FulfillingBouncingCircleSpinner,
},
computed: {
...mapFields('device', [
'triggerList',
]),
},
created () {
},
data () {
@@ -248,6 +283,44 @@ export default {
})
}
},
'+/data-server/trigger_manager/#' (data, topic) {
const mes = new TextDecoder('utf-8').decode(data)
const topicSplit = topic.split('/')
const baseIndex = topicSplit.indexOf('trigger_manager')
const actionIndex = baseIndex + 1
const action = topicSplit[actionIndex]
const macIndex = baseIndex + 2
let _mac
if (topicSplit.length - 1 >= macIndex) {
_mac = topicSplit[macIndex]
}
let foundIndex
let mesJson
switch (action) {
case 'info_all': // control_ID/data-server/trigger_manager/info_all
this.triggerList = JSON.parse(mes).connected
break
case 'register': // control_ID/data-server/trigger_manager/register
mesJson = JSON.parse(mes)
foundIndex = this.triggerList.findIndex(o => o.mac === mesJson.mac)
if (foundIndex < 0) { // -1: not found
this.triggerList.push(mesJson)
} else {
this.triggerList[foundIndex] = mesJson
}
break
case 'remove': // control_ID/data-server/trigger_manager/remove/device_mac
foundIndex = this.triggerList.findIndex(o => o.mac === _mac)
this.triggerList.splice(foundIndex, 1)
break
case 'info': // control_ID/data-server/trigger_manager/info/device_mac
foundIndex = this.triggerList.findIndex(o => o.mac === _mac)
this.triggerList[foundIndex] = mesJson
break
default:
break
}
},
async '+/broadcast' (data, topic) {
const topicSplit = topic.split('/')
const mes = String.fromCharCode.apply(null, data).split(':')
@@ -289,7 +362,7 @@ export default {
device: parseInt(mes[1]),
}))
this.mqttPub(topicSplit[0] + '_user', JSON.stringify({
header: 'device_sd_card_status/0',
header: 'get_sd_status/0',
device: parseInt(mes[1]),
}))
// this.mqttPub(topicSplit[0] + '_user', JSON.stringify({
@@ -379,6 +452,7 @@ export default {
this.mqttSub(controller.id + '/hardware_device_connect/+')
this.mqttSub(controller.id + '/hardware_scan/+')
this.mqttSub(controller.id + '/broadcast')
this.mqttSub(controller.id + '/data-server/trigger_manager/#')
})
},
pageMqttUnSub: function () {
@@ -389,6 +463,7 @@ export default {
this.mqttUnSub(controller.id + '/hardware_device_connect/+')
this.mqttUnSub(controller.id + '/hardware_scan/+')
this.mqttUnSub(controller.id + '/broadcast')
this.mqttUnSub(controller.id + '/data-server/trigger_manager/#')
})
},
pageInitPub: function () {
@@ -403,9 +478,8 @@ export default {
this.mqttPub(controller.id + '_user', JSON.stringify({
header: 'hardware_info/0',
}))
// this.mqttPub(controller.id + '_user', JSON.stringify({
// header: 'device_update_usb/0',
// }))
this.mqttPub(controller.id + '/client/trigger_manager/scan', '{}')
this.mqttPub(controller.id + '/client/trigger_manager/info_all', '{}')
this.createInitialCollection('admin')
})
},
@@ -427,6 +501,7 @@ export default {
this.mqttPub(controller.id + '_user', JSON.stringify({
header: 'hardware_scan/0',
}))
this.mqttPub(controller.id + '/client/trigger_manager/scan', '{}')
}
}
})
+76 -23
View File
@@ -69,11 +69,11 @@ export default {
...mapActions('download',
[
types.raw.rawDataByID,
types.raw.rawDataByIDs,
types.raw.rawSdDataByID,
types.export.setHeader,
types.export.setTitle,
types.export.setData,
types.export.getChipIndex,
types.download.closeWriter,
types.download.setStream,
types.download.setSampleRate,
@@ -111,11 +111,23 @@ export default {
// split to different subfile by channel
if (meta.device.library_name.indexOf('Neulive') >= 0) {
if (channelArray.some(el => el >= 256)) {
channelList.push(channelArray.filter(el => (el >= 256 && el <= 259)))
channelList.push(channelArray.filter(el => el < 256))
if (meta.device.library_name === 'Neulive3.5ex') {
if (channelArray.some(el => el >= 256)) { // 3-axis
channelList.push(channelArray.filter(el => (el >= 256 && el <= 259)))
}
if (channelArray.some(el => (el >= 32 && el <= 63))) { // chip 2
channelList.push(channelArray.filter(el => (el >= 32 && el <= 63)))
}
if (channelArray.some(el => (el >= 0 && el <= 31))) { // chip 1
channelList.push(channelArray.filter(el => (el >= 0 && el <= 31)))
}
} else {
channelList.push(channelArray)
if (channelArray.some(el => el >= 256)) {
channelList.push(channelArray.filter(el => (el >= 256 && el <= 259)))
channelList.push(channelArray.filter(el => el < 256))
} else {
channelList.push(channelArray)
}
}
} else if (meta.device.library_name.indexOf('Elite') >= 0) {
channelList.push(channelArray)
@@ -156,6 +168,15 @@ export default {
this.downloadInfo.filename = meta.name
this.downloadInfo.filename += (sd !== undefined) ? '-sd' : ''
this.downloadInfo.size = parseInt(meta.size / (maxSplitFiles))
if (this.downloadInfo.device[1] === '3.5ex') {
const chipIndex = await this.getChipIndexExport({ downloadInfo: this.downloadInfo })
if (chipIndex === 1) {
this.downloadInfo.filename += '_ch01-32'
} else if (chipIndex === 2) {
this.downloadInfo.filename += '_ch33-64'
}
}
}
} else if (meta.device.library_name.indexOf('Elite') >= 0) {
this.downloadInfo.device = [meta.device.library_name.slice(0, 5), meta.device.library_name.slice(5)]
@@ -188,24 +209,56 @@ export default {
await this.setTitleExport()
// start download data
let rowIndex = 0
while (rowIndex < this.downloadInfo.maxRows) {
for (const channel of this.downloadInfo.channel) {
for (const idx in this.downloadInfo.channelInfo[channel].downloadIDList) {
const _idx = parseInt(idx)
const rawID = this.downloadInfo.channelInfo[channel].downloadIDList[_idx][rowIndex]
// const rawID = this.downloadInfo.channelInfo[channel].downloadIDList[_idx].splice(0, 10).join(',')
// console.log(rowIndex, _idx, rawID)
if (rawID !== undefined) {
if (sd === 'sd') {
await this.rawSdDataByIDRaw({
channel: channel,
rawID: rawID,
idx: _idx,
api: api,
})
if (sd === 'sd') {
const dataList = await api.raw.getSdDataAllTimeslotByUUID(this.metaInfo.uuid, this.downloadInfo.channel[0])
const timeslots = dataList.data
for (let idx = 0; idx < timeslots.length; idx++) { // 哪一個row的data (index, start from 0)
let dataString = ''
let maxLength = -1
// download all channel data
for (const channel of this.downloadInfo.channel) {
await this.rawSdDataByIDRaw({
uuid: this.metaInfo.uuid,
channel: channel,
idx: idx,
api: api,
})
if (this.downloadInfo.channelInfo[channel].downloadDataBuffer.length > maxLength) {
maxLength = this.downloadInfo.channelInfo[channel].downloadDataBuffer.length
}
}
// write data stream
const rowData = []
for (let i = 0; i < maxLength; i++) {
for (const channel of this.downloadInfo.channel) {
const dataBuffer = this.downloadInfo.channelInfo[channel].downloadDataBuffer
if (dataBuffer[i] === undefined) {
rowData.push(['', ''])
} else {
rowData.push(dataBuffer[i])
}
}
dataString = dataString + rowData.join(',') + '\r\n'
rowData.length = 0
}
const encode = TextEncoder.prototype.encode.bind(new TextEncoder())
this.downloadInfo.stream.writer.write(encode(dataString))
// reset buffer
for (const channel of this.downloadInfo.channel) {
this.downloadInfo.channelInfo[channel].downloadDataBuffer.length = 0
}
}
} else {
let rowIndex = 0
while (rowIndex < this.downloadInfo.maxRows) {
for (const channel of this.downloadInfo.channel) {
for (const idx in this.downloadInfo.channelInfo[channel].downloadIDList) {
const _idx = parseInt(idx)
const rawID = this.downloadInfo.channelInfo[channel].downloadIDList[_idx][rowIndex]
if (rawID !== undefined) {
await this.rawDataByIDRaw({
channel: channel,
rawID: rawID,
@@ -215,9 +268,9 @@ export default {
}
}
}
await this.setDataExport({ last: (rowIndex >= this.downloadInfo.maxRows - 1) })
rowIndex += 1
}
await this.setDataExport({ last: (rowIndex >= this.downloadInfo.maxRows - 1) })
rowIndex += 1
}
this.closeWriterDownload()
fileIndex += 1
+64 -20
View File
@@ -74,16 +74,18 @@ export default {
...mapActions('download',
[
types.raw.rawDataByID,
types.raw.rawDataByIDs,
types.raw.rawSdDataByID,
types.export.setHeader,
types.export.setTitle,
types.export.setData,
types.export.setAll,
types.export.getHeader,
types.export.getTitle,
types.download.closeWriter,
types.download.setStream,
types.download.setSampleRate,
types.download.setIDList,
types.download.saveFile,
],
),
...mapMutations('download',
@@ -189,24 +191,66 @@ export default {
// start download data
fileIndex += 1
let rowIndex = 0
while (rowIndex < this.downloadInfo.maxRows) {
this.$refs.progressModalRef.setValue(parseInt((rowIndex / this.downloadInfo.maxRows) * 100))
for (const channel of this.downloadInfo.channel) {
for (const idx in this.downloadInfo.channelInfo[channel].downloadIDList) {
const _idx = parseInt(idx)
const rawID = this.downloadInfo.channelInfo[channel].downloadIDList[_idx][rowIndex]
// console.log(rowIndex, _idx, rawID)
if (rawID !== undefined) {
if (sd === 'sd') {
await this.rawSdDataByIDRaw({
channel: channel,
rawID: rawID,
idx: _idx,
api: api,
})
if (sd === 'sd') {
let dataString = ''
const headerString = await this.getHeaderExport()
const { titleString, channelString } = await this.getTitleExport()
const stringArray = [] // use for generate Blob
stringArray.push(headerString)
stringArray.push(channelString)
stringArray.push(titleString)
const dataList = await api.raw.getSdDataAllTimeslotByUUID(this.metaInfo.uuid, this.downloadInfo.channel[0])
const timeslots = dataList.data
for (let idx = 0; idx < timeslots.length; idx++) { // 哪一個row的data (index, start from 0)
let maxLength = -1
// download all channel data
for (const channel of this.downloadInfo.channel) {
await this.rawSdDataByIDRaw({
uuid: this.metaInfo.uuid,
channel: channel,
idx: idx,
api: api,
})
if (this.downloadInfo.channelInfo[channel].downloadDataBuffer.length > maxLength) {
maxLength = this.downloadInfo.channelInfo[channel].downloadDataBuffer.length
}
}
// write data
const rowData = []
for (let i = 0; i < maxLength; i++) {
for (const channel of this.downloadInfo.channel) {
const dataBuffer = this.downloadInfo.channelInfo[channel].downloadDataBuffer
if (dataBuffer[i] === undefined) {
rowData.push(['', ''])
} else {
rowData.push(dataBuffer[i])
}
}
dataString = dataString + rowData.join(',') + '\r\n'
rowData.length = 0
}
// reset buffer
for (const channel of this.downloadInfo.channel) {
this.downloadInfo.channelInfo[channel].downloadDataBuffer.length = 0
}
this.$refs.progressModalRef.setValue(parseInt((100 / timeslots.length) * (idx + 1)))
}
stringArray.push(dataString)
const exportString = new Blob(stringArray)
this.saveFileDownload({ data: exportString })
} else {
let rowIndex = 0
while (rowIndex < this.downloadInfo.maxRows) {
this.$refs.progressModalRef.setValue(parseInt((rowIndex / this.downloadInfo.maxRows) * 100))
for (const channel of this.downloadInfo.channel) {
for (const idx in this.downloadInfo.channelInfo[channel].downloadIDList) {
const _idx = parseInt(idx)
const rawID = this.downloadInfo.channelInfo[channel].downloadIDList[_idx][rowIndex]
// console.log(rowIndex, _idx, rawID)
if (rawID !== undefined) {
await this.rawDataByIDRaw({
channel: channel,
rawID: rawID,
@@ -216,11 +260,11 @@ export default {
}
}
}
rowIndex += 1
}
rowIndex += 1
await this.setAllExport({ last: (rowIndex >= this.downloadInfo.maxRows - 1) })
this.$refs.progressModalRef.setValue(100)
}
await this.setAllExport({ last: (rowIndex >= this.downloadInfo.maxRows - 1) })
this.$refs.progressModalRef.setValue(100)
}
}
}
+70
View File
@@ -0,0 +1,70 @@
<template>
<va-card>
<sdcard-file-option />
<sdcard-file-table />
<sdcard-upload-modal />
<sdcard-delete-modal />
</va-card>
</template>
<script>
import SdcardFileOption from './option/SdcardFileOption.vue'
import SdcardFileTable from './sdcard/SdcardFileTable.vue'
import SdcardUploadModal from './sdcard/SdcardUploadModal.vue'
import SdcardDeleteModal from './sdcard/SdcardDeleteModal.vue'
import api from '@/data/api/index'
import { mapFields } from 'vuex-map-fields'
export default {
components: {
SdcardFileOption,
SdcardFileTable,
SdcardUploadModal,
SdcardDeleteModal,
},
data () {
return {
}
},
computed: {
...mapFields('sdcard', [
'controllerList',
]),
},
methods: {
mqttSub: function (val) {
this.$mqtt.subscribe(val)
},
mqttUnSub: function (val) {
this.$mqtt.unsubscribe(val)
},
mqttPub: function (topic, mes) {
this.$mqtt.publish(topic, mes)
},
pageMqttSub: function () {
this.controllerList.forEach(controller => {
this.mqttSub(controller.mac_address + '/data-server/s_d_card_manager/#')
})
},
pageMqttUnSub: function () {
this.controllerList.forEach(controller => {
this.mqttUnSub(controller.mac_address + '/data-server/s_d_card_manager/#')
})
},
pageInitPub: function () {
this.controllerList.forEach(controller => {
this.mqttPub(controller.mac_address + '/client/s_d_card_manager/info_all', '{}')
})
},
},
async mounted () {
const ret = await api.controller.getAll()
this.controllerList = ret.data
this.pageMqttSub()
this.pageInitPub()
},
destroyed () {
this.pageMqttUnSub()
},
}
</script>
+28
View File
@@ -0,0 +1,28 @@
<template>
<va-card>
<file-option />
<trigger-file-table />
</va-card>
</template>
<script>
import FileOption from './option/FileOption.vue'
import TriggerFileTable from './trigger/TriggerFileTable.vue'
export default {
components: {
FileOption,
TriggerFileTable,
},
data () {
return {
}
},
methods: {
},
async mounted () {
},
destroyed () {
},
}
</script>
+47 -51
View File
@@ -64,9 +64,6 @@
<va-card class="flex sm12 xl12 md12 xs12">
<template slot="header">
<div class="flex sm12 xl12 md12 xs12" style="text-align: end;">
<!-- <va-button class="" color="success" v-if="selected.length" @click="downloadSelected" target="_blank" small>
DOWNLOAD
</va-button> -->
<va-button class="" color="danger" v-if="selected.length" @click="deleteMultiFilesConfirm" target="_blank" small>
DELETE
</va-button>
@@ -166,6 +163,9 @@
<template slot="size" slot-scope="props">
{{transFileSize(props.rowData.size)}}
</template>
<template slot="sd_size" slot-scope="props">
{{estimateSDFileSize(props.rowData)}}
</template>
<template slot="time_duration" slot-scope="props">
{{transTime(props.rowData.time_duration)}}
</template>
@@ -177,17 +177,13 @@
<sd-card-button
v-if="(props.rowData.type !== 'folder') && (props.rowData.device.library_name.indexOf('Neulive') >= 0) && (props.rowData.sd_data_record === true)"
:meta="props.rowData"
:uuidList="SDuuidList"
:controller="controllerList[0]"
@refreshPage="refreshPage"
@downloadSdCard="downloadSdCard"
/>
</va-popover>
<va-popover :message="`download ${props.rowData.name} in csv format`" placement="top">
<va-button flat small color="gray" icon="fa fa-download" v-if="props.rowData.type !== 'folder' && props.rowData.device.library_name.indexOf('Neulive') >= 0" @click.stop="downloadCsv(props.rowData)"/>
</va-popover>
<va-popover :message="`download ${props.rowData.name} in excel format`" placement="top">
<va-button flat small color="gray" icon="fa fa-download" v-if="props.rowData.type !== 'folder' && props.rowData.device.library_name.indexOf('Elite') >= 0" @click.stop="downloadExcel(props.rowData)"/>
<va-button flat small color="success" icon="fa fa-download" v-if="props.rowData.type !== 'folder' && props.rowData.device.library_name.indexOf('Neulive') >= 0" @click.stop="downloadCsv(props.rowData)"/>
</va-popover>
<va-popover :message="`download ${props.rowData.name} in csv-edf format`" placement="top">
<va-button flat small color="gray" icon="fa fa-download" v-if="props.rowData.type !== 'folder' && props.rowData.device.library_name.indexOf('Neulive') >= 0" @click.stop="downloadExcel(props.rowData)"/>
@@ -273,7 +269,6 @@ export default {
height: '2rem',
},
},
SDuuidList: [],
controllerList: [],
}
},
@@ -308,7 +303,14 @@ export default {
dataClass: 'text-center',
}, {
name: '__slot:size',
title: 'size',
title: 'size (BLE)',
sortField: 'size',
width: '10%',
titleClass: 'text-center',
dataClass: 'text-center',
}, {
name: '__slot:sd_size',
title: 'size (SD)',
sortField: 'size',
width: '10%',
titleClass: 'text-center',
@@ -371,6 +373,28 @@ export default {
return this.GLOBAL.transFileSize(fileSize)
}
},
estimateSDFileSize: function (_meta) {
if (_meta.size === undefined || !_meta.sd_data_record) {
return '--'
} else {
if (_meta.parameter_set && _meta.parameter_set.SAMPLE_RATE > 1e4) {
let scale = _meta.parameter_set.SAMPLE_RATE / 1e4 // 1e4: 以10k為基準
const channels = JSON.parse(_meta.channels)
if (_meta.device.library_name === 'Neulive3.5ex') {
const chip1ChannelLen = channels.filter(c => c >= 0 && c <= 31).length
const chip2ChannelLen = channels.filter(c => c >= 32 && c <= 63).length
if (chip1ChannelLen > 0 && chip2ChannelLen > 0) { // select two chip
scale = _meta.parameter_set.SAMPLE_RATE * 2 * 2 / 1e4 // 多一個*2是擴充板downsample那邊的issue
} else { // only select one chip
// do nothing
}
}
return this.GLOBAL.transFileSize(_meta.size * scale)
} else {
return this.GLOBAL.transFileSize(_meta.size)
}
}
},
transDate: function (dateTime) {
return this.GLOBAL.dateToStr(dateTime)
},
@@ -501,6 +525,7 @@ export default {
// TODO editFile
if (this.editInfo.type !== 'folder') {
await api.meta.update(this.editInfo.info.id, { name: this.editInfo.changedName })
await api.meta.update30(this.editInfo.info.uuid, { name: this.editInfo.changedName })
} else {
}
await this.editFileDone()
@@ -671,53 +696,25 @@ export default {
downloadExcel: async function (meta) {
const href = location.href.split('/')[2]
const connectCheck = await api.connection.check()
if (meta.device.library_name.indexOf('Elite') >= 0) {
if (connectCheck.status === 200) {
window.open('http://' + href + '/#/download/online/excel/' + meta.id)
} else {
window.open('http://' + href + '/#/download/offline/excel/' + meta.id)
}
} else if (meta.device.library_name.indexOf('Neulive') >= 0) {
if (connectCheck.status === 200) {
window.open('http://' + href + '/#/download/online/csv-edf/' + meta.id)
} else {
window.open('http://' + href + '/#/download/offline/csv-edf/' + meta.id)
}
if (connectCheck.status === 200) {
window.open('http://' + href + '/#/download/online/csv-edf/' + meta.id)
} else {
window.open('http://' + href + '/#/download/offline/csv-edf/' + meta.id)
}
},
downloadSdCard: async function (meta) {
const href = location.href.split('/')[2]
if (meta.device.library_name.indexOf('Neulive') >= 0) {
if (window.navigator.onLine) {
window.open('http://' + href + '/#/download/online/csv/' + meta.id + '/sd')
} else {
window.open('http://' + href + '/#/download/offline/csv/' + meta.id + '/sd')
}
}
},
async getSDuuidList () {
// const uuidList = ['32abdb14-fc0c-11eb-9aa8-dca6326cbc6b', '940ca938-f662-11eb-8a41-dca6326cbc6b']
if (this.controllerList.length > 0) {
if (this.controllerList[0].sd_uploading === false) {
const ret = await api.sdcard.getuuidList()
if (ret.status === 200) {
this.SDuuidList = ret.data
}
}
const connectCheck = await api.connection.check()
if (connectCheck.status === 200) {
window.open('http://' + href + '/#/download/online/csv/' + meta.id + '/sd')
} else {
window.open('http://' + href + '/#/download/offline/csv/' + meta.id + '/sd')
}
},
replay: function (meta) {
if (meta.device.library_name.includes('Neulive') === true) {
// window.open('http://' + location.href.split('/')[2].split(':')[0] + '/#/admin/data-replay/' + meta.id)
this.$router.push({
path: '/admin/data-replay/' + meta.id,
})
} else if (meta.device.library_name.includes('Elite') === true) {
// window.open('http://' + location.href.split('/')[2].split(':')[0] + '/#/admin/data-replay/elite/' + meta.id)
this.$router.push({
path: '/admin/data-replay/elite/' + meta.id,
})
}
this.$router.push({
path: '/admin/data-replay/' + meta.id,
})
},
spike: function (meta) {
if (meta.device.library_name.includes('Neulive') === true) {
@@ -730,7 +727,6 @@ export default {
async mounted () {
const controllerList = await api.controller.getAll()
this.controllerList = controllerList.data
this.getSDuuidList()
},
destroyed () {
},
+109
View File
@@ -0,0 +1,109 @@
<template>
<div class="row">
<div class="row flex xs12 md6 xl6 sm6 select-box px-0">
<va-select
:ref="'selection'"
class="flex xs6 md6 xl6 sm6 my-0 py-0 pr-0 file-option-select-box"
:label="'select'"
v-model="selectionModel"
textBy="description"
:options="selectionOptions"
:input="selectionEvent()"
/>
<va-select
:ref="'action'"
class="flex xs6 md6 xl6 sm6 my-0 py-0 pr-0 file-option-select-box"
:label="'action'"
v-model="actionModel"
textBy="description"
:options="actionOptions"
:input="actionEvent()"
/>
</div>
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields'
import { mapActions } from 'vuex'
import fileTypes from '@/store/modules/file/types'
export default {
components: {
},
computed: {
...mapFields('file', [
'dataList',
]),
},
data () {
return {
selectionModel: {},
selectionOptions: [
{
id: 0,
description: 'Select All File',
},
{
id: 1,
description: 'Clear',
},
],
actionModel: {},
actionOptions: [
{
id: 0,
description: 'Export (.csv)',
},
],
}
},
methods: {
selectionEvent () {
if (this.selectionModel == null) {
return null
}
switch (this.selectionModel.description) {
case 'Select All File':
this.fileTableSelectAll()
break
case 'Clear':
this.fileTableSelectClear()
break
default:
break
}
if (this.$refs.selection !== undefined && this.$refs.selection !== null) {
this.$refs.selection.clear()
}
},
actionEvent () {
if (this.actionModel == null) {
return null
}
switch (this.actionModel.description) {
case 'Export (.csv)':
this.fileDownloadCsv()
break
default:
break
}
if (this.$refs.action !== undefined && this.$refs.action !== null) {
this.$refs.action.clear()
}
},
...mapActions('file', [fileTypes.file.downloadCsv]),
...mapActions('file', [fileTypes.table.selectAll, fileTypes.table.selectClear]),
},
}
</script>
<style lang="scss">
.file-option-select-box {
text-align: right;
font-weight: normal;
letter-spacing: 0;
text-transform: none;
}
</style>
@@ -0,0 +1,194 @@
<template>
<div class="row">
<div class="row flex xs12 md6 xl6 sm6 select-box px-0">
<va-select
:ref="'selection'"
class="flex xs6 md6 xl6 sm6 my-0 py-0 pr-0 file-option-select-box"
:label="'select'"
v-model="selectionModel"
textBy="description"
:options="selectionOptions"
:input="selectionEvent()"
/>
<va-select
:ref="'action'"
class="flex xs6 md6 xl6 sm6 my-0 py-0 pr-0 file-option-select-box"
:label="'action'"
v-model="actionModel"
textBy="description"
:options="actionOptions"
:input="actionEvent()"
/>
</div>
<va-modal
class="flex sm12 xl12 md12 xs12"
v-model="deleteConfirmModal"
:title="'Those files will be deleted:'"
:okText=" $t('modal.confirm') "
@ok="deleteFile()"
>
<div v-for="(data, index) in dataList" :key="index">
<div v-if="data.selected" class="my-2 py-2">
<p class="display-6"> {{ data.name }} </p>
</div>
</div>
</va-modal>
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields'
import { mapActions } from 'vuex'
import fileTypes from '@/store/modules/sdcard/types'
import api from '@/data/api/index'
export default {
components: {
},
computed: {
...mapFields('sdcard', [
'sdInfoList',
'dataList',
'controllerList',
'sdcardUploadModal',
'sdcardDeleteModal',
]),
},
data () {
return {
selectionModel: {},
selectionOptions: [
{
id: 0,
description: 'Select All File',
},
{
id: 1,
description: 'Clear',
},
],
actionModel: {},
actionOptions: [
{
id: 0,
description: 'Upload to hub',
},
{
id: 1,
description: 'Delete',
},
],
deleteConfirmModal: false,
}
},
mqtt: {
async '+/data-server/s_d_card_manager/#' (data, topic) {
const mes = new TextDecoder('utf-8').decode(data)
const topicSplit = topic.split('/')
const baseIndex = topicSplit.indexOf('s_d_card_manager')
const actionIndex = baseIndex + 1
const action = topicSplit[actionIndex]
let ret
let dataObj
switch (action) {
case 'upload_raw':
ret = JSON.parse(mes)
dataObj = this.dataList.find(d => (d.uuid === ret.meta_uuid && d.sdName === ret.sd_name))
dataObj.uploading = false
if (ret.status === 'success') {
dataObj.uploaded = true
const ret = await api.meta.getByUUID(dataObj.uuid) // control system 2.0 meta
const metaID = ret.data[0].id
await api.meta.update(metaID, { sd_data_uploaded: true })
}
break
case 'delete_meta':
ret = JSON.parse(mes)
dataObj = this.dataList.find(d => (d.uuid === ret.meta_uuid && d.sdName === ret.sd_name))
dataObj.deleting = false
if (ret.status === 'success') {
dataObj.deleted = true
}
break
default:
break
}
},
},
methods: {
mqttPub: function (topic, mes) {
this.$mqtt.publish(topic, mes)
},
selectionEvent () {
if (this.selectionModel == null) {
return null
}
switch (this.selectionModel.description) {
case 'Select All File':
this.sdcardTableSelectAll()
break
case 'Clear':
this.sdcardTableSelectClear()
break
default:
break
}
if (this.$refs.selection !== undefined && this.$refs.selection !== null) {
this.$refs.selection.clear()
}
},
actionEvent () {
if (this.actionModel == null) {
return null
}
switch (this.actionModel.description) {
case 'Upload to hub':
this.uploadFile()
this.sdcardUploadModal = true
break
case 'Delete':
this.deleteConfirmModal = true
break
default:
break
}
if (this.$refs.action !== undefined && this.$refs.action !== null) {
this.$refs.action.clear()
}
},
uploadFile () {
for (let i = 0; i < this.dataList.length; i++) {
const file = this.dataList[i]
if (file.selected) {
file.uploading = true
const sdName = this.sdInfoList.find(info => info.meta_list.includes(file.uuid)).name
this.mqttPub(this.controllerList[0].mac_address + '/client/s_d_card_manager/upload_raw', JSON.stringify({ sd_name: sdName, meta_uuid: file.uuid }))
}
}
},
async deleteFile () {
for (let i = 0; i < this.dataList.length; i++) {
const file = this.dataList[i]
if (file.selected) {
file.deleting = true
const sdName = this.sdInfoList.find(info => info.meta_list.includes(file.uuid)).name
this.mqttPub(this.controllerList[0].mac_address + '/client/s_d_card_manager/delete_meta', JSON.stringify({ sd_name: sdName, meta_uuid: file.uuid }))
}
}
this.sdcardDeleteModal = true
},
...mapActions('sdcard', [fileTypes.file.uploadToHub]),
...mapActions('sdcard', [fileTypes.table.selectAll, fileTypes.table.selectClear]),
},
}
</script>
<style lang="scss">
.file-option-select-box {
text-align: right;
font-weight: normal;
letter-spacing: 0;
text-transform: none;
}
</style>
@@ -45,7 +45,11 @@ export default {
this.initMetaInfo(meta)
this.pageToast('Start Moving to database...')
const result = await apiManager.call('Move_to_DB', controller.id, this.metaID, this.mac_address, this.uuid)
return result
if (result !== undefined) {
return result
} else {
throw new Error('error')
}
},
async deleteSDCard (meta, controller) {
this.initMetaInfo(meta)
+13 -28
View File
@@ -10,16 +10,12 @@
flat
small
>
<va-icon
<i
v-if="meta.sd_data_uploaded === false"
class="va-button__content__icon va-button__content__icon-left"
name="material-icons"
color="grey"
@click.native.stop="performButtonAction()"
small
fixed-width
> sd_card
</va-icon>
class="i-sd-card"
style="width: 1rem; height: 1rem;"
@click="performButtonAction()"
/>
</va-button>
<sd-card-api-action :ref="'sd_card_api'"/>
@@ -33,7 +29,7 @@
/>
<progress-modal
title="SD card data uploading"
:time="(parseInt(meta.time_duration) / 2e6).toString()"
:meta="meta"
:ref="'progressModalRef'"
/>
</div>
@@ -54,10 +50,6 @@ export default {
type: Object,
required: true,
},
uuidList: {
type: Array,
required: true,
},
meta: {
type: Object,
required: true,
@@ -77,7 +69,7 @@ export default {
mqtt: {
},
methods: {
init () {
async init () {
this.metaID = this.meta.id
this.mac_address = this.meta.device.device_address.map(data =>
data.toString(16)
@@ -90,11 +82,7 @@ export default {
const uploaded = this.meta.sd_data_uploaded // true or false
if (recorded === true) {
if (uploaded !== true) {
if (this.uuidList.includes(this.uuid)) {
this.moveToDBModal = true
} else {
this.pageToast('Can not access data. Please connect your device to hub by usb.')
}
this.moveToDBModal = true
} else {
this.executeDownload()
}
@@ -104,19 +92,16 @@ export default {
await this.executeUpload()
this.$emit('refreshPage')
},
// async executeCheckInSD () {
// return await this.$refs.sd_card_api.checkInSD(this.meta)
// },
async executeUpload () {
this.$refs.progressModalRef.show()
const result = await this.$refs.sd_card_api.moveToDB(this.meta, this.controller).catch(_ => {
this.$refs.progressModalRef.hide()
this.pageToast('Upload SD card data error, please connect Neullive to Hub by usb or data corrupted.')
this.pageToast('Upload SD card data error, please connect Neulive to Hub by usb or data corrupted or data deleted.')
})
if (result.data === true) {
this.pageToast('Upload SD card data done.')
} else {
this.pageToast('Upload SD card data error, please connect Neullive to Hub by usb or data corrupted.')
this.pageToast('Upload SD card data error, please connect Neulive to Hub by usb or data corrupted or data deleted.')
}
this.$refs.progressModalRef.progressDone()
// remove delete sd card first, still have problem.
@@ -137,11 +122,11 @@ export default {
},
},
async mounted () {
this.init()
await this.init()
},
watch: { // cuz data table will not call mounted
meta: function (val) {
this.init()
meta: async function (val) {
await this.init()
},
},
}
@@ -0,0 +1,54 @@
<template>
<div>
<va-modal
class="flex sm12 xl12 md12 xs12"
v-model="sdcardDeleteModal"
:cancelText="''"
:okText=" $t('modal.close') "
@cancel="removeDeletedFiles()"
@ok="removeDeletedFiles()"
>
<div v-for="(data, index) in dataList" :key="index">
<div v-if="data.selected" class="my-2 py-2">
<p class="display-6"> {{ data.name }} </p>
<vm-progress v-if="data.deleting" :percentage="50" :text-inside="true" :stroke-width="18" :striped="true"></vm-progress>
<vm-progress v-if="!data.deleting && !data.deleted" :percentage="0" status="exception"></vm-progress>
<vm-progress v-if="!data.deleting && data.deleted" :percentage="100" status="success"></vm-progress>
</div>
</div>
</va-modal>
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields'
export default {
components: {
},
props: {
},
created () {
},
data () {
return {}
},
computed: {
...mapFields('sdcard', [
'dataList',
'sdcardDeleteModal',
]),
},
watch: {
},
methods: {
removeDeletedFiles () {
this.dataList.forEach(item => {
item.deleting = false
})
this.dataList = this.dataList.filter(d => d.deleted !== true)
},
},
}
</script>
@@ -0,0 +1,156 @@
<template>
<div style="height: 94vh; overflow: auto;">
<va-card class="flex sm12 xl12 md12 xs12">
<!-- FileListTable -->
<div v-if="tableLoading" class="row flex sm12 xl12 md12 xs12">
<div class="flex-center spinner-box mt-4 mb-5">
<fulfilling-bouncing-circle-spinner
:animation-duration="3000"
:color="'#6c7fee'"
:size="60"
>
</fulfilling-bouncing-circle-spinner>
</div>
</div>
<va-data-table
:fields="fields"
:data="dataList"
:per-page="parseInt(perPage)"
hoverable
clickable
v-else
:ref="'datatable_ref'"
>
<template slot="select" slot-scope="props">
<va-checkbox v-model="props.rowData.selected"/>
</template>
<template slot="name" slot-scope="props">
<va-icon
:name="'fa fa-file'"
size="0.9rem"
class="mr-1"
style="cursor: pointer;"
:color="'dark'"
/>
{{ props.rowData.name }}
</template>
<template slot="create_date" slot-scope="props">
{{ props.rowData.create_date }}
</template>
<template slot="sd_name" slot-scope="props">
{{ props.rowData.sdName }}
</template>
<template slot="device_info" slot-scope="props">
{{ props.rowData.device.library_name + ', ' + props.rowData.device.device_version }}
</template>
</va-data-table>
</va-card>
</div>
</template>
<script>
import { FulfillingBouncingCircleSpinner } from 'epic-spinners'
import { mapFields } from 'vuex-map-fields'
import { mapActions } from 'vuex'
import fileTypes from '@/store/modules/sdcard/types'
export default {
components: {
FulfillingBouncingCircleSpinner,
},
computed: {
...mapFields('sdcard', [
'tableLoading',
'tableKey',
'sdInfoList',
'dataList',
]),
},
data () {
return {
perPage: 80,
fields: [
{
name: '__slot:select',
width: '5%',
},
{
name: '__slot:name',
title: 'name',
sortField: 'name',
width: '15%',
titleClass: 'text-center',
dataClass: 'text-center',
},
{
name: '__slot:create_date',
title: 'create date',
sortField: 'create_date',
width: '15%',
titleClass: 'text-center',
dataClass: 'text-center',
},
{
name: '__slot:sd_name',
title: 'sd_name',
width: '15%',
titleClass: 'text-center',
dataClass: 'text-center',
}, {
name: '__slot:device_info',
title: 'device info',
width: '15%',
titleClass: 'text-center',
dataClass: 'text-center',
},
],
}
},
mqtt: {
async '+/data-server/s_d_card_manager/#' (data, topic) {
const mes = new TextDecoder('utf-8').decode(data)
const topicSplit = topic.split('/')
const baseIndex = topicSplit.indexOf('s_d_card_manager')
const actionIndex = baseIndex + 1
const action = topicSplit[actionIndex]
let foundIndex
let mesJson
switch (action) {
case 'info_all':
this.sdInfoList = JSON.parse(mes)
await this.sdcardUpdateDataList()
break
case 'register':
mesJson = JSON.parse(mes)
foundIndex = this.sdInfoList.findIndex(o => o.name === mesJson.name)
if (foundIndex < 0) { // -1: not found
this.sdInfoList.push(mesJson)
} else {
this.sdInfoList[foundIndex] = mesJson
}
await this.sdcardUpdateDataList()
break
case 'remove':
foundIndex = this.sdInfoList.findIndex(o => o.name === mes)
this.sdInfoList.splice(foundIndex, 1)
await this.sdcardUpdateDataList()
break
// case 'info':
// foundIndex = this.triggerList.findIndex(o => o.mac === _mac)
// this.triggerList[foundIndex] = mesJson
// break
default:
break
}
},
},
methods: {
...mapActions('sdcard', [fileTypes.file.updateList]),
},
async mounted () {
},
destroyed () {
},
}
</script>
@@ -0,0 +1,80 @@
<template>
<div>
<vm-progress v-if="meta.uploading" :percentage="progressValue" :text-inside="true" :stroke-width="18" :striped="true"></vm-progress>
<vm-progress v-if="!meta.uploading && !meta.uploaded" :percentage="0" status="exception"></vm-progress>
<vm-progress v-if="!meta.uploading && meta.uploaded" :percentage="100" status="success"></vm-progress>
</div>
</template>
<script>
import api from '@/data/api/index'
export default {
name: 'sdcard-reader-progress-bar',
data () {
return {
progressValue: 0,
timeInterval: null,
}
},
props: {
meta: { // control system 3.0 meta
type: Object,
required: true,
},
},
methods: {
async estimateTime () {
const oldMetaList = await api.meta.getByUUID(this.meta.uuid) // control system 2.0 meta table
const oldMeta = oldMetaList.data[0]
const timeDuration = oldMeta.time_duration
let sampleRate
if (oldMeta.device.library_name !== 'Neulive3.5ex') {
sampleRate = oldMeta.parameter_set.SAMPLE_RATE
} else {
const channelList = JSON.parse(oldMeta.channels)
const chip1Channels = channelList.filter(c => c >= 0 && c <= 31)
const chip2Channels = channelList.filter(c => c >= 32 && c <= 63)
if (chip1Channels.length !== 0 && chip2Channels.length !== 0) {
sampleRate = oldMeta.parameter_set.SAMPLE_RATE * 2
} else {
sampleRate = oldMeta.parameter_set.SAMPLE_RATE
}
}
return parseFloat(timeDuration / 1e6) * parseFloat(sampleRate / 1e3) * 0.006 * 1
// 經驗公式
// time_duration/1e6: us -> s
// sampleRate/1e3: sample rate unit to (k)
// *3.5: control system 2.0 upload rate is about 250~350kb/s
},
animateValues (estimatedTime) { // estimatedTime unit: seconds
let currentTime = this.progressValue = 0
this.timeInterval = setInterval(() => {
if (estimatedTime < 1) {
this.progressValue = 99
clearInterval(this.timeInterval)
} else if (this.progressValue < 90) {
currentTime += (1 / estimatedTime)
this.progressValue = parseInt(currentTime * 100)
} else if (this.progressValue >= 90 && this.progressValue < 99) {
currentTime += (1 / (estimatedTime * 10)) // *10: buffer
this.progressValue = parseInt(currentTime * 100)
} else {
this.progressValue = 99
clearInterval(this.timeInterval)
}
}, 1000)
},
progressDone () {
this.progressValue = 100
},
},
async mounted () {
const estimatedTime = await this.estimateTime()
this.animateValues(estimatedTime)
},
destroyed () {
clearInterval(this.timeInterval)
},
}
</script>
@@ -0,0 +1,44 @@
<template>
<div>
<va-modal
class="flex sm12 xl12 md12 xs12"
v-model="sdcardUploadModal"
:cancelText="''"
:okText=" $t('modal.close') "
>
<div v-for="(data, index) in dataList" :key="index">
<div v-if="data.selected" class="my-2 py-2">
<p class="display-6"> {{ data.name }} </p>
<progress-bar :meta="data" />
</div>
</div>
</va-modal>
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields'
import ProgressBar from '@/components/file/sdcard/SdcardReaderProgressBar.vue'
export default {
components: {
ProgressBar,
},
props: {
},
created () {
},
data () {
return {}
},
computed: {
...mapFields('sdcard', [
'dataList',
'sdcardUploadModal',
]),
},
watch: {
},
}
</script>
@@ -0,0 +1,126 @@
<template>
<div style="height: 94vh; overflow: auto;">
<va-card class="flex sm12 xl12 md12 xs12">
<!-- FileListTable -->
<div v-if="tableLoading" class="row flex sm12 xl12 md12 xs12">
<div class="flex-center spinner-box mt-4 mb-5">
<fulfilling-bouncing-circle-spinner
:animation-duration="3000"
:color="'#6c7fee'"
:size="60"
>
</fulfilling-bouncing-circle-spinner>
</div>
</div>
<va-data-table
:fields="fields"
:data="dataList"
:per-page="parseInt(perPage)"
hoverable
clickable
v-else
:ref="'datatable_ref'"
>
<template slot="select" slot-scope="props">
<va-checkbox v-model="props.rowData.selected"/>
</template>
<template slot="name" slot-scope="props">
<va-icon
:name="'fa fa-file'"
size="0.9rem"
class="mr-1"
style="cursor: pointer;"
:color="'dark'"
/>
{{ props.rowData.name }}
</template>
<template slot="create_date" slot-scope="props">
{{ props.rowData.create_date }}
</template>
<template slot="time_duration" slot-scope="props">
{{ transTime(props.rowData.time_duration) }}
</template>
<template slot="device_info" slot-scope="props">
{{ props.rowData.device.library }}
</template>
</va-data-table>
</va-card>
</div>
</template>
<script>
import { FulfillingBouncingCircleSpinner } from 'epic-spinners'
import { mapFields } from 'vuex-map-fields'
import { mapActions } from 'vuex'
import fileTypes from '@/store/modules/file/types'
export default {
components: {
FulfillingBouncingCircleSpinner,
},
computed: {
...mapFields('file', [
'tableLoading',
'tableKey',
'dataList',
]),
},
data () {
return {
perPage: 80,
fields: [
{
name: '__slot:select',
width: '5%',
},
{
name: '__slot:name',
title: 'name',
sortField: 'name',
width: '15%',
titleClass: 'text-center',
dataClass: 'text-center',
},
{
name: '__slot:create_date',
title: 'create date',
sortField: 'create_date',
width: '15%',
titleClass: 'text-center',
dataClass: 'text-center',
},
{
name: '__slot:time_duration',
title: 'duration',
width: '15%',
titleClass: 'text-center',
dataClass: 'text-center',
}, {
name: '__slot:device_info',
title: 'device info',
width: '15%',
titleClass: 'text-center',
dataClass: 'text-center',
},
],
}
},
methods: {
transTime: function (time) {
if (time === undefined) {
return '--'
} else {
return this.GLOBAL.microsecondsFormat(time * 1e3) // ms -> us
}
},
...mapActions('file', [fileTypes.file.updateList]),
},
async mounted () {
await this.fileUpdateDataList()
},
destroyed () {
},
}
</script>
+143 -13
View File
@@ -54,6 +54,12 @@
<va-button class="" color="success" target="_blank" @click="startAllClick()" small>
START ALL
</va-button>
<va-button class="" color="primary" target="_blank" @click="recordAllClick()" small>
RECORD ALL
</va-button>
<va-button class="" color="warning" target="_blank" @click="stimulateAllClick()" small>
STIMULATE ALL
</va-button>
<va-button class="no-dark-style-va-button" color="danger" target="_blank" @click="stopAllClick()" small>
STOP ALL
</va-button>
@@ -78,6 +84,15 @@
@remove="removeChartCheck(chart.chartID)"
/>
</div>
<div
v-for="trigger in triggerList"
:key="trigger.id"
>
<trigger-chart
v-show="trigger.display === true"
:mac="trigger.mac"
/>
</div>
</div>
<div style="height: 150px;">
@@ -99,9 +114,11 @@ import { dataStreamBuffer } from '../../../data/task/DataStreamBuffer'
import { taskInfo } from '../../../data/task/TaskInfo'
import CanvasChart from './chart/CanvasChart'
import TaskDevices from './controller/TaskDevices'
import TriggerChart from './chart/TriggerChart'
// import api from '@/data/api'
import { mapFields } from 'vuex-map-fields'
import { mapActions } from 'vuex'
import deviceTypes from '@/store/modules/device/types'
import taskTypes from '@/store/modules/task/content/types'
// import TaskFormula from './formula/TaskFormula'
@@ -112,6 +129,7 @@ export default {
components: {
CanvasChart,
TaskDevices,
TriggerChart,
},
computed: {
...mapFields('taskContent', [
@@ -120,6 +138,9 @@ export default {
'taskContentChartPanelKey',
'taskContentChartNumber',
]),
...mapFields('device', [
'triggerList',
]),
},
data () {
return {
@@ -187,16 +208,92 @@ export default {
taskInfo.deviceSavePath(String.fromCharCode.apply(null, data))
// console.log(taskInfo.getTaskInfo())
},
async '+/device_sd_card_status/+' (data, topic) {
const dataDecode = JSON.parse(String.fromCharCode.apply(null, data))
for (const device of this.$refs.devices_ref.deviceList) {
if (dataDecode.device === taskInfo.getRawDeviceID(device.id)) {
device.sdCardStatus = dataDecode.data
async '+/device_get_sd_storage/+' (data, topic) {
const mes = new TextDecoder('utf-8').decode(data)
const mesJson = JSON.parse(mes)
const storage = parseInt(mesJson.data) // unit: kb
const deviceID = parseInt(mesJson.device)
const libraryName = taskInfo.getDeviceInfoByRawID(deviceID).info.library_name
let sampleRate
if (libraryName !== 'Neulive3.5ex') {
sampleRate = taskInfo.getDeviceInfoByRawID(deviceID).parameterSet.SAMPLE_RATE
} else {
const channelList = this.findAllIndexes(taskInfo.getDeviceInfoByRawID(deviceID).parameterSet.CH_LIST, 1)
const chip1Channels = channelList.filter(c => c >= 0 && c <= 31)
const chip2Channels = channelList.filter(c => c >= 32 && c <= 63)
if (chip1Channels.length !== 0 && chip2Channels.length !== 0) {
sampleRate = taskInfo.getDeviceInfoByRawID(deviceID).parameterSet.SAMPLE_RATE * 2
} else {
sampleRate = taskInfo.getDeviceInfoByRawID(deviceID).parameterSet.SAMPLE_RATE
}
}
await taskInfo.updateSdCardStatus(String.fromCharCode.apply(null, data))
// 經驗公式
// 3: 1 sample = 3 bytes
// /1000: sample rate unit to k
// 1.1: 10% buffer
const estimatedRemainTime = parseInt(storage / (3 * (sampleRate / 1000) * 1.1))
const showTime = this.GLOBAL.transTime(estimatedRemainTime * 1e6) // *1e6: s -> us
this.pageToast(`The SD card can still record ${showTime} with sampling rate ${(sampleRate / 1e3).toFixed(1) + 'k'}`)
},
async '+/get_sd_status/+' (data, topic) {
const mes = new TextDecoder('utf-8').decode(data)
const mesJson = JSON.parse(mes)
for (const device of this.$refs.devices_ref.deviceList) {
if (mesJson.device === taskInfo.getRawDeviceID(device.id)) {
device.sdCardStatus = mesJson.data.sd_mount
}
}
await taskInfo.updateSdCardStatus(mes)
this.refreshDevicesWindow()
},
'+/data-server/trigger_manager/#' (data, topic) {
let foundIndex
let mesJson
const mes = new TextDecoder('utf-8').decode(data)
const topicSplit = topic.split('/')
const baseIndex = topicSplit.indexOf('trigger_manager')
const actionIndex = baseIndex + 1
const action = topicSplit[actionIndex]
const macIndex = baseIndex + 2
let _mac
if (topicSplit.length - 1 >= macIndex) {
_mac = topicSplit[macIndex]
foundIndex = this.triggerList.findIndex(o => o.mac === _mac)
}
switch (action) {
case 'info_all': // control_ID/data-server/trigger_manager/info_all
this.triggerList = JSON.parse(mes).connected
this.deviceTriggerChartSetDisplay()
this.taskContentChartRefreshPanel()
break
case 'register': // control_ID/data-server/trigger_manager/register
mesJson = JSON.parse(mes)
foundIndex = this.triggerList.findIndex(o => o.mac === mesJson.mac)
if (foundIndex < 0) { // -1: not found
this.triggerList.push(mesJson)
} else {
this.triggerList[foundIndex] = mesJson
}
break
case 'remove': // control_ID/data-server/trigger_manager/remove/device_mac
this.triggerList.splice(foundIndex, 1)
break
case 'info': // control_ID/data-server/trigger_manager/info/device_mac
this.triggerList[foundIndex] = JSON.parse(mes)
this.deviceTriggerChartSetDisplay()
this.taskContentChartRefreshPanel()
break
case 'set_parameter': // control_ID/data-server/trigger_manager/set_parameter/device_mac
this.triggerList[foundIndex].parameter = JSON.parse(mes)
break
case 'runtime': // control_ID/data-server/trigger_manager/runtime/device_mac
this.deviceTriggerChartSetDisplay()
this.taskContentChartRefreshPanel()
break
default:
break
}
},
async '+/broadcast' (data, topic) {
const mes = String.fromCharCode.apply(null, data).split(':')
// let content
@@ -282,7 +379,9 @@ export default {
this.mqttSub(id + '/get_device_info/+')
this.mqttSub(id + '/device_parameter/+')
this.mqttSub(id + '/device_parameter_value/+')
this.mqttSub(id + '/device_sd_card_status/+')
this.mqttSub(id + '/device_get_sd_storage/+')
this.mqttSub(id + '/get_sd_status/+')
this.mqttSub(id + '/data-server/trigger_manager/#')
},
pageMqttUnSub: function (id) {
for (const device in taskInfo.getTaskInfo().deviceList) {
@@ -294,7 +393,9 @@ export default {
this.mqttUnSub(id + '/get_device_info/+')
this.mqttUnSub(id + '/device_parameter/+')
this.mqttUnSub(id + '/device_parameter_value/+')
this.mqttUnSub(id + '/device_sd_card_status/+')
this.mqttUnSub(id + '/device_get_sd_storage/+')
this.mqttUnSub(id + '/get_sd_status/+')
this.mqttUnSub(id + '/data-server/trigger_manager/#')
},
pageInit: async function () {
if (await this.$refs.devices_ref != null) {
@@ -314,6 +415,8 @@ export default {
}))
this.taskContentChartNumber = Object.keys(this.chartData).length
this.mqttPub(taskInfo.getTaskInfo().controllerID + '/client/trigger_manager/scan', '{}')
this.mqttPub(taskInfo.getTaskInfo().controllerID + '/client/trigger_manager/info_all', '{}')
},
pageToast: function (mes) {
this.showToast(
@@ -325,8 +428,15 @@ export default {
},
)
},
findAllIndexes (arr, val) {
var index = []; var i = -1
while ((i = arr.indexOf(val, i + 1)) !== -1) {
index.push(i)
}
return index
},
refreshData: function () {
// TODO don't need to refresh chart with stop status
// [TODO] don't need to refresh chart with stop status
for (const chart in this.chartData) {
this.taskContentChartRefreshData({
chartID: parseInt(chart),
@@ -338,9 +448,6 @@ export default {
if (this.$refs.devices_ref != null) {
this.$refs.devices_ref.refresh()
}
// api.task.update({
// device_config: taskInfo.getTaskInfo().deviceList,
// })
},
addChart: async function (isDefault) {
this.taskContentChartAdd({
@@ -489,6 +596,28 @@ export default {
this.refreshDevicesWindow()
this.pageToast('The all devices have been started')
},
recordAllClick: async function () {
for (const device of this.deviceList) {
if (this.$refs.devices_ref === undefined) {
this.startDevice(device)
} else {
await this.$refs.devices_ref.$refs.pdlist_ref.record(device)
}
}
this.refreshDevicesWindow()
this.pageToast('The all devices are recording')
},
stimulateAllClick: async function () {
for (const device of this.deviceList) {
if (this.$refs.devices_ref === undefined) {
this.startDevice(device)
} else {
await this.$refs.devices_ref.$refs.pdlist_ref.stimulate(device)
}
}
this.refreshDevicesWindow()
this.pageToast('The all devices are stimulating')
},
startDevice: async function (device) {
await this.taskContentChartDefaultChartGenerator({
_deviceList: this.deviceList,
@@ -566,6 +695,8 @@ export default {
taskTypes.chart.refreshChart,
taskTypes.chart.refreshPanel,
]),
...mapActions('device', [deviceTypes.triggerChart.setDisplay]),
},
watch: {
},
@@ -578,7 +709,6 @@ export default {
await this.pageInit()
this.dataRefreshTimer = setInterval(this.refreshData, 100)
this.chartRefreshTimer = setInterval(this.taskContentChartRefreshChart, 1000)
// const _this = this
// window.onresize = function () { // define the event of window size changing
@@ -26,7 +26,7 @@
</va-tabs>
<div v-if="tabValue == 0" >
<div v-if="chartData[chartID].legend.data.length != 0">
<chart :key="taskContentChartKey" :ref="'chart_ref'" :style="'width: '+ cardwidth + '; height: ' + cardheight + ';'" :options="chartData[chartID]" :auto-resize="true"></chart>
<chart :key="taskContentChartKey" :ref="'chart_ref'" :style="'width: '+ cardwidth + '; height: ' + cardheight + ';'" :options="data" :auto-resize="true"></chart>
</div>
</div>
<div v-else-if="tabValue == 1" :style="'width: '+ cardwidth + '; height: ' + cardheight + ';'" >
@@ -147,6 +147,7 @@ export default {
},
data () {
return {
data: null,
tabValue: 0,
resizeTimer: null,
mappingSelect: '',
@@ -324,6 +325,7 @@ export default {
]),
},
async mounted () {
this.data = this.chartData[this.chartID]
this.resizeTimer = setInterval(this.resize, 1000)
this.nameCard = this.chartData[this.chartID].cardName
await this.refreshMappingSelect()
@@ -0,0 +1,225 @@
<template>
<div class="flex" :class="cardclass">
<va-card>
<chart :ref="'chart_ref'" :style="'width: '+ cardwidth + '; height: ' + cardheight + ';'" :options="sketchmapData" :auto-resize="true"></chart>
</va-card>
</div>
</template>
<script>
import { mapActions } from 'vuex'
import { taskInfo } from '@/data/task/TaskInfo'
import { mapFields } from 'vuex-map-fields'
import deviceTypes from '@/store/modules/device/types'
export default {
name: 'TriggerChart',
components: {
},
computed: {
...mapFields('device', [
'triggerList',
'allChannels',
]),
},
props: {
mac: {
type: String,
required: true,
},
},
created () {
},
data () {
return {
timer: null,
cardclass: ['xs12', 'md12', 'xl12', 'sm12'],
cardheight: '400px',
cardwidth: 'auto',
sketchmapData: {},
activeChannels: [],
}
},
mqtt: {
async '+/data-server/trigger_manager/#' (data, topic) {
const mes = new TextDecoder('utf-8').decode(data)
const topicSplit = topic.split('/')
const baseIndex = topicSplit.indexOf('trigger_manager')
const actionIndex = baseIndex + 1
const action = topicSplit[actionIndex]
const macIndex = baseIndex + 2
let _mac
if (topicSplit.length - 1 >= macIndex) {
_mac = topicSplit[macIndex]
}
if (_mac === this.mac) {
switch (action) {
case 'set_parameter': // control_ID/data-server/trigger_manager/set_parameter/device_mac
await this.initActiveChannels()
this.initChart()
break
case 'runtime': // control_ID/data-server/trigger_manager/runtime/device_mac
this.updateTimer(mes)
break
default:
break
}
}
},
'+/data-server/trigger_runtime/#' (data, topic) { // control_ID/data-server/trigger_runtime/pin_event/device_mac/channel
const mes = new TextDecoder('utf-8').decode(data)
const topicSplit = topic.split('/')
const baseIndex = topicSplit.indexOf('trigger_runtime')
const actionIndex = baseIndex + 1
const action = topicSplit[actionIndex]
const macIndex = baseIndex + 2
const channelIndex = baseIndex + 3
let _mac
if (topicSplit.length - 1 >= macIndex) {
_mac = topicSplit[macIndex]
}
let channel
if (topicSplit.length - 1 >= channelIndex) {
channel = parseInt(topicSplit[channelIndex])
}
let mesJson
if (_mac === this.mac) {
switch (action) {
case 'pin_event':
mesJson = JSON.parse(mes)
this.addSketchMapData(mesJson.timestamp, mesJson.status === 'high' ? 3.3 : 0.0, channel)
break
default:
break
}
}
},
},
methods: {
addSketchMapData (timestamp, val, channel) {
const gridIndex = this.activeChannels.indexOf(channel)
this.sketchmapData.series[gridIndex].data.push([parseInt(timestamp), val])
if (this.sketchmapData.series[gridIndex].data.length > 1000) {
this.sketchmapData.series[gridIndex].data.shift()
}
},
mqttSub: function (val) {
this.$mqtt.subscribe(val)
},
mqttUnSub: function (val) {
this.$mqtt.unsubscribe(val)
},
makeGrid (index, length) {
const _height = (100 / length) - 2 + '%'
const _top = index * (100 / length) + 2 + '%'
const _left = '0%'
const _right = '3%'
return {
index: index,
top: _top,
height: _height,
left: _left,
right: _right,
containLabel: true,
}
},
makeSeries (xAxisIndex, yAxisIndex) {
return {
type: 'line',
step: 'start',
data: [],
animation: false,
xAxisIndex: xAxisIndex,
yAxisIndex: yAxisIndex,
}
},
makeXAxis (gridIndex) {
return {
show: false,
type: 'time',
gridIndex: gridIndex,
min: 'dataMin',
max: 'dataMax',
}
},
makeYAxis (gridIndex) {
return {
type: 'value',
gridIndex: gridIndex,
splitNumber: 1,
splitLine: {
show: false,
},
axisLabel: {
formatter: function (value) {
return value + 'V'
},
},
min: 'dataMin',
max: 'dataMax',
}
},
async initActiveChannels () {
this.activeChannels = await this.deviceTriggerGetActiveChannels({ mac: this.mac })
},
initChart () {
this.clearChart()
const currImplementedChannels = this.activeChannels.filter(ch => ch < 4) // [TODO] now only open 4 channels (2022/01/05)
const gridLength = currImplementedChannels.length
for (let gridIndex = 0; gridIndex < gridLength; gridIndex++) {
this.sketchmapData.grid.push(this.makeGrid(gridIndex, gridLength))
this.sketchmapData.xAxis.push(this.makeXAxis(gridIndex))
this.sketchmapData.yAxis.push(this.makeYAxis(gridIndex))
this.sketchmapData.series.push(this.makeSeries(gridIndex, gridIndex))
if (gridIndex === gridLength - 1) {
this.sketchmapData.xAxis[gridLength - 1].show = true
}
}
},
clearChart () {
this.sketchmapData = {
xAxis: [],
yAxis: [],
tooltip: {
trigger: 'axis',
},
series: [],
grid: [],
}
},
updateTimer (status) {
if (status === 'start') {
if (this.timer === null) {
this.timer = setInterval(this.updateCurrentTime, 1000)
}
} else {
clearInterval(this.timer)
this.timer = null
}
},
updateCurrentTime () {
const currTime = Date.now()
this.sketchmapData.xAxis.forEach(axis => {
axis.min = currTime - 10000 // 10000ms = 10s
axis.max = currTime + 1000
})
},
...mapActions('device', [deviceTypes.trigger.getActiveChannels]),
},
async mounted () {
this.mqttSub(taskInfo.getTaskInfo().controllerID + '/data-server/trigger_manager/#')
this.mqttSub(taskInfo.getTaskInfo().controllerID + '/data-server/trigger_runtime/#')
await this.initActiveChannels()
this.initChart()
this.updateTimer('start')
},
destroyed () {
this.mqttUnSub(taskInfo.getTaskInfo().controllerID + '/data-server/trigger_manager/#')
this.mqttUnSub(taskInfo.getTaskInfo().controllerID + '/data-server/trigger_runtime/#')
this.clearChart()
this.updateTimer('stop')
},
}
</script>
@@ -38,14 +38,13 @@
</va-item-section>
<va-item-section class="mr-3" v-if="device.type == 'Neulive'" side>
<i v-if="!device.sdCardStatus" class="i-sd-card mr-3" style="width: 1rem; height: 1rem;"></i>
<!-- <va-icon
name="fas fa-sd-card"
:color="(device.sdCardStatus === true) ? 'primary' : 'grey'"
class="mr-3"
@click.native.stop="sdCardStatus(device)"
>
</va-icon> -->
<img
v-if="device.sdCardStatus"
src="@/assets/icons/sd-card-solid.svg"
:class="getSDEnableStatus(device.id) ? 'mr-3 filter-none' : 'mr-3 filter-grey'"
style="width: 1rem; height: 1rem;"
@click.stop="enableSD(device)"
/>
<va-icon
size="1rem"
:name="(device.transmission_type === 0) ? 'fa fa-bluetooth-b' : 'fa fa-usb'"
@@ -66,6 +65,7 @@
<va-button @click.stop="stimulate(device)" v-if="device.type == 'Neulive' && !device.isStimulate" color="warning" class="px-2 py-0 mr-0 no-dark-style-va-button" small>STIMULATE</va-button>
<va-button @click.stop="stopStimulate(device)" v-if="device.type == 'Neulive' && device.isStimulate" color="danger" class="px-2 py-0 mr-0" small>STOP STI</va-button>
<va-button @click.stop="fastSettle(device)" v-if="device.type == 'Neulive'" color="info" class="px-2 py-0 mr-0 no-dark-style-va-button" small>R</va-button>
<va-button @click.stop="checkSDStatus(device)" v-if="device.type == 'Neulive'" :disabled="device.isSync || device.isStimulate" color="success" class="px-2 py-0 mr-0 no-dark-style-va-button" small>SD</va-button>
</va-item-label>
</va-item-section>
</va-item>
@@ -98,6 +98,7 @@ import NeuliveParameterWindow from '../parameter/NeuliveParameterWindow'
import FileControlWindow from '../meta/FileControlWindow'
import { mapActions } from 'vuex'
import types from '@/store/modules/task/content/types'
import { taskInfo } from '@/data/task/TaskInfo'
export default {
name: 'TaskDeviceList',
@@ -122,12 +123,6 @@ export default {
computed: {
},
methods: {
sdCardStatus: function (device) {
this.$emit('sd_card_status', {
id: device.id,
sdCardStatus: device.sdCardStatus,
})
},
removeCheck: function (device) {
this.selectDevice = device
this.showDeleteModal = true
@@ -233,14 +228,28 @@ export default {
this.$emit('fast_settle', device)
this.pageToast('The device ' + device.name + ' fast settle.')
},
checkSDStatus (device) {
this.$emit('check_sd_card_status', device)
this.pageToast('Check ' + device.name + ' SD card status.')
},
getSDEnableStatus (deviceID) {
return taskInfo.getDeviceInfo(deviceID).parameterSet.SD_ENABLE
},
enableSD (device) {
if (!device.isSync && !device.isStimulate) {
if (taskInfo.getDeviceInfo(device.id).parameterSet.SD_ENABLE) {
this.pageToast('Try to disable' + device.name + ' SD card.')
} else {
this.pageToast('Try to enable' + device.name + ' SD card.')
}
this.$emit('enable_sd', device)
}
},
recoverDetectButton () {
document.getElementById('detectButton').className += ' no-dark-style-va-button'
},
...mapActions('taskContent', [types.param.checkIfRecordingSetDone, types.param.checkIfStimulationSetDone, types.param.checkIfDeviceSetDone]),
},
destroyed () {
// this.deviceList.length = 0
},
}
</script>
@@ -250,4 +259,12 @@ export default {
filter: brightness(100%);
}
}
.filter-none {
filter: grayscale(0%);
}
.filter-grey {
filter: grayscale(100%);
}
</style>
@@ -25,9 +25,32 @@
@sti="sti($event)"
@sti_stop="stiStop($event)"
@fast_settle="fastSettle($event)"
@enable_sd="enableSD($event)"
@generate="generateChart($event)"
@detect="detectDevice($event)"
@sd_card_status="sdCardStatus($event)"
@check_sd_card_status="checkSDCardStatus($event)"
/>
</div>
</va-card>
<va-card class="mt-2">
<template slot="header">
<va-icon name="fa fa-bluetooth-b mr-3" color="primary"/>
<h5 class="mt-0 mb-0">TRIGGERS</h5>
</template>
<div v-if="state.LOADING" class="flex-center spinner-box mt-4 mb-5">
<fulfilling-bouncing-circle-spinner
:animation-duration="3000"
:color="this.$themes.primary"
:size="60"
>
</fulfilling-bouncing-circle-spinner>
</div>
<div class="flex row align--center">
<trigger-list
@start="startTrigger($event)"
@stop="stopTrigger($event)"
/>
</div>
@@ -38,6 +61,7 @@
<script>
import { FulfillingBouncingCircleSpinner } from 'epic-spinners'
import TaskDeviceList from './TaskDeviceList'
import TriggerList from './TriggerList'
// import TaskDeviceListGM from './neulive-gimer/TaskDeviceListGM'
import { taskInfo } from '../../../../data/task/TaskInfo'
import { dataStreamBuffer } from '../../../../data/task/DataStreamBuffer'
@@ -52,6 +76,7 @@ export default {
// TaskDeviceListGM,
FulfillingBouncingCircleSpinner,
TaskDeviceList,
TriggerList,
},
props: {
cardclass: {
@@ -72,7 +97,6 @@ export default {
return {
deviceSelect: '',
deviceOptions: taskInfo.getDeviceListNotRegister(),
// deviceList: this.deviceList,
state: {
LOADING: false,
},
@@ -99,6 +123,10 @@ export default {
const index = this.deviceList.findIndex(d => taskInfo.getRawDeviceID(d.id) === i)
if (index < 0) {
await this.addDevice(i)
this.mqttPub(taskInfo.getTaskInfo().controllerID + '_user', JSON.stringify({
header: 'get_sd_status/0',
device: i,
}))
}
} else {
const index = this.deviceList.findIndex(d => taskInfo.getRawDeviceID(d.id) === i)
@@ -129,6 +157,10 @@ export default {
device: taskInfo.getRawDeviceID(device.id),
instruction: 'record',
}))
this.mqttPub(taskInfo.getTaskInfo().controllerID + '_user', JSON.stringify({
header: 'device_get_sd_storage/0',
device: taskInfo.getRawDeviceID(device.id),
}))
this.mqttPub(taskInfo.getTaskInfo().controllerID + '_user', JSON.stringify({
header: 'device_instruction/0',
device: taskInfo.getRawDeviceID(device.id),
@@ -182,25 +214,25 @@ export default {
},
stopDevice: async function (device) {
await dataStreamBuffer.removeByDevice(taskInfo.getRawDeviceID(device.id))
this.mqttPub(taskInfo.getTaskInfo().controllerID + '_user', JSON.stringify({
header: 'device_instruction/0',
device: taskInfo.getRawDeviceID(device.id),
instruction: 'stop_stimulate',
}))
this.mqttPub(taskInfo.getTaskInfo().controllerID + '_user', JSON.stringify({
header: 'device_parameter/0',
device: taskInfo.getRawDeviceID(device.id),
parameter: 'STIMULATING',
content: 0,
}))
device.isStimulate = false
if (taskInfo.getDeviceInfo(device.id).info.isSync) {
if (taskInfo.getDeviceInfo(device.id).info.library_name.startsWith('Neulive')) {
this.mqttPub(taskInfo.getTaskInfo().controllerID + '_user', JSON.stringify({
header: 'device_instruction/0',
device: taskInfo.getRawDeviceID(device.id),
instruction: 'stop_stimulate',
}))
this.mqttPub(taskInfo.getTaskInfo().controllerID + '_user', JSON.stringify({
header: 'device_instruction/0',
device: taskInfo.getRawDeviceID(device.id),
instruction: 'stop_record',
}))
this.mqttPub(taskInfo.getTaskInfo().controllerID + '_user', JSON.stringify({
header: 'device_parameter/0',
device: taskInfo.getRawDeviceID(device.id),
parameter: 'STIMULATING',
content: 0,
}))
device.isStimulate = false
} else if (taskInfo.getDeviceInfo(device.id).info.library_name.startsWith('Elite')) {
this.mqttPub(taskInfo.getTaskInfo().controllerID + '_user', JSON.stringify({
header: 'device_instruction/0',
@@ -237,6 +269,10 @@ export default {
_chartID: null,
_type: 'device',
})
this.mqttPub(taskInfo.getTaskInfo().controllerID + '_user', JSON.stringify({
header: 'device_get_sd_storage/0',
device: taskInfo.getRawDeviceID(device.id),
}))
if (!taskInfo.getDeviceInfo(device.id).info.isSync) {
this.mqttPub(taskInfo.getTaskInfo().controllerID + '_user', JSON.stringify({
header: 'device_instruction/0',
@@ -314,11 +350,47 @@ export default {
instruction: 'FAST_SET',
}))
},
sdCardStatus: function (device) {
checkSDCardStatus: function (device) {
this.mqttPub(taskInfo.getTaskInfo().controllerID + '_user', JSON.stringify({
header: 'device_sd_card_status/0',
header: 'get_sd_status/0',
device: taskInfo.getRawDeviceID(device.id),
}))
this.mqttPub(taskInfo.getTaskInfo().controllerID + '_user', JSON.stringify({
header: 'device_get_sd_storage/0',
device: taskInfo.getRawDeviceID(device.id),
}))
},
enableSD (device) {
this.mqttPub(taskInfo.getTaskInfo().controllerID + '_user', JSON.stringify({
header: 'device_parameter/0',
device: taskInfo.getRawDeviceID(device.id),
parameter: 'SD_ENABLE',
content: !taskInfo.getDeviceInfo(device.id).parameterSet.SD_ENABLE,
}))
this.mqttPub(taskInfo.getTaskInfo().controllerID + '_user', JSON.stringify({
header: 'get_sd_status/0',
device: taskInfo.getRawDeviceID(device.id),
}))
},
startTrigger (trigger) {
// save automatically when start
this.mqttPub(taskInfo.getTaskInfo().controllerID + '/client/trigger_manager/set_parameter', JSON.stringify({
mac: trigger.mac,
parameter_set: trigger.parameter,
}),
)
this.mqttPub(taskInfo.getTaskInfo().controllerID + '/client/trigger_manager/runtime', JSON.stringify({
mac: trigger.mac,
instruction: 'start',
}),
)
},
stopTrigger (trigger) {
this.mqttPub(taskInfo.getTaskInfo().controllerID + '/client/trigger_manager/runtime', JSON.stringify({
mac: trigger.mac,
instruction: 'stop',
}),
)
},
addDevice: async function (selectID) {
if (selectID == null) {
@@ -0,0 +1,85 @@
<template>
<div class="flex sm12 xl12 md12 xs12">
<va-accordion v-for="trigger in triggerList" :key="trigger.id">
<va-collapse class="mb-2" :isOpenDefault='true'>
<span slot="header" >
<va-item :key="trigger.id" class="pa-0 ma-0"
>
<va-item-section class="ml-3" caption>
<va-item-label>
{{ trigger.name }}
</va-item-label>
<va-item-label caption>
{{ trigger.mac }}
</va-item-label>
</va-item-section>
</va-item>
<va-item class="pa-0 ma-0 mt-2">
<va-item-section class="ml-3">
<va-item-label>
<va-button @click.stop="start(trigger)" v-if="trigger.status !== 'RUNNING'" class="px-2 py-0 mr-0" color="success" small>START</va-button>
<va-button @click.stop="stop(trigger)" v-if="trigger.status === 'RUNNING'" class="px-2 py-0 mr-0" color="danger" small>STOP</va-button>
</va-item-label>
</va-item-section>
</va-item>
</span>
<div slot="body">
<div class="mt-0 mb-0" style="height: 350px; overflow-y: scroll; overflow-x: hidden;" :key="'detail' + trigger.id">
<div>
<trigger-control-window :mac="trigger.mac" />
</div>
</div>
</div>
</va-collapse>
</va-accordion>
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields'
import TriggerControlWindow from '../trigger/TriggerControlWindow'
export default {
name: 'TriggerList',
components: {
TriggerControlWindow,
},
data () {
return {
}
},
computed: {
...mapFields('device', [
'triggerList',
'chartKey',
]),
},
methods: {
start: async function (trigger) {
this.$emit('start', trigger)
this.triggerList.find(o => o.mac === trigger.mac).display = true
this.triggerList.find(o => o.mac === trigger.mac).status = 'RUNNING'
this.chartKey++
this.pageToast('The trigger ' + trigger.name + ' has been started.')
},
stop: function (trigger) {
this.$emit('stop', trigger)
this.triggerList.find(o => o.mac === trigger.mac).status = 'CONNTECTED'
this.pageToast('The trigger ' + trigger.name + ' has been stopped.')
},
pageToast: function (mes) {
this.showToast(
mes,
{
icon: 'fa-bell',
position: 'bottom-right',
duration: 4000,
},
)
},
},
destroyed () {
},
}
</script>
@@ -1,130 +1,132 @@
<template>
<div class="flex" >
<va-tree-root >
<va-tree-category
isOpen
style="font-size: 20px;"
:label="'Channels'">
<va-tree-node>
<div class="row">
<div class="flex sm12 xl12 md12 xs12">
<div
style='display: inline-block;'
v-for="channel in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]"
:key="channel"
<div>
<div class="mt-4">
<div class="row sm12 xl12 md12 xs12">
<p class="display-6">CHANNELS</p>
</div>
<div class="row">
<div class="flex sm12 xl12 md12 xs12">
<div
style='display: inline-block;'
v-for="channel in channelArray"
:key="channel"
>
<va-button
class="ma-1 pa-0 circle-button"
v-if="channelList != null && channelList.indexOf(channel) >= 0"
@click="channelClickRemove(channel)"
small
>
<va-button
class="mx-1 px-1"
v-if="channelList != null && channelList.indexOf(channel) >= 0"
@click="channelClickRemove(channel)"
small
>
{{channel}}
</va-button>
<va-button
class="mx-1 px-1"
v-if="channelList == null || channelList.indexOf(channel) < 0"
@click="channelClickAdd(channel)"
small outline
>
{{channel}}
</va-button>
</div>
</div>
</div>
</va-tree-node>
</va-tree-category>
<va-tree-category
style="font-size: 20px;"
:label="'3-AXIS'">
<va-tree-node>
<div class="row">
<div class="flex sm12 xl12 md12 xs12">
<div
style='display: inline-block;'
v-for="axisChannel in ['x','y','z','m']"
:key="axisChannel"
{{channel}}
</va-button>
<va-button
class="ma-1 pa-0 circle-button"
v-if="channelList == null || channelList.indexOf(channel) < 0"
@click="channelClickAdd(channel)"
small outline
>
<va-button
class="mx-1 px-1"
v-if="axisChannelList != null && axisChannelList.indexOf(axisChannel) >= 0"
@click="axisChannelClickRemove(axisChannel)"
small
>
{{axisChannel}}
</va-button>
<va-button
class="mx-1 px-1"
v-if="axisChannelList == null || axisChannelList.indexOf(axisChannel) < 0"
@click="axisChannelClickAdd(axisChannel)"
small outline
>
{{axisChannel}}
</va-button>
</div>
{{channel}}
</va-button>
</div>
</div>
</va-tree-node>
</va-tree-category>
</div>
</div>
<va-tree-category
style="font-size: 20px;"
:label="'Amp gain'">
<va-tree-node>
<div class="row">
<div class="flex sm12 xl12 md12 xs12">
<va-select
@input="ampGainSelectChange()"
:label="''"
v-model="ampGainSelect"
textBy="description"
:options="ampGainOptions"
:noClear='true'
/>
<div class="mt-4">
<div class="row sm12 xl12 md12 xs12">
<p class="display-6">3-AXIS SENSITIVITY</p>
</div>
<div class="row">
<div class="flex sm12 xl12 md12 xs12">
<div
style='display: inline-block;'
v-for="axisChannel in ['x','y','z','m']"
:key="axisChannel"
>
<va-button
class="ma-1 pa-0 circle-button"
v-if="axisChannelList != null && axisChannelList.indexOf(axisChannel) >= 0"
@click="axisChannelClickRemove(axisChannel)"
small
>
{{axisChannel}}
</va-button>
<va-button
class="ma-1 pa-0 circle-button"
v-if="axisChannelList == null || axisChannelList.indexOf(axisChannel) < 0"
@click="axisChannelClickAdd(axisChannel)"
small outline
>
{{axisChannel}}
</va-button>
</div>
</div>
</va-tree-node>
</va-tree-category>
</div>
</div>
<va-tree-category
style="font-size: 20px;"
:label="'Sample Rate'">
<va-tree-node>
<div class="row">
<div class="flex sm12 xl12 md12 xs12">
<va-select
@input="sampleRateSelectChange()"
:label="''"
v-model="sampleRateSelect"
textBy="description"
:options="sampleRateOptions"
:noClear='true'
/>
</div>
<div class="mt-4">
<div class="row sm12 xl12 md12 xs12">
<p class="display-6">AMPLITUDE GAIN</p>
</div>
<div class="row" v-if="ampGainSelect">
<div class="flex sm12 xl12 md12 xs12">
<va-select
@input="ampGainSelectChange()"
:label="''"
v-model="ampGainSelect"
textBy="description"
:options="ampGainOptions"
:noClear='true'
/>
</div>
</va-tree-node>
</va-tree-category>
</div>
</div>
<va-tree-category
style="font-size: 20px;"
:label="'Accelerator Sensitivity'">
<va-tree-node>
<div class="row">
<div class="flex sm12 xl12 md12 xs12">
<va-select
@input="acceleratorSensitivitySelectChange()"
:label="''"
v-model="acceleratorSensitivitySelect"
textBy="description"
:options="acceleratorSensitivityOptions"
:noClear='true'
/>
</div>
<div class="mt-3">
<div class="row sm12 xl12 md12 xs12">
<p class="display-6">SAMPLING RATE</p>
</div>
<div class="row" v-if="sampleRateSelect">
<div class="flex sm6 xl6 md6 xs6 pt-3 pb-0">
<va-select
@input="sampleRateSelectChange()"
:label="''"
v-model="sampleRateSelect"
textBy="description"
:options="sampleRateOptions"
:noClear='true'
/>
</div>
</va-tree-node>
</va-tree-category>
</va-tree-root>
<div class="flex sm6 xl6 md6 xs6 pt-3 pb-0">
<span> sps in total </span>
</div>
<!-- v-if="sampleRateSelect" -> ref: https://stackoverflow.com/questions/6003884/how-do-i-check-for-null-values-in-javascript -->
<div class="flex sm12 xl12 md12 xs12 pt-0 pb-2 my-0" v-if="sampleRateSelect">
<p v-if="channelList.length !== 0"> {{ (sampleRateSelect.value / (channelList.length * 1e3)).toFixed(1) + 'k' }} sps per selected channel </p>
<p v-else> NaN sps per selected channel </p>
</div>
</div>
</div>
<div class="mt-3">
<div class="row sm12 xl12 md12 xs12">
<p class="display-6">ACCELERATOR SENSITIVITY</p>
</div>
<div class="row" v-if="acceleratorSensitivitySelect">
<div class="flex sm12 xl12 md12 xs12">
<va-select
@input="acceleratorSensitivitySelectChange()"
:label="''"
v-model="acceleratorSensitivitySelect"
textBy="description"
:options="acceleratorSensitivityOptions"
:noClear='true'
/>
</div>
</div>
</div>
</div>
</div>
</template>
@@ -153,6 +155,7 @@ export default {
},
data () {
return {
channelArray: Array.from({ length: 16 }, (_, i) => i + 1),
channelList: [],
ampGainSelect: null,
ampGainOptions: [
@@ -1,130 +1,130 @@
<template>
<div class="flex" >
<va-tree-root >
<va-tree-category
style="font-size: 20px;"
:label="'Channels'">
<va-tree-node>
<div class="row">
<div class="flex sm12 xl12 md12 xs12">
<div
style='display: inline-block;'
v-for="channel in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32]"
:key="channel"
<div class="mt-4">
<div class="row sm12 xl12 md12 xs12">
<p class="display-6">CHANNELS</p>
</div>
<div class="row">
<div class="flex sm12 xl12 md12 xs12">
<div
style='display: inline-block;'
v-for="channel in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32]"
:key="channel"
>
<va-button
class="ma-1 pa-0 circle-button"
v-if="channelList != null && channelList.indexOf(channel) >= 0"
@click="channelClickRemove(channel)"
small
>
{{channel}}
</va-button>
<va-button
class="ma-1 pa-0 circle-button"
v-if="channelList == null || channelList.indexOf(channel) < 0"
@click="channelClickAdd(channel)"
small outline
>
{{channel}}
</va-button>
</div>
</div>
</div>
<div class="mt-4">
<div class="row sm12 xl12 md12 xs12">
<p class="display-6">3-AXIS SENSITIVITY</p>
</div>
<div class="row">
<div class="flex sm12 xl12 md12 xs12">
<div
style='display: inline-block;'
v-for="axisChannel in ['x','y','z','m']"
:key="axisChannel"
>
<va-button
class="ma-1 pa-0 circle-button"
v-if="axisChannelList != null && axisChannelList.indexOf(axisChannel) >= 0"
@click="axisChannelClickRemove(axisChannel)"
small
>
<va-button
class="ma-1 pa-0 circle-button"
v-if="channelList != null && channelList.indexOf(channel) >= 0"
@click="channelClickRemove(channel)"
small
>
{{channel}}
</va-button>
<va-button
class="ma-1 pa-0 circle-button"
v-if="channelList == null || channelList.indexOf(channel) < 0"
@click="channelClickAdd(channel)"
small outline
>
{{channel}}
</va-button>
</div>
</div>
</div>
</va-tree-node>
</va-tree-category>
<va-tree-category
style="font-size: 20px;"
:label="'3-AXIS'">
<va-tree-node>
<div class="row">
<div class="flex sm12 xl12 md12 xs12">
<div
style='display: inline-block;'
v-for="axisChannel in ['x','y','z','m']"
:key="axisChannel"
{{axisChannel}}
</va-button>
<va-button
class="ma-1 pa-0 circle-button"
v-if="axisChannelList == null || axisChannelList.indexOf(axisChannel) < 0"
@click="axisChannelClickAdd(axisChannel)"
small outline
>
<va-button
class="ma-1 pa-0 circle-button"
v-if="axisChannelList != null && axisChannelList.indexOf(axisChannel) >= 0"
@click="axisChannelClickRemove(axisChannel)"
small
>
{{axisChannel}}
</va-button>
<va-button
class="ma-1 pa-0 circle-button"
v-if="axisChannelList == null || axisChannelList.indexOf(axisChannel) < 0"
@click="axisChannelClickAdd(axisChannel)"
small outline
>
{{axisChannel}}
</va-button>
</div>
{{axisChannel}}
</va-button>
</div>
</div>
</va-tree-node>
</va-tree-category>
</div>
</div>
<va-tree-category
style="font-size: 20px;"
:label="'Amp gain'">
<va-tree-node>
<div class="row">
<div class="flex sm12 xl12 md12 xs12">
<va-select
@input="ampGainSelectChange()"
:label="''"
v-model="ampGainSelect"
textBy="description"
:options="ampGainOptions"
:noClear='true'
/>
</div>
<div class="mt-4">
<div class="row sm12 xl12 md12 xs12">
<p class="display-6">AMPLITUDE GAIN</p>
</div>
<div class="row" v-if="ampGainSelect">
<div class="flex sm12 xl12 md12 xs12">
<va-select
@input="ampGainSelectChange()"
:label="''"
v-model="ampGainSelect"
textBy="description"
:options="ampGainOptions"
:noClear='true'
/>
</div>
</va-tree-node>
</va-tree-category>
</div>
</div>
<va-tree-category
style="font-size: 20px;"
:label="'Sample Rate'">
<va-tree-node>
<div class="row">
<div class="flex sm12 xl12 md12 xs12">
<va-select
@input="sampleRateSelectChange()"
:label="''"
v-model="sampleRateSelect"
textBy="description"
:options="sampleRateOptions"
:noClear='true'
/>
</div>
<div class="mt-3">
<div class="row sm12 xl12 md12 xs12">
<p class="display-6">SAMPLING RATE</p>
</div>
<div class="row" v-if="sampleRateSelect">
<div class="flex sm6 xl6 md6 xs6 pt-3 pb-0">
<va-select
@input="sampleRateSelectChange()"
:label="''"
v-model="sampleRateSelect"
textBy="description"
:options="sampleRateOptions"
:noClear='true'
/>
</div>
</va-tree-node>
</va-tree-category>
<va-tree-category
style="font-size: 20px;"
:label="'Accelerator Sensitivity'">
<va-tree-node>
<div class="row">
<div class="flex sm12 xl12 md12 xs12">
<va-select
@input="acceleratorSensitivitySelectChange()"
:label="''"
v-model="acceleratorSensitivitySelect"
textBy="description"
:options="acceleratorSensitivityOptions"
:noClear='true'
/>
</div>
<div class="flex sm6 xl6 md6 xs6 pt-3 pb-0">
<span> sps in total </span>
</div>
</va-tree-node>
</va-tree-category>
</va-tree-root>
<!-- v-if="sampleRateSelect" -> ref: https://stackoverflow.com/questions/6003884/how-do-i-check-for-null-values-in-javascript -->
<div class="flex sm12 xl12 md12 xs12 pt-0 pb-2 my-0" v-if="sampleRateSelect">
<p v-if="channelList.length !== 0"> {{ (sampleRateSelect.value / (channelList.length * 1e3)).toFixed(1) + 'k' }} sps per selected channel </p>
<p v-else> NaN sps per selected channel </p>
</div>
</div>
</div>
<div class="mt-3">
<div class="row sm12 xl12 md12 xs12">
<p class="display-6">ACCELERATOR SENSITIVITY</p>
</div>
<div class="row" v-if="acceleratorSensitivitySelect">
<div class="flex sm12 xl12 md12 xs12">
<va-select
@input="acceleratorSensitivitySelectChange()"
:label="''"
v-model="acceleratorSensitivitySelect"
textBy="description"
:options="acceleratorSensitivityOptions"
:noClear='true'
/>
</div>
</div>
</div>
</div>
</div>
</template>
@@ -1,130 +1,144 @@
<template>
<div class="flex" >
<va-tree-root >
<va-tree-category
style="font-size: 20px;"
:label="'Channels'">
<va-tree-node>
<div class="row">
<div class="flex sm12 xl12 md12 xs12">
<div
style='display: inline-block;'
v-for="channel in channelArray"
:key="channel"
<div>
<div class="mt-4">
<div class="row sm12 xl12 md12 xs12">
<p class="display-6">CHANNELS</p>
</div>
<div class="row">
<div class="flex sm12 xl12 md12 xs12">
<div
style='display: inline-block;'
v-for="channel in channelArray"
:key="channel"
>
<va-button
class="ma-1 pa-0 circle-button"
v-if="channelList != null && channelList.indexOf(channel) >= 0"
@click="channelClickRemove(channel)"
small
>
<va-button
class="ma-1 pa-0 circle-button"
v-if="channelList != null && channelList.indexOf(channel) >= 0"
@click="channelClickRemove(channel)"
small
>
{{channel}}
</va-button>
<va-button
class="ma-1 pa-0 circle-button"
v-if="channelList == null || channelList.indexOf(channel) < 0"
@click="channelClickAdd(channel)"
small outline
>
{{channel}}
</va-button>
</div>
</div>
</div>
</va-tree-node>
</va-tree-category>
<va-tree-category
style="font-size: 20px;"
:label="'3-AXIS'">
<va-tree-node>
<div class="row">
<div class="flex sm12 xl12 md12 xs12">
<div
style='display: inline-block;'
v-for="axisChannel in ['x','y','z','m']"
:key="axisChannel"
{{channel}}
</va-button>
<va-button
class="ma-1 pa-0 circle-button"
v-if="channelList == null || channelList.indexOf(channel) < 0"
@click="channelClickAdd(channel)"
small outline
>
<va-button
class="ma-1 pa-0 circle-button"
v-if="axisChannelList != null && axisChannelList.indexOf(axisChannel) >= 0"
@click="axisChannelClickRemove(axisChannel)"
small
>
{{axisChannel}}
</va-button>
<va-button
class="ma-1 pa-0 circle-button"
v-if="axisChannelList == null || axisChannelList.indexOf(axisChannel) < 0"
@click="axisChannelClickAdd(axisChannel)"
small outline
>
{{axisChannel}}
</va-button>
</div>
{{channel}}
</va-button>
</div>
</div>
</va-tree-node>
</va-tree-category>
</div>
</div>
<va-tree-category
style="font-size: 20px;"
:label="'Amp gain'">
<va-tree-node>
<div class="row">
<div class="flex sm12 xl12 md12 xs12">
<va-select
@input="ampGainSelectChange()"
:label="''"
v-model="ampGainSelect"
textBy="description"
:options="ampGainOptions"
:noClear='true'
/>
<div class="mt-4">
<div class="row sm12 xl12 md12 xs12">
<p class="display-6">3-AXIS SENSITIVITY</p>
</div>
<div class="row">
<div class="flex sm12 xl12 md12 xs12">
<div
style='display: inline-block;'
v-for="axisChannel in ['x','y','z','m']"
:key="axisChannel"
>
<va-button
class="ma-1 pa-0 circle-button"
v-if="axisChannelList != null && axisChannelList.indexOf(axisChannel) >= 0"
@click="axisChannelClickRemove(axisChannel)"
small
>
{{axisChannel}}
</va-button>
<va-button
class="ma-1 pa-0 circle-button"
v-if="axisChannelList == null || axisChannelList.indexOf(axisChannel) < 0"
@click="axisChannelClickAdd(axisChannel)"
small outline
>
{{axisChannel}}
</va-button>
</div>
</div>
</va-tree-node>
</va-tree-category>
</div>
</div>
<va-tree-category
style="font-size: 20px;"
:label="'Sample Rate'">
<va-tree-node>
<div class="row">
<div class="flex sm12 xl12 md12 xs12">
<va-select
@input="sampleRateSelectChange()"
:label="''"
v-model="sampleRateSelect"
textBy="description"
:options="sampleRateOptions"
:noClear='true'
/>
</div>
<div class="mt-4">
<div class="row sm12 xl12 md12 xs12">
<p class="display-6">AMPLITUDE GAIN</p>
</div>
<div class="row" v-if="ampGainSelect">
<div class="flex sm12 xl12 md12 xs12">
<va-select
@input="ampGainSelectChange()"
:label="''"
v-model="ampGainSelect"
textBy="description"
:options="ampGainOptions"
:noClear='true'
/>
</div>
</va-tree-node>
</va-tree-category>
</div>
</div>
<va-tree-category
style="font-size: 20px;"
:label="'Accelerator Sensitivity'">
<va-tree-node>
<div class="row">
<div class="flex sm12 xl12 md12 xs12">
<va-select
@input="acceleratorSensitivitySelectChange()"
:label="''"
v-model="acceleratorSensitivitySelect"
textBy="description"
:options="acceleratorSensitivityOptions"
:noClear='true'
/>
</div>
<div class="mt-3">
<div class="row sm12 xl12 md12 xs12">
<p class="display-6">SAMPLING RATE</p>
</div>
<div class="row" v-if="sampleRateSelect">
<div class="flex sm6 xl6 md6 xs6 pt-3 pb-0">
<va-select
@input="sampleRateSelectChange()"
:label="''"
v-model="sampleRateSelect"
textBy="description"
:options="sampleRateOptions"
:noClear='true'
/>
</div>
</va-tree-node>
</va-tree-category>
</va-tree-root>
<div class="flex sm6 xl6 md6 xs6 pt-3 pb-0">
<span> sps in total </span>
</div>
<!-- v-if="sampleRateSelect" -> ref: https://stackoverflow.com/questions/6003884/how-do-i-check-for-null-values-in-javascript -->
<div class="flex sm12 xl12 md12 xs12 pt-0 pb-2 my-0" v-if="sampleRateSelect">
<va-popover
:delay="{ show: 250 }"
placement="bottom"
message="channel 1 ~ 32"
>
<p> {{ computePerChipSampleRate(1) }} sps per selected channel in <span style="text-decoration: underline;"> chip 1 </span></p>
</va-popover>
<va-popover
:delay="{ show: 250 }"
placement="bottom"
message="channel 33 ~ 64"
>
<p> {{ computePerChipSampleRate(2) }} sps per selected channel in <span style="text-decoration: underline;"> chip 2 </span></p>
</va-popover>
</div>
</div>
</div>
<div class="mt-3">
<div class="row sm12 xl12 md12 xs12">
<p class="display-6">ACCELERATOR SENSITIVITY</p>
</div>
<div class="row" v-if="acceleratorSensitivitySelect">
<div class="flex sm12 xl12 md12 xs12">
<va-select
@input="acceleratorSensitivitySelectChange()"
:label="''"
v-model="acceleratorSensitivitySelect"
textBy="description"
:options="acceleratorSensitivityOptions"
:noClear='true'
/>
</div>
</div>
</div>
</div>
</div>
</template>
@@ -208,43 +222,43 @@ export default {
},
{
id: 6,
value: 16000,
description: '16k',
},
{
id: 7,
value: 20000,
description: '20k',
},
{
id: 7,
value: 30000,
description: '30k',
id: 8,
value: 25000,
description: '25k',
},
{
id: 8,
id: 9,
value: 32000,
description: '32k',
},
{
id: 10,
value: 40000,
description: '40k',
},
{
id: 9,
id: 11,
value: 50000,
description: '50k',
},
{
id: 10,
value: 60000,
description: '60k',
},
{
id: 11,
value: 70000,
description: '70k',
},
{
id: 12,
value: 80000,
description: '80k',
value: 64000,
description: '64k',
},
{
id: 13,
value: 90000,
description: '90k',
value: 80000,
description: '80k',
},
{
id: 14,
@@ -279,6 +293,7 @@ export default {
// sampleRate: null,
recordOn: false,
axisChannelList: [],
chipSampleRate: 0,
}
},
mqtt: {
@@ -312,7 +327,21 @@ export default {
this.ampGainSelect = option
}
})
this.sampleRateSelect = this.sampleRateOptions[this.sampleRateOptions.findIndex(ele => ele.value === taskInfo.getDeviceInfo(this.deviceID).parameterSet.SAMPLE_RATE)]
const chip1Channels = this.channelList.filter(c => c >= 1 && c <= 32)
const chip2Channels = this.channelList.filter(c => c >= 33 && c <= 64)
if (chip1Channels.length !== 0 && chip2Channels.length !== 0) {
const obj = this.sampleRateOptions.find(ele => ele.value === taskInfo.getDeviceInfo(this.deviceID).parameterSet.SAMPLE_RATE * 2)
if (obj) {
this.sampleRateSelect = obj
this.updatePerChipSampleRate()
}
} else {
const obj = this.sampleRateOptions.find(ele => ele.value === taskInfo.getDeviceInfo(this.deviceID).parameterSet.SAMPLE_RATE)
if (obj) {
this.sampleRateSelect = obj
this.updatePerChipSampleRate()
}
}
this.recordOn = taskInfo.getDeviceInfo(this.deviceID).parameterSet.RECORDING
this.axisChannelList.length = 0
const axisChannelSet = taskInfo.getDeviceInfo(this.deviceID).parameterSet.AXIS_CH.toString(2).split('').reverse()
@@ -332,6 +361,19 @@ export default {
this.acceleratorSensitivitySelect = this.acceleratorSensitivityOptions[taskInfo.getDeviceInfo(this.deviceID).parameterSet.ACCELERATOR_S]
},
computePerChipSampleRate (whichChip) {
let channelLen
if (whichChip === 1) {
channelLen = this.channelList.filter(c => c >= 1 && c <= 32).length
} else if (whichChip === 2) {
channelLen = this.channelList.filter(c => c >= 33 && c <= 64).length
}
if (channelLen !== 0) {
return (this.chipSampleRate / (channelLen * 1e3)).toFixed(1) + 'k'
} else {
return 'NaN'
}
},
channelClickAdd (val) {
if (this.channelList.indexOf(val) < 0 && val <= 32 && val > 0) {
this.channelList.push(val)
@@ -378,6 +420,7 @@ export default {
} else {
this.pageToast('Recording channels cannot choose more than 64.')
}
this.sampleRateSelectChange()
},
channelClickRemove (val) {
if (this.channelList.indexOf(val) >= 0 && val <= 32 && val > 0) {
@@ -419,6 +462,7 @@ export default {
}))
}
}
this.sampleRateSelectChange()
},
ampGainSelectChange () {
if (this.ampGainSelect != null && this.ampGainSelect.id !== ((taskInfo.getDeviceInfo(this.deviceID).parameterSet.AMP_GAIN) % 4)) {
@@ -430,14 +474,26 @@ export default {
}))
}
},
updatePerChipSampleRate () {
if (this.sampleRateSelect != null) {
const chip1Channels = this.channelList.filter(c => c >= 1 && c <= 32)
const chip2Channels = this.channelList.filter(c => c >= 33 && c <= 64)
if (chip1Channels.length !== 0 && chip2Channels.length !== 0) {
this.chipSampleRate = parseInt(this.sampleRateSelect.value / 2)
} else {
this.chipSampleRate = this.sampleRateSelect.value
}
}
},
sampleRateSelectChange () {
if (this.sampleRateSelect != null && this.sampleRateSelect.value !== ((taskInfo.getDeviceInfo(this.deviceID).parameterSet.SAMPLE_RATE))) {
const adcClock = 8e6 / this.sampleRateSelect.value
if (this.sampleRateSelect != null) {
this.updatePerChipSampleRate()
const adcClock = 8e6 / this.chipSampleRate
this.mqttPub(taskInfo.getTaskInfo().controllerID + '_user', JSON.stringify({
header: 'device_parameter/0',
device: taskInfo.getRawDeviceID(this.deviceID),
parameter: 'SAMPLE_RATE|ADC_CLOCK',
content: `${this.sampleRateSelect.value}|${String(adcClock)}`,
content: `${this.chipSampleRate}|${String(adcClock)}`,
}))
}
},
@@ -1,63 +1,65 @@
<template>
<div class="flex" >
<va-tree-root >
<va-tree-category
style="font-size: 20px;"
:label="'Radio Frequency'">
<va-tree-node>
<div class="row">
<div class="flex sm12 xl12 md12 xs12">
<va-select
@input="radioFrequencySelectChange()"
:label="''"
v-model="radioFrequencySelect"
textBy="description"
:options="radioFrequencyOptions"
:noClear='true'
/>
</div>
</div>
</va-tree-node>
</va-tree-category>
<div class="mt-4">
<div class="row sm12 xl12 md12 xs12">
<p class="display-6">NEULIVE RF</p>
</div>
<div class="row" v-if="radioFrequencySelect">
<div class="flex sm10 xl10 md10 xs10">
<va-select
@input="radioFrequencySelectChange()"
:label="''"
v-model="radioFrequencySelect"
textBy="description"
:options="radioFrequencyOptions"
:noClear='true'
>
</va-select>
</div>
<div class="flex sm2 xl2 md2 xs2">
dB
</div>
</div>
</div>
<va-tree-category
style="font-size: 20px;"
:label="'Mem Radio Frequency'">
<va-tree-node>
<div class="row">
<div class="flex sm12 xl12 md12 xs12">
<va-select
@input="memRadioFrequencySelectChange()"
:label="''"
v-model="memRadioFrequencySelect"
textBy="description"
:options="memRadioFrequencyOptions"
/>
</div>
</div>
</va-tree-node>
</va-tree-category>
<va-tree-category
style="font-size: 20px;"
:label="'Led Brightness'">
<va-tree-node>
<div class="row">
<div class="flex sm12 xl12 md12 xs12">
<va-select
@input="ledBrightnessSelectChange()"
:label="''"
v-model="ledBrightnessSelect"
textBy="description"
:options="ledBrightnessOptions"
:noClear='true'
/>
</div>
</div>
</va-tree-node>
</va-tree-category>
</va-tree-root>
<div class="mt-4">
<div class="row sm12 xl12 md12 xs12">
<p class="display-6">HUB RF</p>
</div>
<div class="row" v-if="memRadioFrequencySelect">
<div class="flex sm10 xl10 md10 xs10">
<va-select
@input="memRadioFrequencySelectChange()"
:label="''"
v-model="memRadioFrequencySelect"
textBy="description"
:options="memRadioFrequencyOptions"
:noClear='true'
/>
</div>
<div class="flex sm2 xl2 md2 xs2">
dB
</div>
</div>
</div>
<div class="mt-4">
<div class="row sm12 xl12 md12 xs12">
<p class="display-6">LED BRIGHTNESS</p>
</div>
<div class="row" v-if="ledBrightnessSelect">
<div class="flex sm12 xl12 md12 xs12">
<va-select
@input="ledBrightnessSelectChange()"
:label="''"
v-model="ledBrightnessSelect"
textBy="description"
:options="ledBrightnessOptions"
:noClear='true'
/>
</div>
</div>
</div>
</div>
</template>
@@ -394,7 +394,7 @@ export default {
cycleNumberSelect: null,
cycleNumberOptions: [
{
label: 'limited',
label: 'Limited',
value: 0,
},
{
@@ -1,30 +1,6 @@
<template>
<div class="flex">
<div>
<!-- <div>
<div class="row sm12 xl12 md12 xs12">
<va-button
class="mx-1 px-2"
color="success"
v-if="!stimulationOn"
:disabled="stimulateDisable"
@click="stimulateClick"
small
>
Stimulate
</va-button>
<va-button
class="mx-1 px-2"
color="danger"
v-else
@click="stimulateClick"
small
>
Stop
</va-button>
</div>
</div> -->
<div class="mt-4">
<div class="row sm12 xl12 md12 xs12">
<p class="display-6">ELECTRODE</p>
@@ -88,7 +64,7 @@
<div class="mt-4">
<div class="row sm12 xl12 md12 xs12">
<p class="display-6">CYCLE NUMBER</p>
<va-button flat color="gray" icon="fa fa-info-circle" style="margin: 0.001rem; padding: 0.001rem;" @click.stop="toggleModal('SKETCHMAP')"/>
<va-button flat color="gray" icon="fa fa-info-circle" style="margin: 0.001rem; padding: 0.001rem;" @click.stop="toggleModal('REPEAT')"/>
</div>
<div class="row">
<div class="flex sm12 xl12 md12 xs12">
@@ -128,7 +104,7 @@
<div class="mt-4" v-if="cycleNumberSelect==0">
<div class="row sm12 xl12 md12 xs12">
<p class="display-6">REPEAT NUMBER</p>
<va-button flat color="gray" icon="fa fa-info-circle" style="margin: 0.001rem; padding: 0.001rem;" @click.stop="toggleModal('SKETCHMAP')"/>
<va-button flat color="gray" icon="fa fa-info-circle" style="margin: 0.001rem; padding: 0.001rem;" @click.stop="toggleModal('REPEAT')"/>
</div>
<div class="row">
@@ -157,7 +133,7 @@
<div class="mt-4" v-if="cycleNumberSelect==0">
<div class="row sm12 xl12 md12 xs12">
<p class="display-6">REPEAT INTERVAL = Ti = (Ts + Tr)</p>
<va-button flat color="gray" icon="fa fa-info-circle" style="margin: 0.001rem; padding: 0.001rem;" @click.stop="toggleModal('SKETCHMAP')"/>
<va-button flat color="gray" icon="fa fa-info-circle" style="margin: 0.001rem; padding: 0.001rem;" @click.stop="toggleModal('REPEAT')"/>
</div>
<div class="row">
@@ -182,7 +158,7 @@
<div class="mt-4">
<div class="row sm12 xl12 md12 xs12">
<p class="display-6">AMPLITUDE PHASE1 (uA)</p>
<p class="display-6">AMPLITUDE DURING T1</p>
</div>
<div class="row">
@@ -210,7 +186,7 @@
<div class="mt-4">
<div class="row sm12 xl12 md12 xs12">
<p class="display-6">AMPLITUDE PHASE2 (uA)</p>
<p class="display-6">AMPLITUDE DURING T3</p>
</div>
<div class="row">
@@ -432,26 +408,26 @@
<img src='@/assets/img/sti_user_guide.png' class="sti_guideline">
</va-modal>
<va-modal
v-model="showGuideLineModal.SKETCHMAP"
v-model="showGuideLineModal.REPEAT"
position="right"
cancelText=""
>
<img src='@/assets/img/repeat.jpg' class="sti_guideline">
<p class="display-6 my-2"> Pattern: </p>
<!-- <p class="display-6 my-2"> Pattern: </p>
<chart :ref="'chart_ref'" :style="'width: '+ cardwidth + '; height: ' + cardheight + ';'" :options="sketchmapData" :auto-resize="true"></chart>
<neulive-parameter-stimulation-sketchmap-param class="my-2" :deviceID="deviceID" :unitMultiply="unitMultiply" />
<neulive-parameter-stimulation-sketchmap-param class="my-2" :deviceID="deviceID" :unitMultiply="unitMultiply" /> -->
</va-modal>
</div>
</template>
<script>
import { taskInfo } from '../../../../data/task/TaskInfo'
import NeuliveParameterStimulationSketchmapParam from './NeuliveParameterStimulationSketchmapParam'
// import NeuliveParameterStimulationSketchmapParam from './NeuliveParameterStimulationSketchmapParam'
export default {
name: 'NeuliveParameterStimulation35',
components: {
NeuliveParameterStimulationSketchmapParam,
// NeuliveParameterStimulationSketchmapParam,
},
props: {
deviceID: {
@@ -465,7 +441,7 @@ export default {
},
data () {
return {
showGuideLineModal: { ELECTRODE: false, PATTERN: false, SKETCHMAP: false },
showGuideLineModal: { ELECTRODE: false, PATTERN: false, REPEAT: false },
stimulationOn: false,
posChannelList: [2],
negChannelList: [1],
@@ -484,7 +460,7 @@ export default {
cycleNumberSelect: null,
cycleNumberOptions: [
{
label: 'limited',
label: 'Limited',
value: 0,
},
{
@@ -1,30 +1,6 @@
<template>
<div class="flex">
<div>
<!-- <div>
<div class="row sm12 xl12 md12 xs12">
<va-button
class="mx-1 px-2"
color="success"
v-if="!stimulationOn"
:disabled="stimulateDisable"
@click="stimulateClick"
small
>
Stimulate
</va-button>
<va-button
class="mx-1 px-2"
color="danger"
v-else
@click="stimulateClick"
small
>
Stop
</va-button>
</div>
</div> -->
<div class="mt-4">
<div class="row sm12 xl12 md12 xs12">
<p class="display-6">ELECTRODE</p>
@@ -88,7 +64,7 @@
<div class="mt-4">
<div class="row sm12 xl12 md12 xs12">
<p class="display-6">CYCLE NUMBER</p>
<va-button flat color="gray" icon="fa fa-info-circle" style="margin: 0.001rem; padding: 0.001rem;" @click.stop="toggleModal('SKETCHMAP')"/>
<va-button flat color="gray" icon="fa fa-info-circle" style="margin: 0.001rem; padding: 0.001rem;" @click.stop="toggleModal('REPEAT')"/>
</div>
<div class="row">
<div class="flex sm12 xl12 md12 xs12">
@@ -128,7 +104,7 @@
<div class="mt-4" v-if="cycleNumberSelect==0">
<div class="row sm12 xl12 md12 xs12">
<p class="display-6">REPEAT NUMBER</p>
<va-button flat color="gray" icon="fa fa-info-circle" style="margin: 0.001rem; padding: 0.001rem;" @click.stop="toggleModal('SKETCHMAP')"/>
<va-button flat color="gray" icon="fa fa-info-circle" style="margin: 0.001rem; padding: 0.001rem;" @click.stop="toggleModal('REPEAT')"/>
</div>
<div class="row">
@@ -157,7 +133,7 @@
<div class="mt-4" v-if="cycleNumberSelect==0">
<div class="row sm12 xl12 md12 xs12">
<p class="display-6">REPEAT INTERVAL = Ti = (Ts + Tr)</p>
<va-button flat color="gray" icon="fa fa-info-circle" style="margin: 0.001rem; padding: 0.001rem;" @click.stop="toggleModal('SKETCHMAP')"/>
<va-button flat color="gray" icon="fa fa-info-circle" style="margin: 0.001rem; padding: 0.001rem;" @click.stop="toggleModal('REPEAT')"/>
</div>
<div class="row">
@@ -182,7 +158,7 @@
<div class="mt-4">
<div class="row sm12 xl12 md12 xs12">
<p class="display-6">AMPLITUDE PHASE1 (uA)</p>
<p class="display-6">AMPLITUDE DURING T1</p>
</div>
<div class="row">
@@ -210,7 +186,7 @@
<div class="mt-4">
<div class="row sm12 xl12 md12 xs12">
<p class="display-6">AMPLITUDE PHASE2 (uA)</p>
<p class="display-6">AMPLITUDE DURING T3</p>
</div>
<div class="row">
@@ -432,26 +408,26 @@
<img src='@/assets/img/sti_user_guide.png' class="sti_guideline">
</va-modal>
<va-modal
v-model="showGuideLineModal.SKETCHMAP"
v-model="showGuideLineModal.REPEAT"
position="right"
cancelText=""
>
<img src='@/assets/img/repeat.jpg' class="sti_guideline">
<p class="display-6 my-2"> Pattern: </p>
<!-- <p class="display-6 my-2"> Pattern: </p>
<chart :ref="'chart_ref'" :style="'width: '+ cardwidth + '; height: ' + cardheight + ';'" :options="sketchmapData" :auto-resize="true"></chart>
<neulive-parameter-stimulation-sketchmap-param class="my-2" :deviceID="deviceID" :unitMultiply="unitMultiply" />
<neulive-parameter-stimulation-sketchmap-param class="my-2" :deviceID="deviceID" :unitMultiply="unitMultiply" /> -->
</va-modal>
</div>
</template>
<script>
import { taskInfo } from '../../../../data/task/TaskInfo'
import NeuliveParameterStimulationSketchmapParam from './NeuliveParameterStimulationSketchmapParam'
// import NeuliveParameterStimulationSketchmapParam from './NeuliveParameterStimulationSketchmapParam'
export default {
name: 'NeuliveParameterStimulation35',
components: {
NeuliveParameterStimulationSketchmapParam,
// NeuliveParameterStimulationSketchmapParam,
},
props: {
deviceID: {
@@ -466,7 +442,7 @@ export default {
data () {
return {
channelArray: Array.from({ length: 32 }, (_, i) => i + 1),
showGuideLineModal: { ELECTRODE: false, PATTERN: false, SKETCHMAP: false },
showGuideLineModal: { ELECTRODE: false, PATTERN: false, REPEAT: false },
stimulationOn: false,
posChannelList: [2],
negChannelList: [1],
@@ -485,7 +461,7 @@ export default {
cycleNumberSelect: null,
cycleNumberOptions: [
{
label: 'limited',
label: 'Limited',
value: 0,
},
{
@@ -956,10 +932,10 @@ export default {
return false
}
if (usVal < 60) {
this.periodNumber = 60
this.periodNumberInput = String(60 / this.unitMultiply)
this.pageToast('Reset to 60 us')
if (usVal < 10) {
this.periodNumber = 10
this.periodNumberInput = String(10 / this.unitMultiply)
this.pageToast('Reset to 10 us')
} else if (usVal > 1e6) { // freq: 1 to 20000Hz, so period < 1e6us (1e6/1e6=1)
this.periodNumber = 1e6
this.periodNumberInput = String(1e6 / this.unitMultiply)
@@ -1138,7 +1114,7 @@ export default {
// val unit: us
const val = (this.t1Range + this.t2Range + this.t3Range + this.t4Range) * 10 // UI us
if (val < 80) { // us
if (val < 10) { // us
for (const tRange of [1, 2, 3, 4]) {
this.updateTRangeErrorMsg({ stiIimeSelect: tRange, status: true, msg: 'Period < 0.08ms' })
}
@@ -0,0 +1,92 @@
<template>
<div class="row flex px-0 mx-0 my-0 py-0">
<div class="flex sm5 xl5 md5 xs5 px-0 mx-0 my-0 py-2">
<p class="display-6">Action<p/>
</div>
<div class="flex sm7 xl7 md7 xs7 px-0 mx-0 my-0 py-0" :key="inputKey">
<!-- <p class="display-6">Method<p/> -->
<va-input
@mouseleave.native="itemChange()"
@keyup.enter="itemChange()"
@blur="itemChange()"
v-model="methodInput"
class="my-2"
label="Method"
/>
<va-input
@mouseleave.native="itemChange()"
@keyup.enter="itemChange()"
@blur="itemChange()"
v-model="dataInput"
class="mb-0"
label="Data"
/>
</div>
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields'
import { mapActions } from 'vuex'
import deviceTypes from '@/store/modules/device/types'
export default {
name: 'Action',
components: {
},
computed: {
...mapFields('device', [
'triggerList',
]),
},
props: {
mac: {
type: String,
required: true,
},
channelIndex: {
type: Number,
required: true,
},
},
created () {
},
data () {
return {
inputKey: 0,
methodInput: '',
dataInput: '',
}
},
mqtt: {
},
methods: {
itemChange () {
const key = 'channel_event_' + this.channelIndex
const obj = this.triggerList.find(o => o.mac === this.mac)
obj.parameter[key].action = {
method: this.methodInput,
data: this.dataInput,
}
},
async refresh () {
const key = 'channel_event_' + this.channelIndex
const obj = this.triggerList.find(o => o.mac === this.mac)
const inputChannels = await this.deviceTriggerGetInputChannels({ mac: this.mac })
if (inputChannels.includes(String(this.channelIndex))) {
this.methodInput = obj.parameter[key].action.method
this.dataInput = String(obj.parameter[key].action.data)
}
},
...mapActions('device', [deviceTypes.trigger.getInputChannels]),
},
mounted () {
},
destroyed () {
},
watch: {
},
}
</script>
@@ -0,0 +1,52 @@
<template>
<div class="row flex sm12 xl12 md12 xs12 my-3 py-0">
<div class="flex sm4 xl4 md4 xs4 px-0 py-0 mx-0 my-0">
<p class="display-6"> CHANNEL {{ channelIndex + 1 }} <p/>
</div>
<div class="flex sm8 xl8 md8 xs8 px-0 py-0 mx-0 mt-1" :key="inputKey">
<mode :ref="'mode'" class="mb-1" :mac="mac" :channelIndex="channelIndex" />
</div>
</div>
</template>
<script>
import Mode from './Mode'
export default {
name: 'Channel',
components: {
Mode,
},
props: {
mac: {
type: String,
required: true,
},
channelIndex: {
type: Number,
required: true,
},
},
created () {
},
data () {
return {
inputKey: 0,
}
},
mqtt: {
},
methods: {
refresh () {
this.$refs.mode.refresh()
},
},
mounted () {
},
watch: {
},
destroyed () {
},
}
</script>
@@ -0,0 +1,79 @@
<template>
<div class="row flex px-0 mx-0 my-0 py-0">
<div class="flex sm5 xl5 md5 xs5 px-0 mx-0 my-0 py-2">
<p class="display-6">Description<p/>
</div>
<div class="flex sm7 xl7 md7 xs7 px-0 mx-0 my-0 py-0">
<va-input
@mouseleave.native="itemChange()"
@keyup.enter="itemChange()"
@blur="itemChange()"
:key="inputKey"
style="float: left;"
v-model="itemInput"
class="mb-0"
/>
</div>
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields'
import { mapActions } from 'vuex'
import deviceTypes from '@/store/modules/device/types'
export default {
name: 'Description',
components: {
},
computed: {
...mapFields('device', [
'triggerList',
]),
},
props: {
mac: {
type: String,
required: true,
},
channelIndex: {
type: Number,
required: true,
},
},
created () {
},
data () {
return {
inputKey: 0,
itemInput: '',
}
},
mqtt: {
},
methods: {
itemChange () {
const key = 'channel_event_' + this.channelIndex
const obj = this.triggerList.find(o => o.mac === this.mac)
obj.parameter[key].description = this.itemInput
},
async refresh () {
const key = 'channel_event_' + this.channelIndex
const obj = this.triggerList.find(o => o.mac === this.mac)
const inputChannels = await this.deviceTriggerGetInputChannels({ mac: this.mac })
if (inputChannels.includes(String(this.channelIndex))) {
this.itemInput = obj.parameter[key].description
}
},
...mapActions('device', [deviceTypes.trigger.getInputChannels]),
},
mounted () {
},
destroyed () {
},
watch: {
},
}
</script>
@@ -0,0 +1,64 @@
<template>
<div class="row flex px-0 mx-0 my-0 py-0">
<div class="flex sm4 xl4 md4 xs4 px-0 mx-0 my-0 py-2">
<p class="display-6">INTERVAL<p/>
</div>
<div class="flex sm4 xl4 md4 xs4 px-0 mx-0 my-0 py-0">
<va-input
@mouseleave.native="itemChange()"
@keyup.enter="itemChange()"
@blur="itemChange()"
:key="inputKey"
v-model="itemInput"
class="mb-0"
/>
</div>
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields'
export default {
name: 'Interval',
components: {
},
computed: {
...mapFields('device', [
'triggerList',
]),
},
props: {
mac: {
type: String,
required: true,
},
},
created () {
},
data () {
return {
inputKey: 0,
itemInput: 0.01,
}
},
mqtt: {
},
methods: {
itemChange () {
const obj = this.triggerList.find(o => o.mac === this.mac)
obj.parameter.runtime_interval = parseFloat(this.itemInput)
},
refresh () {
this.itemInput = this.triggerList.find(o => o.mac === this.mac).parameter.runtime_interval
},
},
async mounted () {
},
destroyed () {
},
watch: {
},
}
</script>
@@ -0,0 +1,135 @@
<template>
<div class="row">
<va-radio-button
v-for="(option, index) in optionMode"
:key="index"
v-model="selectMode"
:option="optionMode[index]"
:label="optionMode[index]"
class="pl-2"
@click.native.stop="itemChange(index)"
/>
<div v-show="selectMode === 'INPUT'" class="my-3 pl-3">
<ttl :ref="'ttl'" :mac="mac" :channelIndex="channelIndex" />
<description :ref="'desc'" :mac="mac" :channelIndex="channelIndex" />
<action :ref="'action'" :mac="mac" :channelIndex="channelIndex" />
</div>
</div>
</template>
<script>
import { mapActions } from 'vuex'
import { mapFields } from 'vuex-map-fields'
import deviceTypes from '@/store/modules/device/types'
import Ttl from './Ttl'
import Description from './Description'
import Action from './Action'
export default {
name: 'Mode',
components: {
Ttl,
Description,
Action,
},
computed: {
...mapFields('device', [
'triggerList',
'allChannels',
]),
},
props: {
mac: {
type: String,
required: true,
},
channelIndex: {
type: Number,
required: true,
},
},
created () {
},
data () {
return {
inputeKey: 0,
selectMode: 'OUTPUT',
optionMode: [
'INPUT',
'OUTPUT',
],
}
},
mqtt: {
},
methods: {
getBitMode (channelList) {
let channelSet = ''
for (let i = 7; i >= 0; i--) {
if (channelList.indexOf(i) >= 0) {
channelSet += '1'
} else {
channelSet += '0'
}
}
return parseInt(channelSet, 2)
},
async updateBitMode (obj) {
const outputChannels = await this.deviceTriggerGetOutputChannels({ mac: this.mac })
this.selectMode === 'OUTPUT' ? outputChannels.add(this.channelIndex) : outputChannels.delete(this.channelIndex)
obj.parameter.bitmode = this.getBitMode(Array.from(outputChannels))
},
itemChange () {
const key = 'channel_event_' + this.channelIndex
const obj = this.triggerList.find(o => o.mac === this.mac)
if (this.selectMode === 'INPUT') {
if (Object.keys(obj.parameter[key]).length === 0) {
obj.parameter[key] = {
ttl: 'high',
description: 'camera on',
action: {},
broadcast: 1,
}
}
this.$refs.ttl.itemChange()
this.$refs.desc.itemChange()
this.$refs.action.itemChange()
} else {
obj.parameter[key] = {}
}
this.updateBitMode(obj)
},
async refreshSelectMode () {
const outputChannels = await this.deviceTriggerGetOutputChannels({ mac: this.mac })
if (outputChannels.has(this.channelIndex)) {
this.selectMode = 'OUTPUT'
}
const inputChannels = await this.deviceTriggerGetInputChannels({ mac: this.mac })
if (inputChannels.includes(this.channelIndex)) {
this.selectMode = 'INPUT'
}
},
refresh () {
this.refreshSelectMode()
if (this.$refs.ttl != null) {
this.$refs.ttl.refresh()
}
if (this.$refs.desc != null) {
this.$refs.desc.refresh()
}
if (this.$refs.action != null) {
this.$refs.action.refresh()
}
},
...mapActions('device', [deviceTypes.trigger.getOutputChannels, deviceTypes.trigger.getInputChannels]),
},
mounted () {
},
watch: {
},
destroyed () {
},
}
</script>
@@ -0,0 +1,80 @@
<template>
<div>
<div style="text-align: right;">
<!-- <va-button small v-if="display !== true" color="primary" @click="setDisplayParam(true)"> DISPLAY </va-button>
<va-button small v-if="display === true" color="danger" @click="setDisplayParam(false)"> HIDE </va-button> -->
<va-button small @click="updateParam"> SAVE </va-button>
</div>
<interval :ref="'interval'" :mac="mac" />
<div v-for="channelIndex in allChannels" :key="channelIndex">
<channel :ref="'channel'" :mac="mac" :channelIndex="channelIndex" />
</div>
</div>
</template>
<script>
import Interval from './Interval'
import Channel from './Channel'
import { taskInfo } from '@/data/task/TaskInfo'
import { mapFields } from 'vuex-map-fields'
export default {
name: 'TriggerControlWindow',
components: {
Interval,
Channel,
},
computed: {
...mapFields('device', [
'triggerList',
'allChannels',
'chartKey',
]),
},
props: {
mac: {
type: String,
required: true,
},
},
created () {
},
data () {
return {
}
},
mqtt: {
},
methods: {
// setDisplayParam (status) {
// this.triggerList.find(o => o.mac === this.mac).display = status
// this.display = status
// this.chartKey++
// },
updateParam () {
const trigger = this.triggerList.find(o => o.mac === this.mac)
this.mqttPub(taskInfo.getTaskInfo().controllerID + '/client/trigger_manager/set_parameter', JSON.stringify({
mac: trigger.mac,
parameter_set: trigger.parameter,
}),
)
},
mqttPub: function (topic, mes) {
this.$mqtt.publish(topic, mes)
},
},
mounted () {
if (this.$refs.interval != null) {
this.$refs.interval.refresh()
}
if (this.$refs.channel != null) {
this.$refs.channel.forEach(ch => ch.refresh())
}
},
watch: {
},
destroyed () {
},
}
</script>
@@ -0,0 +1,77 @@
<template>
<div class="row pl-2">
<va-radio-button
v-for="(option, index) in optionMode"
:key="index"
v-model="selectMode"
:option="optionMode[index]"
:label="optionMode[index]"
@click.native.stop="itemChange()"
/>
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields'
import { mapActions } from 'vuex'
import deviceTypes from '@/store/modules/device/types'
export default {
name: 'Ttl',
components: {
},
computed: {
...mapFields('device', [
'triggerList',
]),
},
props: {
mac: {
type: String,
required: true,
},
channelIndex: {
type: Number,
required: true,
},
},
created () {
},
data () {
return {
inputeKey: 0,
selectMode: 'HIGH',
optionMode: [
'HIGH',
'LOW',
],
}
},
mqtt: {
},
methods: {
itemChange () {
const key = 'channel_event_' + this.channelIndex
const obj = this.triggerList.find(o => o.mac === this.mac)
obj.parameter[key].ttl = this.selectMode.toLowerCase()
},
async refresh () {
const key = 'channel_event_' + this.channelIndex
const obj = this.triggerList.find(o => o.mac === this.mac)
const inputChannels = await this.deviceTriggerGetInputChannels({ mac: this.mac })
if (inputChannels.includes(String(this.channelIndex))) {
this.selectMode = obj.parameter[key].ttl.toUpperCase()
}
},
...mapActions('device', [deviceTypes.trigger.getInputChannels]),
},
mounted () {
},
watch: {
},
destroyed () {
},
}
</script>
+1 -1
View File
@@ -7,7 +7,7 @@ async function spikeDetect (attrs) {
return await api.axios.post('/api/analysis/spike_detect', attrs, { headers: _header })
} catch (e) {
console.error(e)
return false
return e.response
}
}
+2
View File
@@ -10,6 +10,7 @@ import mini from './mini'
import analysis from './analysis'
import sdcard from './sdcard'
import connection from './connection'
import trigger from './trigger'
// axios.defaults.baseURL = 'http://192.168.3.211:3000'
axios.defaults.baseURL = 'http://' + location.href.split('/')[2].split(':')[0] + ':3000'
@@ -27,6 +28,7 @@ const api = {
analysis,
sdcard,
connection,
trigger,
}
export default api
+46
View File
@@ -62,6 +62,16 @@ async function getByID (id) {
}
}
async function getByUUID (uuid) {
const _header = auth.getHeader()
try {
return await api.axios.get(`/api/file/meta/get_by_uuid/${uuid}`, { responseType: 'json', headers: _header })
} catch (e) {
console.error(e)
return false
}
}
async function getByName (name) {
const _header = auth.getHeader()
try {
@@ -72,6 +82,37 @@ async function getByName (name) {
}
}
// control system 3.0
async function update30 (uuid, attrs) {
const _header = auth.getHeader()
try {
return await api.axios.post(`/api/file/meta/update3_0/${uuid}`, attrs, { headers: _header })
} catch (e) {
console.error(e)
return false
}
}
async function getMetaByUUID (uuid) {
const _header = auth.getHeader()
try {
return await api.axios.get(`/api/file/meta/get_meta_by_uuid/${uuid}`, { responseType: 'json', headers: _header })
} catch (e) {
console.error(e)
return false
}
}
async function getMetaAll () {
const _header = auth.getHeader()
try {
return await api.axios.get('/api/file/meta/get_meta_all', { responseType: 'json', headers: _header })
} catch (e) {
console.error(e)
return false
}
}
const meta = {
create,
update,
@@ -79,7 +120,12 @@ const meta = {
getByParent,
getByParentWithLimit,
getByID,
getByUUID,
getByName,
// control system 3.0
update30,
getMetaByUUID,
getMetaAll,
}
export default meta
+22
View File
@@ -31,6 +31,26 @@ async function getByID (channel, id) {
}
}
async function getSdDataByUUID (uuid, channel, startTime, endTime) {
const _header = auth.getHeader()
try {
return await api.axios.get(`/api/file/raw/get_sd_data_by_uuid/${uuid}/${channel}/${startTime}/${endTime}`, { responseType: 'json', headers: _header })
} catch (e) {
console.error(e)
return false
}
}
async function getSdDataAllTimeslotByUUID (uuid, channel) {
const _header = auth.getHeader()
try {
return await api.axios.get(`/api/file/raw/get_sd_data_all_timeslot_by_uuid/${uuid}/${channel}`, { responseType: 'json', headers: _header })
} catch (e) {
console.error(e)
return false
}
}
async function getAttrByID (channel, id, attr) {
const _header = auth.getHeader()
try {
@@ -85,6 +105,8 @@ const raw = {
update,
clearDel,
getByID,
getSdDataByUUID,
getSdDataAllTimeslotByUUID,
getAttrByID,
getByIDs,
getAttrByIDs,
-10
View File
@@ -28,20 +28,10 @@ async function upload (attrs) {
}
}
async function getuuidList () {
const _header = auth.getHeader()
try {
return await api.axios.get('/api/file/sdcard/get', { headers: _header })
} catch (e) {
console.error(e)
}
}
const sdcard = {
exist,
deleted,
upload,
getuuidList,
}
export default sdcard
+18
View File
@@ -0,0 +1,18 @@
import api from './index'
import auth from './auth'
async function getRawByUUID (uuid) {
const _header = auth.getHeader()
try {
return await api.axios.get(`/api/file/trigger/get_raw_by_uuid/${uuid}`, { headers: _header })
} catch (e) {
console.error(e)
return false
}
}
const trigger = {
getRawByUUID,
}
export default trigger
+55 -44
View File
@@ -76,11 +76,31 @@ export const download = {
await this.parseRawData(rawData.data, null, meta, data)
return data
},
rawSdDataByIDChannels: async function (id, channel, meta) {
const data = []
const rawSdData = await api.raw.getByID(channel, id)
await this.parseRawSdData(rawSdData.data, null, meta, data)
return data
rawSdDataByUUIDAndChannel: async function (uuid, channel, startTime, endTime) {
// console.log(channel)
const rawSdData = await api.raw.getSdDataByUUID(uuid, channel, startTime, endTime)
const metaList = await api.meta.getByUUID(uuid) // control system 2.0 meta table
// console.log(channel)
const sampleRate = metaList.data[0].parameter_set.SAMPLE_RATE
const allChannels = JSON.parse(metaList.data[0].channels)
allChannels.sort((a, b) => a > b ? 1 : -1)
let channels
if (metaList.data[0].device.library_name === 'Neulive3.5ex') {
if (channel >= 0 && channel <= 31) {
channels = allChannels.filter(c => c >= 0 && c <= 31)
} else if (channel >= 32 && channel <= 63) {
channels = allChannels.filter(c => c >= 32 && c <= 63)
}
} else {
channels = allChannels
}
const dataTimeStep = parseFloat((1e6 / sampleRate) * channels.length)
const offset = dataTimeStep * (channels.findIndex((c) => c === parseInt(channel)) / channels.length)
return await this.parseRawSdData(rawSdData.data, channel, dataTimeStep, offset, startTime, endTime)
},
rawSdDataAllTimeslotByUUID: async function (uuid, channel) {
return await api.raw.getSdDataAllTimeslotByUUID(uuid, channel)
},
miniMeanByIDChannel: async function (id, channel, meta) {
const miniData = await api.mini.getByID(channel, id)
@@ -107,22 +127,41 @@ export const download = {
}
return prevLastTime
},
parseRawSdData: async function (rawData, prevLastTime, meta, dataArray) {
parseRawSdData: async function (rawData, channel, dataTimeStep, offset, startTime, endTime) {
const dataArray = []
for (const raw of rawData) {
if (raw.sd_data !== null && raw.sd_data.length > 0) {
const encodedData = raw.sd_data.split('"***"').slice(0, -1)
const unzipDatas = encodedData.map(ctx => {
return ctx.split(' ')
})
for (let i = 0; i <= unzipDatas.length - 1; i++) {
if (unzipDatas[i].length > 0) {
prevLastTime = this.parseDataWithTimeStamp(unzipDatas[i], prevLastTime, meta, dataArray)
if (raw.data !== null) {
const rawDataArr = raw.data.split(' ')
const rawDataArrLen = rawDataArr.length
if (rawDataArrLen > 0) {
// data_time format: 'raw_data_index1 data_start_time raw_data_index2 data_start_time ...'
const rawDataIndexArr = raw.data_time.split(' ').filter((item, index) => index % 2 === 0) // 留奇數項
const rawDataTimeArr = raw.data_time.split(' ').filter((item, index) => index % 2 !== 0) // 留偶數項
let rawDataIndexArrPointer = 0
let currTime
for (let i = 0; i < rawDataArrLen; i++) {
if (i !== parseInt(rawDataIndexArr[rawDataIndexArrPointer])) {
currTime += dataTimeStep
} else {
currTime += dataTimeStep
const nextTime = parseInt(rawDataTimeArr[rawDataIndexArrPointer])
for (let lossTime = currTime; lossTime < nextTime; lossTime += dataTimeStep) {
dataArray.push([parseInt(lossTime), null])
}
currTime = nextTime + offset
if (rawDataIndexArrPointer < rawDataIndexArr.length) {
rawDataIndexArrPointer++
}
}
const data = (channel >= 256 && channel <= 259) ? (rawDataArr[i] / 100).toFixed(3) : rawDataArr[i]
if (startTime <= currTime && currTime <= endTime) {
dataArray.push([parseInt(currTime), data])
}
}
}
}
}
return prevLastTime
return dataArray
},
parseMiniData: async function (miniData, miniType, meta) {
const channelData = []
@@ -155,37 +194,9 @@ export const download = {
return channelData
},
parseDataWithTimeStamp: function (currentData, prevLastTime, meta, dataArray) {
// const transData = []
// let timeSpare = 0
// let channel = 0
// if (meta !== undefined) {
// channel = JSON.parse(meta.channels).length
// if (meta.device.library_name.startsWith('Neulive')) {
// timeSpare = parseInt((1 / (parseInt(meta.parameter_set.ADC_CLOCK) / 200)) * 1000 / 1000)
// } else if (meta.device.library_name.startsWith('Elite')) {
// // TODO Elite
// }
// }
// const realTimeSpare = channel * timeSpare
for (let i = 0; i <= currentData.length - 1; i += 2) {
if (currentData[i].length !== 0) {
const time = parseInt(currentData[i])
// if (prevLastTime && (time - prevLastTime >= 2 * realTimeSpare) && realTimeSpare !== 0) {
// console.log('data loss time', time, prevLastTime)
// let timeDiff = time - prevLastTime
// let timeCorrection = 0
// while (timeDiff > 0) {
// timeCorrection += realTimeSpare
// timeDiff -= realTimeSpare
// if (timeDiff > 0) {
// const _time = parseInt(prevLastTime) + parseInt(timeCorrection)
// const data = 'NaN'
// const tmpData = [_time, data]
// transData.push(tmpData)
// }
// }
// }
if (meta !== undefined) {
if (time <= parseInt(meta.time_duration)) {
const data = currentData[i + 1]
+21
View File
@@ -120,6 +120,26 @@ const transTime = (microsecond) => {
return result
}
function microsecondsFormat (microsec) {
if (isNaN(microsec) || microsec == null || microsec === 0) {
return '--'
}
const sec = Math.ceil(microsec / 1000000)
let hour = Math.floor(sec / 3600)
let minute = Math.floor((sec - hour * 3600) / 60)
let second = sec - hour * 3600 - minute * 60
if (hour < 10) {
hour = '0' + hour
}
if (minute < 10) {
minute = '0' + minute
}
if (second < 10) {
second = '0' + second
}
return hour + ':' + minute + ':' + second
}
const dateToStr = (time) => {
const _time = new Date(time).toLocaleString('tw', { hour12: false })
return _time
@@ -136,6 +156,7 @@ export default
transMac,
transFileSize,
transTime,
microsecondsFormat,
dateToStr,
TaskTotalSettingView,
controllerMaxNumber,
+398
View File
@@ -0,0 +1,398 @@
const Neulive31 = {
showParameterRec: ['RECORDING_CH', 'AXIS_CH', 'AMP_GAIN', 'SAMPLE_RATE', 'ACCELERATOR_S'],
showParameterSti: ['STI_H_BRIDGE', 'STI_CHANNEL', 'CONTINUOUS_STI', 'STI_CYCLE_LIST',
'STI_AMPLITUDE', 'STI_PATTERN', 'STI_T1', 'STI_T2', 'STI_T3', 'STI_T4'],
// rec
RECORDING_CH: {
type: 'array',
showName: 'Channels',
range: Array(16).fill([...Array(16).keys()].map(x => ++x)), // UI上能輸入的最大最小值
func: (chList) => {
let channelSet = ''
for (let i = 16; i >= 1; i--) {
if (chList.indexOf(i) >= 0) {
channelSet += '1'
} else {
channelSet += '0'
}
}
return parseInt(channelSet, 2)
},
rev_func: (val) => {
const channelList = []
const channelSet = val.toString(2).split('').reverse()
channelSet.forEach((element, index) => {
if (parseInt(element) > 0) {
channelList.push(index + 1)
}
})
return channelList
},
},
AXIS_CH: {
type: 'array',
showName: '3-AXIS',
start: 256,
range: Array(4).fill(['x', 'y', 'z', 'm']),
func: (chList) => {
const axisChannel = ['m', 'z', 'y', 'x']
let axisChannelSet = ''
for (const axis of axisChannel) {
if (chList.indexOf(axis) >= 0) {
axisChannelSet += '1'
} else {
axisChannelSet += '0'
}
}
return parseInt(axisChannelSet, 2)
},
rev_func: (val) => {
const axisChannelList = []
const axisChannelSet = val.toString(2).split('').reverse()
axisChannelSet.forEach((element, index) => {
if (parseInt(element) === 1) {
if (index === 0) {
axisChannelList.push('x')
} else if (index === 1) {
axisChannelList.push('y')
} else if (index === 2) {
axisChannelList.push('z')
} else if (index === 3) {
axisChannelList.push('m')
}
}
})
return axisChannelList
},
},
AMP_GAIN: {
type: 'array',
showName: 'Amp gain',
range: ['100', '400', '800'],
func: (val) => {
const ampGainArr = ['100', '400', '800']
return ampGainArr.indexOf(val.toString())
},
rev_func: (idx) => {
const ampGainArr = ['100', '400', '800']
return ampGainArr[parseInt(idx)]
},
},
SAMPLE_RATE: {
type: 'array',
showName: 'Sample Rate',
range: [1e3, 2e3, 4e3, 5e3, 8e3, 1e4, 2e4, 2.5e4, 3.125e4],
func: (val) => {
return val
},
rev_func: (val) => {
// const sampleRateArr = [1e3, 2e3, 4e3, 5e3, 8e3, 1e4, 2e4, 2.5e4, 3.125e4]
// const idx = sampleRateArr.indexOf(val)
// const sampleRateStrArr = ['1k', '2k', '4k', '5k', '8k', '10k', '20k', '25k', '31.25k']
// return sampleRateStrArr[idx]
return val
},
},
ADC_CLOCK: {
type: 'number',
showName: 'ADC_CLOCK',
range: Object.freeze({ min: 1e3, max: 2e4 }),
func: (val) => {
return 8e5 / parseInt(val)
},
rev_func: (val) => {
return 8e5 / parseInt(val)
},
},
RECORDING: {
type: 'bool',
showName: 'RECORDING',
range: [false, true],
func: (val) => {
return val
},
rev_func: (val) => {
return val
},
},
ACCELERATOR_S: {
type: 'array',
showName: 'Accelerator Sensitivity',
range: ['2G', '4G', '8G', '16G'],
func: (val) => {
const accArr = ['2G', '4G', '8G', '16G']
const index = accArr.indexOf(val.toString())
return index
},
rev_func: (idx) => {
const accArr = ['2G', '4G', '8G', '16G']
return accArr[parseInt(idx)]
},
},
// sti
STIMULATING: {
type: 'bool',
showName: 'STIMULATING',
range: [false, true],
func: (val) => {
return val
},
rev_func: (val) => {
return val
},
},
STI_CHANNEL: {
type: 'array',
showName: 'Channels',
range: Object.freeze({ pos: Array(8).fill([...Array(8).keys()].map(x => ++x)), neg: Array(8).fill([...Array(8).keys()].map(x => ++x)) }),
func: (posChList, negChList) => {
let stiChannelSet = ''
for (let i = 8; i >= 1; i--) {
if (posChList.indexOf(i) >= 0) {
stiChannelSet += '1'
} else if (negChList.indexOf(i) >= 0) {
stiChannelSet += '1'
} else {
stiChannelSet += '0'
}
}
return parseInt(stiChannelSet, 2)
},
rev_func: (val, stiPolarityVal) => {
const stiChannelSet = val.toString(2).split('').reverse()
const stiPolaritySet = stiPolarityVal.toString(2).split('').reverse()
const posChannelList = []
const negChannelList = []
stiChannelSet.forEach((element, index) => {
if (parseInt(element) > 0) {
if (parseInt(stiPolaritySet[index]) > 0) {
posChannelList.push(index + 1)
} else {
negChannelList.push(index + 1)
}
}
})
return JSON.stringify({ pos: posChannelList, neg: negChannelList })
},
},
STI_POLARITY: {
type: 'array',
showName: 'Polarity',
range: Object.freeze({ pos: Array(8).fill([...Array(8).keys()].map(x => ++x)), neg: Array(8).fill([...Array(8).keys()].map(x => ++x)) }),
func: (posChList, negChList) => {
let stiPolaritySet = ''
for (let i = 8; i >= 1; i--) {
if (posChList.indexOf(i) >= 0) {
stiPolaritySet += '1'
} else if (negChList.indexOf(i) >= 0) {
stiPolaritySet += '0'
} else {
stiPolaritySet += '0'
}
}
return parseInt(stiPolaritySet, 2)
},
rev_func: (val, stiChannelVal) => {
const stiPolaritySet = val.toString(2).split('').reverse()
const stiChannelSet = stiChannelVal.toString(2).split('').reverse()
const posChannelList = []
const negChannelList = []
stiChannelSet.forEach((element, index) => {
if (parseInt(element) > 0) {
if (parseInt(stiPolaritySet[index]) > 0) {
posChannelList.push(index + 1)
} else {
negChannelList.push(index + 1)
}
}
})
return JSON.stringify({ pos: posChannelList, neg: negChannelList })
},
},
STI_H_BRIDGE: {
type: 'array',
showName: 'ELECTRODE',
range: ['Monopolar', 'Bipolar'],
func: (val) => {
const electrodeArr = ['Monopolar', 'Bipolar']
return electrodeArr.indexOf(val.toString())
},
rev_func: (val) => {
const idx = +val // +: bool to int, ref: https://stackoverflow.com/questions/7820683/convert-boolean-result-into-number-integer
const electrodeArr = ['Monopolar', 'Bipolar']
return electrodeArr[idx]
},
},
CONTINUOUS_STI: {
type: 'array',
showName: 'CYCLE',
range: ['Limited', 'Unlimited'],
func: (val) => {
const cycleNumberArr = ['Limited', 'Unlimited']
return cycleNumberArr.indexOf(val.toString())
},
rev_func: (val) => {
const idx = +val // +: bool to int, ref: https://stackoverflow.com/questions/7820683/convert-boolean-result-into-number-integer
const cycleNumberArr = ['Limited', 'Unlimited']
return cycleNumberArr[idx]
},
},
STI_CYCLE_LIST: {
type: 'number',
showName: 'CYCLE NUMBER',
range: Object.freeze({ min: 1, max: 255 }),
func: (cycleNum) => {
const cycleNumberArray = Array(9).fill(1)
cycleNumberArray.fill(cycleNum, 0)
return '0:9=' + cycleNumberArray.toString()
},
rev_func: (_list) => {
return _list[0]
},
},
STI_AMPLITUDE: {
type: 'number',
showName: 'AMPLITUDE',
range: Object.freeze({ min: 5, max: 4000 }),
func: (val) => {
return parseInt(val / 5)
},
rev_func: (val) => {
return val * 5
},
defaultUnit: 'uA',
unit: {
uA: 1,
},
},
STI_AMPLITUDE_H: {
type: 'number',
showName: 'AMPLITUDE_HIGH',
range: Object.freeze({ min: 5, max: 4000 }),
func: (val) => {
return parseInt(val / 5)
},
rev_func: (val) => {
return val * 5
},
defaultUnit: 'uA',
unit: {
uA: 1,
},
},
STI_AMPLITUDE_L: {
type: 'number',
showName: 'AMPLITUDE_LOW',
range: Object.freeze({ min: 5, max: 4000 }),
func: (val) => {
return parseInt(val / 5)
},
rev_func: (val) => {
return val * 5
},
defaultUnit: 'uA',
unit: {
uA: 1,
},
},
STI_PATTERN: {
type: 'array',
showName: 'PATTERN',
range: ['Monophasic', 'Biphasic'],
func: (val) => {
const patternArr = ['Monophasic', 'Biphasic']
return patternArr.indexOf(val.toString())
},
rev_func: (val) => {
const idx = +val // +: bool to int, ref: https://stackoverflow.com/questions/7820683/convert-boolean-result-into-number-integer
const patternArr = ['Monophasic', 'Biphasic']
return patternArr[idx]
},
},
STI_T1: {
type: 'number',
showName: 'T1',
amount: 8, // total 8 channels
range: Object.freeze({ min: 100, max: 10230 }), // 0.1ms~10.23ms
func: (val) => { // input unit: us
const tRange = parseFloat(val / 10)
const tRangeArray = Array(9).fill(0)
tRangeArray.fill(tRange, 0)
return '0:9=' + String(tRangeArray)
},
rev_func: (_list) => { // output unit: us
return _list[0] * 10
},
defaultUnit: 'ms',
unit: {
us: 1,
ms: 1e3,
s: 1e6,
},
},
STI_T2: {
type: 'number',
showName: 'T2',
amount: 8, // total 8 channels
range: Object.freeze({ min: 0, max: 10230 }), // 0ms~10.23ms
func: (val) => { // input unit: us
const tRange = parseFloat(val / 10)
const tRangeArray = Array(9).fill(0)
tRangeArray.fill(tRange, 0)
return '0:9=' + String(tRangeArray)
},
rev_func: (_list) => { // output unit: us
return _list[0] * 10
},
defaultUnit: 'ms',
unit: {
us: 1,
ms: 1e3,
s: 1e6,
},
},
STI_T3: {
type: 'number',
showName: 'T3',
amount: 8, // total 8 channels
range: Object.freeze({ min: 100, max: 10230 }), // 0.1ms~10.23ms
func: (val) => { // input unit: us
const tRange = parseFloat(val / 10)
const tRangeArray = Array(9).fill(0)
tRangeArray.fill(tRange, 0)
return '0:9=' + String(tRangeArray)
},
rev_func: (_list) => { // output unit: us
return _list[0] * 10
},
defaultUnit: 'ms',
unit: {
us: 1,
ms: 1e3,
s: 1e6,
},
},
STI_T4: {
type: 'number',
showName: 'T4',
amount: 8, // total 8 channels
range: Object.freeze({ min: 0, max: 1310710 }), // 0ms~1310.71ms
func: (val) => { // input unit: us
const tRange = parseFloat(val / 10)
const tRangeArray = Array(9).fill(0)
tRangeArray.fill(tRange, 0)
return '0:9=' + String(tRangeArray)
},
rev_func: (_list) => { // output unit: us
return _list[0] * 10
},
defaultUnit: 'ms',
unit: {
us: 1,
ms: 1e3,
s: 1e6,
},
},
}
export default Neulive31
+469
View File
@@ -0,0 +1,469 @@
const Neulive35 = {
showParameterRec: ['RECORDING_CH_L', 'RECORDING_CH_H', 'AXIS_CH', 'AMP_GAIN', 'SAMPLE_RATE', 'ACCELERATOR_S'],
showParameterSti: ['STI_H_BRIDGE', 'STI_CHANNEL', 'CONTINUOUS_STI', 'STI_CYCLE_CH0',
'REPEAT_TIME_littleEndian', 'REPEAT_INTERVAL_littleEndian',
'STI_AMPLITUDE_L1', 'STI_AMPLITUDE_H3',
'STI_PATTERN', 'STI_T1_CH0', 'STI_T2_CH0', 'STI_T3_CH0', 'STI_T4_CH0'],
// rec
RECORDING_CH_L: {
type: 'array',
showName: 'CHANNEL LOW',
range: Array(16).fill([...Array(16).keys()].map(x => ++x)), // UI上能輸入的最大最小值
func: (chList) => {
let channelSet = ''
for (let i = 16; i >= 1; i--) {
if (chList.indexOf(i) >= 0) {
channelSet += '1'
} else {
channelSet += '0'
}
}
return parseInt(channelSet, 2)
},
rev_func: (val) => {
const channelList = []
const channelSet = val.toString(2).split('').reverse()
channelSet.forEach((element, index) => {
if (parseInt(element) > 0) {
channelList.push(index + 1)
}
})
return channelList
},
},
RECORDING_CH_H: {
type: 'array',
showName: 'CHANNEL HIGH',
range: Array(16).fill([...Array(16).keys()].map(x => x + 17)), // UI上能輸入的最大最小值
func: (chList) => {
let channelSet = ''
for (let i = 32; i >= 17; i--) {
if (chList.indexOf(i) >= 0) {
channelSet += '1'
} else {
channelSet += '0'
}
}
return parseInt(channelSet, 2)
},
rev_func: (val) => {
const channelList = []
const channelSet = val.toString(2).split('').reverse()
channelSet.forEach((element, index) => {
if (parseInt(element) > 0) {
channelList.push(index + 17)
}
})
return channelList
},
},
AMP_GAIN: {
type: 'array',
showName: 'AMPLITUDE GAIN',
range: ['100', '500', '300'],
func: (val) => {
const ampGainArr = ['100', '500', '300']
return ampGainArr.indexOf(val.toString())
},
rev_func: (idx) => {
const ampGainArr = ['100', '500', '300']
return ampGainArr[parseInt(idx)]
},
},
SAMPLE_RATE: {
type: 'array',
showName: 'SAMPLING RATE',
range: [1e3, 2e3, 4e3, 5e3, 8e3, 1e4, 1.6e4, 2e4, 2.5e4, 3.2e4, 4e4, 5e4, 6.4e4, 8e4, 1e5],
func: (val) => {
return val
},
rev_func: (val) => {
// const sampleRateArr = [1e3, 2e3, 4e3, 5e3, 8e3, 1e4, 1.6e4, 2e4, 2.5e4, 3.2e4, 4e4, 5e4, 6.4e4, 8e4, 1e5]
// const idx = sampleRateArr.indexOf(val)
// const sampleRateStrArr = ['1k', '2k', '4k', '5k', '8k', '10k', '16k', '20k', '25k', '32k', '40k', '50k', '64k', '80k', '100k']
// return sampleRateStrArr[idx]
return val
},
defaultUnit: 'sps',
unit: {
sps: 1,
},
},
ADC_CLOCK: {
type: 'number',
showName: 'ADC_CLOCK',
range: Object.freeze({ min: 1e3, max: 1e5 }),
func: (val) => {
return 8e6 / parseInt(val)
},
rev_func: (val) => {
return 8e6 / parseInt(val)
},
},
RECORDING: {
type: 'bool',
showName: 'RECORDING',
range: [false, true],
func: (val) => {
return val
},
rev_func: (val) => {
return val
},
},
AXIS_CH: {
type: 'array',
showName: '3-AXIS',
range: Array(4).fill(['x', 'y', 'z', 'm']),
func: (chList) => {
const axisChannel = ['m', 'z', 'y', 'x']
let axisChannelSet = ''
for (const axis of axisChannel) {
if (chList.indexOf(axis) >= 0) {
axisChannelSet += '1'
} else {
axisChannelSet += '0'
}
}
return parseInt(axisChannelSet, 2)
},
rev_func: (val) => {
const axisChannelList = []
const axisChannelSet = val.toString(2).split('').reverse()
axisChannelSet.forEach((element, index) => {
if (parseInt(element) === 1) {
if (index === 0) {
axisChannelList.push('x')
} else if (index === 1) {
axisChannelList.push('y')
} else if (index === 2) {
axisChannelList.push('z')
} else if (index === 3) {
axisChannelList.push('m')
}
}
})
return axisChannelList
},
},
ACCELERATOR_S: {
type: 'array',
showName: 'ACCELERATOR SENSITIVITY',
range: ['2G', '4G', '8G', '16G'],
func: (val) => {
const accArr = ['2G', '4G', '8G', '16G']
const index = accArr.indexOf(val.toString())
return index
},
rev_func: (idx) => {
const accArr = ['2G', '4G', '8G', '16G']
return accArr[parseInt(idx)]
},
},
// sti
STIMULATING: {
type: 'bool',
showName: 'STIMULATING',
range: [false, true],
func: (val) => {
return val
},
rev_func: (val) => {
return val
},
},
STI_CHANNEL: {
type: 'array',
showName: 'CHANNELS',
range: Object.freeze({ pos: Array(16).fill([...Array(16).keys()].map(x => x + 17)), neg: Array(16).fill([...Array(16).keys()].map(x => x + 17)) }),
func: (posChList, negChList) => {
let stiChannelSet = ''
for (let i = 32; i >= 17; i--) {
if (posChList.indexOf(i) >= 0) {
stiChannelSet += '1'
} else if (negChList.indexOf(i) >= 0) {
stiChannelSet += '1'
} else {
stiChannelSet += '0'
}
}
return parseInt(stiChannelSet, 2)
},
rev_func: (val, stiPolarityVal) => {
const stiChannelSet = val.toString(2).split('').reverse()
const stiPolaritySet = stiPolarityVal.toString(2).split('').reverse()
const posChannelList = []
const negChannelList = []
stiChannelSet.forEach((element, index) => {
if (parseInt(element) > 0) {
if (parseInt(stiPolaritySet[index]) > 0) {
posChannelList.push(index + 17)
} else {
negChannelList.push(index + 17)
}
}
})
return JSON.stringify({ pos: posChannelList, neg: negChannelList })
},
},
STI_POLARITY: {
type: 'array',
showName: 'POLARITY',
range: Object.freeze({ pos: Array(16).fill([...Array(16).keys()].map(x => ++x)), neg: Array(16).fill([...Array(16).keys()].map(x => ++x)) }),
func: (posChList, negChList) => {
let stiPolaritySet = ''
for (let i = 16; i >= 1; i--) {
if (posChList.indexOf(i) >= 0) {
stiPolaritySet += '1'
} else if (negChList.indexOf(i) >= 0) {
stiPolaritySet += '0'
} else {
stiPolaritySet += '0'
}
}
return parseInt(stiPolaritySet, 2)
},
rev_func: (val, stiChannelVal) => {
const stiPolaritySet = val.toString(2).split('').reverse()
const stiChannelSet = stiChannelVal.toString(2).split('').reverse()
const posChannelList = []
const negChannelList = []
stiChannelSet.forEach((element, index) => {
if (parseInt(element) > 0) {
if (parseInt(stiPolaritySet[index]) > 0) {
posChannelList.push(index + 1)
} else {
negChannelList.push(index + 1)
}
}
})
return JSON.stringify({ pos: posChannelList, neg: negChannelList })
},
},
STI_H_BRIDGE: {
type: 'array',
showName: 'ELECTRODE',
range: ['Monopolar', 'Bipolar'],
func: (val) => {
const electrodeArr = ['Monopolar', 'Bipolar']
return electrodeArr.indexOf(val.toString())
},
rev_func: (val) => {
const idx = +val // +: bool to int, ref: https://stackoverflow.com/questions/7820683/convert-boolean-result-into-number-integer
const electrodeArr = ['Monopolar', 'Bipolar']
return electrodeArr[idx]
},
},
CONTINUOUS_STI: {
type: 'array',
showName: 'CYCLE',
range: ['Limited', 'Unlimited'],
func: (val) => {
const cycleNumberArr = ['Limited', 'Unlimited']
return cycleNumberArr.indexOf(val.toString())
},
rev_func: (val) => {
const idx = +val // +: bool to int, ref: https://stackoverflow.com/questions/7820683/convert-boolean-result-into-number-integer
const cycleNumberArr = ['Limited', 'Unlimited']
return cycleNumberArr[idx]
},
},
STI_CYCLE_CH0: {
type: 'number',
showName: 'CYCLE NUMBER',
range: Object.freeze({ min: 1, max: 256 }),
func: (cycleNum) => {
return String(cycleNum - 1)
},
rev_func: (val) => {
return parseInt(val) + 1
},
defaultUnit: 'rounds',
unit: {
rounds: 1,
},
},
REPEAT_TIME_littleEndian: {
type: 'number',
showName: 'REPEAT NUMBER',
range: Object.freeze({ min: 1, max: 256 }),
func: (val) => {
return String(val)
},
rev_func: (val) => {
return parseInt(val)
},
defaultUnit: 'rounds',
unit: {
rounds: 1,
},
},
REPEAT_INTERVAL_littleEndian: {
type: 'number',
showName: 'REPEAT INTERVAL',
range: Object.freeze({ min: 0, max: Infinity }),
func: (val) => {
return parseFloat(parseFloat(val).toFixed(2)) * 1e3
},
rev_func: (val) => {
return val
},
defaultUnit: 's',
unit: {
s: 1e3,
ms: 1,
},
},
STI_AMPLITUDE_L1: {
type: 'number',
showName: 'AMPLITUDE DURING T1',
range: Object.freeze({ min: 5, max: 4000 }),
func: (val) => {
return parseInt(val / 5)
},
rev_func: (val) => {
return val * 5
},
defaultUnit: 'uA',
unit: {
uA: 1,
},
},
STI_AMPLITUDE_H3: {
type: 'number',
showName: 'AMPLITUDE DURING T3',
range: Object.freeze({ min: 5, max: 4000 }),
func: (val) => {
return parseInt(val / 5)
},
rev_func: (val) => {
return val * 5
},
defaultUnit: 'uA',
unit: {
uA: 1,
},
},
STI_AMPLITUDE_H1: {
type: 'number',
showName: 'AMPLITUDE_HIGH_1',
range: Object.freeze({ min: 5, max: 4000 }),
func: (val) => {
return parseInt(val / 5 / 2)
},
rev_func: (val) => {
return val * 5 * 2
},
defaultUnit: 'uA',
unit: {
uA: 1,
},
},
STI_AMPLITUDE_L3: {
type: 'number',
showName: 'AMPLITUDE_LOW_3',
range: Object.freeze({ min: 5, max: 4000 }),
func: (val) => {
return parseInt(val / 5 / 2)
},
rev_func: (val) => {
return val * 5 * 2
},
defaultUnit: 'uA',
unit: {
uA: 1,
},
},
STI_PATTERN: {
type: 'array',
showName: 'PATTERN',
range: ['Monophasic', 'Biphasic'],
func: (val) => {
const patternArr = ['Monophasic', 'Biphasic']
return patternArr.indexOf(val.toString())
},
rev_func: (val) => {
const idx = +val // +: bool to int, ref: https://stackoverflow.com/questions/7820683/convert-boolean-result-into-number-integer
const patternArr = ['Monophasic', 'Biphasic']
return patternArr[idx]
},
},
STI_T1_CH0: {
type: 'number',
showName: 'T1',
amount: 16, // total 16 channels
range: Object.freeze({ min: 100, max: 10230 }), // 0.1ms~10.23ms
func: (val) => { // input unit: us
const tRange = parseFloat(val / 10)
return String(tRange)
},
rev_func: (val) => { // output unit: us
return val * 10
},
defaultUnit: 'ms',
unit: {
us: 1,
ms: 1e3,
s: 1e6,
},
},
STI_T2_CH0: {
type: 'number',
showName: 'T2',
amount: 16, // total 16 channels
range: Object.freeze({ min: 0, max: 10230 }), // 0ms~10.23ms
func: (val) => { // input unit: us
const tRange = parseFloat(val / 10)
return String(tRange)
},
rev_func: (val) => { // output unit: us
return val * 10
},
defaultUnit: 'ms',
unit: {
us: 1,
ms: 1e3,
s: 1e6,
},
},
STI_T3_CH0: {
type: 'number',
showName: 'T3',
amount: 16, // total 16 channels
range: Object.freeze({ min: 0, max: 10230 }), // 0ms~10.23ms
func: (val) => { // input unit: us
const tRange = parseFloat(val / 10)
return String(tRange)
},
rev_func: (val) => { // output unit: us
return val * 10
},
defaultUnit: 'ms',
unit: {
us: 1,
ms: 1e3,
s: 1e6,
},
},
STI_T4_CH0: {
type: 'number',
showName: 'T4',
amount: 16, // total 16 channels
range: Object.freeze({ min: 0, max: 1310710 }), // 0ms~1310.71ms
func: (val) => { // input unit: us
const tRange = parseFloat(val / 10)
return String(tRange)
},
rev_func: (val) => { // output unit: us
return val * 10
},
defaultUnit: 'ms',
unit: {
us: 1,
ms: 1e3,
s: 1e6,
},
},
}
export default Neulive35
+466
View File
@@ -0,0 +1,466 @@
const Neulive35ex = {
showParameterRec: ['REC_CH_DEV00', 'REC_CH_DEV01', 'AXIS_CH', 'AMP_GAIN', 'SAMPLE_RATE', 'ACCELERATOR_S'],
showParameterSti: ['STI_H_BRIDGE', 'STI_CHANNEL', 'CONTINUOUS_STI', 'STI_CYCLE_CH0',
'REPEAT_TIME_littleEndian', 'REPEAT_INTERVAL_littleEndian',
'STI_AMPLITUDE_L1', 'STI_AMPLITUDE_H3',
'STI_PATTERN', 'STI_T1_CH0', 'STI_T2_CH0', 'STI_T3_CH0', 'STI_T4_CH0'],
// rec
REC_CH_DEV00: {
type: 'array',
showName: 'Channel Low',
range: Array(32).fill([...Array(32).keys()].map(x => ++x)), // UI上能輸入的最大最小值
func: (chList) => {
let channelSet = ''
for (let i = 32; i >= 1; i--) {
if (chList.indexOf(i) >= 0) {
channelSet += '1'
} else {
channelSet += '0'
}
}
return parseInt(channelSet, 2)
},
rev_func: (val) => {
const channelList = []
const channelSet = val.toString(2).split('').reverse()
channelSet.forEach((element, index) => {
if (parseInt(element) > 0) {
channelList.push(index + 1)
}
})
return channelList
},
},
REC_CH_DEV01: {
type: 'array',
showName: 'Channel High',
range: Array(32).fill([...Array(32).keys()].map(x => x + 33)), // UI上能輸入的最大最小值
func: (chList) => {
let channelSet = ''
for (let i = 64; i >= 33; i--) {
if (chList.indexOf(i) >= 0) {
channelSet += '1'
} else {
channelSet += '0'
}
}
return parseInt(channelSet, 2)
},
rev_func: (val) => {
const channelList = []
const channelSet = val.toString(2).split('').reverse()
channelSet.forEach((element, index) => {
if (parseInt(element) > 0) {
channelList.push(index + 33)
}
})
return channelList
},
},
AMP_GAIN: {
type: 'array',
showName: 'Amp gain',
range: ['100', '500', '300'],
func: (val) => {
const ampGainArr = ['100', '500', '300']
return ampGainArr.indexOf(val.toString())
},
rev_func: (idx) => {
const ampGainArr = ['100', '500', '300']
return ampGainArr[parseInt(idx)]
},
},
SAMPLE_RATE: { // [TODO] sample rate per chip
type: 'array',
showName: 'Sample Rate',
range: [1e3, 2e3, 4e3, 5e3, 8e3, 1e4, 1.6e4, 2e4, 2.5e4, 3.2e4, 4e4, 5e4, 6.4e4, 8e4, 1e5],
func: (val) => {
return val
},
rev_func: (val) => {
// const sampleRateArr = [1e3, 2e3, 4e3, 5e3, 8e3, 1e4, 1.6e4, 2e4, 2.5e4, 3.2e4, 4e4, 5e4, 6.4e4, 8e4, 1e5]
// const idx = sampleRateArr.indexOf(val)
// const sampleRateStrArr = ['1k', '2k', '4k', '5k', '8k', '10k', '16k', '20k', '25k', '32k', '40k', '50k', '64k', '80k', '100k']
// return sampleRateStrArr[idx]
return val
},
},
ADC_CLOCK: {
type: 'number',
showName: 'ADC_CLOCK',
range: Object.freeze({ min: 1e3, max: 1e5 }),
func: (val) => {
return 8e6 / parseInt(val)
},
rev_func: (val) => {
return 8e6 / parseInt(val)
},
},
RECORDING: {
type: 'bool',
showName: 'RECORDING',
range: [false, true],
func: (val) => {
return val
},
rev_func: (val) => {
return val
},
},
AXIS_CH: {
type: 'array',
showName: '3-AXIS',
range: Array(4).fill(['x', 'y', 'z', 'm']),
func: (chList) => {
const axisChannel = ['m', 'z', 'y', 'x']
let axisChannelSet = ''
for (const axis of axisChannel) {
if (chList.indexOf(axis) >= 0) {
axisChannelSet += '1'
} else {
axisChannelSet += '0'
}
}
return parseInt(axisChannelSet, 2)
},
rev_func: (val) => {
const axisChannelList = []
const axisChannelSet = val.toString(2).split('').reverse()
axisChannelSet.forEach((element, index) => {
if (parseInt(element) === 1) {
if (index === 0) {
axisChannelList.push('x')
} else if (index === 1) {
axisChannelList.push('y')
} else if (index === 2) {
axisChannelList.push('z')
} else if (index === 3) {
axisChannelList.push('m')
}
}
})
return axisChannelList
},
},
ACCELERATOR_S: {
type: 'array',
showName: 'Accelerator Sensitivity',
range: ['2G', '4G', '8G', '16G'],
func: (val) => {
const accArr = ['2G', '4G', '8G', '16G']
const index = accArr.indexOf(val.toString())
return index
},
rev_func: (idx) => {
const accArr = ['2G', '4G', '8G', '16G']
return accArr[parseInt(idx)]
},
},
// sti
STIMULATING: {
type: 'bool',
showName: 'STIMULATING',
range: [false, true],
func: (val) => {
return val
},
rev_func: (val) => {
return val
},
},
STI_CHANNEL: {
type: 'array',
showName: 'Channels',
range: Object.freeze({ pos: Array(32).fill([...Array(32).keys()].map(x => ++x)), neg: Array(32).fill([...Array(32).keys()].map(x => ++x)) }),
func: (posChList, negChList) => {
let stiChannelSet = ''
for (let i = 32; i >= 1; i--) {
if (posChList.indexOf(i) >= 0) {
stiChannelSet += '1'
} else if (negChList.indexOf(i) >= 0) {
stiChannelSet += '1'
} else {
stiChannelSet += '0'
}
}
return parseInt(stiChannelSet, 2)
},
rev_func: (val, stiPolarityVal) => {
const stiChannelSet = val.toString(2).split('').reverse()
const stiPolaritySet = stiPolarityVal.toString(2).split('').reverse()
const posChannelList = []
const negChannelList = []
stiChannelSet.forEach((element, index) => {
if (parseInt(element) > 0) {
const channel = index + 1
if (parseInt(stiPolaritySet[index]) > 0) {
channel > 16 ? posChannelList.push(channel + 32) : posChannelList.push(channel + 16)
} else {
channel > 16 ? negChannelList.push(channel + 32) : negChannelList.push(channel + 16)
}
}
})
return JSON.stringify({ pos: posChannelList, neg: negChannelList })
},
},
STI_POLARITY: {
type: 'array',
showName: 'Polarity',
range: Object.freeze({ pos: Array(32).fill([...Array(32).keys()].map(x => ++x)), neg: Array(32).fill([...Array(32).keys()].map(x => ++x)) }),
func: (posChList, negChList) => {
let stiPolaritySet = ''
for (let i = 32; i >= 1; i--) {
if (posChList.indexOf(i) >= 0) {
stiPolaritySet += '1'
} else if (negChList.indexOf(i) >= 0) {
stiPolaritySet += '0'
} else {
stiPolaritySet += '0'
}
}
return parseInt(stiPolaritySet, 2)
},
rev_func: (val, stiChannelVal) => {
const stiPolaritySet = val.toString(2).split('').reverse()
const stiChannelSet = stiChannelVal.toString(2).split('').reverse()
const posChannelList = []
const negChannelList = []
stiChannelSet.forEach((element, index) => {
if (parseInt(element) > 0) {
if (parseInt(stiPolaritySet[index]) > 0) {
posChannelList.push(index + 1)
} else {
negChannelList.push(index + 1)
}
}
})
return JSON.stringify({ pos: posChannelList, neg: negChannelList })
},
},
STI_H_BRIDGE: {
type: 'array',
showName: 'ELECTRODE',
range: ['Monopolar', 'Bipolar'],
func: (val) => {
const electrodeArr = ['Monopolar', 'Bipolar']
return electrodeArr.indexOf(val.toString())
},
rev_func: (val) => {
const idx = +val // +: bool to int, ref: https://stackoverflow.com/questions/7820683/convert-boolean-result-into-number-integer
const electrodeArr = ['Monopolar', 'Bipolar']
return electrodeArr[idx]
},
},
CONTINUOUS_STI: {
type: 'array',
showName: 'CYCLE',
range: ['Limited', 'Unlimited'],
func: (val) => {
const cycleNumberArr = ['Limited', 'Unlimited']
return cycleNumberArr.indexOf(val.toString())
},
rev_func: (val) => {
const idx = +val // +: bool to int, ref: https://stackoverflow.com/questions/7820683/convert-boolean-result-into-number-integer
const cycleNumberArr = ['Limited', 'Unlimited']
return cycleNumberArr[idx]
},
},
STI_CYCLE_CH0: {
type: 'number',
showName: 'CYCLE NUMBER',
range: Object.freeze({ min: 1, max: 256 }),
func: (cycleNum) => {
return String(cycleNum - 1)
},
rev_func: (val) => {
return parseInt(val) + 1
},
defaultUnit: 'rounds',
unit: {
rounds: 1,
},
},
REPEAT_TIME_littleEndian: {
type: 'number',
showName: 'REPEAT NUMBER',
range: Object.freeze({ min: 1, max: 256 }),
func: (val) => {
return String(val)
},
rev_func: (val) => {
return parseInt(val)
},
defaultUnit: 'rounds',
unit: {
rounds: 1,
},
},
REPEAT_INTERVAL_littleEndian: {
type: 'number',
showName: 'REPEAT INTERVAL',
range: Object.freeze({ min: 0, max: Infinity }),
func: (val) => {
return parseFloat(parseFloat(val).toFixed(2)) * 1e3
},
rev_func: (val) => {
return val
},
defaultUnit: 's',
unit: {
s: 1e3,
ms: 1,
},
},
STI_AMPLITUDE_L1: {
type: 'number',
showName: 'AMPLITUDE DURING T1',
range: Object.freeze({ min: 5, max: 4000 }),
func: (val) => {
return parseInt(val / 5)
},
rev_func: (val) => {
return val * 5
},
defaultUnit: 'uA',
unit: {
uA: 1,
},
},
STI_AMPLITUDE_H3: {
type: 'number',
showName: 'AMPLITUDE DURING T3',
range: Object.freeze({ min: 5, max: 4000 }),
func: (val) => {
return parseInt(val / 5)
},
rev_func: (val) => {
return val * 5
},
defaultUnit: 'uA',
unit: {
uA: 1,
},
},
STI_AMPLITUDE_H1: {
type: 'number',
showName: 'AMPLITUDE_HIGH_1',
range: Object.freeze({ min: 5, max: 4000 }),
func: (val) => {
return parseInt(val / 5 / 2)
},
rev_func: (val) => {
return val * 5 * 2
},
defaultUnit: 'uA',
unit: {
uA: 1,
},
},
STI_AMPLITUDE_L3: {
type: 'number',
showName: 'AMPLITUDE_LOW_3',
range: Object.freeze({ min: 5, max: 4000 }),
func: (val) => {
return parseInt(val / 5 / 2)
},
rev_func: (val) => {
return val * 5 * 2
},
defaultUnit: 'uA',
unit: {
uA: 1,
},
},
STI_PATTERN: {
type: 'array',
showName: 'PATTERN',
range: ['Monophasic', 'Biphasic'],
func: (val) => {
const patternArr = ['Monophasic', 'Biphasic']
return patternArr.indexOf(val.toString())
},
rev_func: (val) => {
const idx = +val // +: bool to int, ref: https://stackoverflow.com/questions/7820683/convert-boolean-result-into-number-integer
const patternArr = ['Monophasic', 'Biphasic']
return patternArr[idx]
},
},
STI_T1_CH0: {
type: 'number',
showName: 'T1',
amount: 16, // total 16 channels
range: Object.freeze({ min: 100, max: 10230 }), // 0.1ms~10.23ms
func: (val) => { // input unit: us
const tRange = parseFloat(val / 10)
return String(tRange)
},
rev_func: (val) => { // output unit: us
return val * 10
},
defaultUnit: 'ms',
unit: {
us: 1,
ms: 1e3,
s: 1e6,
},
},
STI_T2_CH0: {
type: 'number',
showName: 'T2',
amount: 16, // total 16 channels
range: Object.freeze({ min: 0, max: 10230 }), // 0ms~10.23ms
func: (val) => { // input unit: us
const tRange = parseFloat(val / 10)
return String(tRange)
},
rev_func: (val) => { // output unit: us
return val * 10
},
defaultUnit: 'ms',
unit: {
us: 1,
ms: 1e3,
s: 1e6,
},
},
STI_T3_CH0: {
type: 'number',
showName: 'T3',
amount: 16, // total 16 channels
range: Object.freeze({ min: 0, max: 10230 }), // 0ms~10.23ms
func: (val) => { // input unit: us
const tRange = parseFloat(val / 10)
return String(tRange)
},
rev_func: (val) => { // output unit: us
return val * 10
},
defaultUnit: 'ms',
unit: {
us: 1,
ms: 1e3,
s: 1e6,
},
},
STI_T4_CH0: {
type: 'number',
showName: 'T4',
amount: 16, // total 16 channels
range: Object.freeze({ min: 0, max: 1310710 }), // 0ms~1310.71ms
func: (val) => { // input unit: us
const tRange = parseFloat(val / 10)
return String(tRange)
},
rev_func: (val) => { // output unit: us
return val * 10
},
defaultUnit: 'ms',
unit: {
us: 1,
ms: 1e3,
s: 1e6,
},
},
}
export default Neulive35ex
+6
View File
@@ -1,7 +1,13 @@
import Neulive31 from './Neulive31/index'
import Neulive35 from './Neulive35/index'
import Neulive35ex from './Neulive35ex/index'
import EliteZM15 from './EliteZM15/index'
import EliteEIS from './EliteEIS/index'
const paramTable = {
Neulive31,
Neulive35,
Neulive35ex,
EliteZM15,
EliteEIS,
}
+2 -1
View File
@@ -105,7 +105,8 @@ export const taskInfo = {
const mesObj = JSON.parse(mes)
const update = async () => {
try {
_taskInfo.deviceList[mesObj.device].info.sd_card_status = mesObj.data
_taskInfo.deviceList[mesObj.device].info.sd_card_status = mesObj.data.sd_mount
// _taskInfo.deviceList[mesObj.device].info.sd_card_enable_status = mesObj.data.sd_enable
} catch (e) {
console.log(e)
return false
+1 -1
View File
@@ -37,7 +37,6 @@ function newSeries (type, legendName, xAxisIndex, yAxisIndex, xAxisSource, yAxis
showAllSymbol: false,
symbolSize: 4,
// [modified 2021/08/10] sampling method managed by vuex
// sampling: 'average',
// sampling: function (frame) {
// return frame[0]
// },
@@ -60,6 +59,7 @@ function newSeries (type, legendName, xAxisIndex, yAxisIndex, xAxisSource, yAxis
dataXMin: null,
dataYMax: null,
dataYMin: null,
connectNulls: true,
itemStyle: {
normal: {
},
+2 -106
View File
@@ -1,29 +1,4 @@
function getTime (val) {
let label = ''
const hour = Math.floor(val / 3.6e9)
const minute = Math.floor((val % 3.6e9) / 1000000 / 60)
const second = Math.floor((val % 6e7) / 1000000)
const millisecond = Math.floor((val % 1e6) / 1000)
const microsecond = Math.floor(val % 1e6) - 1000 * millisecond
if (hour > 0) {
label += hour + ' h '
}
if (minute > 0) {
label += minute + ' m '
}
if (second > 0) {
label += second + ' s '
}
if (millisecond > 0) {
label += millisecond + ' ms '
}
if (microsecond > 0 && hour === 0 && minute === 0 && second === 0) {
label += microsecond + ' µs '
}
return label
}
function newTooltip (chartID) {
function newTooltip (formatter) {
return {
hideDelay: 5000,
trigger: 'axis',
@@ -39,86 +14,7 @@ function newTooltip (chartID) {
// backgroundColor: '#505765',
// },
// },
formatter: function (params) {
if (params.length) {
if (params[0].seriesName.split('_')[0] === 'T' && params[0].seriesName.split('_')[1] !== 'T') {
params.unshift({ seriesName: 'Time', value: [null, getTime(parseFloat(params[0].value[0]))], color: '#5193f2' })
} else if (params[0].seriesName.split('_')[0] !== 'T' && params[0].seriesName.split('_')[1] === 'T') {
params.unshift({ seriesName: 'Time', value: [null, getTime(parseFloat(params[0].value[1]))], color: '#5193f2' })
} else {
params.unshift({ seriesName: 'Time', value: [null, getTime(parseFloat(params[0].value[2]))], color: '#5193f2' })
}
let tip = ''
for (var i = 0; i < params.length; i++) {
const param = params[i]
const style = 'color: ' + param.color
let value
// console.log(param.value)
if (param.seriesName === 'Time') {
value = param.value[1]
} else if (param.seriesName.split('_')[1] === 'T' && param.seriesName.split('_')[0] !== 'T') {
const physicalQuantity = param.seriesName.split('_')[0][param.seriesName.split('_')[0].length - 1]
if (physicalQuantity === 'V') {
value = parseFloat(param.value[0]).toFixed(3) + ' μV'
} else if (physicalQuantity === 'I') {
value = parseFloat(param.value[0]).toFixed(3) + ' nA'
} else if (physicalQuantity === 'R') {
value = parseFloat(param.value[0]).toFixed(3) + ' mΩ'
} else if (physicalQuantity === 'X' || physicalQuantity === 'Y' || physicalQuantity === 'Z' || physicalQuantity === 'M') {
value = (parseFloat(param.value[0]) / 100).toFixed(3) + ' (g)'
} else if (!isNaN(physicalQuantity)) {
value = (parseFloat(param.value[0]) / 1000).toFixed(3) + ' (mV)'
} else {
value = parseFloat(param.value[0]).toFixed(3)
}
} else if (param.seriesName.split('_')[1] !== 'T' && param.seriesName.split('_')[0] === 'T') {
const physicalQuantity = param.seriesName.split('_')[1][param.seriesName.split('_')[1].length - 1]
if (physicalQuantity === 'V') {
value = parseFloat(param.value[1]).toFixed(3) + ' μV'
} else if (physicalQuantity === 'I') {
value = parseFloat(param.value[1]).toFixed(3) + ' nA'
} else if (physicalQuantity === 'R') {
value = parseFloat(param.value[1]).toFixed(3) + ' mΩ'
} else if (physicalQuantity === 'X' || physicalQuantity === 'Y' || physicalQuantity === 'Z' || physicalQuantity === 'M') {
value = (parseFloat(param.value[1]) / 100).toFixed(3) + ' (g)'
} else if (!isNaN(physicalQuantity)) {
value = (parseFloat(param.value[1]) / 1000).toFixed(3) + ' (mV)'
} else {
value = parseFloat(param.value[1]).toFixed(3)
}
} else {
let value1
let value2
if (param.seriesName.split('_')[0][param.seriesName.split('_')[0].length - 1] === 'V') {
value1 = parseFloat(param.value[0]).toFixed(3) + ' μV'
} else if (param.seriesName.split('_')[0][param.seriesName.split('_')[0].length - 1] === 'I') {
value1 = parseFloat(param.value[0]).toFixed(3) + ' nA'
} else if (param.seriesName.split('_')[0][param.seriesName.split('_')[0].length - 1] === 'R') {
value1 = parseFloat(param.value[0]).toFixed(3) + ' mΩ'
} else {
value1 = parseFloat(param.value[0]).toFixed(3)
}
if (param.seriesName.split('_')[1][param.seriesName.split('_')[1].length - 1] === 'V') {
value2 = parseFloat(param.value[1]).toFixed(3) + ' μV'
} else if (param.seriesName.split('_')[1][param.seriesName.split('_')[1].length - 1] === 'I') {
value2 = parseFloat(param.value[1]).toFixed(3) + ' nA'
} else if (param.seriesName.split('_')[1][param.seriesName.split('_')[1].length - 1] === 'R') {
value2 = parseFloat(param.value[1]).toFixed(3) + ' mΩ'
} else {
value2 = parseFloat(param.value[1]).toFixed(3)
}
value = value1 + ', ' + value2
}
tip += '<span style="' + style + '">' +
param.seriesName +
'</span><span style="' +
style + '">' + value + '</span><br>'
}
return tip
}
},
formatter: formatter,
}
}
@@ -1,6 +1,7 @@
function newParameterSet () {
return {
// Neulive
SD_ENABLE: false,
CH_NUMBER: 0,
ADC_CLOCK: 0,
ADDRESS: 0,
+11 -1
View File
@@ -114,7 +114,7 @@ export default new Router({
},
{
name: 'data-spike',
path: 'data-spike/:metaList?',
path: 'data-spike/:metaList?/:startTime?/:endTime?/:sd?',
component: () => import('../components/analysis/spike/Spike.vue'),
},
{
@@ -135,6 +135,16 @@ export default new Router({
// keepAlive: true,
// },
},
{
name: 'trigger-file',
path: 'trigger-file',
component: () => import('../components/file/TriggerFile.vue'),
},
{
name: 'sdcard-reader',
path: 'sdcard-reader',
component: () => import('../components/file/SdcardFile.vue'),
},
{
name: 'settings',
path: 'settings',
+5 -1
View File
@@ -3,6 +3,7 @@ import Vuex from 'vuex'
import VuexI18n from 'vuex-i18n' // load vuex i18n module
// import app from './modules/app'
import setting from './modules/setting'
import device from './modules/device'
import taskContent from './modules/task/content'
import state from './state'
@@ -10,8 +11,9 @@ import mutations from './mutations'
import replay from './modules/analysis/replay'
import spike from './modules/analysis/spike'
import file from './modules/file'
import download from './modules/download'
import file from './modules/file' // [2022/02/10] for trigger file now
import sdcard from './modules/sdcard'
import * as getters from './getters'
@@ -25,8 +27,10 @@ const store = new Vuex.Store({
replay,
spike,
setting,
device,
download,
file,
sdcard,
},
state: state,
mutations: mutations,
@@ -3,6 +3,7 @@ import newChart from '@/factories/chart/chartFactory'
import newTooltip from '@/factories/chart/tooltipFactory'
import newDataZoom from '@/factories/chart/dataZoomFactory'
import types from '@/store/modules/analysis/replay/types'
import { download } from '@/data/download/Download'
const typePath = types.chart
@@ -27,6 +28,7 @@ function makeXAxis (gridIndex, type) {
scale: true,
min: null,
max: null,
data: [],
}
return xAxis
}
@@ -91,6 +93,9 @@ function getLegendName (sourceList) {
case 'Time':
legendName += 'T'
break
case 'Category':
legendName += 'T'
break
case 'Neulive':
switch (source.channel) {
case 256:
@@ -173,14 +178,14 @@ function getLegendName (sourceList) {
// generate grid
function makeGrid (index, length, chartID) {
const _height = (82 / length) - 2 + '%'
const _top = index * (82 / length) + 1 + '%'
const _top = index * (82 / length) + 7 + '%'
let _left
let _right
if (chartID === 0) {
_left = '0%'
_right = '0%'
} else {
_left = '8%'
_left = '5%'
_right = '4%'
}
return {
@@ -193,7 +198,79 @@ function makeGrid (index, length, chartID) {
}
}
function getTime (val) {
let label = ''
const hour = Math.floor(val / 3.6e9)
const minute = Math.floor((val % 3.6e9) / 1000000 / 60)
const second = Math.floor((val % 6e7) / 1000000)
const millisecond = Math.floor((val % 1e6) / 1000)
const microsecond = Math.floor(val % 1e6) - 1000 * millisecond
if (hour > 0) {
label += hour + ' h '
}
if (minute > 0) {
label += minute + ' m '
}
if (second > 0) {
label += second + ' s '
}
if (millisecond > 0) {
label += millisecond + ' ms '
}
if (microsecond > 0 && hour === 0 && minute === 0 && second === 0) {
label += microsecond + ' µs '
}
return label
}
function tooltipFormatter (params) {
if (params.length) {
params.unshift({ seriesName: 'Time', value: getTime(parseFloat(params[params.length - 1].axisValue)), color: '#5193f2' })
let tip = ''
for (var i = 0; i < params.length; i++) {
const param = params[i]
const style = 'color: ' + param.color
let value
if (param.seriesName === 'Time') {
value = param.value
} else {
value = (parseFloat(param.value) / 1000).toFixed(3) + ' (mV)'
}
tip += '<span style="' + style + '">' +
param.seriesName +
'</span><span style="' +
style + '">' + value + '</span><br>'
}
return tip
}
}
const actions = {
/**
* download data from API
*
* @param {number} scale
* @param {string} sampleMethod
* @param {number} id
* @param {number} channel
* @param {object} meta
*/
[typePath.getData]: async ({ state, getters, commit }, payload) => {
const { scale, sampleMethod, id, channel, meta, uuid, startTime, endTime } = payload
if (state.useSdCardData) {
return await download.rawSdDataByUUIDAndChannel(uuid, channel, startTime, endTime)
} else { // BLE data
if (scale === 1) {
return await download.rawByIDChannels(id, channel, meta)
} else {
if (sampleMethod === 'mean') {
return await download.miniMeanByIDChannel(id, channel, meta)
} else if (sampleMethod === 'random') {
return await download.miniRandomByIDChannel(id, channel, meta)
}
}
}
},
/**
* init data to chartData via chartID
@@ -210,7 +287,9 @@ const actions = {
if (payload.chartAllData[payload.chartID] == null) {
payload.chartAllData[payload.chartID] = newChart(payload.chartID)
if (payload.type !== 'overview') {
payload.chartAllData[payload.chartID].tooltip = newTooltip()
payload.chartAllData[payload.chartID].tooltip = newTooltip(tooltipFormatter)
payload.chartAllData[payload.chartID].legend.type = 'scroll'
payload.chartAllData[payload.chartID].legend.top = '1%'
}
payload.chartAllData[payload.chartID].chartType = payload.type
payload.chartAllData[payload.chartID].chartDevice = payload.device
@@ -329,6 +408,7 @@ const actions = {
payload.chartAllData[payload.chartID].dataZoom[0].xAxisIndex = dataZoomXAxisIndex
payload.chartAllData[payload.chartID].dataZoom[1].xAxisIndex = dataZoomXAxisIndex
payload.chartAllData[payload.chartID].dataZoom[2].yAxisIndex = dataZoomYAxisIndex
payload.chartAllData[payload.chartID].dataZoom[0].top = '0%'
}
}
},
@@ -377,6 +457,10 @@ const actions = {
xAxisType = 'time'
xAxisSource = [-1, -1]
break
case 'Category':
xAxisType = 'category'
xAxisSource = [-1, -1]
break
default:
xAxisType = 'value'
xAxisSource = [sourceList[0].id, sourceList[0].channel]
@@ -387,6 +471,10 @@ const actions = {
yAxisType = 'time'
yAxisSource = [-1, -1]
break
case 'Category':
yAxisType = 'category'
yAxisSource = [-1, -1]
break
default:
yAxisType = 'value'
yAxisSource = [sourceList[1].id, sourceList[1].channel]
@@ -521,90 +609,6 @@ const actions = {
return chartData
},
// /**
// * remove device-channel from chart TODO
// *
// * @param {object} chartAllData
// * @param {int} chartID
// * @param {int} legendName
// * @return {object} : chart data
// */
// [typePath.remove]: ({ state, getters, commit }, payload) => {
// const chartData = getters.getDataByIDChart(payload.chartID)
// if (chartData == null || chartData.legend.data.length === 0) {
// return false
// }
// let _xAxisIndex = -1
// let _yAxisIndex = -1
// // remove legend
// const _legendIndex = chartData.legend.data.indexOf(payload.legendName)
// // console.log(_legendIndex)
// if (_legendIndex >= 0) {
// chartData.legend.data.splice(chartData.legend.data.indexOf(payload.legendName), 1)
// } else {
// return false
// }
// // remove series
// for (let i = 0; i < chartData.series.length; i++) {
// if (chartData.series[i].name === payload.legendName) {
// _xAxisIndex = chartData.series[i].xAxisIndex
// _yAxisIndex = chartData.series[i].yAxisIndex
// chartData.series.splice(i, 1)
// break
// }
// }
// // remove grid series name
// const _gridIndex = chartData.xAxis[_xAxisIndex].gridIndex
// for (let i = 0; i < chartData.grid[_gridIndex].series.length; i++) {
// if (chartData.grid[_gridIndex].series[i] === payload.legendName) {
// chartData.grid[_gridIndex].series.splice(i, 1)
// break
// }
// }
// for (let i = 0; i < chartData.grid.length; i++) {
// if (chartData.grid[i].name === payload.legendName) {
// _xAxisIndex = chartData.series[i].xAxisIndex
// _yAxisIndex = chartData.series[i].yAxisIndex
// chartData.series.splice(i, 1)
// break
// }
// }
// chartData.series.forEach(series => {
// if (series.xAxisIndex === _xAxisIndex) {
// _xAxisIndex = -1
// }
// if (series.yAxisIndex === _yAxisIndex) {
// _yAxisIndex = -1
// }
// })
// // remove xAxis
// if (_xAxisIndex !== -1) {
// chartData.xAxis.splice(_xAxisIndex, 1)
// chartData.series.forEach(series => {
// if (series.xAxisIndex > _xAxisIndex) {
// series.xAxisIndex--
// }
// })
// chartData.dataZoom[0].xAxisIndex.pop()
// }
// // remove yAxis
// if (_yAxisIndex !== -1) {
// chartData.yAxis.splice(_yAxisIndex, 1)
// chartData.series.forEach(series => {
// if (series.yAxisIndex > _yAxisIndex) {
// series.yAxisIndex--
// }
// })
// chartData.dataZoom[2].yAxisIndex.pop()
// }
// // console.log(chartData)
// return true
// },
}
export default actions
@@ -116,10 +116,11 @@ const chartMutations = {
axisLabel.formatter = function (val) {
let label = ''
if (unit === 'auto') { // time
const hour = parseInt(val / 3.6e9)
const minute = parseInt((val % 3.6e9) / 1000000 / 60)
const second = parseInt((val % 6e7) / 1000000)
const millisecond = parseInt((val % 1e6) / 1000)
const hour = Math.floor(val / 3.6e9)
const minute = Math.floor((val % 3.6e9) / 1000000 / 60)
const second = Math.floor((val % 6e7) / 1000000)
const millisecond = Math.floor((val % 1e6) / 1000)
const microsecond = Math.floor(val % 1e6) - 1000 * millisecond
if (hour > 0) {
label += hour + ' h '
}
@@ -132,8 +133,11 @@ const chartMutations = {
if (millisecond > 0) {
label += millisecond + ' ms '
}
if (microsecond > 0 && hour === 0 && minute === 0 && second === 0) {
label += microsecond + ' µs '
}
} else { // not time, e.g. I, V, R
const num = String(val / unitScale)
const num = (val / unitScale).toFixed(1)
label = num + unit
}
return label
@@ -164,6 +168,7 @@ const chartMutations = {
return label
}
}
state.chartKey++
},
/**
@@ -413,12 +418,15 @@ const chartMutations = {
/**
* for manually set datazoom start and end
* @param {number} start
* @param {number} end
*
*/
[typePath.setOverviewDataZoomStartAndEnd]: (state, payload) => {
const { start, end } = payload
if (state.chartOverviewData.dataZoom != null) {
state.chartOverviewData.dataZoom[0].start = 0
state.chartOverviewData.dataZoom[0].end = 100
state.chartOverviewData.dataZoom[0].start = start
state.chartOverviewData.dataZoom[0].end = end
}
},
}
@@ -35,6 +35,14 @@ function newState () {
showWhichCycles: '1',
useSdCardData: false,
isLockDataZoom: false,
chartKey: 0,
isInitDone: false,
isDownloading: false,
// for sd replay
currentChunkDataSplitIndex: 1, // 1 ~ maxChunkDataSplitNum
maxChunkDataSplitNum: 10,
sdDataChunkDict: {},
useFilter: false,
}
}
@@ -35,6 +35,7 @@ const types = {
updateChartSampling: 'updateChartSamplingChart',
lockDataZoom: 'lockDataZoomChart',
setOverviewDataZoomStartAndEnd: 'setOverviewDataZoomStartAndEndChart',
getData: 'replayChartGetData',
},
meta: {
init: 'initMeta',

Some files were not shown because too many files have changed in this diff Show More