Commit 846eb423c2b0c38f7ce168977f9749a40265cc13

Authored by xp.Huang
2 parents 72019892 3b113770

Merge branch 'fix/DEFECT-1855' into 'main_dev'

fix: 修复组态属性下发tcp设备没有缩放因子

See merge request yunteng/thingskit-scada!205
... ... @@ -27,7 +27,7 @@ export interface GenModbusCommandType {
27 27 crc: string
28 28 deviceCode: string
29 29 method: string
30   - registerAddress: string
  30 + registerAddress: number
31 31 registerNumber?: number
32 32 registerValues?: number[]
33 33 }
... ...
  1 +import type { Specs, StructJSON } from '@/api/device/model'
  2 +import type { NodeDataDataSourceJsonType } from '@/api/node/model'
  3 +import { type FormSchema, useComponentRegister } from '@/components/Form'
  4 +import { ComponentEnum } from '@/components/Form/src/enum'
  5 +import { ThingsModelForm, validateTCPCustomCommand } from '@/core/Library/components/ThingsModelForm'
  6 +import { getFormSchemas } from '@/core/Library/components/ThingsModelForm/config'
  7 +import { CodeTypeEnum, DataTypeEnum, TransportTypeEnum } from '@/enums/datasource'
  8 +import { TCPObjectModelActionTypeEnum } from '@/enums/objectModelEnum'
  9 +
  10 +useComponentRegister(ComponentEnum.THINGS_MODEL_FORM, ThingsModelForm)
  11 +
  12 +export enum FormFieldsEnum {
  13 + ATTR_VALUE = 'attrValue',
  14 + PASSWORD = 'password',
  15 +}
  16 +
  17 +export interface AttributeDeliverModalOpenParamsType {
  18 + title?: string
  19 + operationPassword?: string
  20 + operationPasswordEnable?: boolean
  21 + dataSourceJson: NodeDataDataSourceJsonType
  22 +}
  23 +
  24 +function getStructJsonFromDataSourceJson(dataSourceJson: NodeDataDataSourceJsonType): StructJSON {
  25 + const { attrInfo } = dataSourceJson
  26 + const { identifier, name, detail } = attrInfo || {}
  27 + return {
  28 + functionName: name,
  29 + identifier,
  30 + dataType: detail.dataType,
  31 + }
  32 +}
  33 +
  34 +function getTCPModbusSchemas({ structJson, required, actionType }: { structJson: StructJSON; required?: boolean; actionType: string }): FormSchema {
  35 + const { dataType } = structJson
  36 + const { specs, type } = dataType || {}
  37 + const { valueRange, length = 10240 } = specs! as Specs
  38 + const { max = Number.MAX_SAFE_INTEGER, min = Number.MIN_SAFE_INTEGER } = valueRange || {}
  39 +
  40 + function createInputNumber({
  41 + identifier,
  42 + functionName,
  43 + }: StructJSON): FormSchema {
  44 + return {
  45 + field: identifier,
  46 + label: functionName,
  47 + component: ComponentEnum.INPUT_NUMBER,
  48 + required,
  49 + componentProps: {
  50 + max: actionType === TCPObjectModelActionTypeEnum.BOOL ? 1 : max,
  51 + min: actionType === TCPObjectModelActionTypeEnum.BOOL ? 0 : min,
  52 + precision: actionType === TCPObjectModelActionTypeEnum.BOOL ? 0 : 2,
  53 + },
  54 + }
  55 + }
  56 +
  57 + const createInput = ({ identifier, functionName }: StructJSON): FormSchema => {
  58 + return {
  59 + field: identifier,
  60 + label: functionName,
  61 + component: ComponentEnum.INPUT,
  62 + rules: [
  63 + {
  64 + required,
  65 + message: `${functionName}是必填项`,
  66 + },
  67 + {
  68 + validator: validateTCPCustomCommand,
  69 + },
  70 + ],
  71 + componentProps: {
  72 + maxLength: length,
  73 + },
  74 + } as FormSchema
  75 + }
  76 +
  77 + return type === DataTypeEnum.STRING ? createInput(structJson) : createInputNumber(structJson)
  78 +}
  79 +
  80 +export const createFormSchemas = ({ operationPassword, operationPasswordEnable, dataSourceJson }: AttributeDeliverModalOpenParamsType): FormSchema[] => {
  81 + const schemas: FormSchema[] = []
  82 +
  83 + const { deviceInfo } = dataSourceJson
  84 + if (deviceInfo?.transportType === TransportTypeEnum.TCP && deviceInfo.codeType === CodeTypeEnum.MODBUS_RTU && dataSourceJson.deviceInfo?.codeType) {
  85 + schemas.push(getTCPModbusSchemas({ required: true, structJson: getStructJsonFromDataSourceJson(dataSourceJson), actionType: dataSourceJson.deviceInfo?.codeType }))
  86 + }
  87 + else {
  88 + const isStructType = dataSourceJson.attrInfo?.detail?.dataType?.type === DataTypeEnum.STRUCT
  89 + schemas.push(
  90 + ...getFormSchemas({ structJSON: isStructType ? dataSourceJson?.attrInfo?.detail?.dataType?.specs as StructJSON[] || [] : [getStructJsonFromDataSourceJson(dataSourceJson)], required: !isStructType }),
  91 + )
  92 + }
  93 +
  94 + if (operationPassword && operationPasswordEnable) {
  95 + schemas.unshift({
  96 + field: FormFieldsEnum.PASSWORD,
  97 + label: '操作密码',
  98 + component: ComponentEnum.INPUT_PAWSSWORD,
  99 + required: true,
  100 + rules: [
  101 + {
  102 + validator(_rule, value) {
  103 + if (value && value !== operationPassword) return Promise.reject(new Error('操作密码不正确'))
  104 + return Promise.resolve()
  105 + },
  106 + },
  107 + ],
  108 + })
  109 + }
  110 +
  111 + return schemas
  112 +}
... ...
1 1 <script setup lang="ts">
2 2 import { Modal } from 'ant-design-vue'
3 3 import { nextTick, ref, unref } from 'vue'
4   -import type { FormSchema } from '@/components/Form'
  4 +import type { AttributeDeliverModalOpenParamsType } from './AttributeDeliverModal.config'
  5 +import { createFormSchemas } from './AttributeDeliverModal.config'
  6 +import { useGetModbusCommand } from './useGetModbusCommand'
5 7 import { BasicForm, useForm } from '@/components/Form'
6   -import { ComponentEnum, FormLayoutEnum } from '@/components/Form/src/enum'
  8 +import { FormLayoutEnum } from '@/components/Form/src/enum'
  9 +import type { NodeDataDataSourceJsonType } from '@/api/node/model'
  10 +import { CodeTypeEnum, DataTypeEnum, TransportTypeEnum } from '@/enums/datasource'
7 11
8 12 const resolveFn = ref<Fn>()
9 13
... ... @@ -11,61 +15,50 @@ const visible = ref(false)
11 15
12 16 const password = ref()
13 17
  18 +const currentDataSourceJson = ref<NodeDataDataSourceJsonType>()
  19 +
14 20 const [register, { getFieldsValue, resetFields, validate, setProps, clearValidate }] = useForm({
15 21 layout: FormLayoutEnum.VERTICAL,
16 22 showActionButtonGroup: false,
17 23 })
18 24
19   -enum FormFieldsEnum {
20   - ATTR_VALUE = 'attrValue',
21   - PASSWORD = 'password',
22   -}
23   -
24   -const createFormSchemas = (title?: string, password?: string, operationPasswordEnable?: boolean): FormSchema[] => {
25   - const schemas: FormSchema[] = [
26   - {
27   - field: FormFieldsEnum.ATTR_VALUE,
28   - label: title || '属性值',
29   - component: ComponentEnum.INPUT,
30   - required: true,
31   - },
32   - ]
33   -
34   - if (password && operationPasswordEnable) {
35   - schemas.unshift({
36   - field: FormFieldsEnum.PASSWORD,
37   - label: '操作密码',
38   - component: ComponentEnum.INPUT_PAWSSWORD,
39   - required: true,
40   - rules: [
41   - {
42   - validator(_rule, value) {
43   - if (value && value !== password) return Promise.reject(new Error('操作密码不正确'))
44   - return Promise.resolve()
45   - },
46   - },
47   - ],
48   - })
49   - }
50   -
51   - return schemas
52   -}
53   -
54   -const open = async ({ title, operationPassword, operationPasswordEnable }: Partial<Record<'operationPassword' | 'title', string>> & { operationPasswordEnable: boolean }) => {
  25 +const open = async ({ title, operationPassword, operationPasswordEnable, dataSourceJson }: AttributeDeliverModalOpenParamsType) => {
55 26 visible.value = true
56 27 password.value = operationPassword
  28 + currentDataSourceJson.value = dataSourceJson
57 29 return new Promise((resolve) => {
58 30 resolveFn.value = resolve
59 31 nextTick(() => {
60   - setProps({ schemas: createFormSchemas(title, operationPassword, operationPasswordEnable) })
  32 + setProps({ schemas: createFormSchemas({ title, operationPassword, operationPasswordEnable, dataSourceJson }) })
61 33 })
62 34 })
63 35 }
64 36
  37 +async function getResult() {
  38 + const result = getFieldsValue()
  39 + const isTCPModbusDevice = unref(currentDataSourceJson)?.deviceInfo?.transportType === TransportTypeEnum.TCP && unref(currentDataSourceJson)?.deviceInfo?.codeType === CodeTypeEnum.MODBUS_RTU
  40 + const isStructJSON = unref(currentDataSourceJson)?.attrInfo?.detail?.dataType?.type === DataTypeEnum.STRUCT
  41 + const attrKey = unref(currentDataSourceJson)!.attr
  42 + if (!isTCPModbusDevice) { return isStructJSON ? result : result[attrKey] }
  43 + else {
  44 + const value = result[attrKey]
  45 + const isString = unref(currentDataSourceJson)?.attrInfo?.detail?.dataType?.type === DataTypeEnum.STRING
  46 +
  47 + if (isString) return value
  48 +
  49 + const { getModbusCommand, validateCanGetCommand } = useGetModbusCommand()
  50 + if (!validateCanGetCommand(unref(currentDataSourceJson)!.attrInfo.extensionDesc, unref(currentDataSourceJson)!.deviceInfo?.code).flag) return
  51 +
  52 + const res = await getModbusCommand(value as unknown as number, unref(currentDataSourceJson)!.attrInfo.extensionDesc!, unref(currentDataSourceJson)!.deviceInfo!.code!)
  53 + return res
  54 + }
  55 +}
  56 +
65 57 const handleOk = async () => {
66 58 await validate()
67   - const value = getFieldsValue()
68   - unref(resolveFn)?.(value[FormFieldsEnum.ATTR_VALUE])
  59 + const result = await getResult()
  60 + if (!result) return
  61 + unref(resolveFn)?.(result)
69 62 visible.value = false
70 63 resetFields()
71 64 }
... ... @@ -80,17 +73,9 @@ defineExpose({ open })
80 73
81 74 <template>
82 75 <Modal
83   - v-model:open="visible" title="属性下发" ok-text="确认" :width="400" cancel-text="取消" @cancel="handleCancel" @ok="handleOk"
  76 + v-model:open="visible" title="属性下发" ok-text="确认" :width="400" cancel-text="取消" @cancel="handleCancel"
  77 + @ok="handleOk"
84 78 >
85   - <!-- <section>
86   - <FormItem label="操作密码" :label-col="{ span: 24 }">
87   - <Input v-model:value="value" placeholder="请输入下发值" />
88   - </FormItem>
89   - <FormItem :label="fieldTitle" :label-col="{ span: 24 }">
90   - <Input v-model:value="value" placeholder="请输入下发值" />
91   - </FormItem>
92   - </section> -->
93   -
94 79 <BasicForm @register="register" />
95 80 </Modal>
96 81 </template>
... ...
  1 +import { SingleToHex, getArray } from './config'
  2 +import { type GenModbusCommandType, genModbusCommand } from '@/api/device'
  3 +import type { ExtensionDesc } from '@/api/device/model'
  4 +import { TCPObjectModelActionTypeEnum } from '@/enums/objectModelEnum'
  5 +import { useMessage } from '@/hooks/web/useMessage'
  6 +
  7 +const getFloatPart = (number: string | number) => {
  8 + const isLessZero = Number(number) < 0
  9 + number = number.toString()
  10 + const floatPartStartIndex = number.indexOf('.')
  11 + const value = ~floatPartStartIndex
  12 + ? `${isLessZero ? '-' : ''}0.${number.substring(floatPartStartIndex + 1)}`
  13 + : '0'
  14 + return Number(value)
  15 +}
  16 +
  17 +const REGISTER_MAX_VALUE = Number(0xffff)
  18 +
  19 +export function useGetModbusCommand() {
  20 + const { createMessage } = useMessage()
  21 +
  22 + const getModbusCommand = async (value: number, extensionDesc: ExtensionDesc, deviceAddressCode: string) => {
  23 + const { registerAddress, actionType, zoomFactor } = extensionDesc as Required<ExtensionDesc>
  24 + const params: GenModbusCommandType = {
  25 + crc: 'CRC_16_LOWER',
  26 + registerNumber: 1,
  27 + deviceCode: deviceAddressCode,
  28 + registerAddress,
  29 + method: actionType,
  30 + registerValues: [value],
  31 + }
  32 +
  33 + if (actionType === TCPObjectModelActionTypeEnum.INT) {
  34 + const newValue
  35 + = Math.trunc(value) * zoomFactor
  36 + + getFloatPart(value) * zoomFactor
  37 +
  38 + if (newValue % 1 !== 0) {
  39 + createMessage.warning(`属性下发类型必须是整数,缩放因子为${zoomFactor}`)
  40 + return
  41 + }
  42 +
  43 + if (value * zoomFactor > REGISTER_MAX_VALUE) {
  44 + createMessage.warning(`属性下发值不能超过${REGISTER_MAX_VALUE},缩放因子是${zoomFactor}`)
  45 + return
  46 + }
  47 + }
  48 + else if (actionType === TCPObjectModelActionTypeEnum.DOUBLE) {
  49 + const regex = /^-?\d+(\.\d{0,2})?$/
  50 + const values
  51 + = Math.trunc(value) * zoomFactor
  52 + + getFloatPart(value) * zoomFactor
  53 +
  54 + if (!regex.test(values.toString())) {
  55 + createMessage.warning(`属性下发值精确到两位小数,缩放因子是${zoomFactor}`)
  56 + return
  57 + }
  58 +
  59 + const newValue
  60 + = values === 0 ? [0, 0] : getArray(SingleToHex(values))
  61 + params.registerValues = newValue
  62 + params.registerNumber = 2
  63 + }
  64 +
  65 + return await genModbusCommand(params)
  66 + }
  67 +
  68 + /**
  69 + *
  70 + * @param extensionDesc 物模型拓展描述符
  71 + * @param deviceAddressCode 设备地址码
  72 + */
  73 + const validateCanGetCommand = (extensionDesc?: ExtensionDesc, deviceAddressCode?: string, createValidateMessage = true) => {
  74 + const result = { flag: true, message: '' }
  75 + if (!extensionDesc) {
  76 + result.flag = false
  77 + result.message = '当前物模型扩展描述没有填写'
  78 + }
  79 +
  80 + if (!deviceAddressCode) {
  81 + result.flag = false
  82 + result.message = '当前设备未绑定设备地址码'
  83 + }
  84 +
  85 + if (result.message && createValidateMessage)
  86 + createMessage.warning(result.message)
  87 +
  88 + return result
  89 + }
  90 +
  91 + return {
  92 + getModbusCommand,
  93 + validateCanGetCommand,
  94 + }
  95 +}
... ...
... ... @@ -9,7 +9,7 @@ export const getFormSchemas = ({ structJSON: structJson, required, transportType
9 9 functionName,
10 10 dataType,
11 11 }: StructJSON): FormSchema => {
12   - const { specs } = dataType || {}
  12 + const { specs, type } = dataType || {}
13 13 const { valueRange } = specs! as Specs
14 14 const { max = Number.MAX_SAFE_INTEGER, min = Number.MIN_SAFE_INTEGER } = valueRange || {}
15 15 return {
... ... @@ -21,22 +21,11 @@ export const getFormSchemas = ({ structJSON: structJson, required, transportType
21 21 required,
22 22 message: `${functionName}是必填项`,
23 23 },
24   - {
25   - type: 'number',
26   - trigger: 'change',
27   - validator: (_rule, value) => {
28   - const reg = /^[0-9]*$/
29   - if (!reg.test(value)) return Promise.reject(new Error(`${functionName}不是一个有效的数字`))
30   - if (value < min || value > max)
31   - return Promise.reject(new Error(`${functionName}取值范围在${min}~${max}之间`))
32   -
33   - return Promise.resolve(value)
34   - },
35   - },
36 24 ],
37 25 componentProps: {
38 26 max,
39 27 min,
  28 + precision: type === DataTypeEnum.NUMBER_INT ? 0 : 2,
40 29 },
41 30 } as FormSchema
42 31 }
... ... @@ -142,7 +131,6 @@ export const getFormSchemas = ({ structJSON: structJson, required, transportType
142 131 }
143 132
144 133 const schemas: FormSchema[] = []
145   -
146 134 for (const item of structJson) {
147 135 const { dataType } = item
148 136 const { type } = dataType || {}
... ...
  1 +import type { ValidatorRule } from 'ant-design-vue/lib/form/interface'
  2 +
1 3 export { default as ThingsModelForm } from './index.vue'
  4 +
  5 +export const validateTCPCustomCommand: ValidatorRule['validator'] = (_rule, value) => {
  6 + const reg = /^[\s0-9a-fA-F]+$/
  7 + if (reg.test(value)) return Promise.resolve()
  8 + return Promise.reject(new Error('请输入ASCII或HEX服务命令(0~9/A~F)'))
  9 +}
... ...
... ... @@ -22,10 +22,6 @@ const props = withDefaults(defineProps<{
22 22
23 23 const thingsModelFormListElMap = reactive<Record<string, { el: InstanceType<typeof ThingsModelForm>; structJSON: StructJSON }>>({})
24 24
25   -// const getLabelWidth = () => {
26   -// return Math.max(...((props.inputData || [])?.map(item => item?.functionName?.length))) * 12
27   -// }
28   -
29 25 const [register, formActionType] = useForm({
30 26 schemas: getFormSchemas({ structJSON: props.inputData || [], required: props.required, transportType: props.transportType }),
31 27 showActionButtonGroup: false,
... ...
... ... @@ -118,9 +118,9 @@ export function useNodeEvent(eventJson: NodeDataEventJsonType, dataSourceJson: N
118 118 else {
119 119 const instance = h(AttributeDeliverModal)
120 120 render(instance, document.body)
121   - const value = await (instance.component?.exposed as InstanceType<typeof AttributeDeliverModal>)?.open({ title: `${alias || deviceName}-${attrInfo.name}`, operationPassword, operationPasswordEnable }) as string
122   -
  121 + const value = await (instance.component?.exposed as InstanceType<typeof AttributeDeliverModal>)?.open({ title: `${alias || deviceName}-${attrInfo.name}`, operationPassword, operationPasswordEnable, dataSourceJson }) as string
123 122 command.params = transportType === TransportTypeEnum.TCP ? value : { [attr]: value }
  123 + if (!command.params) return
124 124 await doCommandDelivery({ way, command, deviceId })
125 125 createMessage.success('命令下发成功')
126 126 }
... ...
  1 +/**
  2 + * @description TCP物模型拓展描述符数据格式
  3 + */
  4 +export enum TCPObjectModelActionTypeEnum {
  5 + BOOL = '05',
  6 + INT = '06',
  7 + DOUBLE = '16',
  8 +}
... ...