Commit cd0263e1511050325cb6f1d67777dc2752f95275

Authored by xp.Huang
2 parents 36e69e2d a4392a5d

Merge branch 'feat/object-model' into 'main_dev'

feat:支持标准modbus_rtu解析及命令下发

See merge request yunteng/thingskit-scada!216
Showing 39 changed files with 1305 additions and 763 deletions
1   -import type { AlarmStatusEnum } from '@/enums/datasource'
  1 +import type { AlarmStatusEnum } from '@/enums/alarmEnum'
2 2
3 3 /**
4 4 * 告警列表
... ...
1 1 import type { DeviceActiveType, DeviceAttributeItemType, DeviceItemType, DeviceProfileItemType, OrganizationItemType, ProductsDetailWithThingsModelType, RpcCommandType, SendValue, ThingsModel, ThingsModelItemType } from './model'
2 2 import { CommandWayEnum } from '@/enums/commandEnum'
3   -import type { DeviceTypeEnum } from '@/enums/datasource'
4   -import { FunctionTypeEnum } from '@/enums/datasource'
  3 +import type { DeviceTypeEnum } from '@/enums/deviceEnum'
  4 +import { FunctionTypeEnum } from '@/enums/objectModelEnum'
5 5 import { isShareMode } from '@/utils/env'
6 6 import { defHttp } from '@/utils/http'
7 7
... ... @@ -24,6 +24,7 @@ enum Api {
24 24 GET_LIST_BY_CONFIGURATION_ID = '/configuration/center/getListByConfigurationId',
25 25
26 26 GET_PRODUCTS_DETAIL_WITH_THINGS_MODEL = '/things_model/batch/get_tsl',
  27 +
27 28 }
28 29
29 30 export interface GenModbusCommandType {
... ... @@ -33,6 +34,7 @@ export interface GenModbusCommandType {
33 34 registerAddress: number
34 35 registerNumber?: number
35 36 registerValues?: number[]
  37 + hexByteOrderEnum?: string
36 38 }
37 39
38 40 export const getDeviceProfile = (deviceType?: DeviceTypeEnum) => {
... ... @@ -128,6 +130,17 @@ export const getDeviceActiveTime = (entityId: string) => {
128 130 )
129 131 }
130 132
  133 +export const getDeviceTelemetryValue = (params: { entityId: string; keys: string }) => {
  134 + return defHttp.get({
  135 + url: `${Api.GET_DEVICE_ACTIVE}${params.entityId}/values/timeseries`,
  136 + params: {
  137 + keys: params.keys,
  138 + },
  139 + }, {
  140 + joinPrefix: false,
  141 + })
  142 +}
  143 +
131 144 // 获取设备详情
132 145 export const getDeviceInfo = (deviceId: string) => {
133 146 return defHttp.get<DeviceItemType>(
... ...
1   -import type { DataTypeEnum, FunctionTypeEnum, TransportTypeEnum } from '@/enums/datasource'
  1 +import type { CommandCallWayEnum } from '@/enums/commandEnum'
  2 +import type { TCPProtocolTypeEnum, TransportTypeEnum } from '@/enums/deviceEnum'
  3 +import type { DataTypeEnum, ExtendDescOperationTypeEnum, FunctionTypeEnum, OriginalDataTypeEnum } from '@/enums/objectModelEnum'
2 4
3 5 export interface DeviceProfileItemType {
4 6 id: string
... ... @@ -41,6 +43,7 @@ export interface Configuration {
41 43
42 44 export interface TransportConfiguration {
43 45 type: string
  46 + protocol: TCPProtocolTypeEnum
44 47 }
45 48
46 49 export interface ProvisionConfiguration {
... ... @@ -116,6 +119,10 @@ export interface DeviceItemType {
116 119 deviceType: string
117 120 alarmStatus: number
118 121 enable: boolean
  122 + deviceProfile: {
  123 + transportType: TransportTypeEnum
  124 + profileData: ProfileData
  125 + }
119 126
120 127 transportType: TransportTypeEnum
121 128 }
... ... @@ -135,10 +142,14 @@ export interface ThingsModelItemType {
135 142 }
136 143
137 144 export interface ExtensionDesc {
138   - zoomFactor?: number
139   - actionType?: string
140   - dataType: string
141   - registerAddress: number
  145 + writeOnly?: boolean
  146 + bitMask?: number
  147 + operationType: ExtendDescOperationTypeEnum
  148 + originalDataType: OriginalDataTypeEnum
  149 + registerAddress: string
  150 + scaling?: number
  151 + valueRange?: Record<'min' | 'max', number>
  152 + registerCount?: number
142 153 }
143 154
144 155 export interface Detail {
... ... @@ -146,7 +157,7 @@ export interface Detail {
146 157 }
147 158
148 159 export interface DataType {
149   - type: string
  160 + type: DataTypeEnum
150 161 specs: Specs | StructJSON[]
151 162 specsList: Specs[]
152 163 }
... ... @@ -163,6 +174,7 @@ export interface Specs {
163 174 name?: string
164 175 value?: any
165 176 dataType?: DataTypeEnum
  177 + step?: number
166 178 }
167 179
168 180 export interface ValueRange {
... ... @@ -207,6 +219,8 @@ export interface Tsl {
207 219 functionName: string
208 220 identifier: string
209 221 accessMode: string
  222 + functionType: FunctionTypeEnum
  223 + callType: CommandCallWayEnum
210 224 specs: {
211 225 dataType: DataType
212 226 }
... ...
1 1 import type { ThingsModelItemType } from '@/api/device/model'
2 2 import type { ImageSelectorDataType } from '@/core/Library/components/ImageSelector'
3   -import type { CommandWayEnum } from '@/enums/commandEnum'
4   -import type { ActRangListItemTypeEnum, ActTypeEnum, AggregateTypeEnum, CommandDeliveryWayEnum, DeviceTypeEnum, EventActionTypeEnum, EventTypeEnum, SocketSubscriberEnum } from '@/enums/datasource'
  3 +import type { CommandDeliveryWayEnum, CommandWayEnum } from '@/enums/commandEnum'
  4 +import type { ActRangListItemTypeEnum, ActTypeEnum, AggregateTypeEnum, EventActionTypeEnum, EventTypeEnum, SocketSubscriberEnum } from '@/enums/datasource'
  5 +import type { DeviceTypeEnum } from '@/enums/deviceEnum'
5 6
6 7 export enum DeleteNodeDataTypeEnum {
7 8 DATASOURCE = 'DATASOURCE',
... ...
... ... @@ -2,7 +2,9 @@ import type { FormSchema } from '@/components/Form'
2 2 import { ComponentEnum } from '@/components/Form/src/enum'
3 3 import type { BasicColumn } from '@/components/Table'
4 4 import type { CommandWayEnum } from '@/enums/commandEnum'
5   -import { ActTypeEnum, type CodeTypeEnum, type ContentDataFieldsEnum, type DeviceTypeEnum } from '@/enums/datasource'
  5 +import { ActTypeEnum, type ContentDataFieldsEnum } from '@/enums/datasource'
  6 +import type { DeviceTypeEnum, TCPProtocolTypeEnum } from '@/enums/deviceEnum'
  7 +
6 8 export enum TableColumnFieldEnum {
7 9 DEVICE_ID = 'deviceId',
8 10 WAY = 'way',
... ... @@ -15,7 +17,7 @@ export interface TableRecordItemType {
15 17 [TableColumnFieldEnum.WAY]?: Nullable<CommandWayEnum>
16 18 [TableColumnFieldEnum.COMMAND]?: Nullable<string | Recordable>
17 19 [ContentDataFieldsEnum.DEVICE_TYPE]?: Nullable<DeviceTypeEnum>
18   - [ContentDataFieldsEnum.CODE_TYPE]?: Nullable<CodeTypeEnum>
  20 + [ContentDataFieldsEnum.CODE_TYPE]?: Nullable<TCPProtocolTypeEnum>
19 21 }
20 22
21 23 export const getFormSchemas = (): FormSchema[] => {
... ...
... ... @@ -2,10 +2,10 @@ import type { DeviceItemType, Specs, StructJSON, Tsl } from '@/api/device/model'
2 2 import type { NodeDataDataSourceJsonType, OperationPasswordDataType, SingleClickEventDataType } from '@/api/node/model'
3 3 import { type FormSchema, useComponentRegister } from '@/components/Form'
4 4 import { ComponentEnum } from '@/components/Form/src/enum'
5   -import { ThingsModelForm, validateTCPCustomCommand } from '@/core/Library/components/ThingsModelForm'
  5 +import { ThingsModelForm } from '@/core/Library/components/ThingsModelForm'
6 6 import { getFormSchemas } from '@/core/Library/components/ThingsModelForm/config'
7   -import { CodeTypeEnum, DataTypeEnum, TransportTypeEnum } from '@/enums/datasource'
8   -import { TCPObjectModelActionTypeEnum } from '@/enums/objectModelEnum'
  7 +import { TCPProtocolTypeEnum, TransportTypeEnum } from '@/enums/deviceEnum'
  8 +import { DataTypeEnum, OriginalDataTypeEnum } from '@/enums/objectModelEnum'
9 9
10 10 useComponentRegister(ComponentEnum.THINGS_MODEL_FORM, ThingsModelForm)
11 11
... ... @@ -35,59 +35,57 @@ function getStructJsonFromDataSourceJson(objectModelTsl: Tsl): StructJSON {
35 35 }
36 36 }
37 37
38   -function getTCPModbusSchemas({ objectModelTsl, required }: { objectModelTsl: Tsl; required?: boolean }): FormSchema {
39   - const { specs: tslSpecs, extensionDesc } = objectModelTsl
40   - const { dataType } = tslSpecs
41   - const { specs, type } = dataType || {}
42   - const { valueRange, length = 10240 } = specs! as Specs
43   - const { max = Number.MAX_SAFE_INTEGER, min = Number.MIN_SAFE_INTEGER } = valueRange || {}
44   - const { actionType } = extensionDesc || {}
  38 +const validateDouble = (value: number, min?: number | string, max?: number | string) => {
  39 + min = Number(min) ?? Number.MIN_SAFE_INTEGER
  40 + max = Number(max) ?? Number.MAX_SAFE_INTEGER
45 41
46   - function createInputNumber({
47   - identifier,
48   - functionName,
49   - }: Tsl): FormSchema {
50   - return {
51   - field: identifier,
52   - label: functionName,
53   - component: ComponentEnum.INPUT_NUMBER,
54   - required,
55   - componentProps: {
56   - max: actionType === TCPObjectModelActionTypeEnum.BOOL ? 1 : max,
57   - min: actionType === TCPObjectModelActionTypeEnum.BOOL ? 0 : min,
58   - precision: actionType === TCPObjectModelActionTypeEnum.BOOL ? 0 : 2,
59   - },
60   - }
  42 + return {
  43 + flag: value < min || value > max,
  44 + message: `取值范围在${min}~${max}之间`,
61 45 }
  46 +}
62 47
63   - const createInput = ({ identifier, functionName }: Tsl): FormSchema => {
64   - return {
65   - field: identifier,
66   - label: functionName,
67   - component: ComponentEnum.INPUT,
68   - rules: [
69   - {
70   - required,
71   - message: `${functionName}是必填项`,
72   - },
73   - {
74   - validator: validateTCPCustomCommand,
75   - },
76   - ],
77   - componentProps: {
78   - maxLength: length,
79   - },
80   - } as FormSchema
81   - }
  48 +export const createModbusValueInput = (objectModel: Tsl): FormSchema => {
  49 + const { identifier, functionName, extensionDesc } = objectModel
  50 +
  51 + const { dataType } = objectModel.specs || {}
  52 + const { specs } = dataType || {}
  53 + const { valueRange } = specs as Specs
  54 + const { max, min } = valueRange || {}
82 55
83   - return type === DataTypeEnum.STRING ? createInput(objectModelTsl) : createInputNumber(objectModelTsl)
  56 + const isStringType = extensionDesc?.originalDataType === OriginalDataTypeEnum.STRING
  57 + return {
  58 + field: identifier,
  59 + label: functionName,
  60 + component: isStringType ? ComponentEnum.INPUT : ComponentEnum.INPUT_NUMBER,
  61 + rules: isStringType
  62 + ? []
  63 + : [
  64 + {
  65 + type: 'number',
  66 + validator: (_rule, value) => {
  67 + const { flag, message } = validateDouble(value, min, max)
  68 + if (flag)
  69 + return Promise.reject(Error(`${functionName}${message}`))
  70 +
  71 + return Promise.resolve(value)
  72 + },
  73 + },
  74 + ],
  75 + componentProps: {
  76 + // max: max ?? Number.MAX_SAFE_INTEGER,
  77 + // min: min ?? Number.MIN_SAFE_INTEGER,
  78 + placeholder: `请输入${functionName}`,
  79 + precision: 0,
  80 + },
  81 + }
84 82 }
85 83
86 84 export const createFormSchemas = ({ operationPasswordInfo, objectModelTsl, deviceInfo }: CreateFormSchemasParamsType): FormSchema[] => {
87 85 const schemas: FormSchema[] = []
88 86
89   - if (deviceInfo?.transportType === TransportTypeEnum.TCP && deviceInfo.codeType === CodeTypeEnum.MODBUS_RTU && deviceInfo.code) {
90   - schemas.push(getTCPModbusSchemas({ required: true, objectModelTsl }))
  87 + if (deviceInfo?.transportType === TransportTypeEnum.TCP && deviceInfo.deviceProfile?.profileData?.transportConfiguration?.protocol === TCPProtocolTypeEnum.MODBUS_RTU) {
  88 + schemas.push(createModbusValueInput(objectModelTsl))
91 89 }
92 90 else {
93 91 const isStructType = objectModelTsl.specs.dataType.type === DataTypeEnum.STRUCT
... ...
1 1 <script setup lang="ts">
2 2 import { Modal } from 'ant-design-vue'
3 3 import { nextTick, ref, toRaw, unref } from 'vue'
4   -import type { AttributeDeliverModalOpenParamsType } from './AttributeDeliverModal.config'
5   -import { createFormSchemas } from './AttributeDeliverModal.config'
6   -import { useCommandDeliver } from './useCommadDeliver'
  4 +import { type AttributeDeliverModalOpenParamsType, createFormSchemas } from './AttributeDeliverModal.config'
7 5 import { BasicForm, useForm } from '@/components/Form'
8 6 import { FormLayoutEnum } from '@/components/Form/src/enum'
9   -import { DataTypeEnum } from '@/enums/datasource'
  7 +import type { DeviceItemType, Tsl } from '@/api/device/model'
  8 +import { useProductsStore } from '@/store/modules/products'
  9 +import { getDeviceInfo } from '@/api/device'
  10 +import type { CommandWayEnum } from '@/enums/commandEnum'
  11 +import { useCommandDelivery } from '@/hooks/business/useCommandDelivery'
  12 +import { DataTypeEnum } from '@/enums/objectModelEnum'
  13 +import { useMessage } from '@/hooks/web/useMessage'
10 14
11 15 const visible = ref(false)
12 16
... ... @@ -15,25 +19,41 @@ const [register, { getFieldsValue, resetFields, validate, setProps, clearValidat
15 19 showActionButtonGroup: false,
16 20 })
17 21
18   -const { setup, getDeviceInfo, getObjectModelTsl, doCommandDeliver } = useCommandDeliver()
  22 +const deviceInfo = ref<DeviceItemType>()
  23 +const objectModelTsl = ref<Tsl>()
  24 +const productsStore = useProductsStore()
  25 +const commandWay = ref<CommandWayEnum>()
19 26
20 27 const open = async ({ operationPasswordInfo, dataSourceJson, eventBindData }: AttributeDeliverModalOpenParamsType) => {
21 28 visible.value = true
22 29 const { deviceId, deviceProfileId, attr } = dataSourceJson
23 30 const { way } = eventBindData
24   - await setup({ deviceId, deviceProfileId, attr, callType: way })
  31 + commandWay.value = way
  32 + objectModelTsl.value = productsStore.getObjectModelByIdWithIdentifier(deviceProfileId, attr as string)!
  33 + deviceInfo.value = await getDeviceInfo(deviceId)
25 34
26 35 nextTick(() => {
27   - const schemas = createFormSchemas({ operationPasswordInfo, deviceInfo: toRaw(unref(getDeviceInfo)!), objectModelTsl: toRaw(unref(getObjectModelTsl)!) })
  36 + const schemas = createFormSchemas({ operationPasswordInfo, deviceInfo: toRaw(unref(deviceInfo)!), objectModelTsl: toRaw(unref(objectModelTsl)!) })
28 37 setProps({ schemas })
29 38 })
30 39 }
31 40
32 41 async function getResult() {
33   - const result = getFieldsValue()
34   - const isStructJSON = unref(getObjectModelTsl)?.specs.dataType.type === DataTypeEnum.STRUCT
35   - const identifier = unref(getObjectModelTsl)!.identifier
36   - await doCommandDeliver(isStructJSON ? result : result[identifier])
  42 + let value = getFieldsValue()
  43 + const { doCommandDelivery } = useCommandDelivery()
  44 +
  45 + const isStructJSON = unref(objectModelTsl)?.specs.dataType.type === DataTypeEnum.STRUCT
  46 + const identifier = unref(objectModelTsl)!.identifier
  47 + value = isStructJSON ? value : value[identifier]
  48 +
  49 + await doCommandDelivery({
  50 + objectModel: unref(objectModelTsl),
  51 + deviceDetail: unref(deviceInfo),
  52 + value,
  53 + })
  54 +
  55 + const { createMessage } = useMessage()
  56 + createMessage.success('命令下发成功')
37 57 }
38 58
39 59 const handleOk = async () => {
... ... @@ -53,7 +73,7 @@ defineExpose({ open })
53 73
54 74 <template>
55 75 <Modal
56   - v-model:open="visible" :title="`属性下发 / ${getDeviceInfo?.alias || getDeviceInfo?.name}`" ok-text="确认" :width="400" cancel-text="取消" @cancel="handleCancel"
  76 + v-model:open="visible" :title="`属性下发 / ${deviceInfo?.alias || deviceInfo?.name}`" ok-text="确认" :width="400" cancel-text="取消" @cancel="handleCancel"
57 77 @ok="handleOk"
58 78 >
59 79 <BasicForm @register="register" />
... ...
... ... @@ -2,10 +2,16 @@
2 2 import { Modal } from 'ant-design-vue'
3 3 import { computed, nextTick, ref, unref } from 'vue'
4 4 import { Icon } from '@iconify/vue'
5   -import { useCommandDeliver } from './useCommadDeliver'
6 5 import { BasicForm, type FormSchema, useForm } from '@/components/Form'
7 6 import { ComponentEnum, FormLayoutEnum } from '@/components/Form/src/enum'
8 7 import type { NodeDataDataSourceJsonType, OperationPasswordDataType, SingleClickEventDataType } from '@/api/node/model'
  8 +import type { DoCommandDeliverParamsType } from '@/hooks/business/useCommandDelivery'
  9 +import { useCommandDelivery } from '@/hooks/business/useCommandDelivery'
  10 +import { CommandDeliveryWayEnum } from '@/enums/commandEnum'
  11 +import { useProductsStore } from '@/store/modules/products'
  12 +import { getDeviceInfo } from '@/api/device'
  13 +import { TransportTypeEnum } from '@/enums/deviceEnum'
  14 +import { useMessage } from '@/hooks/web/useMessage'
9 15
10 16 interface SwitchCommandDeliveryModalParamsType {
11 17 operationPasswordInfo?: OperationPasswordDataType
... ... @@ -18,8 +24,6 @@ const visible = ref(false)
18 24
19 25 const currentOperationPasswordInfo = ref<OperationPasswordDataType>()
20 26
21   -const currendSendValue = ref()
22   -
23 27 const [register, { resetFields, validate, setProps }] = useForm({
24 28 showActionButtonGroup: false,
25 29 layout: FormLayoutEnum.VERTICAL,
... ... @@ -45,17 +49,17 @@ const createFormSchemas = (password: string): FormSchema[] => {
45 49 ]
46 50 }
47 51
48   -const getHasEnableOperationPassword = computed(() => !!(unref(currentOperationPasswordInfo)?.checked && unref(currentOperationPasswordInfo)?.value))
  52 +const productsStore = useProductsStore()
49 53
50   -const { setup, doCommandDeliver, getDeviceInfo } = useCommandDeliver()
  54 +const getHasEnableOperationPassword = computed(() => !!(unref(currentOperationPasswordInfo)?.checked && unref(currentOperationPasswordInfo)?.value))
51 55
52   -const open = async ({ operationPasswordInfo, dataSourceJson, eventBindData, sendValue }: SwitchCommandDeliveryModalParamsType) => {
  56 +const currentParams = ref<SwitchCommandDeliveryModalParamsType>()
  57 +const open = async (params: SwitchCommandDeliveryModalParamsType) => {
  58 + const { operationPasswordInfo } = params
53 59 visible.value = true
  60 + currentParams.value = params
54 61 currentOperationPasswordInfo.value = operationPasswordInfo
55   - currendSendValue.value = sendValue
56   - const { deviceId, deviceProfileId, attr } = unref(dataSourceJson)
57   - const { way } = eventBindData
58   - await setup({ deviceId, deviceProfileId, attr, callType: way })
  62 +
59 63 nextTick(() => {
60 64 unref(getHasEnableOperationPassword) && setProps({ schemas: createFormSchemas(operationPasswordInfo!.value!) })
61 65 })
... ... @@ -64,7 +68,36 @@ const open = async ({ operationPasswordInfo, dataSourceJson, eventBindData, send
64 68 const handlerOk = async () => {
65 69 if (unref(getHasEnableOperationPassword))
66 70 await validate()
67   - await doCommandDeliver(unref(currendSendValue))
  71 + const { doCommandDelivery } = useCommandDelivery()
  72 +
  73 + const { sendValue, eventBindData, dataSourceJson } = unref(currentParams)!
  74 + const { way, commandWay } = eventBindData
  75 + const { deviceProfileId, attr, deviceId } = dataSourceJson
  76 +
  77 + const deviceDetail = await getDeviceInfo(deviceId)
  78 +
  79 + const doCommandParams: DoCommandDeliverParamsType = {
  80 + value: sendValue,
  81 + identifier: attr as string,
  82 + deviceDetail,
  83 + deviceProfileId,
  84 + way,
  85 + }
  86 +
  87 + if (commandWay === CommandDeliveryWayEnum.CUSTOM) {
  88 + doCommandParams.penetration = true
  89 + }
  90 + else if (commandWay === CommandDeliveryWayEnum.SERVICE) {
  91 + const { service } = eventBindData
  92 + doCommandParams.objectModel = productsStore.getObjectModelByIdWithIdentifier(deviceProfileId, service!)!
  93 + doCommandParams.value = deviceDetail?.transportType === TransportTypeEnum.TCP ? doCommandParams.value : { [doCommandParams.objectModel.identifier]: doCommandParams.value }
  94 + }
  95 +
  96 + await doCommandDelivery(doCommandParams)
  97 +
  98 + const { createMessage } = useMessage()
  99 + createMessage.success('命令下发成功')
  100 +
68 101 if (unref(getHasEnableOperationPassword)) resetFields()
69 102 visible.value = false
70 103 }
... ... @@ -73,22 +106,7 @@ defineExpose({ open })
73 106 </script>
74 107
75 108 <template>
76   - <Modal
77   - v-model:open="visible"
78   - :title="`属性下发 / ${getDeviceInfo?.alias || getDeviceInfo?.name}`"
79   - :width="300"
80   - ok-text="确认"
81   - cancel-text="取消"
82   - destroy-on-close
83   - @ok="handlerOk"
84   - >
85   - <!-- <template #title>
86   - <div v-if="getHasEnableOperationPassword" />
87   - <div v-else class="flex">
88   - <span><Icon icon="ant-design:exclamation-circle-filled" class="text-yellow-300 text-xl" /></span>
89   - <span class="font-bold ml-2">属性下发 / 提示</span>
90   - </div>
91   - </template> -->
  109 + <Modal v-model:open="visible" :width="300" ok-text="确认" cancel-text="取消" destroy-on-close @ok="handlerOk">
92 110 <BasicForm v-if="getHasEnableOperationPassword" @register="register" />
93 111 <div v-else class="flex items-center">
94 112 <Icon icon="ant-design:exclamation-circle-filled" class="text-yellow-300 text-xl" />
... ...
1 1 import { ComponentEnum } from '@/components/Form/src/enum'
2 2 import type { FormSchema } from '@/components/Form'
3   -import type { Tsl } from '@/api/device/model'
4 3 import type { OperationPasswordDataType } from '@/api/node/model'
5 4 import { validateTCPCustomCommand } from '@/core/Library/components/ThingsModelForm'
6 5 import { JSONEditorValidator } from '@/components/CodeEditor/src/JSONEditor'
7   -import { DataTypeEnum } from '@/enums/datasource'
8 6
9 7 export enum FormFieldsEnum {
10 8 CUSTOM_COMMAND = 'customCommand',
... ... @@ -22,75 +20,6 @@ export const formSchemas: FormSchema[] = [
22 20 },
23 21 ]
24 22
25   -export const getModbusSchemas = (objectModelTsl: Tsl): FormSchema[] => {
26   - const { functionName, identifier, specs } = objectModelTsl
27   - const { type } = specs.dataType!
28   - const HEX_MAX_VALUE = Number(0xffff)
29   -
30   - const _formSchema: FormSchema = {
31   - field: identifier,
32   - label: functionName,
33   - component: ComponentEnum.INPUT_NUMBER,
34   - }
35   -
36   - const getIntSchema = () => {
37   - return Object.assign(_formSchema, {
38   - required: true,
39   - componentProps: {
40   - min: 0,
41   - max: HEX_MAX_VALUE,
42   - precision: 2,
43   - placeholder: '请输入正数',
44   - },
45   - } as Partial<FormSchema>)
46   - }
47   -
48   - const getBoolSchema = () => {
49   - return Object.assign(_formSchema, {
50   - required: true,
51   - componentProps: {
52   - min: 0,
53   - max: 1,
54   - precision: 0,
55   - placeholder: '请输入0或1',
56   - },
57   - } as Partial<FormSchema>)
58   - }
59   -
60   - const getDoubleSchema = () => {
61   - return Object.assign(_formSchema, {
62   - required: true,
63   - componentProps: {
64   - placeholder: '请输入数字',
65   - precision: 2,
66   - max: HEX_MAX_VALUE,
67   - },
68   - } as Partial<FormSchema>)
69   - }
70   -
71   - const getStringSchema = () => {
72   - return Object.assign(_formSchema, {
73   - component: ComponentEnum.INPUT,
74   - required: true,
75   - rules: [{ validator: validateTCPCustomCommand }],
76   - componentProps: {
77   - placeholder: '请输入自定义命令',
78   - },
79   - } as Partial<FormSchema>)
80   - }
81   -
82   - const schemasMap: Partial<Record<DataTypeEnum, Fn<any, FormSchema>>> = {
83   - [DataTypeEnum.NUMBER_INT]: getIntSchema,
84   - [DataTypeEnum.BOOL]: getBoolSchema,
85   - [DataTypeEnum.NUMBER_DOUBLE]: getDoubleSchema,
86   - [DataTypeEnum.STRING]: getStringSchema,
87   - }
88   -
89   - const schemas = schemasMap[type as DataTypeEnum]?.()
90   -
91   - return [schemas!]
92   -}
93   -
94 23 export const getOperationPasswordSchemas = (operationPasswordInfo: OperationPasswordDataType): FormSchema[] => {
95 24 return [
96 25 {
... ...
1 1 <script setup lang="ts">
2 2 import { nextTick, reactive, ref, toRaw, unref } from 'vue'
3 3 import { Button } from 'ant-design-vue'
4   -import { FormFieldsEnum, formSchemas, getJSONCommandSchemas, getModbusSchemas, getOperationPasswordSchemas, getTcpCustomCommandSchemas } from './config.ts'
5   -import { useCommandDeliver } from './useCommadDeliver'
  4 +import { FormFieldsEnum, formSchemas, getJSONCommandSchemas, getOperationPasswordSchemas, getTcpCustomCommandSchemas } from './config.ts'
  5 +import { createModbusValueInput } from './AttributeDeliverModal.config'
6 6 import { BasicModal, useModalInner } from '@/components/Modal'
7 7 import type { NodeDataDataSourceJsonType, OperationPasswordDataType, SingleClickEventDataType } from '@/api/node/model'
8 8 import { BasicForm, useForm } from '@/components/Form'
9 9 import { FormLayoutEnum } from '@/components/Form/src/enum'
10   -import { CommandDeliveryWayEnum, TransportTypeEnum } from '@/enums/datasource'
11 10 import { ThingsModelForm } from '@/core/Library/components/ThingsModelForm'
12 11 import type { DeviceItemType, Tsl } from '@/api/device/model'
13   -import { useProductsStoreWithOut } from '@/store/modules/products'
  12 +import { useProductsStore, useProductsStoreWithOut } from '@/store/modules/products'
14 13 import { useJsonParse } from '@/hooks/business/useJSONParse'
15   -import { CommandCallWayEnum, CommandWayEnum } from '@/enums/commandEnum'
  14 +import { CommandDeliveryWayEnum } from '@/enums/commandEnum'
  15 +import { TransportTypeEnum } from '@/enums/deviceEnum'
  16 +import { getDeviceInfo } from '@/api/device'
  17 +import type { DoCommandDeliverParamsType } from '@/hooks/business/useCommandDelivery'
  18 +import { useCommandDelivery } from '@/hooks/business/useCommandDelivery'
  19 +import { useMessage } from '@/hooks/web/useMessage'
16 20
17 21 interface OpenParamsType {
18 22 dataSource: NodeDataDataSourceJsonType
... ... @@ -82,66 +86,60 @@ function handleOpenServiceCommandModal(deviceInfo: DeviceItemType, eventBindData
82 86
83 87 function handleOpenModbusCommandModal(objectModelTsl: Tsl) {
84 88 const { setProps } = customCommandFormAction
85   - setProps({ schemas: getModbusSchemas(objectModelTsl) })
  89 + setProps({ schemas: [createModbusValueInput(objectModelTsl)] })
86 90 }
87 91
88   -const { setup, getDeviceInfo, getObjectModelTsl, doCommandDeliver } = useCommandDeliver()
  92 +const deviceInfo = ref<DeviceItemType>()
  93 +const objectModelTsl = ref<Tsl>()
  94 +const currentParams = ref<OpenParamsType>()
  95 +const productsStore = useProductsStore()
89 96
90   -const open = async ({ eventBindData, operationPasswordInfo, dataSource }: OpenParamsType) => {
  97 +const open = async (params: OpenParamsType) => {
  98 + const { eventBindData, operationPasswordInfo, dataSource } = params
91 99 const { deviceId, deviceProfileId, attr } = dataSource
92   - const { commandWay, callType, way, service } = eventBindData
  100 + const { commandWay } = eventBindData
93 101 const { checked, value: operationPassword } = operationPasswordInfo || {}
94 102 configuration.commandDeliveryWay = commandWay!
95   -
  103 + currentParams.value = params
  104 + deviceInfo.value = await getDeviceInfo(deviceId)
  105 + objectModelTsl.value = productsStore.getObjectModelByIdWithIdentifier(deviceProfileId, attr as string)!
96 106 setModalProps({
97 107 open: true,
98 108 loading: true,
99 109 })
100 110
101   - await setup({
102   - deviceId,
103   - deviceProfileId,
104   - attr,
105   - commandDeliverWay: commandWay,
106   - callType: configuration.commandDeliveryWay === CommandDeliveryWayEnum.SERVICE
107   - ? callType === CommandCallWayEnum.SYNC
108   - ? CommandWayEnum.TWO_WAY
109   - : CommandWayEnum.ONE_WAY
110   - : way,
111   - serviceIdentifier: service,
112   - })
113   -
114 111 if (checked && operationPassword)
115 112 handleHasOperationPassword(operationPasswordInfo!)
116 113
  114 + await nextTick()
  115 +
117 116 if (commandWay === CommandDeliveryWayEnum.CUSTOM)
118   - handleOpenCustomCommandModal(toRaw(unref(getDeviceInfo)!), eventBindData)
  117 + handleOpenCustomCommandModal(toRaw(unref(deviceInfo)!), eventBindData)
119 118 else if (commandWay === CommandDeliveryWayEnum.SERVICE)
120   - handleOpenServiceCommandModal(toRaw(unref(getDeviceInfo)!), eventBindData)
  119 + handleOpenServiceCommandModal(toRaw(unref(deviceInfo)!), eventBindData)
121 120 else if (commandWay === CommandDeliveryWayEnum.MODBUS)
122   - handleOpenModbusCommandModal(toRaw(unref(getObjectModelTsl)!))
  121 + handleOpenModbusCommandModal(toRaw(unref(objectModelTsl)!))
123 122
124 123 setModalProps({
125 124 loading: false,
126   - title: `参数设置 / ${unref(getDeviceInfo)?.alias || unref(getDeviceInfo)?.name}`,
  125 + title: `参数设置 / ${unref(deviceInfo)?.alias || unref(deviceInfo)?.name}`,
127 126 })
128 127 }
129 128
130 129 function handleGetCustomCommand() {
131 130 const res = customCommandFormAction.getFieldsValue()
132 131 const command = res[FormFieldsEnum.CUSTOM_COMMAND]
133   - return unref(getDeviceInfo)?.transportType === TransportTypeEnum.TCP ? command : useJsonParse(command).value
  132 + return unref(deviceInfo)?.transportType === TransportTypeEnum.TCP ? command : useJsonParse(command).value
134 133 }
135 134
136 135 function handleGetServiceCommand() {
137 136 const res = unref(thingsModelElRef)!.getFieldsValue()
138   -
139   - return unref(getDeviceInfo)?.transportType === TransportTypeEnum.TCP ? res[FormFieldsEnum.SERVICE_COMMAND] : res
  137 + return unref(deviceInfo)?.transportType === TransportTypeEnum.TCP ? res[FormFieldsEnum.SERVICE_COMMAND] : res
140 138 }
141 139
142 140 function handleGetModbusCommand() {
143 141 const res = customCommandFormAction.getFieldsValue()
144   - const identifier = unref(getObjectModelTsl)!.identifier
  142 + const identifier = unref(objectModelTsl)!.identifier
145 143 return res[identifier]
146 144 }
147 145
... ... @@ -156,7 +154,31 @@ const handleSubmit = async () => {
156 154 ? handleGetModbusCommand()
157 155 : handleGetServiceCommand()
158 156
159   - await doCommandDeliver(res)
  157 + const { doCommandDelivery } = useCommandDelivery()
  158 +
  159 + const { eventBindData, dataSource } = unref(currentParams)!
  160 + const { deviceProfileId } = dataSource
  161 + const { commandWay, way } = eventBindData
  162 +
  163 + const params: DoCommandDeliverParamsType = {
  164 + value: res,
  165 + deviceDetail: unref(deviceInfo),
  166 + objectModel: unref(objectModelTsl),
  167 + way,
  168 + }
  169 +
  170 + if (commandWay === CommandDeliveryWayEnum.CUSTOM) {
  171 + params.penetration = true
  172 + }
  173 + else if (commandWay === CommandDeliveryWayEnum.SERVICE) {
  174 + const { service } = eventBindData
  175 + params.objectModel = productsStore.getObjectModelByIdWithIdentifier(deviceProfileId, service!)!
  176 + params.value = unref(deviceInfo)?.transportType === TransportTypeEnum.TCP ? params.value : { [params.objectModel.identifier]: params.value }
  177 + }
  178 +
  179 + await doCommandDelivery(params)
  180 + const { createMessage } = useMessage()
  181 + createMessage.success('命令下发成功')
160 182 closeModal()
161 183 }
162 184
... ...
1   -import { computed, ref, unref } from 'vue'
2   -import { useGetModbusCommand } from './useGetModbusCommand'
3   -import { CodeTypeEnum, CommandDeliveryWayEnum, CommandTypeEnum, DataTypeEnum, TransportTypeEnum } from '@/enums/datasource'
4   -import type { DeviceItemType, RpcCommandType, Tsl } from '@/api/device/model'
5   -import { getDeviceActive, getDeviceInfo, sendRpcOneway } from '@/api/device'
6   -import { useProductsStoreWithOut } from '@/store/modules/products'
7   -import { CommandMethodEnum, CommandWayEnum } from '@/enums/commandEnum'
8   -import { useMessage } from '@/hooks/web/useMessage'
9   -
10   -interface UseCommandDeliverParamsType {
11   - deviceProfileId: string
12   - deviceId: string
13   - attr: string
14   - callType?: CommandWayEnum
15   - commandDeliverWay?: CommandDeliveryWayEnum
16   - serviceIdentifier?: string
17   -}
18   -
19   -/**
20   - * @description 命令下发流程
21   - * - TCP设备(下发命令为String):
22   - * > - Modbus类型(INT,DOUBLE,BOOL,TEXT):
23   - * >> 1. 验证设备是否绑定地址码与物模型是否绑定寄存器地址
24   - * >> 2. 物模型类型为INT,DOUBLE,BOOL: 调用接口生成Modbus命令
25   - * >>> 1. 数据类型为INT,DOUBLE时需要进行乘以缩放因子后验证值是否为整型与浮点型
26   - * >>> 2. 数据类型为INT,DOUBLE缩放处理后进行HEX转码
27   - * >>> 3. DOUBLE类型寄存器个数为2
28   - * >>> 4. BOOL类型值为0与1
29   - * >> 3. 物模型类型为TEXT: 输入值为自定义Modbus命令
30   - * >> 4. 物模型类型为服务: 服务命令为下发值
31   - * > - 自定义类型: 输入值为Modubs命令
32   - * - 非TCP设备(下发命令为(Object), {[identifier]: value}):
33   - * > - 自定义命令下发: JSON格式命令下发
34   - * > - 属性下发与服务下发:
35   - * >> -
36   - *
37   - * @returns
38   - */
39   -export function useCommandDeliver(params?: UseCommandDeliverParamsType) {
40   - const deviceInfo = ref<DeviceItemType>()
41   - const objectModelTsl = ref<Nullable<Tsl>>(null)
42   - const { createMessage } = useMessage()
43   -
44   - const configuration: Partial<UseCommandDeliverParamsType> = {
45   - deviceId: undefined,
46   - deviceProfileId: undefined,
47   - attr: undefined,
48   - }
49   -
50   - const getDeviceInfoById = async () => {
51   - const res = await getDeviceInfo(configuration.deviceId!)
52   - deviceInfo.value = res
53   - }
54   -
55   - const getObjectModelTslByIdWithIdentifier = () => {
56   - const productStore = useProductsStoreWithOut()
57   - const { deviceProfileId, attr } = configuration
58   - objectModelTsl.value = productStore.getObjectModelByIdWithIdentifier(deviceProfileId!, attr!)
59   - }
60   -
61   - const setup = async (params?: UseCommandDeliverParamsType) => {
62   - Object.assign(configuration, params || {})
63   - const { deviceId, deviceProfileId, attr } = configuration
64   - if (!deviceId || !deviceProfileId || !attr) return
65   - getObjectModelTslByIdWithIdentifier()
66   - await getDeviceInfoById()
67   - }
68   -
69   - if (params && params.deviceId && params.deviceProfileId && params.attr) setup()
70   -
71   - const getCommand = async (command: any) => {
72   - const { commandDeliverWay, serviceIdentifier } = configuration
73   -
74   - const isTcpDevice = unref(deviceInfo)?.transportType === TransportTypeEnum.TCP
75   -
76   - // 指定下发命令方式
77   - if (commandDeliverWay) {
78   - if (commandDeliverWay === CommandDeliveryWayEnum.CUSTOM) return command
79   -
80   - if (commandDeliverWay === CommandDeliveryWayEnum.SERVICE && serviceIdentifier) {
81   - return isTcpDevice
82   - ? command
83   - : {
84   - [serviceIdentifier]: command,
85   - }
86   - }
87   - }
88   -
89   - const isTCPModbusDevice = isTcpDevice && unref(deviceInfo)?.codeType === CodeTypeEnum.MODBUS_RTU
90   - const identifier = unref(objectModelTsl)!.identifier
91   -
92   - if (!isTCPModbusDevice) {
93   - return {
94   - [identifier]: command,
95   - }
96   - }
97   - else {
98   - const isString = objectModelTsl.value?.specs.dataType.type === DataTypeEnum.STRING
99   -
100   - if (isString) return command
101   -
102   - const { extensionDesc } = unref(objectModelTsl)!
103   - const { code } = unref(deviceInfo)!
104   -
105   - const { getModbusCommand, validateCanGetCommand } = useGetModbusCommand()
106   - if (!validateCanGetCommand(extensionDesc, code).flag) return
107   -
108   - const res = await getModbusCommand(command as number, extensionDesc!, code)
109   - return res
110   - }
111   - }
112   -
113   - async function doCommandDeliver(command: any) {
114   - const { callType = CommandWayEnum.ONE_WAY, commandDeliverWay } = configuration
115   -
116   - if (callType === CommandWayEnum.TWO_WAY) {
117   - const res = await getDeviceActive(unref(deviceInfo)!.tbDeviceId!)
118   - const [firstItem] = res
119   - const { value } = firstItem
120   -
121   - if (!value) {
122   - createMessage.warning('设备不在线')
123   - return Promise.reject(Error('设备不在线'))
124   - }
125   - }
126   -
127   - const res = await getCommand(command)
128   -
129   - const rpcCommandParams: RpcCommandType = {
130   - additionalInfo: {
131   - cmdType:
132   - unref(commandDeliverWay) === CommandDeliveryWayEnum.SERVICE
133   - ? CommandTypeEnum.SERVICE
134   - : CommandTypeEnum.API,
135   - },
136   - persistent: true,
137   - method: CommandMethodEnum.THINGSKIT,
138   - params: res,
139   - }
140   -
141   - await sendRpcOneway(rpcCommandParams, unref(deviceInfo)?.tbDeviceId, callType)
142   - createMessage.success('命令下发成功')
143   - }
144   -
145   - return {
146   - getDeviceInfo: computed(() => unref(deviceInfo)),
147   - getObjectModelTsl: computed(() => unref(objectModelTsl)),
148   - setup,
149   - getCommand,
150   - doCommandDeliver,
151   - }
152   -}
  1 +import { unref } from 'vue'
  2 +import { DataTypeEnum, OriginalDataTypeEnum } from '@/enums/objectModelEnum'
  3 +import type { FormSchema } from '@/components/Form'
  4 +import type { DataType, DeviceItemType, Specs, StructJSON, Tsl } from '@/api/device/model'
  5 +import { ComponentEnum } from '@/components/Form/src/enum'
  6 +import { TCPProtocolTypeEnum } from '@/enums/deviceEnum'
  7 +
  8 +export interface BasicCreateFormParams {
  9 + identifier: string
  10 + functionName: string
  11 + dataType: DataType
  12 +}
  13 +
  14 +const validateDouble = (value: number, min?: number | string, max?: number | string) => {
  15 + min = Number(min) ?? Number.MIN_SAFE_INTEGER
  16 + max = Number(max) ?? Number.MAX_SAFE_INTEGER
  17 +
  18 + return {
  19 + flag: value < min || value > max,
  20 + message: `取值范围在${min}~${max}之间`,
  21 + }
  22 +}
  23 +
  24 +export const useGenerateFormSchemasByObjectModel = () => {
  25 + const createInputNumber = ({
  26 + identifier,
  27 + functionName,
  28 + dataType,
  29 + }: BasicCreateFormParams): FormSchema => {
  30 + const { specs, type } = dataType
  31 + const { valueRange, step } = specs! as Partial<Specs>
  32 + const { max, min } = valueRange || {}
  33 + return {
  34 + field: identifier,
  35 + label: functionName,
  36 + component: 'InputNumber',
  37 + rules: [
  38 + {
  39 + type: 'number',
  40 + trigger: 'change',
  41 + validator: (_rule, value) => {
  42 + const { flag, message } = validateDouble(value, min, max)
  43 + if (flag)
  44 + return Promise.reject(Error(`${functionName}${message}`))
  45 +
  46 + return Promise.resolve(value)
  47 + },
  48 + },
  49 + ],
  50 + componentProps: {
  51 + max: max ?? Number.MAX_SAFE_INTEGER,
  52 + min: min ?? Number.MIN_SAFE_INTEGER,
  53 + step,
  54 + placeholder: `请输入${functionName}`,
  55 + precision: type === DataTypeEnum.NUMBER_INT ? 0 : 2,
  56 + },
  57 + } as FormSchema
  58 + }
  59 +
  60 + const createInput = ({
  61 + identifier,
  62 + functionName,
  63 + dataType,
  64 + }: BasicCreateFormParams): FormSchema => {
  65 + const { specs } = dataType
  66 + const { length = 10240 } = specs! as Partial<Specs>
  67 + return {
  68 + field: identifier,
  69 + label: functionName,
  70 + component: 'Input',
  71 + rules: [
  72 + {
  73 + type: 'string',
  74 + trigger: 'change',
  75 + validator: (_rule, value) => {
  76 + if (value?.length > length)
  77 + return Promise.reject(Error(`${functionName}数据长度应该小于${length}`))
  78 +
  79 + return Promise.resolve(value)
  80 + },
  81 + },
  82 + ],
  83 + componentProps: {
  84 + maxLength: length,
  85 + placeholder: `请输入${functionName}`,
  86 + },
  87 + } as FormSchema
  88 + }
  89 +
  90 + const createSelect = ({
  91 + identifier,
  92 + functionName,
  93 + dataType,
  94 + }: BasicCreateFormParams): FormSchema => {
  95 + const { specs } = dataType
  96 + const { boolClose, boolOpen } = specs! as Partial<Specs>
  97 + return {
  98 + field: identifier,
  99 + label: functionName,
  100 + component: ComponentEnum.SELECT,
  101 + componentProps: {
  102 + options: [
  103 + { label: `${boolClose}-0`, value: 0 },
  104 + { label: `${boolOpen}-1`, value: 1 },
  105 + ],
  106 + placeholder: `请选择${functionName}`,
  107 + getPopupContainer: () => document.body,
  108 + },
  109 + }
  110 + }
  111 +
  112 + const createEnumSelect = ({
  113 + identifier,
  114 + functionName,
  115 + dataType,
  116 + }: BasicCreateFormParams): FormSchema => {
  117 + const { specsList } = dataType
  118 + return {
  119 + field: identifier,
  120 + label: functionName,
  121 + component: ComponentEnum.SELECT,
  122 + componentProps: {
  123 + options: specsList?.map(item => ({ label: item.name, value: item.value })),
  124 + placeholder: `请选择${functionName}`,
  125 + getPopupContainer: () => document.body,
  126 + },
  127 + }
  128 + }
  129 +
  130 + const createModbusValueInput = (objectModel: Tsl): FormSchema => {
  131 + const { identifier, functionName, extensionDesc } = objectModel
  132 +
  133 + const { dataType } = objectModel.specs || {}
  134 + const { specs } = dataType || {}
  135 + const { valueRange } = specs as Specs
  136 + const { max, min } = valueRange || {}
  137 +
  138 + const isStringType = extensionDesc?.originalDataType === OriginalDataTypeEnum.STRING
  139 + return {
  140 + field: identifier,
  141 + label: functionName,
  142 + component: isStringType ? ComponentEnum.INPUT : ComponentEnum.INPUT_NUMBER,
  143 + rules: isStringType
  144 + ? []
  145 + : [
  146 + {
  147 + type: 'number',
  148 + validator: (_rule, value) => {
  149 + const { flag, message } = validateDouble(value, min, max)
  150 + if (flag)
  151 + return Promise.reject(Error(`${functionName}${message}`))
  152 +
  153 + return Promise.resolve(value)
  154 + },
  155 + },
  156 + ],
  157 + componentProps: {
  158 + max: max ?? Number.MAX_SAFE_INTEGER,
  159 + min: min ?? Number.MIN_SAFE_INTEGER,
  160 + placeholder: `请输入${functionName}`,
  161 + precision: 0,
  162 + },
  163 + }
  164 + }
  165 +
  166 + const schemaMethod: Partial<Record<DataTypeEnum, (...args: any[]) => FormSchema>> = {
  167 + [DataTypeEnum.BOOL]: createSelect,
  168 + [DataTypeEnum.NUMBER_DOUBLE]: createInputNumber,
  169 + [DataTypeEnum.NUMBER_INT]: createInputNumber,
  170 + [DataTypeEnum.STRING]: createInput,
  171 + [DataTypeEnum.ENUM]: createEnumSelect,
  172 + }
  173 +
  174 + const getFormByObjectModel = (
  175 + objectModel: Tsl,
  176 + deviceDetail: DeviceItemType,
  177 + ): FormSchema[] => {
  178 + const { functionName, identifier, specs } = objectModel
  179 +
  180 + const isTCPModbusProduct
  181 + = unref(deviceDetail).deviceProfile?.profileData?.transportConfiguration?.protocol
  182 + === TCPProtocolTypeEnum.MODBUS_RTU
  183 +
  184 + const { dataType } = specs
  185 + const { type } = dataType || {}
  186 +
  187 + if (isTCPModbusProduct)
  188 + return [createModbusValueInput(objectModel)]
  189 +
  190 + if (type === DataTypeEnum.STRUCT) {
  191 + return (dataType?.specs as StructJSON[]).map((item) => {
  192 + const { functionName, identifier, dataType } = item
  193 + const { type } = dataType || {}
  194 +
  195 + return schemaMethod[type!]?.({ identifier, functionName, dataType })
  196 + }) as FormSchema[]
  197 + }
  198 +
  199 + const result = schemaMethod[type!]?.({ identifier, functionName, dataType: dataType! })
  200 + return result ? [result] : []
  201 + }
  202 +
  203 + return { getFormByObjectModel }
  204 +}
... ...
1   -import { type GenModbusCommandType, genModbusCommand } from '@/api/device'
2   -import type { ExtensionDesc } from '@/api/device/model'
3   -import { ModbusCRCEnum } from '@/enums/commandEnum'
4   -import { TCPObjectModelActionTypeEnum } from '@/enums/objectModelEnum'
5   -import { useMessage } from '@/hooks/web/useMessage'
6   -
7   -export const InsertString = (t: any, c: any, n: any) => {
8   - const r: string | number[] = []
9   -
10   - for (let i = 0; i * 2 < t.length; i++)
11   - r.push(t.substr(i * 2, n))
12   -
13   - return r.join(c)
14   -}
15   -
16   -export const FillString = (t: any, c: any, n: any, b: any) => {
17   - if (t === '' || c.length !== 1 || n <= t.length)
18   - return t
19   -
20   - const l = t.length
21   -
22   - for (let i = 0; i < n - l; i++) {
23   - if (b === true)
24   - t = c + t
25   -
26   - else
27   - t += c
28   - }
29   - return t
30   -}
31   -
32   -export const SingleToHex = (t: any) => {
33   - if (t === '')
34   - return ''
35   -
36   - t = parseFloat(t)
37   -
38   - if (isNaN(t) === true)
39   - return 'Error'
40   -
41   - if (t === 0)
42   - return '00000000'
43   -
44   - let s, e, m
45   -
46   - if (t > 0) {
47   - s = 0
48   - }
49   - else {
50   - s = 1
51   -
52   - t = 0 - t
53   - }
54   - m = t.toString(2)
55   -
56   - if (m >= 1) {
57   - if (m.indexOf('.') === -1)
58   - m = `${m}.0`
59   -
60   - e = m.indexOf('.') - 1
61   - }
62   - else {
63   - e = 1 - m.indexOf('1')
64   - }
65   - if (e >= 0)
66   - m = m.replace('.', '')
67   -
68   - else
69   - m = m.substring(m.indexOf('1'))
70   -
71   - if (m.length > 24)
72   - m = m.substr(0, 24)
73   -
74   - else
75   - m = FillString(m, '0', 24, false)
76   -
77   - m = m.substring(1)
78   -
79   - e = (e + 127).toString(2)
80   -
81   - e = FillString(e, '0', 8, true)
82   -
83   - let r = parseInt(s + e + m, 2).toString(16)
84   -
85   - r = FillString(r, '0', 8, true)
86   -
87   - return InsertString(r, ' ', 2).toUpperCase()
88   -}
89   -
90   -export const FormatHex = (t: any, n: any, ie: any) => {
91   - const r: string[] = []
92   -
93   - let s = ''
94   -
95   - let c = 0
96   -
97   - for (let i = 0; i < t.length; i++) {
98   - if (t.charAt(i) !== ' ') {
99   - s += t.charAt(i)
100   -
101   - c += 1
102   -
103   - if (c === n) {
104   - r.push(s)
105   -
106   - s = ''
107   -
108   - c = 0
109   - }
110   - }
111   - if (ie === false) {
112   - if (i === t.length - 1 && s !== '')
113   - r.push(s)
114   - }
115   - }
116   - return r.join('\n')
117   -}
118   -
119   -export const FormatHexBatch = (t: any, n: any, ie: any) => {
120   - const a = t.split('\n')
121   -
122   - const r: string[] = []
123   -
124   - for (let i = 0; i < a.length; i++)
125   - r[i] = FormatHex(a[i], n, ie)
126   -
127   - return r.join('\n')
128   -}
129   -
130   -export const SingleToHexBatch = (t: any) => {
131   - const a = t.split('\n')
132   -
133   - const r: string[] = []
134   -
135   - for (let i = 0; i < a.length; i++)
136   - r[i] = SingleToHex(a[i])
137   -
138   - return r.join('\r\n')
139   -}
140   -
141   -const getArray = (values: any) => {
142   - const str = values.replace(/\s+/g, '')
143   - const array: any = []
144   -
145   - for (let i = 0; i < str.length; i += 4) {
146   - const chunk = parseInt(str.substring(i, i + 4), 16)
147   - array.push(chunk)
148   - }
149   - return array
150   -}
151   -
152   -const getFloatPart = (number: string | number) => {
153   - const isLessZero = Number(number) < 0
154   - number = number.toString()
155   - const floatPartStartIndex = number.indexOf('.')
156   - const value = ~floatPartStartIndex
157   - ? `${isLessZero ? '-' : ''}0.${number.substring(floatPartStartIndex + 1)}`
158   - : '0'
159   - return Number(value)
160   -}
161   -
162   -const REGISTER_MAX_VALUE = Number(0xffff)
163   -
164   -export function useGetModbusCommand() {
165   - const { createMessage } = useMessage()
166   -
167   - const getModbusCommand = async (value: number, extensionDesc: ExtensionDesc, deviceAddressCode: string) => {
168   - const { registerAddress, actionType, zoomFactor } = extensionDesc as Required<ExtensionDesc>
169   -
170   - const params: GenModbusCommandType = {
171   - crc: ModbusCRCEnum.CRC_16_LOWER,
172   - registerNumber: 1,
173   - deviceCode: deviceAddressCode,
174   - registerAddress,
175   - method: actionType,
176   - registerValues: [value],
177   - }
178   -
179   - if (actionType === TCPObjectModelActionTypeEnum.INT) {
180   - const newValue
181   - = Math.trunc(value) * zoomFactor
182   - + getFloatPart(value) * zoomFactor
183   -
184   - if (newValue % 1 !== 0) {
185   - createMessage.warning(`属性下发类型必须是整数,缩放因子为${zoomFactor}`)
186   - return
187   - }
188   -
189   - if (value * zoomFactor > REGISTER_MAX_VALUE) {
190   - createMessage.warning(`属性下发值不能超过${REGISTER_MAX_VALUE},缩放因子为${zoomFactor}`)
191   - return
192   - }
193   -
194   - params.registerValues = [newValue]
195   - }
196   - else if (actionType === TCPObjectModelActionTypeEnum.DOUBLE) {
197   - const regex = /^-?\d+(\.\d{0,2})?$/
198   - const values
199   - = Math.trunc(value) * zoomFactor
200   - + getFloatPart(value) * zoomFactor
201   -
202   - if (!regex.test(values.toString())) {
203   - createMessage.warning(`属性下发值精确到两位小数,缩放因子为${zoomFactor}`)
204   - return
205   - }
206   -
207   - const newValue
208   - = values === 0 ? [0, 0] : getArray(SingleToHex(values))
209   - params.registerValues = newValue
210   - params.registerNumber = 2
211   - params.method = '10'
212   - }
213   -
214   - return await genModbusCommand(params)
215   - }
216   -
217   - /**
218   - *
219   - * @param extensionDesc 物模型拓展描述符
220   - * @param deviceAddressCode 设备地址码
221   - */
222   - const validateCanGetCommand = (extensionDesc?: ExtensionDesc, deviceAddressCode?: string, createValidateMessage = true) => {
223   - const result = { flag: true, message: '' }
224   - if (!extensionDesc) {
225   - result.flag = false
226   - result.message = '当前物模型扩展描述没有填写'
227   - }
228   -
229   - if (!deviceAddressCode) {
230   - result.flag = false
231   - result.message = '当前设备未绑定设备地址码'
232   - }
233   -
234   - if (result.message && createValidateMessage)
235   - createMessage.warning(result.message)
236   -
237   - return result
238   - }
239   -
240   - return {
241   - getModbusCommand,
242   - validateCanGetCommand,
243   - }
244   -}
... ... @@ -6,7 +6,7 @@ import { JSONEditor } from '@/components/CodeEditor/src/JSONEditor'
6 6 import { BasicModal, useModalInner } from '@/components/Modal'
7 7 import { useMessage } from '@/hooks/web/useMessage'
8 8 import { useJsonParse } from '@/hooks/business/useJSONParse'
9   -import { TransportTypeEnum } from '@/enums/datasource'
  9 +import { TransportTypeEnum } from '@/enums/deviceEnum'
10 10
11 11 const emit = defineEmits(['register', 'editComplete'])
12 12
... ...
... ... @@ -2,8 +2,9 @@ import type { FormSchema } from '@/components/Form'
2 2 import { ComponentEnum } from '@/components/Form/src/enum'
3 3 import type { BasicColumn } from '@/components/Table'
4 4 import type { CommandWayEnum } from '@/enums/commandEnum'
5   -import type { CodeTypeEnum, ContentDataFieldsEnum, DeviceTypeEnum, TransportTypeEnum } from '@/enums/datasource'
  5 +import type { ContentDataFieldsEnum } from '@/enums/datasource'
6 6 import { EventActionTypeEnum, EventActionTypeNameEnum, EventTypeEnum, EventTypeNameEnum } from '@/enums/datasource'
  7 +import type { DeviceTypeEnum, TCPProtocolTypeEnum } from '@/enums/deviceEnum'
7 8
8 9 export enum TableColumnFieldEnum {
9 10 DEVICE_ID = 'deviceId',
... ... @@ -18,9 +19,8 @@ export interface TableRecordItemType {
18 19 [TableColumnFieldEnum.WAY]?: Nullable<CommandWayEnum>
19 20 [TableColumnFieldEnum.COMMAND]?: Nullable<string | Recordable>
20 21 [ContentDataFieldsEnum.DEVICE_TYPE]?: Nullable<DeviceTypeEnum>
21   - [ContentDataFieldsEnum.CODE_TYPE]?: Nullable<CodeTypeEnum>
  22 + [ContentDataFieldsEnum.CODE_TYPE]?: Nullable<TCPProtocolTypeEnum>
22 23 [ContentDataFieldsEnum.DEVICE_PROFILE_ID]: Nullable<string>
23   - [ContentDataFieldsEnum.TRANSPORT_TYPE]?: Nullable<TransportTypeEnum>
24 24 }
25 25
26 26 export const getFormSchemas = (event: EventTypeEnum): FormSchema[] => {
... ...
... ... @@ -4,13 +4,13 @@ import { usePublicFormContext } from '../../../usePublicFormContext'
4 4 import { getThingsModelServices } from '@/api/device'
5 5 import { type FormSchema } from '@/components/Form'
6 6 import { ComponentEnum } from '@/components/Form/src/enum'
7   -import { CommandWayEnum, CommandWayNameEnum } from '@/enums/commandEnum'
8   -import { CommandDeliveryWayEnum, CommandDeliveryWayNameEnum, DeviceTypeEnum, EventActionTypeEnum, EventActionTypeNameEnum, EventTypeEnum, EventTypeNameEnum, TransportTypeEnum } from '@/enums/datasource'
  7 +import { CommandDeliveryWayEnum, CommandDeliveryWayNameEnum, CommandWayEnum, CommandWayNameEnum } from '@/enums/commandEnum'
  8 +import { EventActionTypeEnum, EventActionTypeNameEnum, EventTypeEnum, EventTypeNameEnum } from '@/enums/datasource'
9 9 import type { ThingsModel } from '@/api/device/model'
10 10 import { PackageCategoryEnum } from '@/core/Library/enum'
11   -import { useContentDataStoreWithOut } from '@/store/modules/contentData'
12 11 import { validateTCPCustomCommand } from '@/core/Library/components/ThingsModelForm'
13 12 import { JSONEditorValidator } from '@/components/CodeEditor/src/JSONEditor'
  13 +import { DeviceTypeEnum, TCPProtocolTypeEnum, TransportTypeEnum } from '@/enums/deviceEnum'
14 14
15 15 export enum FormFieldsEnum {
16 16 ACTION_TYPE = 'actionType',
... ... @@ -35,16 +35,11 @@ export enum FormFieldsNameEnum {
35 35 SERVICE = '服务',
36 36 }
37 37
38   -const contentDataStore = useContentDataStoreWithOut()
39   -
40 38 export const getFormSchemas = (event: EventTypeEnum): FormSchema[] => {
41 39 const { getNodeData, getCellInfo, getDeviceInfo } = usePublicFormContext()
42 40 const { dataSourceJson } = unref(getNodeData) || {}
43 41 const { deviceProfileId } = dataSourceJson || {}
44   - // transportType:判断是什么类型的设备 code:设备地址码 deviceType:设备类型
45   - const { transportType, deviceType, codeType } = unref(getDeviceInfo) || {}
46   -
47   - const isTemplate = contentDataStore.isTemplate // 判断是否是模板
  42 + const { transportType } = unref(getDeviceInfo) || {}
48 43
49 44 return [
50 45 {
... ... @@ -115,39 +110,30 @@ export const getFormSchemas = (event: EventTypeEnum): FormSchema[] => {
115 110 component: ComponentEnum.SELECT,
116 111 required: true,
117 112 ifShow: ({ model }) => model[FormFieldsEnum.ACTION_TYPE] === EventActionTypeEnum.PARAMS_SETTING,
  113 + defaultValue: CommandDeliveryWayEnum.CUSTOM,
118 114 componentProps: ({ formActionType }) => {
119 115 function getOptions() {
120 116 const options = [
121 117 { label: CommandDeliveryWayNameEnum.CUSTOM, value: CommandDeliveryWayEnum.CUSTOM },
122 118 ]
123 119
124   - const serviceOption = { label: CommandDeliveryWayNameEnum.SERVICE, value: CommandDeliveryWayEnum.SERVICE }
125   - const modbusOption = { label: CommandDeliveryWayNameEnum.MODBUS, value: CommandDeliveryWayEnum.MODBUS }
126   -
127   - function setOptions() {
128   - // 是模板的话选择不到设备标识符类型所以就判断为放开自定义命令
129   - if (isTemplate) {
130   - if (transportType !== TransportTypeEnum.TCP)
131   - options.push(serviceOption)
132   - return
133   - }
134   -
135   - if (transportType !== TransportTypeEnum.TCP && deviceType === DeviceTypeEnum.SENSOR) {
136   - // 判断不是TCP但是是网关子
137   - options.push(serviceOption)
138   - return
139   - }
140   -
141   - if (deviceType !== DeviceTypeEnum.SENSOR)
142   - options.push(serviceOption)
  120 + const isTCPDevice = unref(getDeviceInfo)?.transportType === TransportTypeEnum.TCP
  121 + const isTCPModbusDevice = isTCPDevice && unref(getDeviceInfo)?.deviceProfile?.profileData?.transportConfiguration?.protocol === TCPProtocolTypeEnum.MODBUS_RTU
143 122
144   - if (transportType !== TransportTypeEnum.TCP) return
  123 + // TCP 自定义设备 自定义
  124 + if (isTCPDevice && !isTCPModbusDevice && unref(getDeviceInfo)?.deviceType === DeviceTypeEnum.SENSOR)
  125 + return options
145 126
146   - if (codeType !== CommandDeliveryWayEnum.CUSTOM)
147   - options.push(modbusOption)
  127 + // TCP Modbus设备 自定义&Modbus
  128 + if (isTCPModbusDevice) {
  129 + const modbusOption = { label: CommandDeliveryWayNameEnum.MODBUS, value: CommandDeliveryWayEnum.MODBUS }
  130 + options.push(modbusOption)
  131 + return options
148 132 }
149 133
150   - setOptions()
  134 + // 其他 自定义&服务
  135 + options.push({ label: CommandDeliveryWayNameEnum.SERVICE, value: CommandDeliveryWayEnum.SERVICE })
  136 +
151 137 return options
152 138 }
153 139
... ...
... ... @@ -3,11 +3,12 @@ import { getDeviceAttributes, getListByConfigurationId, getListByDeviceProfileId
3 3 import type { DeviceItemType, ThingsModelItemType } from '@/api/device/model'
4 4 import type { FormSchema } from '@/components/Form'
5 5 import { ComponentEnum } from '@/components/Form/src/enum'
6   -import { ContentDataFieldsEnum, ContentDataFieldsNameEnum, DataTypeEnum } from '@/enums/datasource'
  6 +import { ContentDataFieldsEnum, ContentDataFieldsNameEnum } from '@/enums/datasource'
7 7 import { useContentDataStoreWithOut } from '@/store/modules/contentData'
8 8 import type { ProductAndDevice } from '@/api/content/model'
9 9 import { ControlComponentEnum } from '@/core/Library/packages/Control'
10 10 import { useParseParams } from '@/core/LoadData'
  11 +import { DataTypeEnum } from '@/enums/objectModelEnum'
11 12
12 13 const contentDataStore = useContentDataStoreWithOut()
13 14 export const formSchemas = (componentKey?: string): FormSchema[] => {
... ... @@ -88,12 +89,13 @@ export const formSchemas = (componentKey?: string): FormSchema[] => {
88 89 showSearch: true,
89 90 api: async (params: string) => {
90 91 if (!deviceProfileId) return []
91   - const options = await getDeviceAttributes(params)
  92 + let options = await getDeviceAttributes(params)
92 93 if (componentKey === ControlComponentEnum.SWITCH) { // 开关只返回bool
93   - return options.filter((item) => {
  94 + options = options.filter((item) => {
94 95 return item.detail.dataType.type === DataTypeEnum.BOOL
95 96 })
96 97 }
  98 +
97 99 return options
98 100 },
99 101 params: deviceProfileId,
... ...
... ... @@ -2,7 +2,8 @@ import { validateTCPCustomCommand } from '.'
2 2 import type { Specs, StructJSON } from '@/api/device/model'
3 3 import { type FormSchema } from '@/components/Form'
4 4 import { ComponentEnum } from '@/components/Form/src/enum'
5   -import { DataTypeEnum, TransportTypeEnum } from '@/enums/datasource'
  5 +import { TransportTypeEnum } from '@/enums/deviceEnum'
  6 +import { DataTypeEnum } from '@/enums/objectModelEnum'
6 7
7 8 export enum FormFieldsEnum {
8 9 SERVICE_COMMAND = 'serviceCommand',
... ...
... ... @@ -5,9 +5,9 @@ import { getFormSchemas } from './config'
5 5 import { ThingsModelForm } from '.'
6 6 import type { StructJSON } from '@/api/device/model'
7 7 import { BasicForm, useForm } from '@/components/Form'
8   -import { DataTypeEnum } from '@/enums/datasource'
9 8 import { FormLabelAlignEnum, FormLayoutEnum } from '@/components/Form/src/enum'
10 9 import { deepMerge } from '@/utils'
  10 +import { DataTypeEnum } from '@/enums/objectModelEnum'
11 11
12 12 interface ThingsModelFormPropsType {
13 13 value?: Recordable
... ...
... ... @@ -67,7 +67,7 @@ export function useNodeData({ cell, immediate = true }: UseNodeDataParamsType) {
67 67 })
68 68
69 69 const getDeviceDetail = async () => {
70   - if (!unref(nodeData)?.dataSourceJson.deviceId) return
  70 + if (!unref(nodeData)?.dataSourceJson?.deviceId) return
71 71 deviceInfo.value = await getDeviceInfo(unref(nodeData)!.dataSourceJson.deviceId)
72 72 }
73 73
... ...
... ... @@ -2,8 +2,8 @@ import { h, render, toRaw, unref } from 'vue'
2 2 import { ControlComponentEnum } from '../packages/Control'
3 3 import { doCommandDelivery, getDeviceActive } from '@/api/device'
4 4 import type { MouseUpEventDataType, NodeDataDataSourceJsonType, NodeDataEventJsonType, SingleClickEventDataType } from '@/api/node/model'
5   -import { CommandMethodEnum, CommandWayEnum } from '@/enums/commandEnum'
6   -import { ActRangListItemTypeEnum, CommandTypeEnum, EventActionTypeEnum } from '@/enums/datasource'
  5 +import { CommandMethodEnum, CommandTypeEnum, CommandWayEnum } from '@/enums/commandEnum'
  6 +import { ActRangListItemTypeEnum, EventActionTypeEnum } from '@/enums/datasource'
7 7 import { useMessage } from '@/hooks/web/useMessage'
8 8 import { AttributeDeliverModal, CommandDeliveryModal, SwitchCommandDeliveryModal } from '@/core/Library/components/PublicForm/components/DataEvents/CommandDeliveryModal'
9 9 import type { MxCell } from '@/fitCore/types'
... ...
... ... @@ -8,8 +8,10 @@ import { ComponentEnum } from '@/components/Form/src/enum'
8 8 import { useContentDataStoreWithOut } from '@/store/modules/contentData'
9 9 import { DateFormatEnum } from '@/enums/timeEnum'
10 10 import type { BasicColumn } from '@/components/Table'
11   -import { AlarmStatusEnum, type CodeTypeEnum, type ContentDataFieldsEnum } from '@/enums/datasource'
  11 +import { type ContentDataFieldsEnum } from '@/enums/datasource'
12 12 import { useParseParams } from '@/core/LoadData'
  13 +import type { TCPProtocolTypeEnum } from '@/enums/deviceEnum'
  14 +import { AlarmStatusEnum } from '@/enums/alarmEnum'
13 15
14 16 export const tableColumns: BasicColumn[] = [
15 17 {
... ... @@ -27,7 +29,7 @@ export const tableColumns: BasicColumn[] = [
27 29 export interface TableRecordItemType {
28 30 uuid: string
29 31 [TableColumnFieldEnum.DEVICE_ID]?: Nullable<string>
30   - [ContentDataFieldsEnum.CODE_TYPE]?: Nullable<CodeTypeEnum>
  32 + [ContentDataFieldsEnum.CODE_TYPE]?: Nullable<TCPProtocolTypeEnum>
31 33 }
32 34
33 35 export const AlarmColorMap = {
... ...
... ... @@ -8,8 +8,8 @@ import type { AlarmListItemType } from '@/api/alarm/model'
8 8 import type { CreateComponentType } from '@/core/Library/types'
9 9 import { useContentDataStore } from '@/store/modules/contentData'
10 10 import { isLightboxMode, isShareMode } from '@/utils/env'
11   -import { AlarmStatusColorEnum, AlarmStatusEnum, AlarmStatusNameEnum } from '@/enums/datasource'
12 11 import { formatToDateTime } from '@/utils/dateUtil'
  12 +import { AlarmStatusColorEnum, AlarmStatusNameEnum } from '@/enums/alarmEnum'
13 13
14 14 const props = defineProps<{
15 15 config: CreateComponentType
... ... @@ -67,7 +67,7 @@ onMounted(async () => {
67 67 useIntervalFn(initFetchAlarmList, (initOptions.polling || 30) * 1000)
68 68 }
69 69 else {
70   - const statusList = Object.values(AlarmStatusEnum)
  70 + const statusList = Object.values(AlarmStatusNameEnum)
71 71 Object.assign(initOptions, {
72 72 scroll: false,
73 73 interval: 0,
... ...
... ... @@ -75,6 +75,15 @@ const init = () => {
75 75 videoPlayInstance.value = videoJs(unref(videoPlayEl)!, unref(getOptions), () => {
76 76 emit('ready', unref(videoPlayInstance))
77 77 })
  78 +
  79 + unref(videoPlayInstance)?.on('timeupdate', () => {
  80 + if (!unref(videoPlayInstance)) return
  81 +
  82 + const differTime = unref(videoPlayInstance)!.buffered().end(0) - unref(videoPlayInstance)!.currentTime()
  83 +
  84 + if (differTime > 10)
  85 + init()
  86 + })
78 87 }
79 88
80 89 const customInit = (getOptionsFn: (optios: VideoJsPlayerOptions) => VideoJsPlayerOptions) => {
... ...
... ... @@ -3,11 +3,12 @@ import { getDeviceAttributes, getListByConfigurationId, getListByDeviceProfileId
3 3 import type { DeviceItemType, ThingsModelItemType } from '@/api/device/model'
4 4 import type { FormSchema } from '@/components/Form'
5 5 import { ComponentEnum } from '@/components/Form/src/enum'
6   -import { ContentDataFieldsEnum, ContentDataFieldsNameEnum, DataTypeEnum } from '@/enums/datasource'
  6 +import { ContentDataFieldsEnum, ContentDataFieldsNameEnum } from '@/enums/datasource'
7 7 import { useContentDataStoreWithOut } from '@/store/modules/contentData'
8 8 import type { ProductAndDevice } from '@/api/content/model'
9 9 import { ControlComponentEnum } from '@/core/Library/packages/Control'
10 10 import { useParseParams } from '@/core/LoadData'
  11 +import { DataTypeEnum } from '@/enums/objectModelEnum'
11 12
12 13 const contentDataStore = useContentDataStoreWithOut()
13 14 export const formSchemas = (componentKey?: string): FormSchema[] => {
... ...
... ... @@ -84,11 +84,13 @@ export class DataDynamicEffectHandler {
84 84 if (flag) {
85 85 const nodeEl = this.nodeUtils.getNodesForCells([cell])
86 86 const { type } = record!
  87 +
87 88 if (type === ActRangListItemTypeEnum.SHOW) {
88 89 nodeEl.forEach((node) => {
89 90 node.classList.add(ActAnimationName.VISIBLE)
90 91 node.classList.remove(ActAnimationName.HIDDEN)
91 92 })
  93 + this.nodeUtils.updateCellValue(cell, record?.title ? record.title : latestValue)
92 94 }
93 95 else if (type === ActRangListItemTypeEnum.HIDDEN) {
94 96 nodeEl.forEach((node) => {
... ...
... ... @@ -55,9 +55,9 @@ export class MessageHandler {
55 55 const { data, node } = commandSource
56 56 const { attr, deviceProfileId } = data as NodeDataDataSourceJsonType
57 57
58   - let { latestValue } = useLatestMessageValue(message.data, attr)
  58 + let { latestValue } = useLatestMessageValue(message.data, attr as string)
59 59 if (isNull(latestValue)) return
60   - latestValue = useObjectModelValue(deviceProfileId, attr, latestValue)
  60 + latestValue = useObjectModelValue(deviceProfileId, attr as string, latestValue)
61 61 const cell = this.nodeUtils.getCellById(node)
62 62
63 63 const cellValue = cell.getValue() as Element
... ...
1 1 import type { DataType, Specs, StructJSON } from '@/api/device/model'
2   -import { DataTypeEnum } from '@/enums/datasource'
  2 +import { TransportTypeEnum } from '@/enums/deviceEnum'
  3 +import { DataTypeEnum } from '@/enums/objectModelEnum'
3 4 import { useJsonParse } from '@/hooks/business/useJSONParse'
4 5 import { useProductsStoreWithOut } from '@/store/modules/products'
5 6
... ... @@ -45,6 +46,13 @@ function getValueByType(type: string, value: any, dataType: DataType) {
45 46 }
46 47 }
47 48
  49 +function getModbusDeviceValueByType(value: any, dataType: DataType) {
  50 + if (dataType.type === DataTypeEnum.BOOL && dataType.specsList && dataType.specsList.length)
  51 + return getEnumTypeValue(value, dataType.specsList as Specs[])
  52 +
  53 + return value
  54 +}
  55 +
48 56 export function useObjectModelValue(deviceProfileId: string, attr: string, value: any) {
49 57 const productsStore = useProductsStoreWithOut()
50 58
... ... @@ -52,5 +60,8 @@ export function useObjectModelValue(deviceProfileId: string, attr: string, value
52 60 if (!result)
53 61 return value
54 62
55   - return getValueByType(result.specs.dataType.type as DataTypeEnum, value, result.specs.dataType)
  63 + const products = productsStore.getProductDetailById(deviceProfileId)
  64 + const isTCPModbus = products.transportType === TransportTypeEnum.TCP && result.extensionDesc?.originalDataType
  65 +
  66 + return isTCPModbus ? getModbusDeviceValueByType(value, result.specs.dataType) : getValueByType(result.specs.dataType.type as DataTypeEnum, value, result.specs.dataType)
56 67 }
... ...
  1 +export enum AlarmStatusEnum {
  2 + CLEARED_UN_ACK = 'CLEARED_UNACK',
  3 + ACTIVE_UN_ACK = 'ACTIVE_UNACK',
  4 + CLEARED_ACK = 'CLEARED_ACK',
  5 + ACTIVE_ACK = 'ACTIVE_ACK',
  6 +}
  7 +
  8 +export enum AlarmStatusColorEnum {
  9 + CLEARED_UNACK = 'red',
  10 + ACTIVE_UNACK = 'orange',
  11 + CLEARED_ACK = 'cyan',
  12 + ACTIVE_ACK = 'green',
  13 +}
  14 +
  15 +export enum AlarmStatusNameEnum {
  16 + CLEARED_UNACK = '清除未确认',
  17 + ACTIVE_UNACK = '激活未确认',
  18 + CLEARED_ACK = '清除已确认',
  19 + ACTIVE_ACK = '激活已确认',
  20 +}
... ...
... ... @@ -13,6 +13,31 @@ export enum CommandCallWayEnum {
13 13 ASYNC = 'ASYNC',
14 14 }
15 15
  16 +export enum CommandTypeEnum {
  17 + CUSTOM = 0,
  18 + SERVICE = 1,
  19 + ATTRIBUTE = 2,
  20 + API = 'api',
  21 +}
  22 +
  23 +export enum CommandTypeNameEnum {
  24 + CUSTOM = '自定义',
  25 + SERVICE = '服务',
  26 + ATTRIBUTE = '属性',
  27 +}
  28 +
  29 +export enum CommandDeliveryWayEnum {
  30 + CUSTOM = 'CUSTOM',
  31 + SERVICE = 'SERVICE',
  32 + MODBUS = 'MODBUS',
  33 +}
  34 +
  35 +export enum CommandDeliveryWayNameEnum {
  36 + CUSTOM = '自定义命令',
  37 + SERVICE = '服务调用',
  38 + MODBUS = 'MODBUS',
  39 +}
  40 +
16 41 export enum CommandMethodEnum {
17 42 THINGSKIT = 'methodThingskit',
18 43 }
... ...
... ... @@ -64,50 +64,6 @@ export enum ActTypeNameEnum {
64 64 VARIABLE_IMAGE = '变量图片',
65 65 }
66 66
67   -export enum DeviceTypeEnum {
68   - /**
69   - * @description 网关设备
70   - */
71   - GATEWAY = 'GATEWAY',
72   -
73   - /**
74   - * @description 直连设备
75   - */
76   - DIRECT_CONNECTION = 'DIRECT_CONNECTION',
77   -
78   - /**
79   - * @description 网关子设备
80   - */
81   - SENSOR = 'SENSOR',
82   -}
83   -
84   -export enum DeviceTypeNameEnum {
85   - /**
86   - * @description 网关设备
87   - */
88   - GATEWAY = '网关设备',
89   -
90   - /**
91   - * @description 直连设备
92   - */
93   - DIRECT_CONNECTION = '直连设备',
94   -
95   - /**
96   - * @description 网关子设备
97   - */
98   - SENSOR = '网关子设备',
99   -}
100   -
101   -export enum TransportTypeEnum {
102   - MQTT = 'MQTT',
103   - TCP = 'TCP',
104   -}
105   -
106   -export enum CodeTypeEnum {
107   - CUSTOM = 'CUSTOM',
108   - MODBUS_RTU = 'MODBUS_RTU',
109   -}
110   -
111 67 export enum EventTypeEnum {
112 68 /**
113 69 * @description 鼠标抬起
... ... @@ -271,46 +227,6 @@ export enum VariableImageSourceNameEnum {
271 227 GALLERY = '图库图形',
272 228 }
273 229
274   -export enum CommandDeliveryWayEnum {
275   - CUSTOM = 'CUSTOM',
276   - SERVICE = 'SERVICE',
277   - MODBUS = 'MODBUS',
278   -}
279   -
280   -export enum CommandDeliveryWayNameEnum {
281   - CUSTOM = '自定义命令',
282   - SERVICE = '服务调用',
283   - MODBUS = 'MODBUS',
284   -}
285   -
286   -export enum FunctionTypeEnum {
287   - PROPERTIES = 'properties',
288   - EVENTS = 'events',
289   - SERVICE = 'services',
290   -}
291   -
292   -export enum AssessMode {
293   - READ = 'r',
294   - WRITE = 'w',
295   -}
296   -
297   -export enum CommandTypeEnum {
298   - SERVICE = 1,
299   - API = 'API',
300   -}
301   -
302   -/**
303   - * 新增参数 动态显示表单
304   - */
305   -export enum DataTypeEnum {
306   - NUMBER_INT = 'INT',
307   - NUMBER_DOUBLE = 'DOUBLE',
308   - STRING = 'TEXT',
309   - STRUCT = 'STRUCT',
310   - BOOL = 'BOOL',
311   - ENUM = 'ENUM',
312   -}
313   -
314 230 export enum AggregateTypeEnum {
315 231 MIN = 'MIN',
316 232 MAX = 'MAX',
... ... @@ -333,24 +249,3 @@ export enum SocketSubscriberEnum {
333 249 HISTORY_CMDS = 'historyCmds',
334 250 }
335 251
336   -export enum AlarmStatusEnum {
337   - CLEARED_UN_ACK = 'CLEARED_UNACK',
338   - ACTIVE_UN_ACK = 'ACTIVE_UNACK',
339   - CLEARED_ACK = 'CLEARED_ACK',
340   - ACTIVE_ACK = 'ACTIVE_ACK',
341   -}
342   -
343   -export enum AlarmStatusColorEnum {
344   - CLEARED_UNACK = 'red',
345   - ACTIVE_UNACK = 'orange',
346   - CLEARED_ACK = 'cyan',
347   - ACTIVE_ACK = 'green',
348   -}
349   -
350   -export enum AlarmStatusNameEnum {
351   - CLEARED_UNACK = '清除未确认',
352   - ACTIVE_UNACK = '激活未确认',
353   - CLEARED_ACK = '清除已确认',
354   - ACTIVE_ACK = '激活已确认',
355   -}
356   -
... ...
  1 +export enum DeviceTypeEnum {
  2 + /**
  3 + * @description 网关设备
  4 + */
  5 + GATEWAY = 'GATEWAY',
  6 +
  7 + /**
  8 + * @description 直连设备
  9 + */
  10 + DIRECT_CONNECTION = 'DIRECT_CONNECTION',
  11 +
  12 + /**
  13 + * @description 网关子设备
  14 + */
  15 + SENSOR = 'SENSOR',
  16 +}
  17 +
  18 +export enum DeviceTypeNameEnum {
  19 + /**
  20 + * @description 网关设备
  21 + */
  22 + GATEWAY = '网关设备',
  23 +
  24 + /**
  25 + * @description 直连设备
  26 + */
  27 + DIRECT_CONNECTION = '直连设备',
  28 +
  29 + /**
  30 + * @description 网关子设备
  31 + */
  32 + SENSOR = '网关子设备',
  33 +}
  34 +
  35 +export enum TransportTypeEnum {
  36 + DEFAULT = 'DEFAULT',
  37 + MQTT = 'MQTT',
  38 + COAP = 'COAP',
  39 + LWM2M = 'LWM2M',
  40 + SNMP = 'SNMP',
  41 + TCP = 'TCP',
  42 +}
  43 +
  44 +export enum TCPProtocolTypeEnum {
  45 + CUSTOM = 'CUSTOM',
  46 + MODBUS_RTU = 'MODBUS_RTU',
  47 +}
  48 +
  49 +export enum TCPProtocolTypeNameEnum {
  50 + CUSTOM = '自定义',
  51 + MODBUS_RTU = 'MODBUS_RTU',
  52 +}
... ...
1 1 /**
2   - * @description TCP物模型拓展描述符数据格式
  2 + * 新增参数 动态显示表单
3 3 */
4   -export enum TCPObjectModelActionTypeEnum {
5   - BOOL = '05',
6   - INT = '06',
7   - DOUBLE = '16',
  4 +export enum DataTypeEnum {
  5 + NUMBER_INT = 'INT',
  6 + NUMBER_DOUBLE = 'DOUBLE',
  7 + STRING = 'TEXT',
  8 + STRUCT = 'STRUCT',
  9 + BOOL = 'BOOL',
  10 + ENUM = 'ENUM',
  11 +}
  12 +
  13 +export enum FunctionTypeEnum {
  14 + PROPERTIES = 'properties',
  15 + EVENTS = 'events',
  16 + SERVICE = 'services',
  17 +}
  18 +
  19 +export enum ObjectModelAccessModeEnum {
  20 + READ = 'r',
  21 + READ_AND_WRITE = 'rw',
  22 +}
  23 +
  24 +export enum OriginalDataTypeEnum {
  25 + INT16_AB = 'INT16_AB',
  26 + INT16_BA = 'INT16_BA',
  27 + UINT16_AB = 'UINT16_AB',
  28 + UINT16_BA = 'UINT16_BA',
  29 + INT32_AB_CD = 'INT32_AB_CD',
  30 + INT32_CD_AB = 'INT32_CD_AB',
  31 + INT32_BA_DC = 'INT32_BA_DC',
  32 + INT32_DC_BA = 'INT32_DC_BA',
  33 + UINT32_AB_CD = 'UINT32_AB_CD',
  34 + UINT32_CD_AB = 'UINT32_CD_AB',
  35 + UINT32_BA_DC = 'UINT32_BA_DC',
  36 + UINT32_DC_BA = 'UINT32_DC_BA',
  37 + FLOAT_AB_CD = 'FLOAT_AB_CD',
  38 + FLOAT_CD_AB = 'FLOAT_CD_AB',
  39 + FLOAT_BA_DC = 'FLOAT_BA_DC',
  40 + FLOAT_DC_BA = 'FLOAT_DC_BA',
  41 + DOUBLE = 'DOUBLE',
  42 + STRING = 'STRING',
  43 + BOOLEAN = 'BOOLEAN',
  44 + BITS = 'BITS',
  45 +}
  46 +
  47 +export enum OriginalDataTypeNameEnum {
  48 + INT16_AB = '16位有符号整数AB',
  49 + INT16_BA = '16位有符号整数BA',
  50 + UINT16_AB = '16位无符号整数AB',
  51 + UINT16_BA = '16位无符号整数BA',
  52 + INT32_AB_CD = '32位有符号整数AB_CD',
  53 + INT32_CD_AB = '32位有符号整数CD_AB',
  54 + INT32_BA_DC = '32位有符号整数BA_DC',
  55 + INT32_DC_BA = '32位有符号整数DC_BA',
  56 + UINT32_AB_CD = '32位无符号整数AB_CD',
  57 + UINT32_CD_AB = '32位无符号整数CD_AB',
  58 + UINT32_BA_DC = '32位无符号整数BA_DC',
  59 + UINT32_DC_BA = '32位无符号整数DC_BA',
  60 + FLOAT_AB_CD = '单精度浮点型AB_CD',
  61 + FLOAT_CD_AB = '单精度浮点型CD_AB',
  62 + FLOAT_BA_DC = '单精度浮点型BA_DC',
  63 + FLOAT_DC_BA = '单精度浮点型DC_BA',
  64 + DOUBLE = '双精度浮点型',
  65 + STRING = '字符串',
  66 + BOOLEAN = '布尔型',
  67 + BITS = '位',
  68 +}
  69 +
  70 +export enum ExtendDescOperationTypeEnum {
  71 + INPUT_STATUS_R_02 = 'inputStatus_r_02',
  72 + COIL_STATUS_R_01 = 'coilStatus_r_01',
  73 + COIL_STATUS_RW_01_05 = 'coilStatus_rw_01_05',
  74 + COIL_STATUS_RW_01_0F = 'coilStatus_rw_01_0F',
  75 + COIL_STATUS_W_05 = 'coilStatus_w_05',
  76 + COIL_STATUS_W_0F = 'coilStatus_w_0F',
  77 + HOLDING_REGISTER_R_03 = 'holdingRegister_r_03',
  78 + HOLDING_REGISTER_RW_03_06 = 'holdingRegister_rw_03_06',
  79 + HOLDING_REGISTER_RW_03_10 = 'holdingRegister_rw_03_10',
  80 + HOLDING_REGISTER_W_06 = 'holdingRegister_w_06',
  81 + HOLDING_REGISTER_W_10 = 'holdingRegister_w_10',
  82 + INPUT_REGISTER_R_04 = 'inputRegister_r_04',
  83 +}
  84 +
  85 +export enum ExtendDescOperationTypeNameEnum {
  86 + INPUT_STATUS_R_02 = '离散量输入(只读,0x02)',
  87 + COIL_STATUS_R_01 = '线圈状态(只读,0x01)',
  88 + COIL_STATUS_RW_01_05 = '线圈状态(读写,读取使用0x01,写入使用0x05)',
  89 + COIL_STATUS_RW_01_0F = '线圈状态(读写,读取使用0x01,写入使用0x0F)',
  90 + COIL_STATUS_W_05 = '线圈状态(只写,0x05)',
  91 + COIL_STATUS_W_0F = '线圈状态(只写,0x0F)',
  92 + HOLDING_REGISTER_R_03 = '保持寄存器(只读,0x03)',
  93 + HOLDING_REGISTER_RW_03_06 = '保持寄存器(读写,读取使用0x03,写入使用0x06)',
  94 + HOLDING_REGISTER_RW_03_10 = '保持寄存器(读写,读取使用0x03,写入使用0x10)',
  95 + HOLDING_REGISTER_W_06 = '保持寄存器(只写,0x06)',
  96 + HOLDING_REGISTER_W_10 = '保持寄存器(只写,0x10)',
  97 + INPUT_REGISTER_R_04 = '输入寄存器(只读,0x04)',
8 98 }
... ...
  1 +import { OriginalDataTypeEnum } from '@/enums/objectModelEnum'
  2 +import { useParseOriginalDataType } from '@/hooks/business/useParseOriginalDataType'
  3 +
  4 +export function useBaseConversion() {
  5 + function DecTo32Float(number: number) {
  6 + const arr = new Uint8Array(4)
  7 + const view = new DataView(arr.buffer)
  8 + view.setFloat32(0, +number)
  9 + return arr
  10 + }
  11 +
  12 + function DecTo64Double(number: number) {
  13 + const arr = new Uint8Array(8)
  14 + const view = new DataView(arr.buffer)
  15 + view.setFloat64(0, +number)
  16 + return arr
  17 + }
  18 +
  19 + function arrToBase(toBase: number, arr: Uint8Array) {
  20 + let result = ''
  21 + for (let i = 0; i < arr.length; i++)
  22 + result += (256 + arr[i]).toString(toBase).substring(1).toUpperCase()
  23 +
  24 + return result
  25 + }
  26 +
  27 + function DecTo16Uint(number: number) {
  28 + const arr = new Uint8Array(2)
  29 + const view = new DataView(arr.buffer)
  30 + view.setUint16(0, +number)
  31 + return arr
  32 + }
  33 +
  34 + function DecTo16Int(number: number) {
  35 + const arr = new Uint8Array(2)
  36 + const view = new DataView(arr.buffer)
  37 + view.setInt16(0, +number)
  38 + return arr
  39 + }
  40 +
  41 + function DecTo32Uint(number: number) {
  42 + const arr = new Uint8Array(4)
  43 + const view = new DataView(arr.buffer)
  44 + view.setUint32(0, +number)
  45 + return arr
  46 + }
  47 +
  48 + function DecTo32Int(number: number) {
  49 + const arr = new Uint8Array(4)
  50 + const view = new DataView(arr.buffer)
  51 + view.setInt32(0, +number)
  52 + return arr
  53 + }
  54 +
  55 + function DecToBinaryByType(type: OriginalDataTypeEnum, number: number) {
  56 + switch (type) {
  57 + case OriginalDataTypeEnum.INT16_AB:
  58 + case OriginalDataTypeEnum.INT16_BA:
  59 + return arrToBase(2, DecTo16Int(number))
  60 +
  61 + case OriginalDataTypeEnum.UINT16_AB:
  62 + case OriginalDataTypeEnum.UINT16_BA:
  63 + return arrToBase(2, DecTo16Uint(number))
  64 +
  65 + case OriginalDataTypeEnum.INT32_AB_CD:
  66 + case OriginalDataTypeEnum.INT32_CD_AB:
  67 + case OriginalDataTypeEnum.INT32_BA_DC:
  68 + case OriginalDataTypeEnum.INT32_DC_BA:
  69 + return arrToBase(2, DecTo32Int(number))
  70 +
  71 + case OriginalDataTypeEnum.UINT32_AB_CD:
  72 + case OriginalDataTypeEnum.UINT32_CD_AB:
  73 + case OriginalDataTypeEnum.UINT32_BA_DC:
  74 + case OriginalDataTypeEnum.UINT32_DC_BA:
  75 + return arrToBase(2, DecTo32Uint(number))
  76 +
  77 + case OriginalDataTypeEnum.FLOAT_AB_CD:
  78 + case OriginalDataTypeEnum.FLOAT_CD_AB:
  79 + case OriginalDataTypeEnum.FLOAT_BA_DC:
  80 + case OriginalDataTypeEnum.FLOAT_DC_BA:
  81 + return arrToBase(2, DecTo32Float(number))
  82 +
  83 + case OriginalDataTypeEnum.DOUBLE:
  84 + return arrToBase(2, DecTo64Double(number))
  85 + }
  86 + }
  87 +
  88 + function SplitStringToGroupByItemLength(
  89 + value: string,
  90 + itemLength = 8,
  91 + ignoreLessThan = false,
  92 + ): string[] {
  93 + const reg = new RegExp(`.{${ignoreLessThan ? '' : '1,'}${itemLength}}`, 'g')
  94 + return value.match(reg) || []
  95 + }
  96 +
  97 + function ExchangeByteOrder(binary: string, order: string) {
  98 + const group = SplitStringToGroupByItemLength(binary)
  99 +
  100 + const BASE_ORDER = {
  101 + A: 0,
  102 + B: 1,
  103 + C: 2,
  104 + D: 3,
  105 + }
  106 +
  107 + const array: string[] = Array.from({ length: binary.length / 8 })
  108 +
  109 + order
  110 + .split('')
  111 + .forEach((bytePosition, index) => (array[index] = group[BASE_ORDER[bytePosition as keyof typeof BASE_ORDER] as number]))
  112 +
  113 + return array.join('')
  114 + }
  115 +
  116 + function ByteToHex(binary: string) {
  117 + const group = SplitStringToGroupByItemLength(binary, 16)
  118 + return group.map(byte => parseInt(byte, 2).toString(16))
  119 + }
  120 +
  121 + function ByteToDec(binary: string) {
  122 + const group = SplitStringToGroupByItemLength(binary, 16)
  123 + return group.map(byte => parseInt(byte, 2))
  124 + }
  125 +
  126 + function StringToHEXBuffer(string: string | number) {
  127 + return string
  128 + .toString()
  129 + .split('')
  130 + .map(string => string.charCodeAt(0).toString(16))
  131 + .reverse()
  132 + }
  133 +
  134 + function getRegisterValueByOriginalDataType(
  135 + value: number,
  136 + type: OriginalDataTypeEnum,
  137 + additional?: { bitMask?: number; registerNumber?: number },
  138 + ) {
  139 + const { exchangeSortFlag } = useParseOriginalDataType(type)
  140 + // eslint-disable-next-line no-console
  141 + console.groupCollapsed('Modbus Debug')
  142 +
  143 + let result: number[]
  144 +
  145 + if (type === OriginalDataTypeEnum.BOOLEAN) {
  146 + result = [value]
  147 + }
  148 + else if (type === OriginalDataTypeEnum.BITS) {
  149 + const { bitMask = 0 } = additional || {}
  150 + const binaryArray = Array.from({ length: 16 }, () => 0)
  151 + binaryArray[15 - bitMask] = value
  152 + result = [parseInt(binaryArray.join(''), 2)]
  153 + }
  154 + else if (type === OriginalDataTypeEnum.STRING) {
  155 + let buffer = StringToHEXBuffer(value)
  156 + const { registerNumber = 0 } = additional || {}
  157 +
  158 + if (buffer.length < registerNumber * 2) {
  159 + buffer = [
  160 + ...Array.from({ length: registerNumber * 2 - buffer.length }, () => '00'),
  161 + ...buffer,
  162 + ]
  163 + }
  164 +
  165 + result = SplitStringToGroupByItemLength(buffer.join(''), 4).map(hex => parseInt(hex, 16))
  166 + }
  167 + else {
  168 + // eslint-disable-next-line no-console
  169 + console.table({ input: value, sort: exchangeSortFlag, type })
  170 +
  171 + let binary = DecToBinaryByType(type, value)!
  172 +
  173 + // eslint-disable-next-line no-console
  174 + console.table({ beforeExchange: binary })
  175 +
  176 + if (exchangeSortFlag) binary = ExchangeByteOrder(binary, exchangeSortFlag)
  177 + result = ByteToDec(binary)
  178 +
  179 + // eslint-disable-next-line no-console
  180 + console.table({
  181 + afterEchange: binary,
  182 + dec: result.toString(),
  183 + hex: ByteToHex(binary).toString(),
  184 + })
  185 + }
  186 +
  187 + // eslint-disable-next-line no-console
  188 + console.groupEnd()
  189 +
  190 + return result
  191 + }
  192 +
  193 + return {
  194 + DecToBinaryByType,
  195 + ByteToDec,
  196 + ByteToHex,
  197 + ExchangeByteOrder,
  198 + SplitStringToGroupByItemLength,
  199 + getRegisterValueByOriginalDataType,
  200 + StringToHEXBuffer,
  201 + }
  202 +}
... ...
  1 +import { ref } from 'vue'
  2 +import { isFunction } from '@wry-smile/utils-is'
  3 +import { useMessage } from '../web/useMessage'
  4 +import { useCoverModbusCommand } from './useCoverModbusCommand'
  5 +import {
  6 + TCPProtocolTypeEnum,
  7 + TransportTypeEnum,
  8 +} from '@/enums/deviceEnum'
  9 +import { FunctionTypeEnum } from '@/enums/objectModelEnum'
  10 +import { CommandCallWayEnum, CommandMethodEnum, CommandTypeEnum, CommandWayEnum } from '@/enums/commandEnum'
  11 +import type { DeviceItemType, DeviceProfileItemType, RpcCommandType, Tsl } from '@/api/device/model'
  12 +import { getDeviceActive, getDeviceInfo, doCommandDelivery as rpcCommandApi } from '@/api/device'
  13 +import { useProductsStoreWithOut } from '@/store/modules/products'
  14 +
  15 +interface SetupType {
  16 + entityId: string
  17 + transportType: TransportTypeEnum
  18 + isTCPModbus: boolean
  19 + deviceCode: string | undefined
  20 + deviceDetail: DeviceItemType
  21 + objectModel: Tsl | undefined
  22 + identifier: string
  23 +}
  24 +
  25 +export interface DoCommandDeliverParamsType {
  26 + value: any
  27 + deviceDetail?: DeviceItemType
  28 + deviceId?: string
  29 + identifier?: string
  30 + objectModel?: Tsl
  31 + deviceProfileId?: string
  32 + deviceProfileDetail?: DeviceProfileItemType
  33 + way?: CommandWayEnum
  34 + cmdType?: CommandTypeEnum
  35 + transportType?: TransportTypeEnum
  36 + penetration?: boolean
  37 +
  38 + beforeFetch?: (
  39 + rpcCommand: RpcCommandType,
  40 + setup: SetupType
  41 + ) => RpcCommandType | Promise<RpcCommandType>
  42 +}
  43 +
  44 +export function useCommandDelivery() {
  45 + const loading = ref(false)
  46 + async function doSetup(params: DoCommandDeliverParamsType) {
  47 + let { deviceDetail, identifier, deviceProfileId, objectModel, transportType } = params
  48 + const { deviceId, deviceProfileDetail } = params
  49 +
  50 + const entityId = deviceId || deviceDetail?.tbDeviceId
  51 + if (!entityId)
  52 + throw new Error('not found entityId')
  53 +
  54 + identifier = identifier || objectModel?.identifier
  55 +
  56 + if (!identifier)
  57 + throw new Error('not found identifier')
  58 +
  59 + transportType = transportType || (deviceDetail?.transportType as TransportTypeEnum)
  60 +
  61 + if (
  62 + !transportType
  63 + || (transportType === TransportTypeEnum.TCP && !deviceDetail)
  64 + || !deviceDetail?.deviceProfile
  65 + ) {
  66 + deviceDetail = await getDeviceInfo(entityId)
  67 + transportType = deviceDetail.transportType as TransportTypeEnum
  68 + }
  69 +
  70 + const isTCPModbus
  71 + = transportType === TransportTypeEnum.TCP
  72 + && deviceDetail?.deviceProfile?.profileData?.transportConfiguration?.protocol
  73 + === TCPProtocolTypeEnum.MODBUS_RTU
  74 +
  75 + if (isTCPModbus && !objectModel?.extensionDesc) {
  76 + deviceProfileId = deviceDetail.deviceProfileId || deviceProfileId || deviceProfileDetail?.id
  77 + if (!deviceProfileId)
  78 + throw new Error('not found deviceProfile')
  79 +
  80 + const productStore = useProductsStoreWithOut()
  81 + objectModel = productStore.getObjectModelByIdWithIdentifier(deviceProfileId!, identifier)!
  82 + }
  83 +
  84 + const deviceCode = deviceDetail.code
  85 + return {
  86 + entityId,
  87 + transportType,
  88 + isTCPModbus,
  89 + deviceCode,
  90 + deviceDetail,
  91 + objectModel,
  92 + identifier,
  93 + }
  94 + }
  95 +
  96 + async function doCommandDelivery(params: DoCommandDeliverParamsType) {
  97 + try {
  98 + loading.value = true
  99 +
  100 + const setupResult = await doSetup(params)
  101 +
  102 + const { entityId, transportType, isTCPModbus, deviceCode, objectModel, identifier }
  103 + = setupResult
  104 +
  105 + let command = params.value
  106 +
  107 + let rpcCommand: RpcCommandType = {
  108 + persistent: true,
  109 + method: CommandMethodEnum.THINGSKIT,
  110 + additionalInfo: {
  111 + cmdType: params.cmdType ?? CommandTypeEnum.API,
  112 + },
  113 + params: command,
  114 + }
  115 +
  116 + let way = params.way ?? CommandWayEnum.ONE_WAY
  117 +
  118 + if (!params.penetration) {
  119 + if (transportType === TransportTypeEnum.TCP) {
  120 + command = params.value
  121 + if (isTCPModbus) {
  122 + const { doCoverCommand } = useCoverModbusCommand()
  123 + command = await doCoverCommand(params.value, objectModel!, deviceCode, entityId)
  124 + }
  125 + }
  126 + else {
  127 + command = {
  128 + [identifier]: command,
  129 + }
  130 + }
  131 +
  132 + if (objectModel?.functionType === FunctionTypeEnum.SERVICE) {
  133 + rpcCommand.additionalInfo.cmdType = CommandTypeEnum.SERVICE
  134 + way
  135 + = objectModel.callType === CommandCallWayEnum.ASYNC
  136 + ? CommandWayEnum.ONE_WAY
  137 + : CommandWayEnum.TWO_WAY
  138 + }
  139 +
  140 + rpcCommand.params = command
  141 + }
  142 +
  143 + const sendApi = rpcCommandApi
  144 +
  145 + if (params.beforeFetch && isFunction(params.beforeFetch))
  146 + rpcCommand = await params.beforeFetch(rpcCommand, setupResult)
  147 +
  148 + if (way === CommandWayEnum.TWO_WAY) {
  149 + const result = await getDeviceActive(entityId)
  150 + const [firsetItem] = result || []
  151 +
  152 + if (!firsetItem.value) {
  153 + const { createMessage } = useMessage()
  154 + createMessage.warning('当前设备不在线')
  155 + return
  156 + }
  157 + }
  158 + await sendApi({ way, deviceId: entityId, command: rpcCommand })
  159 + }
  160 + finally {
  161 + loading.value = false
  162 + }
  163 + }
  164 +
  165 + return {
  166 + loading,
  167 + doCommandDelivery,
  168 + }
  169 +}
... ...
  1 +import { isNullOrUnDef } from '@wry-smile/utils-is'
  2 +import { useParseOriginalDataType } from './useParseOriginalDataType'
  3 +
  4 +import { isFloatType, isNumberType, useParseOperationType } from './useParseOperationType'
  5 +import { useBaseConversion } from '@/hooks/business/useBaseConversion'
  6 +import { useMessage } from '@/hooks/web/useMessage'
  7 +import type { ExtensionDesc, Specs, Tsl } from '@/api/device/model'
  8 +import { type GenModbusCommandType, genModbusCommand, getDeviceInfo, getDeviceTelemetryValue } from '@/api/device'
  9 +import { ModbusCRCEnum } from '@/enums/commandEnum'
  10 +import { OriginalDataTypeEnum } from '@/enums/objectModelEnum'
  11 +
  12 +const getFloatPart = (number: string | number) => {
  13 + const isLessZero = Number(number) < 0
  14 + number = number.toString()
  15 + const floatPartStartIndex = number.indexOf('.')
  16 + const value = ~floatPartStartIndex
  17 + ? `${isLessZero ? '-' : ''}0.${number.substring(floatPartStartIndex + 1)}`
  18 + : '0'
  19 + return Number(value)
  20 +}
  21 +
  22 +function getValueFromValueRange(value: number, valueRange: Record<'min' | 'max', number>) {
  23 + const { min, max } = valueRange || {}
  24 + if (!isNullOrUnDef(min) && value < min) return min
  25 + if (!isNullOrUnDef(max) && value > max) return max
  26 + return value
  27 +}
  28 +
  29 +async function getCurrentBitCommand(entityId: string, objectModel: Tsl, value: number) {
  30 + const deviceDetail = await getDeviceInfo(entityId)
  31 + const thingsModels = deviceDetail.deviceProfile.profileData.thingsModel
  32 +
  33 + const { registerAddress } = objectModel.extensionDesc || {}
  34 +
  35 + const bitsModel = thingsModels?.filter(
  36 + item =>
  37 + item.extensionDesc?.originalDataType === OriginalDataTypeEnum.BITS
  38 + && item.extensionDesc.registerAddress === registerAddress,
  39 + )
  40 +
  41 + const valuePositionMap
  42 + = bitsModel?.reduce((prev, next) => {
  43 + return { ...prev, [next.identifier]: next.extensionDesc?.bitMask }
  44 + }, {} as Record<string, number>) || {}
  45 +
  46 + const attrKeys = Object.keys(valuePositionMap)
  47 +
  48 + const latestBitsValues = await getDeviceTelemetryValue({ entityId, keys: attrKeys.join(',') })
  49 +
  50 + const binaryArr = Array.from({ length: 16 }, () => 0)
  51 +
  52 + for (const key of attrKeys) {
  53 + const index = valuePositionMap[key]
  54 +
  55 + if (!isNullOrUnDef(index)) {
  56 + const [latest] = latestBitsValues[key]
  57 + const { value } = latest
  58 + binaryArr[index] = Number(value)
  59 + }
  60 + }
  61 +
  62 + if (!isNullOrUnDef(objectModel.extensionDesc?.bitMask))
  63 + binaryArr[objectModel.extensionDesc.bitMask] = value
  64 +
  65 + return [parseInt(binaryArr.reverse().join(''), 2)]
  66 +}
  67 +
  68 +export function useCoverModbusCommand() {
  69 + const { createMessage } = useMessage()
  70 +
  71 + const doCoverCommand = async (value: number, objectModel: Tsl, deviceAddressCode?: string, entityId?: string) => {
  72 + if (!deviceAddressCode) {
  73 + const message = '当前设备未绑定设备地址码'
  74 + createMessage.warning(message)
  75 + throw new Error(message)
  76 + }
  77 +
  78 + const {
  79 + registerAddress,
  80 + operationType,
  81 + scaling,
  82 + originalDataType,
  83 + bitMask,
  84 + registerCount: registerNumber,
  85 + } = objectModel.extensionDesc as Required<ExtensionDesc>
  86 +
  87 + const { writeRegisterAddress } = useParseOperationType(operationType)
  88 +
  89 + const { unsigned, exchangeSortFlag, registerCount }
  90 + = useParseOriginalDataType(originalDataType)
  91 +
  92 + const params: GenModbusCommandType = {
  93 + crc: ModbusCRCEnum.CRC_16_LOWER,
  94 + registerNumber: registerCount || registerNumber,
  95 + deviceCode: deviceAddressCode,
  96 + registerAddress: parseInt(registerAddress, 16),
  97 + method: writeRegisterAddress!,
  98 + registerValues: [value],
  99 + }
  100 +
  101 + if (exchangeSortFlag) params.hexByteOrderEnum = exchangeSortFlag
  102 +
  103 + const { getRegisterValueByOriginalDataType } = useBaseConversion()
  104 +
  105 + if (isNumberType(originalDataType)) {
  106 + let newValue = Math.trunc(value) * scaling + getFloatPart(value) * scaling
  107 +
  108 + newValue = unsigned ? newValue : Math.abs(newValue)
  109 +
  110 + newValue = getValueFromValueRange(
  111 + newValue,
  112 + (objectModel.specs?.dataType.specs as Specs).valueRange!,
  113 + )
  114 +
  115 + if (!isFloatType(originalDataType) && newValue % 1 !== 0) {
  116 + const message = `属性下发类型必须是整数,缩放因子为${scaling}`
  117 + createMessage.warning(message)
  118 + throw Error(message)
  119 + }
  120 +
  121 + value = newValue
  122 + }
  123 +
  124 + params.registerValues = originalDataType === OriginalDataTypeEnum.BITS
  125 + ? await getCurrentBitCommand(entityId!, objectModel, value)
  126 + : getRegisterValueByOriginalDataType(value, originalDataType, {
  127 + bitMask,
  128 + registerNumber,
  129 + })
  130 +
  131 + if (!params.method) {
  132 + const message = '物模型操作类型无法进行写入'
  133 + createMessage.warning(message)
  134 + throw Error(message)
  135 + }
  136 +
  137 + return await genModbusCommand(params)
  138 + }
  139 +
  140 + return {
  141 + doCoverCommand,
  142 + }
  143 +}
... ...
  1 +import type { ExtendDescOperationTypeEnum } from '@/enums/objectModelEnum'
  2 +import {
  3 + DataTypeEnum,
  4 + ObjectModelAccessModeEnum,
  5 + OriginalDataTypeEnum,
  6 +} from '@/enums/objectModelEnum'
  7 +
  8 +export function isNumberType(originalDataType: OriginalDataTypeEnum) {
  9 + return ![
  10 + OriginalDataTypeEnum.BITS,
  11 + OriginalDataTypeEnum.BOOLEAN,
  12 + OriginalDataTypeEnum.STRING,
  13 + ].includes(originalDataType)
  14 +}
  15 +
  16 +export function isFloatType(originalDataType: OriginalDataTypeEnum) {
  17 + return [
  18 + OriginalDataTypeEnum.FLOAT_AB_CD,
  19 + OriginalDataTypeEnum.FLOAT_BA_DC,
  20 + OriginalDataTypeEnum.FLOAT_CD_AB,
  21 + OriginalDataTypeEnum.FLOAT_DC_BA,
  22 + OriginalDataTypeEnum.DOUBLE,
  23 + ].includes(originalDataType)
  24 +}
  25 +
  26 +export function getDataTypeByOriginalDataType(originalDataType: OriginalDataTypeEnum) {
  27 + if (originalDataType === OriginalDataTypeEnum.STRING) return DataTypeEnum.STRING
  28 + else if (originalDataType === OriginalDataTypeEnum.BOOLEAN) return DataTypeEnum.BOOL
  29 + else if (isFloatType(originalDataType)) return DataTypeEnum.NUMBER_DOUBLE
  30 + else return DataTypeEnum.NUMBER_INT
  31 +}
  32 +
  33 +export function useParseOperationType(actionType: ExtendDescOperationTypeEnum) {
  34 + const [, accessMode, inputOrOutput, output = undefined] = actionType.split('_')
  35 +
  36 + const _accessMode
  37 + = accessMode === ObjectModelAccessModeEnum.READ
  38 + ? ObjectModelAccessModeEnum.READ
  39 + : ObjectModelAccessModeEnum.READ_AND_WRITE
  40 +
  41 + const writeOnly
  42 + = _accessMode === ObjectModelAccessModeEnum.READ_AND_WRITE
  43 + && accessMode !== ObjectModelAccessModeEnum.READ_AND_WRITE
  44 +
  45 + return {
  46 + accessMode: _accessMode,
  47 + originAccessMode: accessMode,
  48 + writeOnly,
  49 + readRegisterAddress: writeOnly ? undefined : inputOrOutput,
  50 + writeRegisterAddress: writeOnly ? inputOrOutput : output,
  51 + }
  52 +}
... ...
  1 +import { OriginalDataTypeEnum } from '@/enums/objectModelEnum'
  2 +
  3 +export type OriginalDataTypePrefixType<S = `${OriginalDataTypeEnum}`> = S extends string
  4 + ? S extends `${infer D}_${string}`
  5 + ? D
  6 + : S
  7 + : ''
  8 +
  9 +function getRegisterCount(originalDataType: OriginalDataTypeEnum) {
  10 + switch (originalDataType) {
  11 + case OriginalDataTypeEnum.INT16_AB:
  12 + case OriginalDataTypeEnum.INT16_BA:
  13 + case OriginalDataTypeEnum.UINT16_AB:
  14 + case OriginalDataTypeEnum.UINT16_BA:
  15 + case OriginalDataTypeEnum.BITS:
  16 + case OriginalDataTypeEnum.BOOLEAN:
  17 + return 1
  18 +
  19 + case OriginalDataTypeEnum.INT32_AB_CD:
  20 + case OriginalDataTypeEnum.INT32_BA_DC:
  21 + case OriginalDataTypeEnum.INT32_CD_AB:
  22 + case OriginalDataTypeEnum.INT32_DC_BA:
  23 + case OriginalDataTypeEnum.UINT32_AB_CD:
  24 + case OriginalDataTypeEnum.UINT32_BA_DC:
  25 + case OriginalDataTypeEnum.UINT32_CD_AB:
  26 + case OriginalDataTypeEnum.UINT32_DC_BA:
  27 + return 2
  28 +
  29 + case OriginalDataTypeEnum.FLOAT_AB_CD:
  30 + case OriginalDataTypeEnum.FLOAT_BA_DC:
  31 + case OriginalDataTypeEnum.FLOAT_CD_AB:
  32 + case OriginalDataTypeEnum.FLOAT_DC_BA:
  33 + return 2
  34 +
  35 + case OriginalDataTypeEnum.DOUBLE:
  36 + return 4
  37 + }
  38 +}
  39 +
  40 +export function useParseOriginalDataType(originalDataType: OriginalDataTypeEnum) {
  41 + const signedMatchRef = /^UN/
  42 +
  43 + const splitArray = originalDataType.split('_') as [OriginalDataTypePrefixType]
  44 +
  45 + const [dataType] = splitArray
  46 +
  47 + const exchangeSortFlag = splitArray.slice(1).join('_')
  48 +
  49 + return {
  50 + registerCount: getRegisterCount(originalDataType),
  51 + unsigned: !signedMatchRef.test(originalDataType),
  52 + dataType,
  53 + exchangeSortFlag: exchangeSortFlag || null,
  54 + }
  55 +}
... ...
... ... @@ -43,11 +43,11 @@ foreignObject[thingskit-component] > div > div > div {
43 43 }
44 44
45 45 .act-visible {
46   - visibility: visible;
  46 + display: block;
47 47 }
48 48
49 49 .act-hidden {
50   - visibility: hidden;
  50 + display: none;
51 51 }
52 52
53 53 .act-spin-animation {
... ...