Commit 66a595da8ba91b1ee121c854c7aff0927aa62be7

Authored by xp.Huang
2 parents 9fc75900 3804a6bf

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

feat:支持标准modbus_rtu解析

See merge request yunteng/thingskit-front!1209
Showing 56 changed files with 2131 additions and 1276 deletions

Too many changes to show.

To preserve performance only 56 of 58 files are displayed.

... ... @@ -29,6 +29,7 @@
29 29 "SNMP",
30 30 "TSLV",
31 31 "UNACK",
  32 + "UNINT",
32 33 "unref",
33 34 "vben",
34 35 "videojs",
... ... @@ -36,5 +37,5 @@
36 37 "vnode",
37 38 "vueuse",
38 39 "windicss"
39   - ]
  40 + ],
40 41 }
... ...
1   -import { DataType } from '../../device/model/modelOfMatterModel';
2   -import { DataTypeEnum } from '/@/enums/objectModelEnum';
  1 +import { RpcCommandType } from '../../device/model/deviceConfigModel';
  2 +import { DataType, ExtensionDesc } from '../../device/model/modelOfMatterModel';
  3 +import { DataTypeEnum, ObjectModelAccessModeEnum } from '/@/enums/objectModelEnum';
3 4 import { DataSource } from '/@/views/visual/palette/types';
4 5
5 6 export interface AddDataBoardParams {
... ... @@ -154,12 +155,14 @@ export interface DeviceAttributeParams {
154 155 export interface DeviceAttributeRecord {
155 156 name: string;
156 157 identifier: string;
  158 + accessMode: ObjectModelAccessModeEnum;
157 159 detail: { dataType: DataType };
  160 + extensionDesc?: ExtensionDesc;
158 161 }
159 162
160 163 export interface SendCommandParams {
161 164 deviceId: string;
162   - value: any;
  165 + value: RpcCommandType;
163 166 }
164 167
165 168 export interface GetMeetTheConditionsDeviceParams {
... ...
  1 +import { ProfileData } from './deviceModel';
1 2 import { BasicPageParams } from '/@/api/model/baseModel';
2 3 import { CommandTypeEnum, RPCCommandMethodEnum } from '/@/enums/deviceEnum';
3 4
... ... @@ -103,13 +104,6 @@ export interface ProvisionConfiguration {
103 104 provisionDeviceSecret?: any;
104 105 }
105 106
106   -export interface ProfileData {
107   - configuration: Configuration;
108   - transportConfiguration: TransportConfiguration;
109   - provisionConfiguration: ProvisionConfiguration;
110   - alarms: any;
111   -}
112   -
113 107 export interface ProfileRecord {
114 108 id: string;
115 109 creator: string;
... ... @@ -168,22 +162,7 @@ export interface DeviceProfileDetail {
168 162 type: string;
169 163 deviceCount: number;
170 164 default: boolean;
171   -}
172   -
173   -export interface ProfileData {
174   - configuration: Configuration;
175   - transportConfiguration: TransportConfiguration;
176   - provisionConfiguration: ProvisionConfiguration;
177   - alarms: any;
178   - thingsModel: any;
179   -}
180   -
181   -export interface Configuration {
182   - type: string;
183   -}
184   -
185   -export interface TransportConfiguration {
186   - type: string;
  165 + ifShowClass?: boolean;
187 166 }
188 167
189 168 export interface RpcCommandType {
... ...
1   -import { StructJSON } from './modelOfMatterModel';
  1 +import { DataType, ExtensionDesc, ModelOfMatterParams } from './modelOfMatterModel';
2 2 import { BasicPageParams } from '/@/api/model/baseModel';
3 3 import { AlarmStatus } from '/@/enums/alarmEnum';
  4 +import { TCPProtocolTypeEnum } from '/@/enums/deviceEnum';
4 5 import { DeviceStatusEnum } from '/@/views/rule/dataFlow/cpns/config';
5 6 export enum DeviceState {
6 7 INACTIVE = 'INACTIVE',
... ... @@ -60,6 +61,7 @@ export interface DeviceProfileModel {
60 61 transportType: string;
61 62 createTime: string;
62 63 description: string;
  64 + profileData: ProfileData;
63 65 }
64 66
65 67 export type ChildDeviceParams = BasicPageParams & {
... ... @@ -148,6 +150,7 @@ export interface TransportConfiguration {
148 150
149 151 // TCP
150 152 scriptId: string;
  153 + protocol?: TCPProtocolTypeEnum;
151 154 }
152 155
153 156 export interface ProvisionConfiguration {
... ... @@ -160,6 +163,7 @@ export interface ProfileData {
160 163 transportConfiguration: TransportConfiguration;
161 164 provisionConfiguration: ProvisionConfiguration;
162 165 alarms?: any;
  166 + thingsModel?: ModelOfMatterParams[];
163 167 }
164 168
165 169 export interface DeviceRecord {
... ... @@ -176,7 +180,6 @@ export interface DeviceRecord {
176 180 deviceCount: number;
177 181 tbDeviceId: string;
178 182 tbProfileId: string;
179   - profileData: ProfileData;
180 183 defaultQueueName: string;
181 184 image: string;
182 185 type: string;
... ... @@ -192,6 +195,7 @@ export interface DeviceRecord {
192 195 default: boolean;
193 196 name: string;
194 197 transportType: string;
  198 + profileData: ProfileData;
195 199 };
196 200 customerAdditionalInfo?: {
197 201 isPublic?: boolean;
... ... @@ -203,9 +207,10 @@ export interface DeviceModelOfMatterAttrs {
203 207 name: string;
204 208 identifier: string;
205 209 accessMode: string;
206   - detail: StructJSON;
207   - deviceDetail?: DeviceRecord;
208   - extensionDesc?: any;
  210 + detail: {
  211 + dataType: DataType;
  212 + };
  213 + extensionDesc?: ExtensionDesc;
209 214 }
210 215
211 216 export interface DeviceStateLogModel {
... ...
1   -import { DataTypeEnum } from '/@/enums/objectModelEnum';
  1 +import {
  2 + DataTypeEnum,
  3 + ExtendDescOperationTypeEnum,
  4 + OriginalDataTypeEnum,
  5 +} from '/@/enums/objectModelEnum';
2 6 import { FunctionTypeEnum } from '/@/enums/objectModelEnum';
3 7
4 8 export interface Specs {
... ... @@ -27,10 +31,14 @@ export interface DataType {
27 31 }
28 32
29 33 export interface ExtensionDesc {
30   - zoomFactor?: number;
31   - actionType?: string;
32   - dataType: string;
33   - registerAddress: number;
  34 + writeOnly?: boolean;
  35 + bitMask?: number;
  36 + operationType: ExtendDescOperationTypeEnum;
  37 + originalDataType: OriginalDataTypeEnum;
  38 + registerAddress: string;
  39 + scaling?: number;
  40 + valueRange?: Record<'min' | 'max', number>;
  41 + registerCount?: number;
34 42 }
35 43
36 44 export interface StructJSON {
... ... @@ -64,7 +72,7 @@ export interface ModelOfMatterParams {
64 72 }
65 73
66 74 export interface GetModelTslParams {
67   - functionType: FunctionTypeEnum;
  75 + functionType?: FunctionTypeEnum;
68 76 deviceProfileId: string;
69 77 ifShowClass?: string | Boolean;
70 78 }
... ... @@ -108,7 +116,7 @@ export interface Tsl {
108 116 specs?: {
109 117 dataType: DataType;
110 118 };
111   - extensionDesc: ExtensionDesc;
  119 + extensionDesc?: ExtensionDesc;
112 120 eventType?: string;
113 121 outputData?: StructJSON[];
114 122 callType?: string;
... ...
... ... @@ -5,6 +5,7 @@ import {
5 5 ImportModelOfMatterType,
6 6 ModelOfMatterItemRecordType,
7 7 ModelOfMatterParams,
  8 + Tsl,
8 9 } from './model/modelOfMatterModel';
9 10 import { FunctionTypeEnum } from '/@/enums/objectModelEnum';
10 11 import { defHttp } from '/@/utils/http/axios';
... ... @@ -26,7 +27,7 @@ enum ModelOfMatter {
26 27 CATEGORY_EXPORT = '/things_model/categoryGetExport',
27 28
28 29 IMPORT_CSV = '/things_model/csvImport',
29   - EXCEL_EXPORT = '/things_model/downloadTemplate',
  30 + EXCEL_EXPORT = '/things_model/download/template',
30 31
31 32 BATCH_GET_TSL_BY_DEVICE_PROFILES = '/things_model/batch/get_tsl',
32 33 }
... ... @@ -47,8 +48,8 @@ export const getModelList = (
47 48 };
48 49
49 50 export const getModelTsl = (params: GetModelTslParams) => {
50   - const { functionType, deviceProfileId } = params;
51   - return defHttp.get({
  51 + const { functionType = 'all', deviceProfileId } = params;
  52 + return defHttp.get<Tsl[]>({
52 53 url: `${ModelOfMatter.TSL}/${functionType}/${deviceProfileId}`,
53 54 });
54 55 };
... ... @@ -149,11 +150,12 @@ export const importCsvCategory = (params: {
149 150 categoryId?: string;
150 151 deviceProfileId?: string;
151 152 file: FormData;
  153 + type?: string;
152 154 }) => {
153   - const { categoryId } = params || {};
  155 + const { categoryId, type } = params || {};
154 156
155 157 return defHttp.post<any>({
156   - url: `${ModelOfMatter.IMPORT_CSV}?categoryId=${categoryId}`,
  158 + url: `${ModelOfMatter.IMPORT_CSV}?categoryId=${categoryId}&type=${type}`,
157 159 params: params.file,
158 160 });
159 161 };
... ... @@ -166,11 +168,12 @@ export const importCsvDeviceProfileId = (params: {
166 168 categoryId?: string;
167 169 deviceProfileId?: string;
168 170 file: FormData;
  171 + type?: string;
169 172 }) => {
170   - const { deviceProfileId } = params || {};
  173 + const { deviceProfileId, type } = params || {};
171 174
172 175 return defHttp.post<any>({
173   - url: `${ModelOfMatter.IMPORT_CSV}?deviceProfileId=${deviceProfileId}`,
  176 + url: `${ModelOfMatter.IMPORT_CSV}?deviceProfileId=${deviceProfileId}&type=${type}`,
174 177 params: params.file,
175 178 });
176 179 };
... ... @@ -178,9 +181,10 @@ export const importCsvDeviceProfileId = (params: {
178 181 /**
179 182 * 物模型excel导出模板
180 183 */
181   -export const excelExport = () => {
  184 +export const excelExport = (type?: string) => {
182 185 return defHttp.get({
183 186 url: `${ModelOfMatter.EXCEL_EXPORT}`,
  187 + params: { type },
184 188 responseType: 'blob',
185 189 });
186 190 };
... ...
... ... @@ -62,6 +62,7 @@ export interface GenModbusCommandType {
62 62 registerAddress: number;
63 63 registerNumber?: number;
64 64 registerValues?: number[];
  65 + hexByteOrderEnum?: string;
65 66 }
66 67
67 68 export interface ImmediateExecuteTaskType {
... ...
... ... @@ -2,6 +2,7 @@
2 2 <div class="flex">
3 3 <InputNumber
4 4 placeholder="最小值"
  5 + v-bind="minInputProps"
5 6 :disabled="$props.disabled"
6 7 :value="getValue.min!"
7 8 @change="(value) => emitChange(value, 'min')"
... ... @@ -9,6 +10,7 @@
9 10 <div class="text-center flex-shrink-0 w-6">~</div>
10 11 <InputNumber
11 12 placeholder="最大值"
  13 + v-bind="maxInputProps"
12 14 :disabled="$props.disabled"
13 15 :value="getValue.max!"
14 16 @change="(value) => emitChange(value, 'max')"
... ... @@ -22,7 +24,7 @@
22 24 </script>
23 25 <script lang="ts" setup>
24 26 import { computed } from 'vue';
25   - import { InputNumber } from 'ant-design-vue';
  27 + import { InputNumber, InputNumberProps } from 'ant-design-vue';
26 28
27 29 const emit = defineEmits(['change', 'update:value']);
28 30 const props = withDefaults(
... ... @@ -32,8 +34,12 @@
32 34 max: Nullable<number>;
33 35 };
34 36 disabled: boolean;
  37 + maxInputProps?: InputNumberProps;
  38 + minInputProps?: InputNumberProps;
35 39 }>(),
36 40 {
  41 + maxInputProps: () => ({}),
  42 + minInputProps: () => ({}),
37 43 value: () => ({ min: null, max: null }),
38 44 }
39 45 );
... ...
... ... @@ -128,6 +128,7 @@ export type ComponentType =
128 128 | 'ProductPicker'
129 129 | 'PollCommandInput'
130 130 | 'RegisterAddressInput'
  131 + | 'HexInput'
131 132 | 'ControlGroup'
132 133 | 'JSONEditor'
133 134 | 'OrgTreeSelect'
... ...
... ... @@ -48,3 +48,13 @@ export enum CommandTypeNameEnum {
48 48 export enum RPCCommandMethodEnum {
49 49 THINGSKIT = 'methodThingskit',
50 50 }
  51 +
  52 +export enum TCPProtocolTypeEnum {
  53 + CUSTOM = 'CUSTOM',
  54 + MODBUS_RTU = 'MODBUS_RTU',
  55 +}
  56 +
  57 +export enum TCPProtocolTypeNameEnum {
  58 + CUSTOM = '自定义',
  59 + MODBUS_RTU = 'MODBUS_RTU',
  60 +}
... ...
... ... @@ -31,31 +31,95 @@ export enum ObjectEventTypeNameEnum {
31 31 ERROR = '故障',
32 32 }
33 33
34   -export enum RegisterDataTypeEnum {
35   - UN_SHORT = 'unshort',
  34 +export enum ObjectModelAccessModeEnum {
  35 + READ = 'r',
  36 + READ_AND_WRITE = 'rw',
36 37 }
37 38
38   -export enum RegisterDataTypeNameEnum {
39   - UN_SHORT = '16位有符号',
  39 +export enum ModbusCRCEnum {
  40 + CRC_16_LOWER = 'CRC_16_LOWER',
40 41 }
41 42
42   -export enum RegisterActionTypeEnum {
43   - BOOL = '05',
44   - INT = '06',
45   - DOUBLE = '16',
  43 +export enum BuiltInIdentifierEnum {
  44 + SOURCE = 'source',
46 45 }
47 46
48   -export enum RegisterActionTypeNameEnum {
49   - BOOL = '05写入单个线圈寄存器',
50   - INT = '06写入单个保持寄存器',
51   - DOUBLE = '16写入多个保持寄存器',
  47 +export enum ModbusMethodEnum {
  48 + WRITE_10 = '10',
52 49 }
53 50
54   -export enum ObjectModelAccessModeEnum {
55   - READ = 'r',
56   - READ_AND_WRITE = 'rw',
  51 +export enum OriginalDataTypeEnum {
  52 + INT16_AB = 'INT16_AB',
  53 + INT16_BA = 'INT16_BA',
  54 + UINT16_AB = 'UINT16_AB',
  55 + UINT16_BA = 'UINT16_BA',
  56 + INT32_AB_CD = 'INT32_AB_CD',
  57 + INT32_CD_AB = 'INT32_CD_AB',
  58 + INT32_BA_DC = 'INT32_BA_DC',
  59 + INT32_DC_BA = 'INT32_DC_BA',
  60 + UINT32_AB_CD = 'UINT32_AB_CD',
  61 + UINT32_CD_AB = 'UINT32_CD_AB',
  62 + UINT32_BA_DC = 'UINT32_BA_DC',
  63 + UINT32_DC_BA = 'UINT32_DC_BA',
  64 + FLOAT_AB_CD = 'FLOAT_AB_CD',
  65 + FLOAT_CD_AB = 'FLOAT_CD_AB',
  66 + FLOAT_BA_DC = 'FLOAT_BA_DC',
  67 + FLOAT_DC_BA = 'FLOAT_DC_BA',
  68 + DOUBLE = 'DOUBLE',
  69 + STRING = 'STRING',
  70 + BOOLEAN = 'BOOLEAN',
  71 + BITS = 'BITS',
57 72 }
58 73
59   -export enum ModbusCRCEnum {
60   - CRC_16_LOWER = 'CRC_16_LOWER',
  74 +export enum OriginalDataTypeNameEnum {
  75 + INT16_AB = '16位有符号整数AB',
  76 + INT16_BA = '16位有符号整数BA',
  77 + UINT16_AB = '16位无符号整数AB',
  78 + UINT16_BA = '16位无符号整数BA',
  79 + INT32_AB_CD = '32位有符号整数AB_CD',
  80 + INT32_CD_AB = '32位有符号整数CD_AB',
  81 + INT32_BA_DC = '32位有符号整数BA_DC',
  82 + INT32_DC_BA = '32位有符号整数DC_BA',
  83 + UINT32_AB_CD = '32位无符号整数AB_CD',
  84 + UINT32_CD_AB = '32位无符号整数CD_AB',
  85 + UINT32_BA_DC = '32位无符号整数BA_DC',
  86 + UINT32_DC_BA = '32位无符号整数DC_BA',
  87 + FLOAT_AB_CD = '单精度浮点型AB_CD',
  88 + FLOAT_CD_AB = '单精度浮点型CD_AB',
  89 + FLOAT_BA_DC = '单精度浮点型BA_DC',
  90 + FLOAT_DC_BA = '单精度浮点型DC_BA',
  91 + DOUBLE = '双精度浮点型',
  92 + STRING = '字符串',
  93 + BOOLEAN = '布尔型',
  94 + BITS = '位',
  95 +}
  96 +
  97 +export enum ExtendDescOperationTypeEnum {
  98 + INPUT_STATUS_R_02 = 'inputStatus_r_02',
  99 + COIL_STATUS_R_01 = 'coilStatus_r_01',
  100 + COIL_STATUS_RW_01_05 = 'coilStatus_rw_01_05',
  101 + COIL_STATUS_RW_01_0F = 'coilStatus_rw_01_0F',
  102 + COIL_STATUS_W_05 = 'coilStatus_w_05',
  103 + COIL_STATUS_W_0F = 'coilStatus_w_0F',
  104 + HOLDING_REGISTER_R_03 = 'holdingRegister_r_03',
  105 + HOLDING_REGISTER_RW_03_06 = 'holdingRegister_rw_03_06',
  106 + HOLDING_REGISTER_RW_03_10 = 'holdingRegister_rw_03_10',
  107 + HOLDING_REGISTER_W_06 = 'holdingRegister_w_06',
  108 + HOLDING_REGISTER_W_10 = 'holdingRegister_w_10',
  109 + INPUT_REGISTER_R_04 = 'inputRegister_r_04',
  110 +}
  111 +
  112 +export enum ExtendDescOperationTypeNameEnum {
  113 + INPUT_STATUS_R_02 = '离散量输入(只读,0x02)',
  114 + COIL_STATUS_R_01 = '线圈状态(只读,0x01)',
  115 + COIL_STATUS_RW_01_05 = '线圈状态(读写,读取使用0x01,写入使用0x05)',
  116 + COIL_STATUS_RW_01_0F = '线圈状态(读写,读取使用0x01,写入使用0x0F)',
  117 + COIL_STATUS_W_05 = '线圈状态(只写,0x05)',
  118 + COIL_STATUS_W_0F = '线圈状态(只写,0x0F)',
  119 + HOLDING_REGISTER_R_03 = '保持寄存器(只读,0x03)',
  120 + HOLDING_REGISTER_RW_03_06 = '保持寄存器(读写,读取使用0x03,写入使用0x06)',
  121 + HOLDING_REGISTER_RW_03_10 = '保持寄存器(读写,读取使用0x03,写入使用0x10)',
  122 + HOLDING_REGISTER_W_06 = '保持寄存器(只写,0x06)',
  123 + HOLDING_REGISTER_W_10 = '保持寄存器(只写,0x10)',
  124 + INPUT_REGISTER_R_04 = '输入寄存器(只读,0x04)',
61 125 }
... ...
  1 +import { useParseOriginalDataType } from './useParseOriginalDataType';
  2 +import { OriginalDataTypeEnum } from '/@/enums/objectModelEnum';
  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 +
  25 + return result;
  26 + }
  27 +
  28 + function DecTo16Uint(number: number) {
  29 + const arr = new Uint8Array(2);
  30 + const view = new DataView(arr.buffer);
  31 + view.setUint16(0, +number);
  32 + return arr;
  33 + }
  34 +
  35 + function DecTo16Int(number: number) {
  36 + const arr = new Uint8Array(2);
  37 + const view = new DataView(arr.buffer);
  38 + view.setInt16(0, +number);
  39 + return arr;
  40 + }
  41 +
  42 + function DecTo32Uint(number: number) {
  43 + const arr = new Uint8Array(4);
  44 + const view = new DataView(arr.buffer);
  45 + view.setUint32(0, +number);
  46 + return arr;
  47 + }
  48 +
  49 + function DecTo32Int(number: number) {
  50 + const arr = new Uint8Array(4);
  51 + const view = new DataView(arr.buffer);
  52 + view.setInt32(0, +number);
  53 + return arr;
  54 + }
  55 +
  56 + function DecToBinaryByType(type: OriginalDataTypeEnum, number: number) {
  57 + switch (type) {
  58 + case OriginalDataTypeEnum.INT16_AB:
  59 + case OriginalDataTypeEnum.INT16_BA:
  60 + return arrToBase(2, DecTo16Int(number));
  61 +
  62 + case OriginalDataTypeEnum.UINT16_AB:
  63 + case OriginalDataTypeEnum.UINT16_BA:
  64 + return arrToBase(2, DecTo16Uint(number));
  65 +
  66 + case OriginalDataTypeEnum.INT32_AB_CD:
  67 + case OriginalDataTypeEnum.INT32_CD_AB:
  68 + case OriginalDataTypeEnum.INT32_BA_DC:
  69 + case OriginalDataTypeEnum.INT32_DC_BA:
  70 + return arrToBase(2, DecTo32Int(number));
  71 +
  72 + case OriginalDataTypeEnum.UINT32_AB_CD:
  73 + case OriginalDataTypeEnum.UINT32_CD_AB:
  74 + case OriginalDataTypeEnum.UINT32_BA_DC:
  75 + case OriginalDataTypeEnum.UINT32_DC_BA:
  76 + return arrToBase(2, DecTo32Uint(number));
  77 +
  78 + case OriginalDataTypeEnum.FLOAT_AB_CD:
  79 + case OriginalDataTypeEnum.FLOAT_CD_AB:
  80 + case OriginalDataTypeEnum.FLOAT_BA_DC:
  81 + case OriginalDataTypeEnum.FLOAT_DC_BA:
  82 + return arrToBase(2, DecTo32Float(number));
  83 +
  84 + case OriginalDataTypeEnum.DOUBLE:
  85 + return arrToBase(2, DecTo64Double(number));
  86 + }
  87 + }
  88 +
  89 + function SplitStringToGroupByItemLength(
  90 + value: string,
  91 + itemLength = 8,
  92 + ignoreLessThan = false
  93 + ): string[] {
  94 + const reg = new RegExp(`.{${ignoreLessThan ? '' : '1,'}${itemLength}}`, 'g');
  95 + return value.match(reg) || [];
  96 + }
  97 +
  98 + function ExchangeByteOrder(binary: string, order: string) {
  99 + const group = SplitStringToGroupByItemLength(binary);
  100 +
  101 + const BASE_ORDER = {
  102 + A: 0,
  103 + B: 1,
  104 + C: 2,
  105 + D: 3,
  106 + };
  107 +
  108 + const array: string[] = Array.from({ length: binary.length / 8 });
  109 +
  110 + order
  111 + .split('')
  112 + .forEach((bytePosition, index) => (array[index] = group[BASE_ORDER[bytePosition]]));
  113 +
  114 + return array.join('');
  115 + }
  116 +
  117 + function ByteToHex(binary: string) {
  118 + const group = SplitStringToGroupByItemLength(binary, 16);
  119 + return group.map((byte) => parseInt(byte, 2).toString(16));
  120 + }
  121 +
  122 + function ByteToDec(binary: string) {
  123 + const group = SplitStringToGroupByItemLength(binary, 16);
  124 + return group.map((byte) => parseInt(byte, 2));
  125 + }
  126 +
  127 + function StringToHEXBuffer(string: string | number) {
  128 + return string
  129 + .toString()
  130 + .split('')
  131 + .map((string) => string.charCodeAt(0).toString(16))
  132 + .reverse();
  133 + }
  134 +
  135 + function getRegisterValueByOriginalDataType(
  136 + value: number,
  137 + type: OriginalDataTypeEnum,
  138 + additional?: { bitMask?: number; registerNumber?: number }
  139 + ) {
  140 + const { exchangeSortFlag } = useParseOriginalDataType(type);
  141 + // eslint-disable-next-line no-console
  142 + console.groupCollapsed('Modbus Debug');
  143 + // eslint-disable-next-line no-console
  144 + console.table({ input: value, sort: exchangeSortFlag, type });
  145 +
  146 + let result: number[];
  147 +
  148 + if (type === OriginalDataTypeEnum.BOOLEAN) {
  149 + result = [value];
  150 + } else if (type === OriginalDataTypeEnum.STRING) {
  151 + let buffer = StringToHEXBuffer(value);
  152 + const { registerNumber = 0 } = additional || {};
  153 +
  154 + if (buffer.length < registerNumber * 2) {
  155 + buffer = [
  156 + ...Array.from({ length: registerNumber * 2 - buffer.length }, () => '00'),
  157 + ...buffer,
  158 + ];
  159 + }
  160 +
  161 + result = SplitStringToGroupByItemLength(buffer.join(''), 4).map((hex) => parseInt(hex, 16));
  162 + } else {
  163 + let binary = DecToBinaryByType(type, value)!;
  164 + // eslint-disable-next-line no-console
  165 + console.table({ beforeExchange: binary });
  166 +
  167 + if (exchangeSortFlag) binary = ExchangeByteOrder(binary, exchangeSortFlag);
  168 + result = ByteToDec(binary);
  169 +
  170 + // eslint-disable-next-line no-console
  171 + console.table({
  172 + afterEchange: binary,
  173 + dec: result.toString(),
  174 + hex: ByteToHex(binary).toString(),
  175 + });
  176 + }
  177 +
  178 + // eslint-disable-next-line no-console
  179 + console.groupEnd();
  180 +
  181 + return result;
  182 + }
  183 +
  184 + return {
  185 + DecToBinaryByType,
  186 + ByteToDec,
  187 + ByteToHex,
  188 + ExchangeByteOrder,
  189 + SplitStringToGroupByItemLength,
  190 + getRegisterValueByOriginalDataType,
  191 + StringToHEXBuffer,
  192 + };
  193 +}
... ...
  1 +import { ref } from 'vue';
  2 +import { useCoverModbusCommand } from './useCoverModbusCommand';
  3 +import { commandIssuanceApi, getDeviceDetail } from '/@/api/device/deviceManager';
  4 +import { RpcCommandType } from '/@/api/device/model/deviceConfigModel';
  5 +import { DeviceProfileModel, DeviceRecord } from '/@/api/device/model/deviceModel';
  6 +import { Tsl } from '/@/api/device/model/modelOfMatterModel';
  7 +import { getModelTsl } from '/@/api/device/modelOfMatter';
  8 +import {
  9 + CommandDeliveryWayEnum,
  10 + CommandTypeEnum,
  11 + RPCCommandMethodEnum,
  12 + ServiceCallTypeEnum,
  13 + TCPProtocolTypeEnum,
  14 + TransportTypeEnum,
  15 +} from '/@/enums/deviceEnum';
  16 +import { FunctionTypeEnum } from '/@/enums/objectModelEnum';
  17 +import { isFunction } from '/@/utils/is';
  18 +import { getDeviceActiveTime } from '/@/api/alarm/position';
  19 +import { useMessage } from '../web/useMessage';
  20 +
  21 +interface SetupType {
  22 + entityId: string;
  23 + transportType: TransportTypeEnum;
  24 + isTCPModbus: boolean;
  25 + deviceCode: string | undefined;
  26 + deviceDetail: DeviceRecord;
  27 + objectModel: Tsl | undefined;
  28 + identifier: string;
  29 +}
  30 +
  31 +interface DoCommandDeliverParamsType {
  32 + value: any;
  33 + deviceDetail?: DeviceRecord;
  34 + deviceId?: string;
  35 + identifier?: string;
  36 + objectModel?: Tsl;
  37 + deviceProfileId?: string;
  38 + deviceProfileDetail?: DeviceProfileModel;
  39 + way?: CommandDeliveryWayEnum;
  40 + cmdType?: CommandTypeEnum;
  41 + transportType?: TransportTypeEnum;
  42 + beforeFetch?: (
  43 + rpcCommand: RpcCommandType,
  44 + setup: SetupType
  45 + ) =>
  46 + | { rpcCommand: RpcCommandType; way?: CommandDeliveryWayEnum }
  47 + | Promise<{ rpcCommand: RpcCommandType; way?: CommandDeliveryWayEnum }>;
  48 +}
  49 +
  50 +export function useCommandDelivery() {
  51 + const loading = ref(false);
  52 + async function doSetup(params: DoCommandDeliverParamsType) {
  53 + let { deviceDetail, identifier, deviceProfileId, objectModel, transportType } = params;
  54 + const { deviceId, deviceProfileDetail } = params;
  55 +
  56 + const entityId = deviceId || deviceDetail?.tbDeviceId;
  57 + if (!entityId) {
  58 + throw new Error('not found entityId');
  59 + }
  60 +
  61 + identifier = identifier || objectModel?.identifier;
  62 +
  63 + if (!identifier) {
  64 + throw new Error('not found identifier');
  65 + }
  66 +
  67 + transportType = transportType || (deviceDetail?.transportType as TransportTypeEnum);
  68 +
  69 + if (
  70 + !transportType ||
  71 + (transportType === TransportTypeEnum.TCP && !deviceDetail) ||
  72 + !deviceDetail?.deviceProfile
  73 + ) {
  74 + deviceDetail = await getDeviceDetail(entityId);
  75 + transportType = deviceDetail.transportType as TransportTypeEnum;
  76 + }
  77 +
  78 + const isTCPModbus =
  79 + transportType === TransportTypeEnum.TCP &&
  80 + deviceDetail?.deviceProfile?.profileData?.transportConfiguration?.protocol ===
  81 + TCPProtocolTypeEnum.MODBUS_RTU;
  82 +
  83 + if (isTCPModbus && !objectModel?.extensionDesc) {
  84 + deviceProfileId = deviceDetail.deviceProfileId || deviceProfileId || deviceProfileDetail?.id;
  85 + if (!deviceProfileId) {
  86 + throw new Error('not found deviceProfile');
  87 + }
  88 +
  89 + const objectModels = await getModelTsl({ deviceProfileId });
  90 + objectModel = objectModels.find((item) => item.identifier === identifier);
  91 + }
  92 +
  93 + const deviceCode = deviceDetail.code;
  94 +
  95 + return {
  96 + entityId,
  97 + transportType,
  98 + isTCPModbus,
  99 + deviceCode,
  100 + deviceDetail,
  101 + objectModel,
  102 + identifier,
  103 + };
  104 + }
  105 +
  106 + async function doCommandDelivery(params: DoCommandDeliverParamsType) {
  107 + try {
  108 + loading.value = true;
  109 +
  110 + const setupResult = await doSetup(params);
  111 +
  112 + const { entityId, transportType, isTCPModbus, deviceCode, objectModel, identifier } =
  113 + setupResult;
  114 +
  115 + let command = params.value;
  116 +
  117 + if (transportType === TransportTypeEnum.TCP) {
  118 + command = params.value;
  119 + if (isTCPModbus) {
  120 + const { doCoverCommand } = useCoverModbusCommand();
  121 + command = await doCoverCommand(params.value, objectModel!, deviceCode, entityId);
  122 + }
  123 + } else {
  124 + command = {
  125 + [identifier]: command,
  126 + };
  127 + }
  128 +
  129 + let rpcCommand: RpcCommandType = {
  130 + persistent: true,
  131 + method: RPCCommandMethodEnum.THINGSKIT,
  132 + additionalInfo: {
  133 + cmdType: params.cmdType ?? CommandTypeEnum.API,
  134 + },
  135 + params: command,
  136 + };
  137 +
  138 + let way = params.way ?? CommandDeliveryWayEnum.ONE_WAY;
  139 +
  140 + if (objectModel?.functionType === FunctionTypeEnum.SERVICE) {
  141 + rpcCommand.additionalInfo.cmdType = CommandTypeEnum.SERVICE;
  142 + way =
  143 + objectModel.callType === ServiceCallTypeEnum.ASYNC
  144 + ? CommandDeliveryWayEnum.ONE_WAY
  145 + : CommandDeliveryWayEnum.TWO_WAY;
  146 + }
  147 +
  148 + const sendApi = commandIssuanceApi;
  149 +
  150 + if (params.beforeFetch && isFunction(params.beforeFetch)) {
  151 + const { rpcCommand: _rpcCommand, way: _way } = await params.beforeFetch(
  152 + rpcCommand,
  153 + setupResult
  154 + );
  155 + rpcCommand = _rpcCommand;
  156 + if (_way) way = _way;
  157 + }
  158 +
  159 + if (way === CommandDeliveryWayEnum.TWO_WAY) {
  160 + const result = await getDeviceActiveTime(entityId);
  161 + const [firsetItem] = result || [];
  162 +
  163 + if (!firsetItem.value) {
  164 + const { createMessage } = useMessage();
  165 + const message = '当前设备不在线';
  166 + createMessage.warning(message);
  167 + throw Error(message);
  168 + }
  169 + }
  170 + await sendApi(way, entityId, rpcCommand);
  171 + } finally {
  172 + loading.value = false;
  173 + }
  174 + }
  175 +
  176 + return {
  177 + loading,
  178 + doCommandDelivery,
  179 + };
  180 +}
... ...
  1 +import { useParseOriginalDataType } from './useParseOriginalDataType';
  2 +import { getDeviceHistoryInfo } from '/@/api/alarm/position';
  3 +import { getDeviceDetail } from '/@/api/device/deviceManager';
  4 +import { ExtensionDesc, Specs, Tsl } from '/@/api/device/model/modelOfMatterModel';
  5 +import { genModbusCommand } from '/@/api/task';
  6 +import { GenModbusCommandType } from '/@/api/task/model';
  7 +import { ModbusCRCEnum, OriginalDataTypeEnum } from '/@/enums/objectModelEnum';
  8 +import { useBaseConversion } from '/@/hooks/business/useBaseConversion';
  9 +import { useMessage } from '/@/hooks/web/useMessage';
  10 +import { isNullOrUnDef } from '/@/utils/is';
  11 +import {
  12 + isFloatType,
  13 + isNumberType,
  14 + useParseOperationType,
  15 +} from '/@/views/device/profiles/components/ObjectModelForm/ExtendDesc/useParseOperationType';
  16 +
  17 +const getFloatPart = (number: string | number) => {
  18 + const isLessZero = Number(number) < 0;
  19 + number = number.toString();
  20 + const floatPartStartIndex = number.indexOf('.');
  21 + const value = ~floatPartStartIndex
  22 + ? `${isLessZero ? '-' : ''}0.${number.substring(floatPartStartIndex + 1)}`
  23 + : '0';
  24 + return Number(value);
  25 +};
  26 +
  27 +function getValueFromValueRange(value: number, valueRange?: Record<'min' | 'max', number>) {
  28 + const { min, max } = valueRange || {};
  29 + if (!isNullOrUnDef(min) && value < min) return min;
  30 + if (!isNullOrUnDef(max) && value > max) return max;
  31 + return value;
  32 +}
  33 +
  34 +async function getCurrentBitCommand(entityId: string, objectModel: Tsl, value: number) {
  35 + const deviceDetail = await getDeviceDetail(entityId);
  36 + const thingsModels = deviceDetail.deviceProfile.profileData.thingsModel;
  37 +
  38 + const { registerAddress } = objectModel.extensionDesc || {};
  39 +
  40 + const bitsModel = thingsModels?.filter(
  41 + (item) =>
  42 + item.extensionDesc?.originalDataType === OriginalDataTypeEnum.BITS &&
  43 + item.extensionDesc.registerAddress === registerAddress
  44 + );
  45 +
  46 + const valuePositionMap =
  47 + bitsModel?.reduce((prev, next) => {
  48 + return { ...prev, [next.identifier]: next.extensionDesc?.bitMask };
  49 + }, {} as Record<string, number>) || {};
  50 +
  51 + const attrKeys = Object.keys(valuePositionMap);
  52 +
  53 + const latestBitsValues = await getDeviceHistoryInfo({ entityId, keys: attrKeys.join(',') });
  54 +
  55 + const binaryArr = Array.from({ length: 16 }, () => 0);
  56 +
  57 + for (const key of attrKeys) {
  58 + const index = valuePositionMap[key];
  59 +
  60 + if (!isNullOrUnDef(index)) {
  61 + const [latest] = latestBitsValues[key];
  62 + const { value } = latest;
  63 + binaryArr[index] = Number(value);
  64 + }
  65 + }
  66 +
  67 + if (objectModel.extensionDesc?.bitMask) {
  68 + binaryArr[objectModel.extensionDesc.bitMask] = value;
  69 + }
  70 +
  71 + return [parseInt(binaryArr.reverse().join(''), 2)];
  72 +}
  73 +
  74 +export function useCoverModbusCommand() {
  75 + const { createMessage } = useMessage();
  76 +
  77 + const doCoverCommand = async (
  78 + value: number,
  79 + objectModel: Tsl,
  80 + deviceAddressCode?: string,
  81 + entityId?: string
  82 + ) => {
  83 + if (!deviceAddressCode) {
  84 + const message = '当前设备未绑定设备地址码';
  85 + createMessage.warning(message);
  86 + throw new Error(message);
  87 + }
  88 +
  89 + const {
  90 + registerAddress,
  91 + operationType,
  92 + scaling,
  93 + originalDataType,
  94 + bitMask,
  95 + registerCount: registerNumber,
  96 + } = objectModel.extensionDesc as Required<ExtensionDesc>;
  97 +
  98 + const { writeRegisterAddress } = useParseOperationType(operationType);
  99 + const { unsigned, exchangeSortFlag, registerCount } =
  100 + useParseOriginalDataType(originalDataType);
  101 +
  102 + const params: GenModbusCommandType = {
  103 + crc: ModbusCRCEnum.CRC_16_LOWER,
  104 + registerNumber: registerCount || registerNumber,
  105 + deviceCode: deviceAddressCode,
  106 + registerAddress: parseInt(registerAddress, 16),
  107 + method: writeRegisterAddress!,
  108 + registerValues: [value],
  109 + };
  110 +
  111 + if (exchangeSortFlag) params.hexByteOrderEnum = exchangeSortFlag;
  112 +
  113 + const { getRegisterValueByOriginalDataType } = useBaseConversion();
  114 +
  115 + if (isNumberType(originalDataType)) {
  116 + let newValue = Math.trunc(value) * scaling + getFloatPart(value) * scaling;
  117 +
  118 + newValue = unsigned ? newValue : Math.abs(newValue);
  119 +
  120 + newValue = getValueFromValueRange(
  121 + newValue,
  122 + (objectModel.specs?.dataType.specs as Specs).valueRange
  123 + );
  124 +
  125 + if (!isFloatType(originalDataType) && newValue % 1 !== 0) {
  126 + const message = `属性下发类型必须是整数,缩放因子为${scaling}`;
  127 + createMessage.warning(message);
  128 + throw Error(message);
  129 + }
  130 +
  131 + value = newValue;
  132 + }
  133 +
  134 + params.registerValues =
  135 + originalDataType === OriginalDataTypeEnum.BITS
  136 + ? await getCurrentBitCommand(entityId!, objectModel, value)
  137 + : getRegisterValueByOriginalDataType(value, originalDataType, {
  138 + bitMask,
  139 + registerNumber,
  140 + });
  141 +
  142 + if (!params.method) {
  143 + const message = '物模型操作类型无法进行写入';
  144 + createMessage.warning(message);
  145 + throw Error(message);
  146 + }
  147 +
  148 + return await genModbusCommand(params);
  149 + };
  150 +
  151 + return {
  152 + doCoverCommand,
  153 + };
  154 +}
... ...
  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 +}
... ...
... ... @@ -5,17 +5,19 @@ import { JSONEditor } from '/@/components/CodeEditor';
5 5 import { DeviceRecord, DeviceTypeEnum } from '/@/api/device/model/deviceModel';
6 6 import { h } from 'vue';
7 7 import { TaskTypeEnum } from '/@/views/task/center/config';
8   -import { AddressTypeEnum } from '/@/views/task/center/components/PollCommandInput';
9 8 import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue';
10 9 import { createImgPreview } from '/@/components/Preview';
11 10 import { uploadThumbnail } from '/@/api/configuration/center/configurationCenter';
12 11 import LockControlGroup from '/@/components/Form/src/components/LockControlGroup.vue';
13 12 import { OrgTreeSelect } from '/@/views/common/OrgTreeSelect';
14   -import { TransportTypeEnum } from '/@/enums/deviceEnum';
  13 +import { TCPProtocolTypeEnum, TransportTypeEnum } from '/@/enums/deviceEnum';
  14 +import { HexInput, InputTypeEnum } from '../../profiles/components/ObjectModelForm/HexInput';
  15 +import { DeviceProfileDetail } from '/@/api/device/model/deviceConfigModel';
15 16
16 17 useComponentRegister('JSONEditor', JSONEditor);
17 18 useComponentRegister('LockControlGroup', LockControlGroup);
18 19 useComponentRegister('OrgTreeSelect', OrgTreeSelect);
  20 +useComponentRegister('HexInput', HexInput);
19 21
20 22 export enum TypeEnum {
21 23 IS_GATEWAY = 'GATEWAY',
... ... @@ -108,6 +110,12 @@ export const step1Schemas: FormSchema[] = [
108 110 show: false,
109 111 },
110 112 {
  113 + field: 'tcpDeviceProtocol',
  114 + label: 'TCP设备协议类型',
  115 + component: 'Input',
  116 + show: false,
  117 + },
  118 + {
111 119 field: 'profileId',
112 120 label: '所属产品',
113 121 required: true,
... ... @@ -127,30 +135,35 @@ export const step1Schemas: FormSchema[] = [
127 135 const options = await queryDeviceProfileBy({
128 136 deviceType: formModel?.isUpdate ? formModel?.deviceType : null,
129 137 });
130   - const { profileId } = formModel;
131   - if (profileId) {
132   - const selectRecord = options.find((item) => item.tbProfileId === profileId);
133   - selectRecord && setFieldsValue({ transportType: selectRecord!.transportType });
134   - }
  138 +
135 139 return options;
136 140 },
137 141 labelField: 'name',
138 142 valueField: 'tbProfileId',
139   - onChange(
140   - _value: string,
141   - option: { deviceType: string; transportType: string; id: string }
142   - ) {
  143 + onChange(_value: string, option: DeviceProfileDetail) {
143 144 const { deviceType, transportType, id } = option;
144 145 setFieldsValue({
145 146 deviceType: deviceType,
146 147 transportType,
147 148 deviceProfileId: id,
148 149 gatewayId: null,
149   - codeType: transportType === TransportTypeEnum.TCP ? TaskTypeEnum.MODBUS_RTU : null,
150 150 code: null,
151 151 addressCode: null,
  152 + tcpDeviceProtocol: option?.profileData?.transportConfiguration?.protocol,
152 153 });
153 154 },
  155 + onOptionsChange(options: (DeviceProfileDetail & Record<'value', string>)[]) {
  156 + const { profileId } = formModel;
  157 + if (profileId) {
  158 + const selectRecord = options.find((item) => item.value === profileId);
  159 +
  160 + selectRecord &&
  161 + setFieldsValue({
  162 + transportType: selectRecord!.transportType,
  163 + tcpDeviceProtocol: selectRecord?.profileData?.transportConfiguration?.protocol,
  164 + });
  165 + }
  166 + },
154 167 showSearch: true,
155 168 placeholder: '请选择产品',
156 169 filterOption: (inputValue: string, option: Record<'label' | 'value', string>) =>
... ... @@ -177,35 +190,6 @@ export const step1Schemas: FormSchema[] = [
177 190 },
178 191 },
179 192 {
180   - field: 'codeType',
181   - label: '标识符类型',
182   - component: 'RadioGroup',
183   - dynamicRules({ values }) {
184   - return [
185   - {
186   - required: values?.transportType === TransportTypeEnum.TCP,
187   - message: '请输入设备标识符',
188   - },
189   - ];
190   - },
191   - // ifShow: ({ values }) =>
192   - // values?.transportType === TransportTypeEnum.TCP &&
193   - // (values.deviceType === DeviceTypeEnum.SENSOR || values.deviceType === DeviceTypeEnum.GATEWAY),
194   - ifShow: ({ values }) => values?.transportType === TransportTypeEnum.TCP,
195   - componentProps: ({ formActionType }) => {
196   - const { setFieldsValue } = formActionType;
197   - return {
198   - options: [
199   - { label: '自定义', value: TaskTypeEnum.CUSTOM },
200   - { label: 'ModBus', value: TaskTypeEnum.MODBUS_RTU },
201   - ],
202   - onChange() {
203   - setFieldsValue({ addressCode: null });
204   - },
205   - };
206   - },
207   - },
208   - {
209 193 field: 'addressCode',
210 194 label: '地址码',
211 195 dynamicRules({ values }) {
... ... @@ -213,32 +197,25 @@ export const step1Schemas: FormSchema[] = [
213 197 {
214 198 required:
215 199 values?.transportType === TransportTypeEnum.TCP &&
216   - values?.deviceType === DeviceTypeEnum.SENSOR,
217   - message: '请输入设备地址码',
  200 + values?.tcpDeviceProtocol === TCPProtocolTypeEnum.MODBUS_RTU,
  201 + message: '地址码范围为00~FF',
  202 + pattern: /^[0-9A-Fa-f]{2}$/,
218 203 },
219 204 ];
220 205 },
221   - component: 'RegisterAddressInput',
  206 + helpMessage: ['地址码范围为00~FF'],
  207 + component: 'HexInput',
222 208 changeEvent: 'update:value',
223 209 valueField: 'value',
224 210 componentProps: {
225   - type: AddressTypeEnum.DEC,
226   - maxValue: 247,
227   - minValue: 0,
228   - disabledSwitch: true,
229   - },
230   - // ifShow: ({ values }) => {
231   - // return (
232   - // values?.transportType === TransportTypeEnum.TCP &&
233   - // (values.deviceType === DeviceTypeEnum.SENSOR ||
234   - // values.deviceType === DeviceTypeEnum.GATEWAY) &&
235   - // values?.codeType === TaskTypeEnum.MODBUS_RTU
236   - // );
237   - // },
  211 + type: InputTypeEnum.HEX,
  212 + maxValue: parseInt('FF', 16),
  213 + placeholder: '请输入寄存器地址',
  214 + },
238 215 ifShow: ({ values }) => {
239 216 return (
240 217 values?.transportType === TransportTypeEnum.TCP &&
241   - values?.codeType === TaskTypeEnum.MODBUS_RTU
  218 + values?.tcpDeviceProtocol === TCPProtocolTypeEnum.MODBUS_RTU
242 219 );
243 220 },
244 221 },
... ... @@ -264,7 +241,8 @@ export const step1Schemas: FormSchema[] = [
264 241 },
265 242 ifShow: ({ values }) => {
266 243 return (
267   - values?.transportType === TransportTypeEnum.TCP && values?.codeType === TaskTypeEnum.CUSTOM
  244 + values?.transportType === TransportTypeEnum.TCP &&
  245 + values?.tcpDeviceProtocol === TaskTypeEnum.CUSTOM
268 246 );
269 247 },
270 248 },
... ...
... ... @@ -88,12 +88,12 @@
88 88 import { validatorLongitude, validatorLatitude } from '/@/utils/rules';
89 89 import { getOrganizationList } from '/@/api/system/system';
90 90 import { copyTransFun } from '/@/utils/fnUtils';
91   - import { TaskTypeEnum } from '/@/views/task/center/config';
92 91 import { toRaw } from 'vue';
93 92 import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue';
94 93 import { buildUUID } from '/@/utils/uuid';
95 94 import { useMessage } from '/@/hooks/web/useMessage';
96 95 import { computed } from 'vue';
  96 + import { TCPProtocolTypeEnum } from '/@/enums/deviceEnum';
97 97
98 98 export default defineComponent({
99 99 components: {
... ... @@ -405,10 +405,11 @@
405 405 icon: [{ uid: buildUUID(), name: 'name', url: deviceInfo.avatar } as FileItem],
406 406 });
407 407 }
  408 +
408 409 setFieldsValue({
409 410 ...data,
410 411 code: data?.code,
411   - addressCode: parseInt(data?.code || '', 16),
  412 + addressCode: data?.code,
412 413 isUpdate: unref(isUpdate1),
413 414 });
414 415 }
... ... @@ -424,9 +425,9 @@
424 425 ...(value?.code || value?.addressCode
425 426 ? {
426 427 code:
427   - value?.codeType === TaskTypeEnum.CUSTOM
  428 + value?.tcpDeviceProtocol === TCPProtocolTypeEnum.CUSTOM
428 429 ? value?.code
429   - : (value?.addressCode || '').toString(16).padStart(2, '0').toUpperCase(),
  430 + : value?.addressCode || '',
430 431 }
431 432 : {}),
432 433 };
... ...
... ... @@ -10,7 +10,9 @@ import {
10 10 ServiceCallTypeEnum,
11 11 CommandDeliveryWayEnum,
12 12 CommandDeliveryWayNameEnum,
  13 + TCPProtocolTypeEnum,
13 14 } from '/@/enums/deviceEnum';
  15 +import { DeviceRecord, DeviceTypeEnum } from '/@/api/device/model/deviceModel';
14 16
15 17 export interface CommandDeliveryFormFieldType {
16 18 [CommandFieldsEnum.COMMAND_TYPE]: CommandTypeEnum;
... ... @@ -36,10 +38,16 @@ export enum CommandFieldsEnum {
36 38
37 39 useComponentRegister('JSONEditor', JSONEditor);
38 40
39   -export const CommandSchemas = (
40   - transportType: TransportTypeEnum,
41   - deviceProfileId: string
42   -): FormSchema[] => {
  41 +export const CommandSchemas = (deviceRecord: DeviceRecord): FormSchema[] => {
  42 + const { transportType, deviceProfileId, deviceType } = deviceRecord;
  43 +
  44 + const isTCPTransport = transportType === TransportTypeEnum.TCP;
  45 +
  46 + const isTCPModbus =
  47 + isTCPTransport &&
  48 + deviceRecord.deviceProfile?.profileData?.transportConfiguration?.protocol ===
  49 + TCPProtocolTypeEnum.MODBUS_RTU;
  50 +
43 51 return [
44 52 {
45 53 field: CommandFieldsEnum.COMMAND_TYPE,
... ... @@ -49,11 +57,19 @@ export const CommandSchemas = (
49 57 required: true,
50 58 componentProps: ({ formActionType }) => {
51 59 const { setFieldsValue } = formActionType;
  60 +
  61 + const getOptions = () => {
  62 + const options = [{ label: CommandTypeNameEnum.CUSTOM, value: CommandTypeEnum.CUSTOM }];
  63 +
  64 + if (isTCPModbus || (isTCPTransport && deviceType === DeviceTypeEnum.SENSOR))
  65 + return options;
  66 +
  67 + options.push({ label: CommandTypeNameEnum.SERVICE, value: CommandTypeEnum.SERVICE });
  68 +
  69 + return options;
  70 + };
52 71 return {
53   - options: [
54   - { label: CommandTypeNameEnum.CUSTOM, value: CommandTypeEnum.CUSTOM },
55   - { label: CommandTypeNameEnum.SERVICE, value: CommandTypeEnum.SERVICE },
56   - ],
  72 + options: getOptions(),
57 73 onChange() {
58 74 setFieldsValue({
59 75 [CommandFieldsEnum.SERVICE]: null,
... ...
... ... @@ -37,14 +37,15 @@
37 37 defineEmits(['register']);
38 38
39 39 const thingsModelFormRef = ref<InstanceType<typeof ThingsModelForm>>();
40   - const deviceDetail = ref<DeviceRecord>();
  40 + const props = defineProps<{
  41 + deviceDetail: DeviceRecord;
  42 + }>();
41 43
42 44 const [registerModal, { setModalProps }] = useModalInner(
43 45 (params: ModalParamsType<DeviceRecord>) => {
44 46 const { record } = params;
45   - deviceDetail.value = record;
46 47 setProps({
47   - schemas: CommandSchemas(record.transportType as TransportTypeEnum, record.deviceProfileId),
  48 + schemas: CommandSchemas(record),
48 49 });
49 50 }
50 51 );
... ... @@ -72,7 +73,7 @@
72 73 };
73 74
74 75 const handleValidateDeviceActive = async (): Promise<boolean> => {
75   - const result = await getDeviceActiveTime(unref(deviceDetail)!.tbDeviceId);
  76 + const result = await getDeviceActiveTime(unref(props.deviceDetail)!.tbDeviceId);
76 77 const [firstItem] = result;
77 78 return !!firstItem.value;
78 79 };
... ... @@ -83,7 +84,7 @@
83 84 ) => {
84 85 const { commandType, service } = values;
85 86
86   - const isTcpDevice = unref(deviceDetail)?.transportType === TransportTypeEnum.TCP;
  87 + const isTcpDevice = unref(props.deviceDetail)?.transportType === TransportTypeEnum.TCP;
87 88 if (commandType === CommandTypeEnum.CUSTOM) {
88 89 if (isTcpDevice) {
89 90 return values.tcpCommandValue;
... ... @@ -124,7 +125,7 @@
124 125 params: handleCommandParams(values, serviceCommand),
125 126 };
126 127
127   - await commandIssuanceApi(callType, unref(deviceDetail)!.tbDeviceId, rpcCommands);
  128 + await commandIssuanceApi(callType, unref(props.deviceDetail)!.tbDeviceId, rpcCommands);
128 129
129 130 createMessage.success('命令下发成功');
130 131 } finally {
... ...
1   -import { DeviceModelOfMatterAttrs } from '/@/api/device/model/deviceModel';
  1 +import { DeviceModelOfMatterAttrs, DeviceRecord } from '/@/api/device/model/deviceModel';
2 2 import { DataType, Specs } from '/@/api/device/model/modelOfMatterModel';
  3 +import { TCPProtocolTypeEnum, TransportTypeEnum } from '/@/enums/deviceEnum';
3 4 import { DataTypeEnum } from '/@/enums/objectModelEnum';
4 5 import { isArray } from '/@/utils/is';
5 6
... ... @@ -31,18 +32,26 @@ export interface SocketInfoDataSourceItemType extends BaseAdditionalInfo {
31 32 }
32 33
33 34 export function buildTableDataSourceByObjectModel(
34   - models: DeviceModelOfMatterAttrs[]
  35 + models: DeviceModelOfMatterAttrs[],
  36 + deviceDetail: DeviceRecord
35 37 ): SocketInfoDataSourceItemType[] {
  38 + const isTCPTransportType = deviceDetail.transportType === TransportTypeEnum.TCP;
  39 +
  40 + const isModbusDevice =
  41 + isTCPTransportType &&
  42 + deviceDetail?.deviceProfile?.profileData?.transportConfiguration?.protocol ===
  43 + TCPProtocolTypeEnum.MODBUS_RTU;
  44 +
36 45 function getAdditionalInfoByDataType(dataType?: DataType) {
37 46 const { specs, specsList, type } = dataType || {};
38 47 if (isArray(specs)) return {};
39 48 const { unit, boolClose, boolOpen, unitName } = (specs as Partial<Specs>) || {};
40 49 const result = { unit, boolClose, boolOpen, unitName };
41   - if (type == DataTypeEnum.ENUM && specsList && specsList.length) {
  50 + if ((type == DataTypeEnum.ENUM && specsList && specsList.length) || isModbusDevice) {
42 51 Reflect.set(
43 52 result,
44 53 'enum',
45   - specsList.reduce((prev, next) => ({ ...prev, [next.value!]: next.name }), {})
  54 + (specsList || []).reduce((prev, next) => ({ ...prev, [next.value!]: next.name }), {})
46 55 );
47 56 }
48 57
... ...
... ... @@ -19,7 +19,7 @@
19 19 import { ModeSwitchButton, EnumTableCardMode } from '/@/components/Widget';
20 20 import { toRaw } from 'vue';
21 21 import { DataActionModeEnum } from '/@/enums/toolEnum';
22   - import { ReadAndWriteEnum } from '/@/enums/deviceEnum';
  22 + import { ReadAndWriteEnum, TCPProtocolTypeEnum, TransportTypeEnum } from '/@/enums/deviceEnum';
23 23 import { ObjectModelCommandDeliveryModal } from './ObjectModelCommandDeliveryModal';
24 24 import { ModalParamsType } from '/#/utils';
25 25 import { AreaChartOutlined } from '@ant-design/icons-vue';
... ... @@ -275,17 +275,30 @@
275 275 openModal(true);
276 276 };
277 277
  278 + const getIsModbusDevice = computed(
  279 + () =>
  280 + props.deviceDetail.transportType === TransportTypeEnum.TCP &&
  281 + props.deviceDetail?.deviceProfile?.profileData?.transportConfiguration?.protocol ===
  282 + TCPProtocolTypeEnum.MODBUS_RTU
  283 + );
  284 +
278 285 onMounted(async () => {
279 286 const { deviceProfileId } = props.deviceDetail;
280 287 const value = await getDeviceAttrs({ deviceProfileId });
281 288 socketInfo.attrKeys = isArray(value) ? value.map((item) => item.identifier) : [];
282   - socketInfo.rawDataSource = buildTableDataSourceByObjectModel(value);
  289 + socketInfo.rawDataSource = buildTableDataSourceByObjectModel(value, props.deviceDetail);
283 290 setDataSource();
284 291 open();
285 292 });
286 293
287 294 const formatValue = (item: SocketInfoDataSourceItemType) => {
288   - if (isNullOrUnDef(item)) return '--';
  295 + if (isNullOrUnDef(item) || isNullOrUnDef(item.value)) return '--';
  296 +
  297 + if (unref(getIsModbusDevice) && item.type === DataTypeEnum.BOOL) {
  298 + const _result = Reflect.get(item.enum || {}, item.value as string);
  299 + return isNullOrUnDef(_result) ? item.value : _result;
  300 + }
  301 +
289 302 switch (item.type) {
290 303 case DataTypeEnum.BOOL:
291 304 return !!Number(item.value) ? item.boolOpen : item.boolClose;
... ... @@ -300,7 +313,7 @@
300 313 const handleSendCommandModal = (data: SocketInfoDataSourceItemType) => {
301 314 openSendCommandModal(true, {
302 315 mode: DataActionModeEnum.READ,
303   - record: { ...toRaw(data.detail), deviceDetail: props.deviceDetail as any },
  316 + record: { objectModel: toRaw(unref(data.detail)), deviceDetail: props.deviceDetail as any },
304 317 } as ModalParamsType);
305 318 };
306 319
... ...
1   -import { StructJSON } from '/@/api/device/model/modelOfMatterModel';
2   -import { FormSchema } from '/@/components/Form';
3   -import { validateTCPCustomCommand } from '/@/components/Form/src/components/ThingsModelForm';
4   -import { DataTypeEnum } from '/@/enums/objectModelEnum';
5   -
6   -const InsertString = (t, c, n) => {
7   - const r: string | number[] = [];
8   -
9   - for (let i = 0; i * 2 < t.length; i++) {
10   - r.push(t.substr(i * 2, n));
11   - }
12   - return r.join(c);
13   -};
14   -const FillString = (t, c, n, b) => {
15   - if (t == '' || c.length != 1 || n <= t.length) {
16   - return t;
17   - }
18   - const l = t.length;
19   -
20   - for (let i = 0; i < n - l; i++) {
21   - if (b == true) {
22   - t = c + t;
23   - } else {
24   - t += c;
25   - }
26   - }
27   - return t;
28   -};
29   -const SingleToHex = (t) => {
30   - if (t == '') {
31   - return '';
32   - }
33   - t = parseFloat(t);
34   -
35   - if (isNaN(t) == true) {
36   - return 'Error';
37   - }
38   - if (t == 0) {
39   - return '00000000';
40   - }
41   - let s, e, m;
42   -
43   - if (t > 0) {
44   - s = 0;
45   - } else {
46   - s = 1;
47   -
48   - t = 0 - t;
49   - }
50   - m = t.toString(2);
51   -
52   - if (m >= 1) {
53   - if (m.indexOf('.') == -1) {
54   - m = m + '.0';
55   - }
56   - e = m.indexOf('.') - 1;
57   - } else {
58   - e = 1 - m.indexOf('1');
59   - }
60   - if (e >= 0) {
61   - m = m.replace('.', '');
62   - } else {
63   - m = m.substring(m.indexOf('1'));
64   - }
65   - if (m.length > 24) {
66   - m = m.substr(0, 24);
67   - } else {
68   - m = FillString(m, '0', 24, false);
69   - }
70   - m = m.substring(1);
71   -
72   - e = (e + 127).toString(2);
73   -
74   - e = FillString(e, '0', 8, true);
75   -
76   - let r = parseInt(s + e + m, 2).toString(16);
77   -
78   - r = FillString(r, '0', 8, true);
79   -
80   - return InsertString(r, ' ', 2).toUpperCase();
81   -};
82   -
83   -const FormatHex = (t, n, ie) => {
84   - const r: string[] = [];
85   -
86   - let s = '';
87   -
88   - let c = 0;
89   -
90   - for (let i = 0; i < t.length; i++) {
91   - if (t.charAt(i) != ' ') {
92   - s += t.charAt(i);
93   -
94   - c += 1;
95   -
96   - if (c == n) {
97   - r.push(s);
98   -
99   - s = '';
100   -
101   - c = 0;
102   - }
103   - }
104   - if (ie == false) {
105   - if (i == t.length - 1 && s != '') {
106   - r.push(s);
107   - }
108   - }
109   - }
110   - return r.join('\n');
111   -};
112   -const FormatHexBatch = (t, n, ie) => {
113   - const a = t.split('\n');
114   -
115   - const r: string[] = [];
116   -
117   - for (let i = 0; i < a.length; i++) {
118   - r[i] = FormatHex(a[i], n, ie);
119   - }
120   - return r.join('\n');
121   -};
122   -const SingleToHexBatch = (t) => {
123   - const a = t.split('\n');
124   -
125   - const r: string[] = [];
126   -
127   - for (let i = 0; i < a.length; i++) {
128   - r[i] = SingleToHex(a[i]);
129   - }
130   - return r.join('\r\n');
131   -};
132   -
133   -const formSchemasConfig = (schemas: StructJSON, actionType: string): FormSchema[] => {
134   - const { identifier, functionName, dataType } = schemas;
135   -
136   - if (dataType?.type === DataTypeEnum.STRING) {
137   - return [
138   - {
139   - field: identifier,
140   - label: functionName!,
141   - component: 'Input',
142   - rules: [{ required: true, validator: validateTCPCustomCommand }],
143   - componentProps: {
144   - placeholder: `请输入${functionName}`,
145   - },
146   - },
147   - ];
148   - }
149   -
150   - if (actionType == '06') {
151   - return [
152   - {
153   - field: identifier,
154   - label: functionName!,
155   - component: 'InputNumber',
156   - rules: [{ required: true, message: '请输入正数' }],
157   - componentProps: {
158   - min: 0,
159   - precision: 2,
160   - placeholder: `请输入正数`,
161   - },
162   - },
163   - ];
164   - } else if (actionType == '05') {
165   - return [
166   - {
167   - field: identifier,
168   - label: functionName!,
169   - component: 'InputNumber',
170   - rules: [{ required: true, message: '请输入值' }],
171   - componentProps: {
172   - min: 0,
173   - max: 1,
174   - precision: 0,
175   - placeholder: `请输入0或1`,
176   - },
177   - },
178   - ];
179   - } else {
180   - return [
181   - {
182   - field: identifier,
183   - label: functionName!,
184   - component: 'InputNumber',
185   - rules: [{ required: true, message: '请输入值' }],
186   - componentProps: {
187   - placeholder: `请输入数字`,
188   - precision: 2,
189   - },
190   - },
191   - ];
192   - }
193   -};
194   -
195   -export {
196   - InsertString,
197   - FillString,
198   - SingleToHex,
199   - FormatHex,
200   - FormatHexBatch,
201   - SingleToHexBatch,
202   - formSchemasConfig,
203   -};
1 1 <script lang="ts" setup>
2   - import { ref } from 'vue';
3   - import { useGenDynamicForm } from './useGenDynamicForm';
  2 + import { ref, unref } from 'vue';
  3 + import { useGenerateFormSchemasByObjectModel } from './useGenerateFormSchemasByObjectModel';
4 4 import { ModalParamsType } from '/#/utils';
5   - import { DeviceModelOfMatterAttrs } from '/@/api/device/model/deviceModel';
6   - import { StructJSON } from '/@/api/device/model/modelOfMatterModel';
  5 + import { DeviceModelOfMatterAttrs, DeviceRecord } from '/@/api/device/model/deviceModel';
7 6 import { BasicForm, useForm } from '/@/components/Form';
8 7 import { BasicModal, useModalInner } from '/@/components/Modal';
9   - import { sendCommandOneway } from '/@/api/dataBoard';
  8 + import { CommandTypeEnum } from '/@/enums/deviceEnum';
  9 + import { DataTypeEnum, FunctionTypeEnum } from '/@/enums/objectModelEnum';
  10 + import { useCommandDelivery } from '/@/hooks/business/useCommandDelivery';
10 11 import { useMessage } from '/@/hooks/web/useMessage';
11   - import { unref } from 'vue';
12   - import { genModbusCommand } from '/@/api/task';
13   - import { TaskTypeEnum } from '/@/views/task/center/config';
14   - import { SingleToHex, formSchemasConfig } from './config';
15   - import { DataTypeEnum } from '/@/enums/objectModelEnum';
16   - import { TransportTypeEnum } from '/@/enums/deviceEnum';
17 12
18 13 defineEmits(['register']);
19 14 const props = defineProps<{ deviceId: string; deviceName: string }>();
... ... @@ -24,110 +19,25 @@
24 19 layout: 'vertical',
25 20 });
26 21
27   - const { genForm, transformValue } = useGenDynamicForm();
28   -
29   - const keys = ref<string[]>([]);
30   -
31   - const modBUSForm = ref<any>({});
32   - const isShowModBUS = ref<Boolean>(false); //用于判断标识符类型是否时自定义还是modBUS
33   - const isShowActionType = ref<Boolean>(true); //判断设备属性标识符为modBus时没有填写扩展描述
34   - const formField = ref(''); //存一个表单取值的field
35   - const zoomFactorValue = ref<number>(1); //缩放因子
36   - const isShowMultiply = ref<Boolean>(false); // 只有tcp --> int和double类型才相乘缩放因子
37   - const deviceTransportType = ref<string>();
38   - const objectDataType = ref<DataTypeEnum>();
39   -
40   - const [register] = useModalInner(async (params: ModalParamsType<DeviceModelOfMatterAttrs>) => {
41   - const { record } = params;
42   - const { name, detail, identifier, deviceDetail, extensionDesc } = record;
43   - const { dataType } = detail;
44   - const { type } = dataType || {};
45   - const { codeType, deviceProfile, code } = deviceDetail || {};
46   - const { transportType } = deviceProfile || {};
47   - const { registerAddress, actionType, zoomFactor } = extensionDesc || {}; //获取扩展描述内容
48   - formField.value = identifier;
49   - zoomFactorValue.value = zoomFactor ? Number(zoomFactor) : 1;
50   - isShowMultiply.value = type == 'INT' || type == 'DOUBLE' ? true : false;
51   - deviceTransportType.value = transportType;
52   - objectDataType.value = type;
53   -
54   - let schemas = [{ dataType: dataType, identifier, functionName: name } as StructJSON];
55   -
56   - if (type === DataTypeEnum.STRUCT) {
57   - schemas = dataType?.specs as StructJSON[];
58   - }
59   -
60   - keys.value = schemas.map((item) => {
61   - return item.identifier!;
62   - });
63   - isShowActionType.value = actionType ? true : false; //判断modBUS类型时 物模型是否填写扩展描述
64   -
65   - //是modBUS类型的就用另外的表单
66   - //判断是否是TCP ==> modBus的下发命令
67   - if (codeType === TaskTypeEnum.MODBUS_RTU && transportType == TransportTypeEnum.TCP) {
68   - isShowModBUS.value = true;
69   - modBUSForm.value = {
70   - crc: 'CRC_16_LOWER',
71   - deviceCode: code,
72   - method: actionType == '16' ? '10' : actionType,
73   - registerAddress,
74   - registerNumber: 1,
75   - registerValues: [],
76   - };
77   - setProps({ schemas: formSchemasConfig(schemas[0], actionType) });
78   - } else {
79   - isShowModBUS.value = false;
80   - if (transportType === TransportTypeEnum.TCP) {
81   - setProps({
82   - schemas: [
83   - {
84   - field: 'command',
85   - label: name,
86   - component: 'Input',
87   - required: true,
88   - rules: [
89   - {
90   - pattern: /^[\s0-9a-fA-F]+$/,
91   - required: true,
92   - message: '请输入ASCII或HEX服务命令(0~9/A~F)',
93   - },
94   - ],
95   - componentProps: {
96   - placeholder: `请输入${name}`,
97   - },
98   - },
99   - ],
100   - });
101   - } else {
102   - const formSchemas = genForm(schemas);
103   - setProps({ schemas: formSchemas });
104   - }
105   - }
106   -
107   - resetFields();
108   - });
109   -
110   - const getArray = (values) => {
111   - const str = values.replace(/\s+/g, '');
112   - const array: any = [];
113   -
114   - for (let i = 0; i < str.length; i += 4) {
115   - const chunk = parseInt(str.substring(i, i + 4), 16);
116   - array.push(chunk);
  22 + const { getFormByObjectModel } = useGenerateFormSchemasByObjectModel();
  23 +
  24 + const currentParams = ref<{
  25 + deviceDetail: DeviceRecord;
  26 + objectModel: DeviceModelOfMatterAttrs;
  27 + }>();
  28 +
  29 + const [register] = useModalInner(
  30 + async (
  31 + params: ModalParamsType<{ deviceDetail: DeviceRecord; objectModel: DeviceModelOfMatterAttrs }>
  32 + ) => {
  33 + const { record } = params;
  34 + currentParams.value = record;
  35 + const { objectModel, deviceDetail } = record;
  36 + const schemas = getFormByObjectModel(objectModel, deviceDetail);
  37 + setProps({ schemas });
  38 + resetFields();
117 39 }
118   - return array;
119   - };
120   -
121   - // 获取小数
122   - const getFloatPart = (number: string | number) => {
123   - const isLessZero = Number(number) < 0;
124   - number = number.toString();
125   - const floatPartStartIndex = number.indexOf('.');
126   - const value = ~floatPartStartIndex
127   - ? `${isLessZero ? '-' : ''}0.${number.substring(floatPartStartIndex + 1)}`
128   - : '0';
129   - return Number(value);
130   - };
  40 + );
131 41
132 42 const { createMessage } = useMessage();
133 43 const loading = ref(false);
... ... @@ -136,82 +46,29 @@
136 46 loading.value = true;
137 47 if (!props.deviceId) return;
138 48
139   - const sendValue = ref({});
140   - //判断tcp类型 标识符是自定义还是ModBus
141   - if (unref(objectDataType) === DataTypeEnum.STRING) {
142   - const flag = await validate();
143   - if (!flag) return;
144   - const value = getFieldsValue()[unref(formField)];
145   - sendValue.value = value;
146   - } else if (unref(isShowModBUS)) {
147   - if (!unref(isShowActionType)) {
148   - createMessage.warning('当前物模型扩展描述没有填写');
149   - return;
150   - }
151   - const flag = await validate();
152   - if (!flag) return;
153   -
154   - const oldValue = getFieldsValue()[unref(formField)];
155   - modBUSForm.value.registerNumber = 1;
156   - modBUSForm.value.registerValues = [oldValue];
157   -
158   - if (unref(isShowMultiply) && unref(modBUSForm).method == '06') {
159   - const newValue =
160   - Math.trunc(oldValue) * unref(zoomFactorValue) +
161   - getFloatPart(oldValue) * unref(zoomFactorValue);
162   - if (newValue % 1 != 0) {
163   - createMessage.warning(`属性下发类型必须是整数,缩放因子为${unref(zoomFactorValue)}`);
164   - return;
165   - }
166   -
167   - if (oldValue * unref(zoomFactorValue) > 65535) {
168   - createMessage.warning(`属性下发值不能超过65535,缩放因子是${unref(zoomFactorValue)}`);
169   - return;
170   - }
171   - //bool类型的就不用去乘缩放因子了
172   - modBUSForm.value.registerValues = [newValue];
173   - }
  49 + await validate();
  50 + const { deviceDetail, objectModel } = unref(currentParams) || {};
174 51
175   - if (unref(modBUSForm).method == '16' || unref(modBUSForm).method == '10') {
176   - const regex = /^-?\d+(\.\d{0,2})?$/;
177   - const values =
178   - Math.trunc(oldValue) * unref(zoomFactorValue) +
179   - getFloatPart(oldValue) * unref(zoomFactorValue);
180   -
181   - if (!regex.test(values as any)) {
182   - createMessage.warning(`属性下发值精确到两位小数,缩放因子是${unref(zoomFactorValue)}`);
183   - return;
184   - }
185   -
186   - const newValue =
187   - values == 0 ? [0, 0] : getArray(SingleToHex(unref(isShowMultiply) ? values : oldValue));
188   - modBUSForm.value.registerValues = newValue;
189   - modBUSForm.value.registerNumber = 2;
190   - modBUSForm.value.method = '10';
191   - }
192   -
193   - sendValue.value = await genModbusCommand(unref(modBUSForm));
194   - } else {
195   - await validate();
196   - const _value = transformValue(getFieldsValue());
197   - sendValue.value = unref(keys).reduce((prev, next) => {
198   - return { ...prev, [next]: _value[next] };
199   - }, {});
200   -
201   - // tcp 设备下发字符串
202   - if (unref(deviceTransportType) === TransportTypeEnum.TCP) {
203   - sendValue.value = Object.values(_value).join('').replaceAll(/\s/g, '');
204   - }
  52 + let value = getFieldsValue();
  53 + if (objectModel?.detail.dataType.type !== DataTypeEnum.STRUCT) {
  54 + value = value[objectModel!.identifier];
205 55 }
206 56
207   - await sendCommandOneway({
208   - deviceId: props.deviceId,
209   - value: {
210   - persistent: true,
211   - method: 'methodThingskit',
212   - params: unref(sendValue),
  57 + const { doCommandDelivery } = useCommandDelivery();
  58 +
  59 + await doCommandDelivery({
  60 + deviceDetail,
  61 + objectModel: {
  62 + ...(objectModel || {}),
  63 + functionName: objectModel!.name,
  64 + identifier: objectModel!.identifier,
  65 + functionType: FunctionTypeEnum.PROPERTIES,
  66 + specs: objectModel?.detail,
213 67 },
  68 + cmdType: CommandTypeEnum.ATTRIBUTE,
  69 + value,
214 70 });
  71 +
215 72 createMessage.success('属性下发成功');
216 73 } catch (error) {
217 74 throw error;
... ...
src/views/device/list/cpns/tabs/ObjectModelCommandDeliveryModal/useGenerateFormSchemasByObjectModel.ts renamed from src/views/device/list/cpns/tabs/ObjectModelCommandDeliveryModal/useGenDynamicForm.ts
  1 +import { unref } from 'vue';
  2 +import { DeviceModelOfMatterAttrs, DeviceRecord } from '/@/api/device/model/deviceModel';
1 3 import { DataType, Specs, StructJSON } from '/@/api/device/model/modelOfMatterModel';
2   -import { JSONEditor } from '/@/components/CodeEditor';
3   -import { FormSchema, useComponentRegister } from '/@/components/Form';
4   -import { DataTypeEnum } from '/@/enums/objectModelEnum';
5   -import { useJsonParse } from '/@/hooks/business/useJsonParse';
  4 +import { FormSchema } from '/@/components/Form';
  5 +import { DataTypeEnum, OriginalDataTypeEnum } from '/@/enums/objectModelEnum';
  6 +import { TCPProtocolTypeEnum } from '/@/enums/deviceEnum';
6 7
7 8 export interface BasicCreateFormParams {
8 9 identifier: string;
... ... @@ -10,11 +11,9 @@ export interface BasicCreateFormParams {
10 11 dataType: DataType;
11 12 }
12 13
13   -useComponentRegister('JSONEditor', JSONEditor);
14   -
15 14 const validateDouble = (value: number, min?: number | string, max?: number | string) => {
16   - min = Number(min) || Number.MIN_SAFE_INTEGER;
17   - max = Number(max) || Number.MAX_SAFE_INTEGER;
  15 + min = Number(min) ?? Number.MIN_SAFE_INTEGER;
  16 + max = Number(max) ?? Number.MAX_SAFE_INTEGER;
18 17
19 18 return {
20 19 flag: value < min || value > max,
... ... @@ -22,7 +21,7 @@ const validateDouble = (value: number, min?: number | string, max?: number | str
22 21 };
23 22 };
24 23
25   -export const useGenDynamicForm = () => {
  24 +export const useGenerateFormSchemasByObjectModel = () => {
26 25 const createInputNumber = ({
27 26 identifier,
28 27 functionName,
... ... @@ -128,24 +127,39 @@ export const useGenDynamicForm = () => {
128 127 };
129 128 };
130 129
131   - const createInputJson = ({ identifier, functionName }: BasicCreateFormParams): FormSchema => {
  130 + const createModbusValueInput = (objectModel: DeviceModelOfMatterAttrs): FormSchema => {
  131 + const { identifier, name, detail, extensionDesc } = objectModel;
  132 +
  133 + const { dataType } = detail || {};
  134 + const { specs } = dataType || {};
  135 + const { valueRange } = specs as Specs;
  136 + const { max, min } = valueRange || {};
  137 +
  138 + const isStringType = extensionDesc?.originalDataType === OriginalDataTypeEnum.STRING;
132 139 return {
133 140 field: identifier,
134   - label: functionName,
135   - component: 'JSONEditor',
136   - valueField: 'value',
137   - changeEvent: 'update:value',
138   - rules: [
139   - {
140   - validator: (_rule, value: any) => {
141   - if (value) {
142   - const { flag } = useJsonParse(value);
143   - if (!flag) return Promise.reject(`${functionName} 不是一个有效的JSON对象`);
144   - }
145   - return Promise.resolve();
146   - },
147   - },
148   - ],
  141 + label: name,
  142 + component: isStringType ? 'Input' : 'InputNumber',
  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(`${name}${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: `请输入${name}`,
  161 + // precision: floatType.includes(extensionDesc!.originalDataType) ? 2 : 0,
  162 + },
149 163 };
150 164 };
151 165
... ... @@ -154,48 +168,38 @@ export const useGenDynamicForm = () => {
154 168 [DataTypeEnum.NUMBER_DOUBLE]: createInputNumber,
155 169 [DataTypeEnum.NUMBER_INT]: createInputNumber,
156 170 [DataTypeEnum.STRING]: createInput,
157   - [DataTypeEnum.STRUCT]: createInputJson,
158 171 [DataTypeEnum.ENUM]: createEnumSelect,
159 172 };
160 173
161   - const fieldTypeMap = new Map<string, DataTypeEnum>();
162   - const genForm = (schemas: StructJSON[]) => {
163   - fieldTypeMap.clear();
164   - const formSchema = schemas.map((item) => {
165   - const { functionName, identifier, dataType } = item;
  174 + const getFormByObjectModel = (
  175 + objectModel: DeviceModelOfMatterAttrs,
  176 + deviceDetail: DeviceRecord
  177 + ): FormSchema[] => {
  178 + const { name, identifier, detail } = objectModel;
166 179
167   - const { type } = dataType || {};
  180 + const isTCPModbusProduct =
  181 + unref(deviceDetail).deviceProfile?.profileData?.transportConfiguration?.protocol ===
  182 + TCPProtocolTypeEnum.MODBUS_RTU;
168 183
169   - fieldTypeMap.set(identifier!, dataType!.type);
170   - const method = schemaMethod[type!];
  184 + const { dataType } = detail;
  185 + const { type } = dataType || {};
171 186
172   - const formSchema = method?.({
173   - identifier: identifier!,
174   - functionName: functionName!,
175   - dataType: dataType!,
176   - });
  187 + if (isTCPModbusProduct) {
  188 + return [createModbusValueInput(objectModel)];
  189 + }
177 190
178   - return formSchema;
179   - });
  191 + if (type === DataTypeEnum.STRUCT) {
  192 + return (dataType?.specs as StructJSON[]).map((item) => {
  193 + const { functionName, identifier, dataType } = item;
  194 + const { type } = dataType || {};
180 195
181   - return formSchema.filter(Boolean);
182   - };
  196 + return schemaMethod[type!]?.({ identifier, functionName, dataType });
  197 + });
  198 + }
183 199
184   - const transformValue = (value: Recordable) => {
185   - return Object.keys(value || {}).reduce((prev, next) => {
186   - const dataType = fieldTypeMap.get(next)!;
187   -
188   - let itemValue = value[next];
189   - if (dataType === DataTypeEnum.STRUCT) {
190   - const { value } = useJsonParse(itemValue);
191   - itemValue = value;
192   - }
193   - return {
194   - ...prev,
195   - [next]: itemValue,
196   - };
197   - }, {} as Recordable);
  200 + const result = schemaMethod[type!]?.({ identifier, functionName: name, dataType: dataType! });
  201 + return result ? [result] : [];
198 202 };
199 203
200   - return { genForm, transformValue };
  204 + return { getFormByObjectModel };
201 205 };
... ...
... ... @@ -39,45 +39,37 @@ export const validateValueRange: ValidatorRule['validator'] = (
39 39 return Promise.resolve();
40 40 };
41 41
42   -export const validateFunctionName: ValidatorRule['validator'] = (_rule, value: any) => {
43   - if (/^[a-zA-Z0-9_\-\u4e00-\u9fa5]+$/.test(value)) return Promise.resolve();
44   - return Promise.reject('支持中文、大小写字母、数字、短划线、下划线.');
45   -};
46   -
47   -export const validateIdentifier: ValidatorRule['validator'] = (_rule, value: any) => {
48   - if (/^[a-zA-Z0-9_]+$/.test(value)) {
49   - return Promise.resolve();
50   - }
51   - return Promise.reject('支持大小写字母、数字和下划线.');
52   -};
53   -
54 42 export const createFunctionNameFormItem = (params: Partial<FormSchema> = {}): FormSchema => {
  43 + const helpMessage = '支持中文、大小写字母、数字、短划线、下划线。';
55 44 return {
56 45 field: FormFieldsEnum.FUNCTION_NAME,
57 46 label: FormFieldsNameEnum.FUNCTION_NAME,
58 47 component: 'Input',
59 48 required: true,
60   - helpMessage: '支持中文、大小写字母、数字、短划线、下划线。',
  49 + helpMessage,
61 50 rules: [
62 51 {
63 52 required: true,
64   - validator: validateFunctionName,
  53 + message: helpMessage,
  54 + pattern: /^[a-zA-Z0-9_\-\u4e00-\u9fa5\(\)]+$/,
65 55 },
66 56 ],
67 57 componentProps: {
68 58 placeholder: `请输入${FormFieldsNameEnum.FUNCTION_NAME}`,
  59 + maxlength: 32,
69 60 },
70 61 ...params,
71 62 };
72 63 };
73 64
74 65 export const createIdentifierFormItem = (params: Partial<FormSchema> = {}): FormSchema => {
  66 + const helpMessage = '支持大小写字母、数字和下划线.';
75 67 return {
76 68 field: FormFieldsEnum.IDENTIFIER,
77 69 label: FormFieldsNameEnum.IDENTIFIER,
78 70 required: true,
79 71 component: 'Input',
80   - helpMessage: '支持大小写字母、数字和下划线.',
  72 + helpMessage,
81 73 componentProps: {
82 74 maxLength: 128,
83 75 placeholder: '请输入标识符',
... ... @@ -85,7 +77,8 @@ export const createIdentifierFormItem = (params: Partial<FormSchema> = {}): Form
85 77 rules: [
86 78 {
87 79 required: true,
88   - validator: validateIdentifier,
  80 + message: helpMessage,
  81 + pattern: /^[a-zA-Z0-9_\-\u4e00-\u9fa5\(\)]+$/,
89 82 },
90 83 ],
91 84 ...params,
... ... @@ -330,7 +323,6 @@ export const getFormSchemas = (dataType: DataTypeEnum[], showRemark: boolean): F
330 323 model[FormFieldsEnum.FUNCTION_TYPE]
331 324 ),
332 325 },
333   -
334 326 {
335 327 field: FormFieldsEnum.REMARK,
336 328 label: FormFieldsNameEnum.REMARK,
... ...
... ... @@ -20,7 +20,7 @@
20 20 }
21 21 );
22 22
23   - defineEmits<{
  23 + const emits = defineEmits<{
24 24 (event: 'field-value-change', field: string, value: any): void;
25 25 }>();
26 26
... ... @@ -37,6 +37,9 @@
37 37 enumListRef,
38 38 });
39 39
  40 + const handleFieldValueChang = (field: string, value: any) =>
  41 + emits('field-value-change', field, value);
  42 +
40 43 defineExpose({ getFieldsValue, setFieldsValue, validate, resetFieldsValue });
41 44 </script>
42 45
... ... @@ -45,7 +48,7 @@
45 48 @register="register"
46 49 class="data-type-form"
47 50 :disabled="mode === DataActionModeEnum.READ"
48   - @field-value-change="(field, value) => $emit('field-value-change', field, value)"
  51 + @field-value-change="handleFieldValueChang"
49 52 >
50 53 <template #enumsData="{ model, field }">
51 54 <EnumList
... ... @@ -77,4 +80,3 @@
77 80 }
78 81 }
79 82 </style>
80   -./config
... ...
1 1 import { Ref, unref } from 'vue';
2 2 import { FormActionType } from '/@/components/Form';
3   -import EnumList from './EnumList.vue';
  3 +import { EnumList } from '../EnumList';
4 4 import { DefineComponentsBasicExpose } from '/#/utils';
5 5 import { DataTypeFormGetFieldsValueType } from './config';
6 6 import { DataType, Specs, StructJSON } from '/@/api/device/model/modelOfMatterModel';
... ...
... ... @@ -6,15 +6,21 @@ export enum FormFieldsEnum {
6 6 DATA_TYPE = 'dataType',
7 7 }
8 8
9   -export const getFormSchemas = (): FormSchema[] => {
  9 +export const getFormSchemas = (required: boolean): FormSchema[] => {
10 10 return [
11 11 {
12 12 field: FormFieldsEnum.VALUE,
13 13 label: '',
14 14 component: 'InputNumber',
15   - rules: [
16   - { required: true, message: `支持整型,取值范围:-2147483648 ~ 2147483647`, type: 'number' },
17   - ],
  15 + dynamicRules: ({ model }) => {
  16 + return [
  17 + {
  18 + required: required || model[FormFieldsEnum.NAME],
  19 + message: `支持整型,取值范围:-2147483648 ~ 2147483647`,
  20 + type: 'number',
  21 + },
  22 + ];
  23 + },
18 24 componentProps: () => {
19 25 return {
20 26 placeholder: '编号如"0"',
... ... @@ -41,7 +47,15 @@ export const getFormSchemas = (): FormSchema[] => {
41 47 field: FormFieldsEnum.NAME,
42 48 label: '',
43 49 component: 'Input',
44   - rules: [{ required: true, message: `参数描述不能为空`, type: 'string' }],
  50 + dynamicRules: ({ model }) => {
  51 + return [
  52 + {
  53 + required: required || model[FormFieldsEnum.VALUE],
  54 + message: `参数描述不能为空`,
  55 + type: 'string',
  56 + },
  57 + ];
  58 + },
45 59 componentProps: () => {
46 60 return {
47 61 placeholder: '对该枚举项的描述',
... ...
... ... @@ -9,7 +9,18 @@
9 9 import { DataTypeEnum } from '/@/enums/objectModelEnum';
10 10 import { isNullOrUnDef } from '/@/utils/is';
11 11
12   - const props = defineProps<{ disabled?: boolean; value?: Specs[] }>();
  12 + const props = withDefaults(
  13 + defineProps<{
  14 + disabled?: boolean;
  15 + value?: Specs[];
  16 + addButtonName?: string;
  17 + required?: boolean;
  18 + }>(),
  19 + {
  20 + addButtonName: '+添加枚举项',
  21 + required: true,
  22 + }
  23 + );
13 24
14 25 interface EnumElItemType {
15 26 uuid: string;
... ... @@ -18,7 +29,7 @@
18 29 }
19 30
20 31 const [registerForm] = useForm({
21   - schemas: getFormSchemas(),
  32 + schemas: getFormSchemas(props.required),
22 33 showActionButtonGroup: false,
23 34 layout: 'inline',
24 35 });
... ... @@ -75,6 +86,8 @@
75 86 const index = unref(enumsListElRef).findIndex((temp) => item.uuid === temp.uuid);
76 87
77 88 ~index && enumsListElRef.value.splice(index, 1);
  89 +
  90 + validateSameEnum();
78 91 };
79 92
80 93 const handleAddEnums = () => {
... ... @@ -102,14 +115,14 @@
102 115 <section class="w-full">
103 116 <header class="flex h-8 items-center">
104 117 <div class="w-1/2">
105   - <span class="mr-1 text-red-400">*</span>
  118 + <span v-if="required" class="mr-1 text-red-400">*</span>
106 119 <span> 参考值 </span>
107 120 <Tooltip title="支持整型,取值范围:-2147483648 ~ 2147483647">
108 121 <Icon icon="ant-design:question-circle-outlined" class="cursor-pointer ml-1" />
109 122 </Tooltip>
110 123 </div>
111 124 <div class="w-1/2">
112   - <span class="mr-1 text-red-400">*</span>
  125 + <span v-if="required" class="mr-1 text-red-400">*</span>
113 126 <span> 参考描述 </span>
114 127 <Tooltip
115 128 title="支持中文、英文大小写、日文、数字、下划线和短划线,必须以中文、英文或数字开头,不超过20个字符"
... ... @@ -144,7 +157,7 @@
144 157 <div v-if="hasSameEnum" class="text-red-400">枚举项中存在相同的参数值或参数描述</div>
145 158 <Tooltip title="枚举项最多创建 100 个">
146 159 <Button type="link" @click="handleAddEnums" :disabled="disabled || getEnumsLimit">
147   - +添加枚举项
  160 + {{ addButtonName }}
148 161 </Button>
149 162 </Tooltip>
150 163 </section>
... ...
1   -import { unref } from 'vue';
2   -import { useObjectModelFormContext } from '../useObjectModelFormContext';
3   -import { findDictItemByCode } from '/@/api/system/dict';
4 1 import { FormSchema } from '/@/components/Table';
5   -import { DictEnum } from '/@/enums/dictEnum';
6 2 import {
7   - DataTypeEnum,
8   - RegisterActionTypeEnum,
9   - RegisterActionTypeNameEnum,
10   - RegisterDataTypeEnum,
  3 + ExtendDescOperationTypeEnum,
  4 + ExtendDescOperationTypeNameEnum,
  5 + OriginalDataTypeEnum,
  6 + OriginalDataTypeNameEnum,
11 7 } from '/@/enums/objectModelEnum';
  8 +import { validateValueRange } from '../DataTypeForm/config';
  9 +import { useComponentRegister } from '/@/components/Form';
  10 +import { HexInput, InputTypeEnum } from '../HexInput';
  11 +import { isFloatType } from './useParseOperationType';
  12 +import { useObjectModelFormContext } from '../useObjectModelFormContext';
  13 +import { unref } from 'vue';
  14 +import { DataActionModeEnum } from '/@/enums/toolEnum';
  15 +
  16 +useComponentRegister('HexInput', HexInput);
12 17
13 18 export enum FormFieldsEnum {
14   - REGISTER_ADDRESS = 'registerAddress',
15   - DATA_TYPE = 'dataType',
16 19 ACTION_TYPE = 'actionType',
17 20 ZOOM_FACTOR = 'zoomFactor',
18 21
19 22 OBJECT_MODEL_TYPE = 'objectModelType',
  23 +
  24 + ORIGINAL_DATA_TYPE = 'originalDataType',
  25 + REGISTER_ADDRESS = 'registerAddress',
  26 + OPERATION_TYPE = 'operationType',
  27 + VALUE_RANGE = 'valueRange',
  28 + SCALING = 'scaling',
  29 + BIT_MASK = 'bitMask',
  30 +
  31 + REGISTER_COUNT = 'registerCount',
  32 +
  33 + VALUE_MAPPING = 'valueMapping',
20 34 }
21 35
22   -function getActionTypeByObjectModelType(dataType: DataTypeEnum) {
23   - const list: Record<'label' | 'value', string>[] = [];
24   - if (dataType === DataTypeEnum.BOOL) {
25   - list.push({ label: RegisterActionTypeNameEnum.BOOL, value: RegisterActionTypeEnum.BOOL });
26   - } else if (dataType === DataTypeEnum.NUMBER_INT) {
27   - list.push({ label: RegisterActionTypeNameEnum.INT, value: RegisterActionTypeEnum.INT });
28   - } else if (dataType === DataTypeEnum.NUMBER_DOUBLE) {
29   - list.push({ label: RegisterActionTypeNameEnum.DOUBLE, value: RegisterActionTypeEnum.DOUBLE });
30   - } else {
31   - list.push(
32   - ...Object.keys(RegisterActionTypeEnum).map((key) => ({
33   - label: RegisterActionTypeNameEnum[key],
34   - value: RegisterActionTypeEnum[key],
35   - }))
36   - );
  36 +export enum FormFieldsNameEnum {
  37 + ORIGINAL_DATA_TYPE = '数据类型',
  38 + REGISTER_ADDRESS = '寄存器地址',
  39 + OPERATION_TYPE = '操作类型',
  40 + VALUE_RANGE = '取值范围',
  41 + SCALING = '缩放因子',
  42 + BIT_MASK = '比特位置',
  43 +
  44 + REGISTER_COUNT = '寄存器个数',
  45 + VALUE_MAPPING = '值映射',
  46 +}
  47 +
  48 +export const BOOL_DEFAULT_VALUE_RANGE = { min: 0, max: 1 };
  49 +
  50 +export const INT16_VALUE_RANGE = {
  51 + min: -(2 ** 15),
  52 + max: 2 ** 15 - 1,
  53 +};
  54 +
  55 +export const UINT16_VALUE_RANGE = {
  56 + min: 0,
  57 + max: 2 ** 16 - 1,
  58 +};
  59 +
  60 +export const INT32_VALUE_RANGE = {
  61 + min: -(2 ** 31),
  62 + max: 2 ** 31 - 1,
  63 +};
  64 +
  65 +export const UINT32_VALUE_RANGE = {
  66 + min: 0,
  67 + max: 2 ** 32 - 1,
  68 +};
  69 +
  70 +function getValueRangeFromOriginDataType(
  71 + originalDataType?: OriginalDataTypeEnum
  72 +): Record<'min' | 'max', number> {
  73 + switch (originalDataType) {
  74 + case OriginalDataTypeEnum.BOOLEAN:
  75 + case OriginalDataTypeEnum.BITS:
  76 + return BOOL_DEFAULT_VALUE_RANGE;
  77 +
  78 + case OriginalDataTypeEnum.INT16_AB:
  79 + case OriginalDataTypeEnum.INT16_BA:
  80 + return INT16_VALUE_RANGE;
  81 +
  82 + case OriginalDataTypeEnum.UINT16_AB:
  83 + case OriginalDataTypeEnum.UINT16_BA:
  84 + return UINT16_VALUE_RANGE;
  85 +
  86 + case OriginalDataTypeEnum.INT32_AB_CD:
  87 + case OriginalDataTypeEnum.INT32_BA_DC:
  88 + case OriginalDataTypeEnum.INT32_CD_AB:
  89 + case OriginalDataTypeEnum.INT32_DC_BA:
  90 + return INT32_VALUE_RANGE;
  91 +
  92 + case OriginalDataTypeEnum.UINT32_AB_CD:
  93 + case OriginalDataTypeEnum.UINT32_BA_DC:
  94 + case OriginalDataTypeEnum.UINT32_CD_AB:
  95 + case OriginalDataTypeEnum.UINT32_DC_BA:
  96 + return UINT32_VALUE_RANGE;
  97 +
  98 + default:
  99 + return INT32_VALUE_RANGE;
37 100 }
  101 +}
38 102
39   - return list;
  103 +const BOOL_OPERATION_TYPE = [
  104 + ExtendDescOperationTypeEnum.INPUT_STATUS_R_02,
  105 + ExtendDescOperationTypeEnum.COIL_STATUS_R_01,
  106 + ExtendDescOperationTypeEnum.COIL_STATUS_RW_01_05,
  107 + ExtendDescOperationTypeEnum.COIL_STATUS_RW_01_0F,
  108 + ExtendDescOperationTypeEnum.COIL_STATUS_W_05,
  109 + ExtendDescOperationTypeEnum.COIL_STATUS_W_0F,
  110 +];
  111 +
  112 +function getOriginalDataTypeByOperationType(
  113 + operationType: ExtendDescOperationTypeEnum
  114 +): Record<'label' | 'value', string>[] {
  115 + switch (operationType) {
  116 + case ExtendDescOperationTypeEnum.INPUT_STATUS_R_02:
  117 + case ExtendDescOperationTypeEnum.COIL_STATUS_R_01:
  118 + case ExtendDescOperationTypeEnum.COIL_STATUS_RW_01_05:
  119 + case ExtendDescOperationTypeEnum.COIL_STATUS_RW_01_0F:
  120 + case ExtendDescOperationTypeEnum.COIL_STATUS_W_05:
  121 + case ExtendDescOperationTypeEnum.COIL_STATUS_W_0F:
  122 + return [{ label: OriginalDataTypeNameEnum.BOOLEAN, value: OriginalDataTypeEnum.BOOLEAN }];
  123 +
  124 + case ExtendDescOperationTypeEnum.HOLDING_REGISTER_RW_03_06:
  125 + case ExtendDescOperationTypeEnum.HOLDING_REGISTER_W_06:
  126 + return [
  127 + { label: OriginalDataTypeNameEnum.INT16_AB, value: OriginalDataTypeEnum.INT16_AB },
  128 + { label: OriginalDataTypeNameEnum.INT16_BA, value: OriginalDataTypeEnum.INT16_BA },
  129 + { label: OriginalDataTypeNameEnum.UINT16_AB, value: OriginalDataTypeEnum.UINT16_AB },
  130 + { label: OriginalDataTypeNameEnum.UINT16_BA, value: OriginalDataTypeEnum.UINT16_BA },
  131 + { label: OriginalDataTypeNameEnum.BITS, value: OriginalDataTypeEnum.BITS },
  132 + { label: OriginalDataTypeNameEnum.BOOLEAN, value: OriginalDataTypeEnum.BOOLEAN },
  133 + ];
  134 +
  135 + case ExtendDescOperationTypeEnum.HOLDING_REGISTER_RW_03_10:
  136 + case ExtendDescOperationTypeEnum.HOLDING_REGISTER_W_10:
  137 + case ExtendDescOperationTypeEnum.HOLDING_REGISTER_R_03:
  138 + case ExtendDescOperationTypeEnum.INPUT_REGISTER_R_04:
  139 + return Object.keys(OriginalDataTypeEnum).map((key) => ({
  140 + label: OriginalDataTypeNameEnum[key],
  141 + value: OriginalDataTypeEnum[key],
  142 + }));
  143 + }
40 144 }
41 145
42   -export const getFormSchemas = (): FormSchema[] => {
43   - const { getDataType } = useObjectModelFormContext();
  146 +export const getExtendDescFormSchemas = (): FormSchema[] => {
  147 + const DEFAULT_OPERATION_TYPE = ExtendDescOperationTypeEnum.INPUT_STATUS_R_02;
  148 + const DEFAULT_ORIGINAL_TYPE = OriginalDataTypeEnum.BOOLEAN;
44 149 return [
45 150 {
  151 + field: FormFieldsEnum.OPERATION_TYPE,
  152 + label: FormFieldsNameEnum.OPERATION_TYPE,
  153 + component: 'Select',
  154 + required: true,
  155 + defaultValue: DEFAULT_OPERATION_TYPE,
  156 + componentProps: ({ formActionType }) => {
  157 + return {
  158 + options: Object.keys(ExtendDescOperationTypeEnum).map((key) => ({
  159 + label: ExtendDescOperationTypeNameEnum[key],
  160 + value: ExtendDescOperationTypeEnum[key],
  161 + })),
  162 + labelField: 'itemText',
  163 + valueField: 'itemValue',
  164 + getPopupContainer: () => document.body,
  165 + placeholder: '请选择操作类型',
  166 + allowClear: false,
  167 + onChange(value: ExtendDescOperationTypeEnum) {
  168 + const isBoolTypeOperationType = BOOL_OPERATION_TYPE.includes(value);
  169 + const originalDataType = isBoolTypeOperationType
  170 + ? OriginalDataTypeEnum.BOOLEAN
  171 + : OriginalDataTypeEnum.INT16_AB;
  172 +
  173 + formActionType.setFieldsValue({
  174 + [FormFieldsEnum.ORIGINAL_DATA_TYPE]: originalDataType,
  175 + [FormFieldsEnum.VALUE_RANGE]: isBoolTypeOperationType
  176 + ? BOOL_DEFAULT_VALUE_RANGE
  177 + : INT16_VALUE_RANGE,
  178 + });
  179 + },
  180 + };
  181 + },
  182 + },
  183 + {
46 184 field: FormFieldsEnum.REGISTER_ADDRESS,
47   - component: 'RegisterAddressInput',
48   - label: '寄存器地址',
  185 + label: FormFieldsNameEnum.REGISTER_ADDRESS,
  186 + component: 'HexInput',
49 187 changeEvent: 'update:value',
50 188 valueField: 'value',
51   - rules: [{ message: '请输入寄存器地址', required: true, type: 'number' }],
  189 + helpMessage: ['范围在 0x0-0xFFFF'],
  190 + rules: [{ message: '请输入寄存器地址', required: true }],
52 191 componentProps: {
53 192 placeholder: '请输入寄存器地址',
  193 + type: InputTypeEnum.HEX,
  194 + maxValue: parseInt('0xFFFF', 16),
54 195 },
55 196 },
56 197 {
57   - field: FormFieldsEnum.DATA_TYPE,
58   - component: 'ApiSelect',
59   - label: '数据格式',
60   - rules: [{ message: '请选择数据格式', required: true }],
61   - defaultValue: RegisterDataTypeEnum.UN_SHORT,
62   - componentProps: {
63   - api: findDictItemByCode,
64   - params: {
65   - dictCode: DictEnum.REGISTER_DATA_FORMAT,
66   - },
67   - labelField: 'itemText',
68   - valueField: 'itemValue',
69   - placeholder: '请选择数据格式',
70   - getPopupContainer: () => document.body,
  198 + field: FormFieldsEnum.ORIGINAL_DATA_TYPE,
  199 + label: FormFieldsNameEnum.ORIGINAL_DATA_TYPE,
  200 + component: 'Select',
  201 + required: true,
  202 + defaultValue: DEFAULT_ORIGINAL_TYPE,
  203 + dynamicDisabled: ({ model }) =>
  204 + BOOL_OPERATION_TYPE.includes(model[FormFieldsEnum.OPERATION_TYPE]) ||
  205 + unref(useObjectModelFormContext().getModalMode) === DataActionModeEnum.READ,
  206 + componentProps: ({ formActionType, formModel }) => {
  207 + const operationType = formModel[FormFieldsEnum.OPERATION_TYPE];
  208 +
  209 + return {
  210 + options: getOriginalDataTypeByOperationType(operationType),
  211 + labelField: 'itemText',
  212 + valueField: 'itemValue',
  213 + placeholder: '请选择数据类型',
  214 + getPopupContainer: () => document.body,
  215 + onChange(value: OriginalDataTypeEnum) {
  216 + const { setFieldsValue } = formActionType;
  217 + setFieldsValue({
  218 + [FormFieldsEnum.VALUE_RANGE]: getValueRangeFromOriginDataType(
  219 + value as OriginalDataTypeEnum
  220 + ),
  221 + });
  222 + },
  223 + };
71 224 },
72 225 },
73 226 {
74   - field: FormFieldsEnum.ACTION_TYPE,
  227 + field: FormFieldsEnum.BIT_MASK,
  228 + label: FormFieldsNameEnum.BIT_MASK,
75 229 component: 'Select',
76   - label: '操作类型',
77   - rules: [{ message: '请选择操作类型', required: true }],
  230 + ifShow: ({ model }) => model[FormFieldsEnum.ORIGINAL_DATA_TYPE] === OriginalDataTypeEnum.BITS,
  231 + required: true,
  232 + defaultValue: 7,
78 233 componentProps: () => {
79 234 return {
80   - options: getActionTypeByObjectModelType(unref(getDataType)),
81   - placeholder: '请选择操作类型',
  235 + options: Array.from({ length: 16 }, (_, index) => ({
  236 + label: `${index}`,
  237 + value: index,
  238 + })),
  239 + allowClear: false,
82 240 getPopupContainer: () => document.body,
83 241 };
84 242 },
85 243 },
86 244 {
87   - field: FormFieldsEnum.ZOOM_FACTOR,
  245 + field: FormFieldsEnum.VALUE_RANGE,
  246 + label: FormFieldsNameEnum.VALUE_RANGE,
  247 + component: 'CustomMinMaxInput',
  248 + rules: [{ required: true, validator: validateValueRange }],
  249 + changeEvent: 'update:value',
  250 + valueField: 'value',
  251 + defaultValue: BOOL_DEFAULT_VALUE_RANGE,
  252 + helpMessage: [
  253 + '取值范围指的是原始数据经过缩放因子处理之后的取值范围,超出取值范围的数据会被丢弃。',
  254 + ],
  255 + ifShow: ({ model }) =>
  256 + model[FormFieldsEnum.ORIGINAL_DATA_TYPE] !== OriginalDataTypeEnum.STRING,
  257 + componentProps: ({ formModel }) => {
  258 + const originalDataType = formModel[FormFieldsEnum.ORIGINAL_DATA_TYPE];
  259 +
  260 + const { min, max } = getValueRangeFromOriginDataType(
  261 + originalDataType as OriginalDataTypeEnum
  262 + );
  263 +
  264 + const flag = isFloatType(originalDataType);
  265 +
  266 + return {
  267 + minInputProps: {
  268 + precision: 0,
  269 + min: flag ? undefined : min,
  270 + max: flag ? undefined : max,
  271 + },
  272 + maxInputProps: {
  273 + precision: 0,
  274 + min: flag ? undefined : min,
  275 + max: flag ? undefined : max,
  276 + },
  277 + };
  278 + },
  279 + },
  280 +
  281 + {
  282 + field: FormFieldsEnum.SCALING,
  283 + label: FormFieldsNameEnum.SCALING,
88 284 component: 'InputNumber',
89   - label: '缩放因子',
90   - helpMessage: ['缩放因子不能为0,默认为1'],
  285 + required: true,
91 286 defaultValue: 1,
  287 + helpMessage: ['缩放因子,不能为 0,默认为 1'],
92 288 ifShow: ({ model }) =>
93   - ![DataTypeEnum.BOOL, DataTypeEnum.STRING].includes(model[FormFieldsEnum.OBJECT_MODEL_TYPE]),
94   - componentProps: {
95   - min: 1,
96   - placeholder: '请输入缩放因子',
  289 + ![OriginalDataTypeEnum.STRING, OriginalDataTypeEnum.BOOLEAN].includes(
  290 + model[FormFieldsEnum.ORIGINAL_DATA_TYPE]
  291 + ),
  292 + rules: [
  293 + {
  294 + validator: (_rule, value: any) => {
  295 + if (Number(value) > 0) return Promise.resolve();
  296 + return Promise.reject(Error('缩放因子不能为0'));
  297 + },
  298 + },
  299 + ],
  300 + componentProps: () => {},
  301 + },
  302 + {
  303 + field: FormFieldsEnum.REGISTER_COUNT,
  304 + label: FormFieldsNameEnum.REGISTER_COUNT,
  305 + component: 'InputNumber',
  306 + helpMessage: ['操作类型为保持寄存器/输入寄存器时限制为1~125'],
  307 + ifShow: ({ model }) =>
  308 + model[FormFieldsEnum.ORIGINAL_DATA_TYPE] === OriginalDataTypeEnum.STRING,
  309 + required: true,
  310 + componentProps: () => {
  311 + return {
  312 + placeholder: '请输入寄存器个数',
  313 + min: 1,
  314 + max: 125,
  315 + precision: 0,
  316 + };
97 317 },
98 318 },
  319 + {
  320 + field: FormFieldsEnum.VALUE_MAPPING,
  321 + component: 'Input',
  322 + label: FormFieldsNameEnum.VALUE_MAPPING,
  323 + slot: FormFieldsEnum.VALUE_MAPPING,
  324 + defaultValue: [
  325 + { value: 0, name: '关' },
  326 + { value: 1, name: '开' },
  327 + ],
  328 + ifShow: ({ model }) =>
  329 + [OriginalDataTypeEnum.BOOLEAN, OriginalDataTypeEnum.BITS].includes(
  330 + model[FormFieldsEnum.ORIGINAL_DATA_TYPE]
  331 + ),
  332 + },
99 333 ];
100 334 };
... ...
1 1 export { default as ExtendDesc } from './index.vue';
  2 +
  3 +import { FormFieldsEnum } from './config';
  4 +import { Specs } from '/@/api/device/model/modelOfMatterModel';
  5 +import { ExtendDescOperationTypeEnum, OriginalDataTypeEnum } from '/@/enums/objectModelEnum';
  6 +
  7 +export interface ExtendDescFormFieldsValueType {
  8 + [FormFieldsEnum.BIT_MASK]?: number;
  9 + [FormFieldsEnum.OPERATION_TYPE]: ExtendDescOperationTypeEnum;
  10 + [FormFieldsEnum.ORIGINAL_DATA_TYPE]: OriginalDataTypeEnum;
  11 + [FormFieldsEnum.REGISTER_ADDRESS]: string;
  12 + [FormFieldsEnum.SCALING]?: number;
  13 + [FormFieldsEnum.VALUE_RANGE]?: Record<'min' | 'max', number>;
  14 + [FormFieldsEnum.REGISTER_COUNT]?: number;
  15 + [FormFieldsEnum.VALUE_MAPPING]?: Specs[];
  16 +}
... ...
1 1 <script lang="ts" setup>
2   - import { Button } from 'ant-design-vue';
3   - import { nextTick, ref, watch } from 'vue';
  2 + import { Button, Divider } from 'ant-design-vue';
  3 + import { nextTick, ref, unref, watch } from 'vue';
4 4 import { BasicForm, useForm } from '/@/components/Form';
5   - import { BasicModal } from '/@/components/Modal';
6 5 import { PlusCircleOutlined } from '@ant-design/icons-vue';
7   - import { getFormSchemas } from './config';
8   - import { ExtensionDesc } from '/@/api/device/model/modelOfMatterModel';
  6 + import { getExtendDescFormSchemas } from './config';
  7 + import { ExtendDescFormFieldsValueType } from '.';
  8 + import { BasicModal } from '/@/components/Modal';
  9 + import { PlusSquareOutlined, MinusSquareOutlined } from '@ant-design/icons-vue';
  10 + import {
  11 + ExtendDescOperationTypeEnum,
  12 + ExtendDescOperationTypeNameEnum,
  13 + OriginalDataTypeNameEnum,
  14 + } from '/@/enums/objectModelEnum';
  15 + import { EnumList } from '../EnumList';
9 16
10 17 const show = ref(false);
11 18
12 19 const props = withDefaults(
13 20 defineProps<{
14   - value?: ExtensionDesc;
  21 + value?: ExtendDescFormFieldsValueType;
15 22 disabled?: boolean;
16 23 }>(),
17 24 {
18   - value: () => ({} as ExtensionDesc),
  25 + value: () => ({} as ExtendDescFormFieldsValueType),
19 26 }
20 27 );
21 28
22   - const emit = defineEmits(['update:value']);
  29 + const emit = defineEmits(['update:value', 'change']);
23 30
24 31 const [registerForm, { setFieldsValue, getFieldsValue, setProps, validate, resetFields }] =
25 32 useForm({
26   - schemas: getFormSchemas(),
  33 + schemas: getExtendDescFormSchemas(),
27 34 showActionButtonGroup: false,
28 35 });
29 36
  37 + const infoExpandFlag = ref(false);
  38 +
  39 + const enumListRef = ref<InstanceType<typeof EnumList>>();
  40 +
30 41 const handleClick = async () => {
31 42 show.value = true;
32 43 await nextTick();
... ... @@ -37,24 +48,111 @@
37 48
38 49 const handleSubmit = async () => {
39 50 await validate();
40   - const value = getFieldsValue();
  51 + await unref(enumListRef)?.validate();
  52 + const value = getFieldsValue() as ExtendDescFormFieldsValueType;
  53 + const valueMapping = unref(enumListRef)?.getFieldsValue();
  54 + value.valueMapping = valueMapping;
41 55 emit('update:value', value);
  56 + emit('change', value);
42 57 show.value = false;
43 58 };
44 59
  60 + const handleDelete = () => {
  61 + emit('update:value', {});
  62 + emit('change', {});
  63 + };
  64 +
45 65 watch(show, async (value) => {
46 66 if (value) {
47 67 await nextTick();
48 68 setFieldsValue({ ...props.value });
49 69 }
50 70 });
  71 +
  72 + const getOperationTypeName = (operationType: ExtendDescOperationTypeEnum) => {
  73 + const key = Object.keys(ExtendDescOperationTypeEnum).find(
  74 + (key) => ExtendDescOperationTypeEnum[key] === operationType
  75 + );
  76 +
  77 + return key ? ExtendDescOperationTypeNameEnum[key] : key;
  78 + };
51 79 </script>
52 80
53 81 <template>
54 82 <section>
55   - <Button type="link" @click="handleClick"><PlusCircleOutlined />新增扩展描述</Button>
56   - <BasicModal title="扩展描述" v-model:visible="show" @ok="handleSubmit" :show-ok-btn="!disabled">
57   - <BasicForm class="extension-form" @register="registerForm" />
  83 + <Button v-if="!value.operationType" type="link" @click="handleClick">
  84 + <PlusCircleOutlined />
  85 + 新增扩展描述
  86 + </Button>
  87 + <main v-if="value.operationType" class="flex">
  88 + <div class="flex-auto flex text-gray-400 bg-blue-50 text-xs p-2 border gap-2 border-gray-100">
  89 + <div text-base>
  90 + <PlusSquareOutlined
  91 + v-if="!infoExpandFlag"
  92 + class="cursor-pointer"
  93 + @click="infoExpandFlag = !infoExpandFlag"
  94 + />
  95 + <MinusSquareOutlined
  96 + v-else
  97 + class="cursor-pointer"
  98 + @click="infoExpandFlag = !infoExpandFlag"
  99 + />
  100 + </div>
  101 + <div>
  102 + <div class="mb-2">
  103 + <span class="text-gray-400">操作类型</span>
  104 + <span class="ml-2">{{ getOperationTypeName(value.operationType) }}</span>
  105 + </div>
  106 + <section
  107 + class="flex flex-col gap-2 overflow-hidden transition"
  108 + :class="infoExpandFlag ? '' : 'h-0'"
  109 + >
  110 + <div>
  111 + <span>寄存器地址:</span>
  112 + <span class="ml-2">{{ value.registerAddress }}</span>
  113 + </div>
  114 + <div>
  115 + <span>原始数据类型:</span>
  116 + <span class="ml-2">{{ OriginalDataTypeNameEnum[value.originalDataType] }}</span>
  117 + </div>
  118 + <div v-if="value.scaling">
  119 + <span>缩放因子:</span>
  120 + <span class="ml-2">{{ value.scaling }}</span>
  121 + </div>
  122 + <div v-if="value.valueRange">
  123 + <span>取值范围:</span>
  124 + <span class="ml-2">{{ value.valueRange?.min }} ~ {{ value.valueRange?.max }}</span>
  125 + </div>
  126 + </section>
  127 + </div>
  128 + </div>
  129 + <div class="ml-2">
  130 + <Button class="!px-0" @click="handleClick" type="link">
  131 + {{ disabled ? '查看' : '编辑' }}
  132 + </Button>
  133 + <Divider v-if="!disabled" type="vertical" />
  134 + <Button class="!px-0" @click="handleDelete" type="link" v-if="!disabled"> 删除 </Button>
  135 + </div>
  136 + </main>
  137 +
  138 + <BasicModal
  139 + title="扩展描述"
  140 + v-model:visible="show"
  141 + @ok="handleSubmit"
  142 + :show-ok-btn="!disabled"
  143 + :width="480"
  144 + >
  145 + <BasicForm class="extension-form" @register="registerForm">
  146 + <template #valueMapping="{ model, field }">
  147 + <EnumList
  148 + ref="enumListRef"
  149 + v-model:value="model[field]"
  150 + :disabled="disabled"
  151 + :required="false"
  152 + add-button-name="+添加值映射"
  153 + />
  154 + </template>
  155 + </BasicForm>
58 156 </BasicModal>
59 157 </section>
60 158 </template>
... ...
  1 +import {
  2 + DataTypeEnum,
  3 + ExtendDescOperationTypeEnum,
  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 +export enum InputTypeEnum {
  2 + /**
  3 + * @description 16进制
  4 + */
  5 + HEX = 'HEX',
  6 +
  7 + /**
  8 + * @description 10进制
  9 + */
  10 + DEC = 'DEC',
  11 +}
... ...
  1 +export { default as HexInput } from './index.vue';
  2 +export { InputTypeEnum } from './config';
... ...
  1 +<script setup lang="ts">
  2 + import { InputGroup, Select, Input } from 'ant-design-vue';
  3 + import { computed, ref, unref } from 'vue';
  4 + import { InputTypeEnum } from './config';
  5 + import { isNullOrUnDef } from '/@/utils/is';
  6 +
  7 + const props = withDefaults(
  8 + defineProps<{
  9 + type?: InputTypeEnum;
  10 + value?: number | string;
  11 + hexWithPrefix?: boolean;
  12 + hexUpperCase?: boolean;
  13 + disabled?: boolean;
  14 + maxValue?: number;
  15 + showScaleSelect?: boolean;
  16 + }>(),
  17 + {
  18 + type: InputTypeEnum.HEX,
  19 + hexWithPrefix: false,
  20 + hexUpperCase: true,
  21 + maxValue: Number.MAX_SAFE_INTEGER,
  22 + showScaleSelect: true,
  23 + }
  24 + );
  25 +
  26 + const emits = defineEmits(['update:value', 'change']);
  27 +
  28 + const typOptions = Object.values(InputTypeEnum).map((value) => ({ label: value, value }));
  29 +
  30 + const inputType = ref(props.type);
  31 +
  32 + const validateDECReg = /^[\d]+$/;
  33 +
  34 + const validateHEXReg = /^(0x)?[\da-fA-F]+$/;
  35 +
  36 + const getMaxValue = computed(() => {
  37 + const { maxValue } = props;
  38 + return maxValue;
  39 + });
  40 +
  41 + const getDecToHex = (value: number | string) => {
  42 + const { hexUpperCase, hexWithPrefix } = props;
  43 + let hex = Number(value).toString(16);
  44 + hex = hexUpperCase ? hex.toUpperCase() : hex;
  45 + return `${hexWithPrefix ? '0x' : ''}${hex}`;
  46 + };
  47 +
  48 + const getHexToDec = (value: number | string) => {
  49 + return parseInt(value, 16);
  50 + };
  51 +
  52 + const getDECValue = (value: number | string) => {
  53 + return parseInt(value, unref(inputType) === InputTypeEnum.HEX ? 16 : 10);
  54 + };
  55 +
  56 + const getFormatValue = (value: number | string) => {
  57 + if (unref(inputType) === InputTypeEnum.HEX) {
  58 + const { hexUpperCase, hexWithPrefix } = props;
  59 + if (hexUpperCase) value = value.toString().toUpperCase();
  60 + if (hexWithPrefix) value = `0x${value}`;
  61 + return value;
  62 + }
  63 +
  64 + return value;
  65 + };
  66 +
  67 + const getInputValue = computed({
  68 + get() {
  69 + const { type, value } = props;
  70 +
  71 + if (isNullOrUnDef(value)) return;
  72 +
  73 + if (type === unref(inputType)) return value;
  74 +
  75 + if (type === InputTypeEnum.HEX && unref(inputType) === InputTypeEnum.DEC) {
  76 + return getHexToDec(value);
  77 + } else {
  78 + return getDecToHex(value);
  79 + }
  80 + },
  81 + set(value: string) {
  82 + const { type } = props;
  83 +
  84 + if (unref(inputType) === InputTypeEnum.HEX) value = value.replace('0x', '');
  85 +
  86 + if (!value) {
  87 + emits('update:value', undefined);
  88 + return;
  89 + }
  90 +
  91 + if (unref(inputType) === InputTypeEnum.HEX && !validateHEXReg.test(value)) return;
  92 +
  93 + if (unref(inputType) === InputTypeEnum.DEC && !validateDECReg.test(value)) return;
  94 +
  95 + if (getDECValue(value) > unref(getMaxValue)) return;
  96 +
  97 + if (type === unref(inputType)) {
  98 + emits('update:value', getFormatValue(value));
  99 + return;
  100 + }
  101 +
  102 + if (type === InputTypeEnum.HEX && unref(inputType) === InputTypeEnum.DEC) {
  103 + emits('update:value', getDecToHex(value));
  104 + } else {
  105 + emits('update:value', getHexToDec(value));
  106 + }
  107 + },
  108 + });
  109 +
  110 + const getNegationValue = computed(() => {
  111 + const { hexWithPrefix } = props;
  112 + if (isNullOrUnDef(unref(getInputValue))) return 0;
  113 + return unref(inputType) === InputTypeEnum.HEX
  114 + ? getHexToDec(unref(getInputValue)!)
  115 + : `${hexWithPrefix ? '0x' : ''}${Number(unref(getInputValue)).toString(16).toUpperCase()}`;
  116 + });
  117 +</script>
  118 +
  119 +<template>
  120 + <InputGroup class="!flex w-full h-full justify-center items-center" compact>
  121 + <Select
  122 + v-if="showScaleSelect"
  123 + v-model:value="inputType"
  124 + class="min-w-20"
  125 + :options="typOptions"
  126 + :disabled="disabled"
  127 + />
  128 + <Input v-bind="$attrs" v-model:value="getInputValue" class="!w-full" :disabled="disabled" />
  129 + <div class="min-w-14 h-full px-2 flex-grow flex-shrink-0 text-center leading-8 bg-gray-200">
  130 + {{ getNegationValue }}
  131 + </div>
  132 + </InputGroup>
  133 +</template>
... ...
1   -import { FormFieldsEnum, FormFieldsNameEnum } from '../config';
2   -import { findDictItemByCode } from '/@/api/system/dict';
3   -import { FormSchema } from '/@/components/Form';
4   -import { DictEnum } from '/@/enums/dictEnum';
5   -import { DataTypeEnum, FunctionTypeEnum, FunctionTypeNameEnum } from '/@/enums/objectModelEnum';
6   -import { isNullOrUnDef } from '/@/utils/is';
7   -
8   -export const validateValueRange = (_rule, value: Record<'min' | 'max', number>, _callback) => {
9   - value = value || {};
10   - const { min, max } = value;
11   - if (min > max) {
12   - return Promise.reject('最大值小于最小值');
13   - }
14   - return Promise.resolve();
15   -};
16   -
17   -export const getFormSchemas = (): FormSchema[] => {
18   - return [
19   - {
20   - field: FormFieldsEnum.FUNCTION_TYPE,
21   - label: FormFieldsNameEnum.FUNCTION_TYPE,
22   - component: 'Segmented',
23   - defaultValue: FunctionTypeEnum.PROPERTIES,
24   - required: true,
25   - componentProps: ({ formActionType }) => {
26   - return {
27   - options: Object.keys(FunctionTypeEnum).map((key) => ({
28   - title: FunctionTypeNameEnum[key],
29   - value: FunctionTypeEnum[key],
30   - })),
31   - onChange() {
32   - const { setFieldsValue, clearValidate } = formActionType;
33   - setFieldsValue({
34   - [FormFieldsEnum.INPUT_DATA]: [],
35   - [FormFieldsEnum.OUTPUT_DATA]: [],
36   - [FormFieldsEnum.STRUCT_DATA]: [],
37   - [FormFieldsEnum.SERVICE_COMMAND]: null,
38   - });
39   - clearValidate();
40   - },
41   - };
42   - },
43   - },
44   - {
45   - field: FormFieldsEnum.FUNCTION_NAME,
46   - label: FormFieldsNameEnum.FUNCTION_NAME,
47   - component: 'Input',
48   - required: true,
49   - dynamicRules: ({ values }) => {
50   - return [
51   - { required: true, message: '请输入功能名称' },
52   - {
53   - validator: () => {
54   - const reg = /[,,]+/;
55   - if (reg.test(values?.[FormFieldsEnum.FUNCTION_NAME])) {
56   - return Promise.reject(`${FormFieldsNameEnum.FUNCTION_NAME}不能包含逗号`);
57   - }
58   - return Promise.resolve();
59   - },
60   - },
61   - ];
62   - },
63   - componentProps: {
64   - placeholder: `请输入${FormFieldsNameEnum.FUNCTION_NAME}`,
65   - },
66   - },
67   - {
68   - field: FormFieldsEnum.IDENTIFIER,
69   - label: FormFieldsNameEnum.IDENTIFIER,
70   - required: true,
71   - component: 'Input',
72   - componentProps: {
73   - maxLength: 128,
74   - placeholder: '请输入标识符',
75   - },
76   - dynamicRules: ({ values }) => {
77   - return [
78   - { required: true, message: '请输入标识符' },
79   - {
80   - validator: () => {
81   - const reg = /[,,]+/;
82   - if (reg.test(values?.[FormFieldsEnum.IDENTIFIER])) {
83   - return Promise.reject(`${FormFieldsNameEnum.IDENTIFIER}不能包含逗号`);
84   - }
85   - return Promise.resolve();
86   - },
87   - },
88   - ];
89   - },
90   - },
91   - {
92   - field: FormFieldsEnum.DATA_TYPE,
93   - label: FormFieldsNameEnum.DATA_TYPE,
94   - required: true,
95   - component: 'ApiSelect',
96   - defaultValue: DataTypeEnum.NUMBER_INT,
97   - ifShow: ({ model }) =>
98   - ![FunctionTypeEnum.SERVICE, FunctionTypeEnum.EVENTS].includes(
99   - model[FormFieldsEnum.FUNCTION_TYPE]
100   - ),
101   - componentProps: () => {
102   - return {
103   - placeholder: '请选择数据类型',
104   - api: async (params: Recordable) => {
105   - const result = await findDictItemByCode(params);
106   - return result;
107   - },
108   - params: {
109   - dictCode: DictEnum.DATA_TYPE,
110   - },
111   - labelField: 'itemText',
112   - valueField: 'itemValue',
113   - getPopupContainer: () => document.body,
114   - };
115   - },
116   - },
117   - {
118   - field: FormFieldsEnum.VALUE_RANGE,
119   - label: FormFieldsNameEnum.VALUE_RANGE,
120   - component: 'CustomMinMaxInput',
121   - valueField: 'value',
122   - changeEvent: 'update:value',
123   - ifShow: ({ model }) =>
124   - ![FunctionTypeEnum.SERVICE, FunctionTypeEnum.EVENTS].includes(
125   - model[FormFieldsEnum.FUNCTION_TYPE]
126   - ) &&
127   - (model[FormFieldsEnum.DATA_TYPE] === DataTypeEnum.NUMBER_INT ||
128   - model[FormFieldsEnum.DATA_TYPE] === DataTypeEnum.NUMBER_DOUBLE),
129   - rules: [{ validator: validateValueRange }],
130   - },
131   - {
132   - field: FormFieldsEnum.STEP,
133   - label: FormFieldsNameEnum.STEP,
134   - component: 'InputNumber',
135   - componentProps: {
136   - maxLength: 255,
137   - placeholder: '请输入步长',
138   - min: 1,
139   - formatter: (value: number | string) => {
140   - return value ? Math.floor(Number(value)) : value;
141   - },
142   - },
143   - ifShow: ({ model }) =>
144   - ![FunctionTypeEnum.SERVICE, FunctionTypeEnum.EVENTS].includes(
145   - model[FormFieldsEnum.FUNCTION_TYPE]
146   - ) &&
147   - (model[FormFieldsEnum.DATA_TYPE] === DataTypeEnum.NUMBER_INT ||
148   - model[FormFieldsEnum.DATA_TYPE] === DataTypeEnum.NUMBER_DOUBLE),
149   - dynamicRules: ({ model }) => {
150   - const valueRange = model[FormFieldsEnum.VALUE_RANGE] || {};
151   - const { min, max } = valueRange;
152   - const step = model[FormFieldsEnum.STEP];
153   - return [
154   - {
155   - validator: () => {
156   - if ([min, max].every(isNullOrUnDef)) return Promise.resolve();
157   - if (step > max - min) {
158   - return Promise.reject('步长不能大于取值范围的差值');
159   - }
160   - return Promise.resolve();
161   - },
162   - },
163   - ];
164   - },
165   - },
166   - {
167   - field: FormFieldsEnum.UNIT_NAME,
168   - label: FormFieldsNameEnum.UNIT_NAME,
169   - component: 'Input',
170   - show: false,
171   - },
172   - {
173   - field: FormFieldsEnum.UNIT,
174   - label: FormFieldsNameEnum.UNIT,
175   - component: 'ApiSelect',
176   - componentProps: ({ formActionType }) => {
177   - const { setFieldsValue } = formActionType;
178   - return {
179   - placeholder: '请选择单位',
180   - api: async (params: Recordable) => {
181   - const list = await findDictItemByCode(params);
182   - list.map((item) => (item.itemText = `${item.itemText} / ${item.itemValue}`));
183   - return list;
184   - },
185   - params: {
186   - dictCode: DictEnum.ATTRIBUTE_UNIT,
187   - },
188   - labelInValue: true,
189   - labelField: 'itemText',
190   - valueField: 'itemValue',
191   - onChange(_, record: Record<'label' | 'value', string>) {
192   - if (record) {
193   - const { label } = record;
194   - setFieldsValue({ [FormFieldsEnum.UNIT_NAME]: label });
195   - }
196   - },
197   - getPopupContainer: () => document.body,
198   - showSearch: true,
199   - filterOption: (inputValue: string, option: Record<'label' | 'value', string>) => {
200   - let { label, value } = option;
201   - label = label.toLowerCase();
202   - value = value.toLowerCase();
203   - inputValue = inputValue.toLowerCase();
204   - return label.includes(inputValue) || value.includes(inputValue);
205   - },
206   - };
207   - },
208   - ifShow: ({ model }) =>
209   - ![FunctionTypeEnum.SERVICE, FunctionTypeEnum.EVENTS].includes(
210   - model[FormFieldsEnum.FUNCTION_TYPE]
211   - ) &&
212   - (model[FormFieldsEnum.DATA_TYPE] === DataTypeEnum.NUMBER_INT ||
213   - model[FormFieldsEnum.DATA_TYPE] === DataTypeEnum.NUMBER_DOUBLE),
214   - },
215   - {
216   - field: FormFieldsEnum.BOOL_CLOSE,
217   - component: 'Input',
218   - required: true,
219   - label: FormFieldsNameEnum.BOOL_CLOSE,
220   - componentProps: {
221   - placeholder: '如:关',
222   - },
223   - defaultValue: '关',
224   - ifShow: ({ model }) =>
225   - ![FunctionTypeEnum.SERVICE, FunctionTypeEnum.EVENTS].includes(
226   - model[FormFieldsEnum.FUNCTION_TYPE]
227   - ) && model[FormFieldsEnum.DATA_TYPE] === DataTypeEnum.BOOL,
228   - dynamicRules: ({ model }) => {
229   - const close = model[FormFieldsEnum.BOOL_CLOSE];
230   - const open = model[FormFieldsEnum.BOOL_OPEN];
231   - return [
232   - {
233   - required: true,
234   - message: `布尔值不能为空`,
235   - },
236   - {
237   - validator() {
238   - if (open === close) return Promise.reject('布尔值不能相同');
239   - return Promise.resolve();
240   - },
241   - },
242   - ];
243   - },
244   - },
245   - {
246   - field: FormFieldsEnum.BOOL_OPEN,
247   - component: 'Input',
248   - required: true,
249   - label: FormFieldsNameEnum.BOOL_OPEN,
250   - componentProps: {
251   - placeholder: '如:开',
252   - },
253   - defaultValue: '开',
254   - ifShow: ({ model }) =>
255   - ![FunctionTypeEnum.SERVICE, FunctionTypeEnum.EVENTS].includes(
256   - model[FormFieldsEnum.FUNCTION_TYPE]
257   - ) && model[FormFieldsEnum.DATA_TYPE] === DataTypeEnum.BOOL,
258   - dynamicRules: ({ model }) => {
259   - const close = model[FormFieldsEnum.BOOL_CLOSE];
260   - const open = model[FormFieldsEnum.BOOL_OPEN];
261   - return [
262   - {
263   - required: true,
264   - message: `布尔值不能为空`,
265   - },
266   - {
267   - validator() {
268   - if (open === close) return Promise.reject('布尔值不能相同');
269   - return Promise.resolve();
270   - },
271   - },
272   - ];
273   - },
274   - },
275   - {
276   - field: FormFieldsEnum.LENGTH,
277   - component: 'Input',
278   - required: true,
279   - label: FormFieldsNameEnum.LENGTH,
280   - defaultValue: '10240',
281   - colProps: {
282   - span: 8,
283   - },
284   - componentProps: {
285   - placeholder: '请输入数据长度',
286   - },
287   - renderComponentContent: () => {
288   - return {
289   - suffix: () => '字节',
290   - };
291   - },
292   - ifShow: ({ model }) =>
293   - ![FunctionTypeEnum.SERVICE, FunctionTypeEnum.EVENTS].includes(
294   - model[FormFieldsEnum.FUNCTION_TYPE]
295   - ) && model[FormFieldsEnum.DATA_TYPE] === DataTypeEnum.STRING,
296   - },
297   - {
298   - field: FormFieldsEnum.ENUMS_DATA,
299   - label: FormFieldsNameEnum.ENUMS_DATA,
300   - component: 'Input',
301   - slot: FormFieldsEnum.ENUMS_DATA,
302   - changeEvent: 'update:value',
303   - valueField: 'value',
304   - ifShow: ({ model }) =>
305   - ![FunctionTypeEnum.SERVICE, FunctionTypeEnum.EVENTS].includes(
306   - model[FormFieldsEnum.FUNCTION_TYPE]
307   - ) && model[FormFieldsEnum.DATA_TYPE] === DataTypeEnum.ENUM,
308   - },
309   - {
310   - field: FormFieldsEnum.STRUCT_DATA,
311   - label: FormFieldsNameEnum.STRUCT_DATA,
312   - component: 'Input',
313   - slot: FormFieldsEnum.STRUCT_DATA,
314   - changeEvent: 'update:value',
315   - valueField: 'value',
316   - required: true,
317   - ifShow: ({ model }) =>
318   - model[FormFieldsEnum.DATA_TYPE] === DataTypeEnum.STRUCT &&
319   - ![FunctionTypeEnum.SERVICE, FunctionTypeEnum.EVENTS].includes(
320   - model[FormFieldsEnum.FUNCTION_TYPE]
321   - ),
322   - },
323   - ];
324   -};
... ... @@ -98,4 +98,3 @@
98 98 />
99 99 </section>
100 100 </template>
101   -./index.config
... ...
... ... @@ -7,6 +7,7 @@ import { FormSchema } from '/@/components/Form';
7 7 import {
8 8 ServiceCallTypeEnum,
9 9 ServiceCallTypeNameEnum,
  10 + TCPProtocolTypeEnum,
10 11 TransportTypeEnum,
11 12 } from '/@/enums/deviceEnum';
12 13 import { DictEnum } from '/@/enums/dictEnum';
... ... @@ -15,8 +16,11 @@ import {
15 16 FunctionTypeEnum,
16 17 FunctionTypeNameEnum,
17 18 ObjectEventTypeEnum,
  19 + BuiltInIdentifierEnum,
18 20 } from '/@/enums/objectModelEnum';
19 21 import { DataActionModeEnum } from '/@/enums/toolEnum';
  22 +import { DeviceTypeEnum } from '/@/api/device/model/deviceModel';
  23 +import { DeviceProfileDetail } from '/@/api/device/model/deviceConfigModel';
20 24
21 25 export enum FormFieldsEnum {
22 26 FUNCTION_TYPE = 'functionType',
... ... @@ -74,10 +78,18 @@ export enum FormFieldsNameEnum {
74 78
75 79 export const getFormSchemas = ({
76 80 transportType,
  81 + deviceRecord,
77 82 }: {
78 83 transportType?: string;
79 84 mode?: DataActionModeEnum;
  85 + deviceRecord?: DeviceProfileDetail;
80 86 }): FormSchema[] => {
  87 + const isTCPTransport = deviceRecord?.transportType === TransportTypeEnum.TCP;
  88 +
  89 + const isTCPModbusProduct =
  90 + isTCPTransport &&
  91 + deviceRecord?.profileData.transportConfiguration.protocol === TCPProtocolTypeEnum.MODBUS_RTU;
  92 +
81 93 return [
82 94 {
83 95 field: FormFieldsEnum.FUNCTION_TYPE,
... ... @@ -85,16 +97,29 @@ export const getFormSchemas = ({
85 97 component: 'Segmented',
86 98 defaultValue: FunctionTypeEnum.PROPERTIES,
87 99 required: true,
  100 + helpMessage: [
  101 + '属性一般是指设备的运行状态,如当前温度等;服务是指设备可被调用的方法,支持定义参数,如执行某项任务;事件则是指设备上报的通知,如告警,需要被及时处理。',
  102 + ],
88 103 dynamicDisabled: () => {
89 104 const { getModalMode } = useObjectModelFormContext();
90 105 return unref(getModalMode) !== DataActionModeEnum.CREATE;
91 106 },
92 107 componentProps: ({ formActionType }) => {
  108 + const isSensor = deviceRecord?.deviceType === DeviceTypeEnum.SENSOR;
93 109 return {
94   - options: Object.keys(FunctionTypeEnum).map((key) => ({
95   - title: FunctionTypeNameEnum[key],
96   - value: FunctionTypeEnum[key],
97   - })),
  110 + options: [
  111 + { title: FunctionTypeNameEnum.PROPERTIES, value: FunctionTypeEnum.PROPERTIES },
  112 + {
  113 + title: FunctionTypeNameEnum.SERVICE,
  114 + value: FunctionTypeEnum.SERVICE,
  115 + disabled: (isTCPTransport && isSensor) || isTCPModbusProduct,
  116 + },
  117 + {
  118 + title: FunctionTypeNameEnum.EVENTS,
  119 + value: FunctionTypeEnum.EVENTS,
  120 + disabled: isTCPTransport,
  121 + },
  122 + ],
98 123 onChange() {
99 124 const { setFieldsValue, clearValidate } = formActionType;
100 125 setFieldsValue({
... ... @@ -112,26 +137,26 @@ export const getFormSchemas = ({
112 137 field: FormFieldsEnum.DATA_TYPE_FORM,
113 138 component: 'Input',
114 139 label: '',
115   - ifShow: ({ model }) => model[FormFieldsEnum.FUNCTION_TYPE] === FunctionTypeEnum.PROPERTIES,
  140 + ifShow: ({ model }) =>
  141 + model[FormFieldsEnum.FUNCTION_TYPE] === FunctionTypeEnum.PROPERTIES && !isTCPModbusProduct,
116 142 colSlot: FormFieldsEnum.DATA_TYPE_FORM,
117 143 },
118 144 createFunctionNameFormItem({
119   - ifShow: ({ model }) => model[FormFieldsEnum.FUNCTION_TYPE] !== FunctionTypeEnum.PROPERTIES,
  145 + ifShow: ({ model }) =>
  146 + model[FormFieldsEnum.FUNCTION_TYPE] !== FunctionTypeEnum.PROPERTIES || isTCPModbusProduct,
120 147 }),
121 148 createIdentifierFormItem({
122   - ifShow: ({ model }) => model[FormFieldsEnum.FUNCTION_TYPE] !== FunctionTypeEnum.PROPERTIES,
123   - }),
124   - {
125   - field: FormFieldsEnum.EXTENSION_DESC,
126   - component: 'Input',
127   - label: FormFieldsNameEnum.EXTENSION_DESC,
128   - slot: FormFieldsEnum.EXTENSION_DESC,
  149 + helpMessage: ['支持大小写字母、数字和下划线', '如要支持原始数据留存,请使用标识符 "source"'],
129 150 ifShow: ({ model }) =>
130   - transportType === TransportTypeEnum.TCP &&
131   - ![FunctionTypeEnum.SERVICE, FunctionTypeEnum.EVENTS].includes(
132   - model[FormFieldsEnum.FUNCTION_TYPE]
133   - ),
134   - },
  151 + model[FormFieldsEnum.FUNCTION_TYPE] !== FunctionTypeEnum.PROPERTIES || isTCPModbusProduct,
  152 + component: 'AutoComplete',
  153 + defaultValue: '',
  154 + componentProps: {
  155 + options: isTCPModbusProduct ? [{ value: BuiltInIdentifierEnum.SOURCE }] : [],
  156 + placeholder: '请输入功能标识符',
  157 + autocomplete: false,
  158 + },
  159 + }),
135 160 {
136 161 field: FormFieldsEnum.ACCESS_MODE,
137 162 component: 'ApiRadioGroup',
... ... @@ -140,7 +165,7 @@ export const getFormSchemas = ({
140 165 ifShow: ({ model }) =>
141 166 ![FunctionTypeEnum.SERVICE, FunctionTypeEnum.EVENTS].includes(
142 167 model[FormFieldsEnum.FUNCTION_TYPE]
143   - ),
  168 + ) && !isTCPModbusProduct,
144 169 defaultValue: ObjectModelAccessModeEnum.READ_AND_WRITE,
145 170 componentProps: {
146 171 placeholder: '请选择读写类型',
... ... @@ -232,6 +257,70 @@ export const getFormSchemas = ({
232 257 ),
233 258 },
234 259
  260 + // TCP Modbus
  261 + {
  262 + field: FormFieldsEnum.UNIT_NAME,
  263 + label: FormFieldsNameEnum.UNIT_NAME,
  264 + component: 'Input',
  265 + show: false,
  266 + },
  267 + {
  268 + field: FormFieldsEnum.UNIT,
  269 + label: FormFieldsNameEnum.UNIT,
  270 + component: 'ApiSelect',
  271 + ifShow: () => isTCPModbusProduct,
  272 + componentProps: ({ formActionType }) => {
  273 + const { setFieldsValue } = formActionType;
  274 + return {
  275 + placeholder: '请选择单位',
  276 + api: async (params: Recordable) => {
  277 + const list = await findDictItemByCode(params);
  278 + list.map((item) => (item.itemText = `${item.itemText} / ${item.itemValue}`));
  279 + return list;
  280 + },
  281 + params: {
  282 + dictCode: DictEnum.ATTRIBUTE_UNIT,
  283 + },
  284 + labelField: 'itemText',
  285 + valueField: 'itemValue',
  286 + onChange(_, record: Record<'label' | 'value', string>) {
  287 + if (record) {
  288 + const { label } = record;
  289 + setFieldsValue({ [FormFieldsEnum.UNIT_NAME]: label });
  290 + }
  291 + },
  292 + getPopupContainer: () => document.body,
  293 + showSearch: true,
  294 + filterOption: (inputValue: string, option: Record<'label' | 'value', string>) => {
  295 + let { label, value } = option;
  296 + label = label.toLowerCase();
  297 + value = value.toLowerCase();
  298 + inputValue = inputValue.toLowerCase();
  299 + return label.includes(inputValue) || value.includes(inputValue);
  300 + },
  301 + };
  302 + },
  303 + },
  304 + {
  305 + field: FormFieldsEnum.EXTENSION_DESC,
  306 + component: 'Input',
  307 + label: FormFieldsNameEnum.EXTENSION_DESC,
  308 + slot: FormFieldsEnum.EXTENSION_DESC,
  309 + helpMessage: ['扩展描述用于配置设备接入网关协议和物模型定义之间的映射关系。'],
  310 + rules: [
  311 + {
  312 + required: true,
  313 + validator(_rule, value: any) {
  314 + return Object.values(value || {}).length
  315 + ? Promise.resolve()
  316 + : Promise.reject('请填写拓展描述符');
  317 + },
  318 + },
  319 + ],
  320 + ifShow: ({ model }) =>
  321 + isTCPModbusProduct && model[FormFieldsEnum.IDENTIFIER] !== BuiltInIdentifierEnum.SOURCE,
  322 + },
  323 +
235 324 {
236 325 field: FormFieldsEnum.REMARK,
237 326 label: FormFieldsNameEnum.REMARK,
... ...
... ... @@ -3,18 +3,14 @@
3 3 import { getFormSchemas, FormFieldsEnum } from './config';
4 4 import { StructFormItem } from './StructFormItem';
5 5 import { useObjectFormData } from './useObjectFormData';
6   - import { computed, ref, unref } from 'vue';
7   - import { TransportTypeEnum } from '/@/enums/deviceEnum';
  6 + import { computed, ref } from 'vue';
8 7 import { DataTypeForm } from './DataTypeForm';
9 8 import { DataActionModeEnum } from '/@/enums/toolEnum';
10 9 import { ExtendDesc } from './ExtendDesc';
11 10 import { createObjectModelFormContext } from './useObjectModelFormContext';
12   - import { DataTypeEnum } from '/@/enums/objectModelEnum';
  11 + import { ObjectModelFormPropsType } from './types';
13 12
14   - const props = defineProps<{
15   - transportType?: TransportTypeEnum;
16   - mode?: DataActionModeEnum;
17   - }>();
  13 + const props = defineProps<ObjectModelFormPropsType>();
18 14
19 15 const dataTypeFormRef = ref<InstanceType<typeof DataTypeForm>>();
20 16
... ... @@ -23,27 +19,20 @@
23 19 layout: 'vertical',
24 20 showActionButtonGroup: false,
25 21 });
26   -
27 22 const { getFieldsValue, setFieldsValue, validate, resetFieldsValue } = useObjectFormData({
28 23 formActionType,
29 24 dataTypeFormRef,
30   - transportType: props.transportType!,
  25 + propsRef: props,
31 26 });
32 27
33   - const objectModelType = ref(DataTypeEnum.NUMBER_INT);
34   -
35 28 createObjectModelFormContext({
36 29 getTransportType: computed(() => props.transportType),
37   - getDataType: computed(() => unref(objectModelType)),
38 30 getModalMode: computed(() => props.mode),
39 31 });
40 32
41   - const handleDataTypeFormChange = (field: string, value: any) => {
42   - if (field === FormFieldsEnum.DATA_TYPE) {
43   - objectModelType.value = value;
44   - }
  33 + const handleValidateExtendDesc = () => {
  34 + formActionType.clearValidate(FormFieldsEnum.EXTENSION_DESC);
45 35 };
46   -
47 36 defineExpose({
48 37 getFieldsValue,
49 38 setFieldsValue,
... ... @@ -55,11 +44,7 @@
55 44 <template>
56 45 <BasicForm @register="register" :disabled="mode === DataActionModeEnum.READ">
57 46 <template #dataTypeForm>
58   - <DataTypeForm
59   - ref="dataTypeFormRef"
60   - :mode="mode"
61   - @field-value-change="handleDataTypeFormChange"
62   - />
  47 + <DataTypeForm ref="dataTypeFormRef" :mode="mode" />
63 48 </template>
64 49 <template #inputData="{ model, field }">
65 50 <StructFormItem v-model:value="model[field]" buttonName="+ 增加参数" :mode="mode" />
... ... @@ -68,7 +53,11 @@
68 53 <StructFormItem v-model:value="model[field]" buttonName="+ 增加参数" :mode="mode" />
69 54 </template>
70 55 <template #extensionDesc="{ model, field }">
71   - <ExtendDesc v-model:value="model[field]" :disabled="mode === DataActionModeEnum.READ" />
  56 + <ExtendDesc
  57 + v-model:value="model[field]"
  58 + :disabled="mode === DataActionModeEnum.READ"
  59 + @change="handleValidateExtendDesc"
  60 + />
72 61 </template>
73 62 </BasicForm>
74 63 </template>
... ...
  1 +import { ExtendDescFormFieldsValueType } from './ExtendDesc';
1 2 import { FormFieldsEnum } from './config';
2   -import { ExtensionDesc, StructJSON } from '/@/api/device/model/modelOfMatterModel';
3   -import { ServiceCallTypeEnum } from '/@/enums/deviceEnum';
  3 +import { DeviceProfileDetail } from '/@/api/device/model/deviceConfigModel';
  4 +import { StructJSON } from '/@/api/device/model/modelOfMatterModel';
  5 +import { ServiceCallTypeEnum, TransportTypeEnum } from '/@/enums/deviceEnum';
4 6 import {
5 7 FunctionTypeEnum,
6 8 ObjectEventTypeEnum,
7 9 ObjectModelAccessModeEnum,
8 10 } from '/@/enums/objectModelEnum';
  11 +import { DataActionModeEnum } from '/@/enums/toolEnum';
  12 +
  13 +export interface ObjectModelFormPropsType {
  14 + transportType?: TransportTypeEnum;
  15 + mode?: DataActionModeEnum;
  16 + deviceRecord?: DeviceProfileDetail;
  17 +}
9 18
10 19 export interface ObjectModelFormGetFieldsValueType {
11 20 [FormFieldsEnum.FUNCTION_TYPE]: FunctionTypeEnum;
... ... @@ -14,16 +23,13 @@ export interface ObjectModelFormGetFieldsValueType {
14 23 [FormFieldsEnum.FUNCTION_NAME]: string;
15 24 [FormFieldsEnum.IDENTIFIER]: string;
16 25 [FormFieldsEnum.EVENT_TYPE]?: ObjectEventTypeEnum;
  26 + [FormFieldsEnum.UNIT]?: string;
  27 + [FormFieldsEnum.UNIT_NAME]: string;
17 28
18 29 [FormFieldsEnum.CALL_TYPE]?: ServiceCallTypeEnum;
19 30 [FormFieldsEnum.INPUT_DATA]?: StructJSON[];
20 31 [FormFieldsEnum.OUTPUT_DATA]?: StructJSON[];
21 32
22 33 [FormFieldsEnum.SERVICE_COMMAND]?: string;
23   - [FormFieldsEnum.EXTENSION_DESC]?: ExtensionDesc;
24   -
25   - // EXTENSION_DESC = 'extensionDesc',
26   - // STRUCT_DATA = 'structData',
27   - // INPUT_DATA = 'inputData',
28   - // OUTPUT_DATA = 'outputData',
  34 + [FormFieldsEnum.EXTENSION_DESC]?: ExtendDescFormFieldsValueType;
29 35 }
... ...
1 1 import { DataTypeForm } from './DataTypeForm';
  2 +import { ExtendDescFormFieldsValueType } from './ExtendDesc';
  3 +import { isFloatType, useParseOperationType } from './ExtendDesc/useParseOperationType';
2 4 import { FormFieldsEnum } from './config';
3   -import { ObjectModelFormGetFieldsValueType } from './types';
  5 +import { ObjectModelFormGetFieldsValueType, ObjectModelFormPropsType } from './types';
4 6 import { DefineComponentsBasicExpose } from '/#/utils';
5   -import { ModelOfMatterParams, StructJSON } from '/@/api/device/model/modelOfMatterModel';
  7 +import { ModelOfMatterParams, Specs, StructJSON } from '/@/api/device/model/modelOfMatterModel';
6 8 import { FormActionType } from '/@/components/Form';
7   -import { TransportTypeEnum } from '/@/enums/deviceEnum';
8   -import { FunctionTypeEnum } from '/@/enums/objectModelEnum';
9   -import { Ref, toRaw, unref } from 'vue';
  9 +import { TCPProtocolTypeEnum, TransportTypeEnum } from '/@/enums/deviceEnum';
  10 +import {
  11 + BuiltInIdentifierEnum,
  12 + DataTypeEnum,
  13 + FunctionTypeEnum,
  14 + ObjectModelAccessModeEnum,
  15 + OriginalDataTypeEnum,
  16 +} from '/@/enums/objectModelEnum';
  17 +import { Ref, computed, toRaw, unref } from 'vue';
10 18
11 19 interface UseObjectFormDataParamsType {
12 20 formActionType: FormActionType;
13 21 dataTypeFormRef: Ref<InstanceType<typeof DataTypeForm> | undefined>;
14   - transportType: TransportTypeEnum;
  22 + propsRef?: ObjectModelFormPropsType;
  23 +}
  24 +
  25 +export function getDataTypeByOriginalDataType(
  26 + originalDataType: OriginalDataTypeEnum,
  27 + identifier: string
  28 +) {
  29 + if (identifier === BuiltInIdentifierEnum.SOURCE) return DataTypeEnum.STRING;
  30 +
  31 + if (isFloatType(originalDataType)) return DataTypeEnum.NUMBER_DOUBLE;
  32 + else if (originalDataType === OriginalDataTypeEnum.STRING) return DataTypeEnum.STRING;
  33 + else if ([OriginalDataTypeEnum.BOOLEAN, OriginalDataTypeEnum.BITS].includes(originalDataType))
  34 + return DataTypeEnum.BOOL;
  35 + else return DataTypeEnum.NUMBER_INT;
  36 +}
  37 +
  38 +function handleTCPModbusProductPropertiesType(
  39 + value: ObjectModelFormGetFieldsValueType
  40 +): ModelOfMatterParams {
  41 + const { functionName, identifier, functionType, remark, extensionDesc, unit, unitName } = value;
  42 +
  43 + const {
  44 + operationType,
  45 + originalDataType,
  46 + bitMask,
  47 + valueRange,
  48 + registerAddress,
  49 + scaling,
  50 + registerCount,
  51 + valueMapping,
  52 + } = extensionDesc! || {};
  53 +
  54 + const { writeOnly, accessMode } = useParseOperationType(operationType);
  55 +
  56 + const dataType = getDataTypeByOriginalDataType(originalDataType, identifier);
  57 +
  58 + return {
  59 + functionType,
  60 + functionName,
  61 + identifier,
  62 + functionJson: {
  63 + dataType: {
  64 + type: dataType,
  65 + specs: {
  66 + unit,
  67 + unitName,
  68 + valueRange,
  69 + },
  70 + specsList: valueMapping || [],
  71 + },
  72 + },
  73 + accessMode:
  74 + identifier === BuiltInIdentifierEnum.SOURCE ? ObjectModelAccessModeEnum.READ : accessMode,
  75 + remark,
  76 + extensionDesc: {
  77 + writeOnly,
  78 + bitMask,
  79 + operationType,
  80 + originalDataType,
  81 + registerAddress,
  82 + scaling,
  83 + registerCount,
  84 + },
  85 + };
15 86 }
16 87
17 88 function handlePropertiesType(
18 89 value: ObjectModelFormGetFieldsValueType,
19   - dataTypeFormValue: StructJSON,
20   - transportType?: TransportTypeEnum
  90 + dataTypeFormValue: StructJSON
21 91 ): ModelOfMatterParams {
22   - const { functionType, accessMode, remark, extensionDesc } = value;
23   - const { functionName, identifier, dataType } = dataTypeFormValue;
  92 + const { functionType, accessMode, remark } = value;
  93 + const { functionName, identifier, dataType } = dataTypeFormValue || {};
24 94
25 95 const result: ModelOfMatterParams = {
26 96 functionType,
... ... @@ -33,15 +103,12 @@ function handlePropertiesType(
33 103 remark,
34 104 };
35 105
36   - if (transportType === TransportTypeEnum.TCP && extensionDesc)
37   - result.extensionDesc = extensionDesc;
38   -
39 106 return result;
40 107 }
41 108
42 109 function handleServiceType(
43 110 value: ObjectModelFormGetFieldsValueType,
44   - transportType: TransportTypeEnum
  111 + transportType?: TransportTypeEnum
45 112 ): ModelOfMatterParams {
46 113 const {
47 114 functionName,
... ... @@ -51,9 +118,11 @@ function handleServiceType(
51 118 inputData = [],
52 119 outputData = [],
53 120 serviceCommand,
  121 + callType,
54 122 } = value;
55 123
56 124 return {
  125 + callType,
57 126 functionType,
58 127 functionName,
59 128 identifier,
... ... @@ -86,26 +155,37 @@ function handleEventType(value: ObjectModelFormGetFieldsValueType): ModelOfMatte
86 155 export const useObjectFormData = ({
87 156 formActionType,
88 157 dataTypeFormRef,
89   - transportType,
  158 + propsRef,
90 159 }: UseObjectFormDataParamsType): DefineComponentsBasicExpose<ModelOfMatterParams> => {
  160 + const getTCPModbusProductFlag = computed(
  161 + () =>
  162 + propsRef?.deviceRecord?.profileData.transportConfiguration.protocol ===
  163 + TCPProtocolTypeEnum.MODBUS_RTU
  164 + );
  165 +
  166 + const getTransportType = computed(
  167 + () => propsRef?.deviceRecord?.transportType as TransportTypeEnum
  168 + );
  169 +
91 170 const getFieldsValue = (): ModelOfMatterParams => {
92 171 const value = formActionType.getFieldsValue() as ObjectModelFormGetFieldsValueType;
93 172 const { functionType } = value;
94   -
95 173 if (functionType === FunctionTypeEnum.PROPERTIES) {
96 174 const dataTypeFormValue = unref(dataTypeFormRef)?.getFieldsValue?.();
97   - return handlePropertiesType(value, dataTypeFormValue!, transportType);
  175 + if (unref(getTCPModbusProductFlag)) return handleTCPModbusProductPropertiesType(value);
  176 + return handlePropertiesType(value, dataTypeFormValue!);
98 177 }
99 178
100 179 if (functionType === FunctionTypeEnum.EVENTS) {
101 180 return handleEventType(value);
102   - } else {
103   - return handleServiceType(value, transportType);
104 181 }
  182 +
  183 + return handleServiceType(value, unref(getTransportType));
105 184 };
106 185
107 186 const setFieldsValue = (values: ModelOfMatterParams) => {
108 187 values = toRaw(unref(values));
  188 + const transportType = unref(getTransportType);
109 189 const {
110 190 functionName,
111 191 identifier,
... ... @@ -115,10 +195,11 @@ export const useObjectFormData = ({
115 195 callType,
116 196 remark,
117 197 extensionDesc,
  198 + eventType,
118 199 } = values;
119 200 const { dataType, inputData = [], outputData = [] } = functionJson || {};
120 201
121   - formActionType.setFieldsValue({
  202 + const setValue = {
122 203 identifier,
123 204 functionName,
124 205 functionType,
... ... @@ -127,9 +208,23 @@ export const useObjectFormData = ({
127 208 inputData,
128 209 outputData,
129 210 remark,
  211 + eventType,
130 212 serviceCommand: transportType === TransportTypeEnum.TCP ? inputData?.[0]?.serviceCommand : '',
131 213 extensionDesc,
132   - } as ObjectModelFormGetFieldsValueType);
  214 + } as ObjectModelFormGetFieldsValueType;
  215 +
  216 + if (unref(getTCPModbusProductFlag)) {
  217 + const { specs, type, specsList } = dataType || {};
  218 + const { unit, unitName, valueRange } = (specs as Specs) || {};
  219 + Object.assign(setValue, { unit, unitName } as ObjectModelFormGetFieldsValueType);
  220 + Object.assign(setValue.extensionDesc || {}, {
  221 + valueRange,
  222 + dataType: type,
  223 + valueMapping: specsList,
  224 + } as Partial<ExtendDescFormFieldsValueType>);
  225 + }
  226 +
  227 + formActionType.setFieldsValue(setValue);
133 228
134 229 if (functionType === FunctionTypeEnum.PROPERTIES) {
135 230 unref(dataTypeFormRef)?.setFieldsValue({
... ... @@ -145,7 +240,7 @@ export const useObjectFormData = ({
145 240 await unref(dataTypeFormRef)?.validate?.();
146 241 };
147 242
148   - const resetFieldsValue = () => {
  243 + const resetFieldsValue = async () => {
149 244 formActionType.resetFields();
150 245 unref(dataTypeFormRef)?.resetFieldsValue?.();
151 246 };
... ...
1 1 import type { InjectionKey, ComputedRef } from 'vue';
2 2 import { createContext, useContext } from '/@/hooks/core/useContext';
3 3 import { TransportTypeEnum } from '/@/enums/deviceEnum';
4   -import { DataTypeEnum } from '/@/enums/objectModelEnum';
5 4 import { DataActionModeEnum } from '/@/enums/toolEnum';
6 5
7 6 export interface ObjectModelFormContextProps {
8 7 getTransportType: ComputedRef<TransportTypeEnum | undefined>;
9   - getDataType: ComputedRef<DataTypeEnum>;
10 8 getModalMode: ComputedRef<DataActionModeEnum | undefined>;
11 9 }
12 10
... ...
... ... @@ -28,10 +28,9 @@
28 28 </BasicModal>
29 29 </template>
30 30 <script lang="ts" setup>
31   - import { ref, unref } from 'vue';
  31 + import { computed, ref, unref } from 'vue';
32 32 import { Button, Radio, RadioGroup } from 'ant-design-vue';
33 33 import { BasicModal, useModalInner } from '/@/components/Modal';
34   - import { DeviceRecord } from '/@/api/device/model/deviceModel';
35 34 import { useMessage } from '/@/hooks/web/useMessage';
36 35 import { isObject, isString } from '/@/utils/is';
37 36 import {
... ... @@ -45,11 +44,13 @@
45 44 import { BasicForm, useForm } from '/@/components/Form';
46 45 import { Authority } from '/@/components/Authority';
47 46 import { usePermission } from '/@/hooks/web/usePermission';
  47 + import { TCPProtocolTypeEnum, TransportTypeEnum } from '/@/enums/deviceEnum';
  48 + import { DeviceProfileDetail } from '/@/api/device/model/deviceConfigModel';
48 49
49 50 const emits = defineEmits(['register', 'handleImportCSV', 'handleReload']);
50 51
51   - defineProps<{
52   - record: DeviceRecord;
  52 + const props = defineProps<{
  53 + record: DeviceProfileDetail;
53 54 }>();
54 55
55 56 const { createMessage } = useMessage();
... ... @@ -196,10 +197,14 @@
196 197 const formData = new FormData();
197 198 formData.set('file', file);
198 199 const flag = isCateGory
199   - ? await importCsvCategory({ categoryId: id, deviceProfileId: undefined, file: formData })
  200 + ? await importCsvCategory({
  201 + categoryId: id,
  202 + type: unref(getIsModbusDeviceFlag) ? 'modbus' : '',
  203 + file: formData,
  204 + })
200 205 : await importCsvDeviceProfileId({
201   - categoryId: undefined,
202 206 deviceProfileId: id,
  207 + type: unref(getIsModbusDeviceFlag) ? 'modbus' : '',
203 208 file: formData,
204 209 });
205 210 flag && createMessage.info(flag?.message);
... ... @@ -224,10 +229,18 @@
224 229 URL.revokeObjectURL(objectURL);
225 230 };
226 231
  232 + const getIsModbusDeviceFlag = computed(() => {
  233 + const isTCPProtocol = props.record.transportType === TransportTypeEnum.TCP;
  234 + return (
  235 + isTCPProtocol &&
  236 + props.record?.profileData?.transportConfiguration?.protocol === TCPProtocolTypeEnum.MODBUS_RTU
  237 + );
  238 + });
  239 +
227 240 // 模板下载
228 241 const handleTemplateDownload = async () => {
229   - const res = await excelExport();
230   - downloadFile(res, '物模型属性导入模板', 'xls');
  242 + const res = await excelExport(unref(getIsModbusDeviceFlag) ? 'modbus' : '');
  243 + downloadFile(res, `${unref(getIsModbusDeviceFlag) ? 'Modbus' : ''}物模型属性导入模板`, 'xls');
231 244 };
232 245
233 246 const handleCancel = () => {
... ...
... ... @@ -140,7 +140,6 @@
140 140 import PhysicalModelTsl from './cpns/physical/PhysicalModelTsl.vue';
141 141 import { Popconfirm, Button, Alert } from 'ant-design-vue';
142 142 import { useMessage } from '/@/hooks/web/useMessage';
143   - import { DeviceRecord } from '/@/api/device/model/deviceModel';
144 143 import {
145 144 deleteModel,
146 145 deleteModelCategory,
... ... @@ -158,12 +157,13 @@
158 157 import { FunctionTypeEnum } from '/@/enums/objectModelEnum';
159 158 import SelectImport from '../components/SelectImport.vue';
160 159 import { DataActionModeEnum } from '/@/enums/toolEnum';
  160 + import { DeviceProfileDetail } from '/@/api/device/model/deviceConfigModel';
161 161
162 162 const { isPlatformAdmin, isSysadmin } = useRole();
163 163 defineEmits(['register']);
164 164
165 165 const props = defineProps<{
166   - record: DeviceRecord;
  166 + record: DeviceProfileDetail;
167 167 }>();
168 168
169 169 const { createMessage } = useMessage();
... ...
... ... @@ -49,7 +49,7 @@
49 49 </div>
50 50 </template>
51 51 <script lang="ts" setup>
52   - import { reactive, ref, onUnmounted, nextTick } from 'vue';
  52 + import { reactive, ref, onUnmounted, nextTick, unref } from 'vue';
53 53 import { BasicForm, useForm } from '/@/components/Form';
54 54 import { step2Schemas } from '../device.profile.data';
55 55 import { Button } from '/@/components/Button';
... ... @@ -183,6 +183,7 @@
183 183 },
184 184 },
185 185 });
  186 + unref(tcpRef)?.setProtocolStatus(status);
186 187 };
187 188 const updateDisabled = async () => {
188 189 await nextTick();
... ...
... ... @@ -8,6 +8,7 @@
8 8 <ObjectModelForm
9 9 ref="objectModelElRef"
10 10 :mode="openModalMode"
  11 + :device-record="record"
11 12 :transport-type="(record.transportType as TransportTypeEnum)"
12 13 />
13 14 </BasicModal>
... ... @@ -23,12 +24,12 @@
23 24 createModelCategory,
24 25 updateModelCategory,
25 26 } from '/@/api/device/modelOfMatter';
26   - import { DeviceRecord } from '/@/api/device/model/deviceModel';
27 27 import { useMessage } from '/@/hooks/web/useMessage';
28 28 import { useRole } from '/@/hooks/business/useRole';
29 29 import { ObjectModelForm } from '../../../components/ObjectModelForm';
30 30 import { TransportTypeEnum } from '/@/enums/deviceEnum';
31 31 import { DataActionModeEnum, DataActionModeNameEnum } from '/@/enums/toolEnum';
  32 + import { DeviceProfileDetail } from '/@/api/device/model/deviceConfigModel';
32 33
33 34 const { isPlatformAdmin, isSysadmin } = useRole();
34 35
... ... @@ -37,7 +38,7 @@
37 38 const objectModelElRef = ref<InstanceType<typeof ObjectModelForm>>();
38 39
39 40 const props = defineProps<{
40   - record: DeviceRecord;
  41 + record: DeviceProfileDetail;
41 42 }>();
42 43
43 44 const blockContent = `属性一般是指设备的运行状态,如当前温度等;服务是指设备可被调用的方法,支持定义参数,如执行某项任务;事件则是指设备上报的通知,如告警,需要被及时处理。`;
... ... @@ -55,13 +56,14 @@
55 56 if (record) {
56 57 await nextTick();
57 58 }
58   - unref(objectModelElRef)?.resetFieldsValue?.();
  59 +
59 60 const title = `${DataActionModeNameEnum[mode]}物模型`;
60 61 setModalProps({
61 62 showOkBtn: mode !== DataActionModeEnum.READ,
62 63 title,
63 64 });
64 65
  66 + unref(objectModelElRef)?.resetFieldsValue?.();
65 67 if (mode !== DataActionModeEnum.CREATE && record) {
66 68 currentActionModel.value = record;
67 69 unref(objectModelElRef)?.setFieldsValue(record || {});
... ...
1   -<template>
2   - <div>
3   - <a-form
4   - ref="formRef"
5   - :model="scriptForm"
6   - name="basic"
7   - :label-col="{ span: 4 }"
8   - :wrapper-col="{ span: 16 }"
9   - autocomplete="off"
10   - style="margin-left: 2.4rem"
11   - >
12   - <a-form-item
13   - v-if="deviceTypeStr !== TypeEnum.SENSOR"
14   - label="鉴权脚本"
15   - name="authScriptId"
16   - :rules="[{ required: true, message: '请选择鉴权脚本' }]"
17   - >
18   - <ScriptSelectItem
19   - ref="scriptSelectItemAuthRef"
20   - v-model:value="scriptForm.authScriptId"
21   - :scriptType="ScriptTypeEnum.TRANSPORT_TCP_AUTH"
22   - />
23   - </a-form-item>
24   - <a-form-item
25   - label="上行脚本"
26   - name="upScriptId"
27   - :rules="[{ required: true, message: '请选择上行脚本' }]"
28   - >
29   - <ScriptSelectItem
30   - ref="scriptSelectItemUpRef"
31   - v-model:value="scriptForm.upScriptId"
32   - :scriptType="ScriptTypeEnum.TRANSPORT_TCP_UP"
33   - />
34   - </a-form-item>
35   - </a-form>
36   - </div>
37   -</template>
38   -<script lang="ts" setup name="index">
39   - import { reactive, ref } from 'vue';
40   - import { useMessage } from '/@/hooks/web/useMessage';
41   - import { ScriptSelectItem } from './components';
42   - import { ScriptTypeEnum } from '/@/views/rule/script/TcpConversionScript/config';
43   - import { TypeEnum } from '/@/views/device/list/config/data';
44   -
45   - const props = defineProps({
46   - deviceTypeStr: { type: String, default: '' },
47   - });
48   -
49   - const scriptForm = reactive({
50   - authScriptId: '',
51   - upScriptId: '',
52   - });
53   -
54   - const { createMessage } = useMessage();
55   -
56   - const scriptSelectItemAuthRef = ref<InstanceType<typeof ScriptSelectItem>>();
57   -
58   - const scriptSelectItemUpRef = ref<InstanceType<typeof ScriptSelectItem>>();
59   -
60   - const getFormData = async () => {
61   - scriptForm.authScriptId = scriptSelectItemAuthRef.value?.getValue();
62   - scriptForm.upScriptId = scriptSelectItemUpRef.value?.getValue();
63   - //业务 网关子设备没有鉴权脚本
64   - if (props.deviceTypeStr === TypeEnum.SENSOR) Reflect.deleteProperty(scriptForm, 'authScriptId');
65   - if (Object.values(scriptForm).some((item) => !item)) {
66   - createMessage.error('请先选择对应脚本');
67   - throw new Error('请先选择对应脚本');
68   - }
69   - return {
70   - ...scriptForm,
71   - type: 'TCP',
72   - };
73   - };
74   -
75   - const resetFormData = () => {};
76   -
77   - const setFormData = (data) => {
78   - scriptForm.authScriptId = data?.authScriptId;
79   - scriptForm.upScriptId = data?.upScriptId;
80   - scriptSelectItemAuthRef.value?.setValue(data?.authScriptId);
81   - scriptSelectItemUpRef.value?.setValue(data?.upScriptId);
82   - };
83   - defineExpose({
84   - getFormData,
85   - resetFormData,
86   - setFormData,
87   - });
88   -</script>
89   -<style lang="less" scoped></style>
  1 +<template>
  2 + <div>
  3 + <Form
  4 + ref="formRef"
  5 + :model="scriptForm"
  6 + name="basic"
  7 + :label-col="{ span: 4 }"
  8 + :wrapper-col="{ span: 16 }"
  9 + autocomplete="off"
  10 + style="margin-left: 2.4rem"
  11 + >
  12 + <Form.Item
  13 + label="类型"
  14 + :rules="[{ required: true, message: '请选择鉴权脚本' }]"
  15 + :wrapper-col="{ span: 10 }"
  16 + >
  17 + <Select
  18 + v-model:value="scriptForm.protocol"
  19 + :options="typeOptions"
  20 + :disabled="protocolStatus"
  21 + />
  22 + </Form.Item>
  23 + <Form.Item
  24 + v-if="
  25 + scriptForm.protocol === TCPProtocolTypeEnum.CUSTOM && deviceTypeStr !== TypeEnum.SENSOR
  26 + "
  27 + label="鉴权脚本"
  28 + name="authScriptId"
  29 + :rules="[{ required: true, message: '请选择鉴权脚本' }]"
  30 + >
  31 + <ScriptSelectItem
  32 + ref="scriptSelectItemAuthRef"
  33 + v-model:value="scriptForm.authScriptId"
  34 + :scriptType="ScriptTypeEnum.TRANSPORT_TCP_AUTH"
  35 + />
  36 + </Form.Item>
  37 + <Form.Item
  38 + v-if="scriptForm.protocol === TCPProtocolTypeEnum.CUSTOM"
  39 + label="上行脚本"
  40 + name="upScriptId"
  41 + :rules="[{ required: true, message: '请选择上行脚本' }]"
  42 + >
  43 + <ScriptSelectItem
  44 + ref="scriptSelectItemUpRef"
  45 + v-model:value="scriptForm.upScriptId"
  46 + :scriptType="ScriptTypeEnum.TRANSPORT_TCP_UP"
  47 + />
  48 + </Form.Item>
  49 + </Form>
  50 + </div>
  51 +</template>
  52 +<script lang="ts" setup name="index">
  53 + import { reactive, ref } from 'vue';
  54 + import { Form, Select } from 'ant-design-vue';
  55 + import { useMessage } from '/@/hooks/web/useMessage';
  56 + import { ScriptSelectItem } from './components';
  57 + import { ScriptTypeEnum } from '/@/views/rule/script/TcpConversionScript/config';
  58 + import { TypeEnum } from '/@/views/device/list/config/data';
  59 + import { TCPProtocolTypeEnum, TCPProtocolTypeNameEnum } from '/@/enums/deviceEnum';
  60 +
  61 + const props = defineProps({
  62 + deviceTypeStr: { type: String, default: '' },
  63 + });
  64 +
  65 + const typeOptions = Object.keys(TCPProtocolTypeEnum).map((key) => ({
  66 + label: TCPProtocolTypeNameEnum[key],
  67 + value: TCPProtocolTypeEnum[key],
  68 + }));
  69 +
  70 + const scriptForm = reactive({
  71 + authScriptId: '',
  72 + upScriptId: '',
  73 + protocol: TCPProtocolTypeEnum.MODBUS_RTU,
  74 + });
  75 +
  76 + const { createMessage } = useMessage();
  77 +
  78 + const scriptSelectItemAuthRef = ref<InstanceType<typeof ScriptSelectItem>>();
  79 +
  80 + const scriptSelectItemUpRef = ref<InstanceType<typeof ScriptSelectItem>>();
  81 +
  82 + const getFormData = async () => {
  83 + scriptForm.authScriptId = scriptSelectItemAuthRef.value?.getValue();
  84 + scriptForm.upScriptId = scriptSelectItemUpRef.value?.getValue();
  85 + //业务 网关子设备没有鉴权脚本
  86 + if (scriptForm.protocol === TCPProtocolTypeEnum.CUSTOM) {
  87 + if (props.deviceTypeStr === TypeEnum.SENSOR)
  88 + Reflect.deleteProperty(scriptForm, 'authScriptId');
  89 + if (Object.values(scriptForm).some((item) => !item)) {
  90 + createMessage.error('请先选择对应脚本');
  91 + throw new Error('请先选择对应脚本');
  92 + }
  93 + }
  94 +
  95 + return {
  96 + ...scriptForm,
  97 + type: 'TCP',
  98 + };
  99 + };
  100 +
  101 + const resetFormData = () => {};
  102 +
  103 + const setFormData = (data) => {
  104 + scriptForm.protocol = data?.protocol;
  105 + scriptForm.authScriptId = data?.authScriptId;
  106 + scriptForm.upScriptId = data?.upScriptId;
  107 + scriptSelectItemAuthRef.value?.setValue(data?.authScriptId);
  108 + scriptSelectItemUpRef.value?.setValue(data?.upScriptId);
  109 + };
  110 +
  111 + const protocolStatus = ref(false);
  112 +
  113 + const setProtocolStatus = (flag: boolean) => {
  114 + protocolStatus.value = flag;
  115 + };
  116 +
  117 + defineExpose({
  118 + getFormData,
  119 + resetFormData,
  120 + setFormData,
  121 + setProtocolStatus,
  122 + });
  123 +</script>
  124 +<style lang="less" scoped></style>
... ...
... ... @@ -5,7 +5,7 @@ import {
5 5 TriggerEntityTypeEnum,
6 6 TriggerEntityTypeNameEnum,
7 7 } from '/@/enums/linkedgeEnum';
8   -import { CommandTypeEnum, CommandTypeNameEnum } from '/@/enums/deviceEnum';
  8 +import { CommandTypeEnum, CommandTypeNameEnum, TCPProtocolTypeEnum } from '/@/enums/deviceEnum';
9 9 import AlarmProfileSelect from './AlarmProfileSelect.vue';
10 10 import {
11 11 byOrganizationIdGetMasterDevice,
... ... @@ -50,6 +50,8 @@ export enum FormFieldsEnum {
50 50 TRANSPORT_TYPE = 'transportType',
51 51 ENABLE_CLEAR_RULE = 'enableClearRule',
52 52 CLEAR_RULE = 'clearRule',
  53 +
  54 + IS_TCP_MODBUS_PRODUCT = 'isTCPModbusProduct',
53 55 }
54 56
55 57 export enum FormFieldsNameEnum {
... ... @@ -199,6 +201,12 @@ export const getFormSchemas = (hasAlarmNotify: Ref<boolean>): FormSchema[] => {
199 201 ifShow: false,
200 202 },
201 203 {
  204 + field: FormFieldsEnum.IS_TCP_MODBUS_PRODUCT,
  205 + label: 'TCPModbus产品',
  206 + component: 'Input',
  207 + ifShow: false,
  208 + },
  209 + {
202 210 field: FormFieldsEnum.DEVICE_PROFILE_ID,
203 211 label: '',
204 212 component: 'ApiSelect',
... ... @@ -216,22 +224,33 @@ export const getFormSchemas = (hasAlarmNotify: Ref<boolean>): FormSchema[] => {
216 224 placeholder: `请选择${FormFieldsNameEnum.DEVICE_PROFILE_ID}`,
217 225 ...createPickerSearch(),
218 226 onChange(_value: string, option: DeviceProfileModel) {
  227 + const transportType = option?.transportType;
219 228 setFieldsValue({
220 229 [FormFieldsEnum.ENTITY_ID]: [],
221   - [FormFieldsEnum.TRANSPORT_TYPE]: option?.transportType,
  230 + [FormFieldsEnum.TRANSPORT_TYPE]: transportType,
222 231 [FormFieldsEnum.SERVICE_ID]: null,
  232 + [FormFieldsEnum.COMMAND_TYPE]: CommandTypeEnum.CUSTOM,
223 233 [FormFieldsEnum.CALL_SERVICE]: null,
224 234 [FormFieldsEnum.CALL_SERVICE_IDENTIFIER]: null,
225 235 [FormFieldsEnum.SERVICE_COMMAND]: null,
  236 + [FormFieldsEnum.IS_TCP_MODBUS_PRODUCT]:
  237 + transportType === TransportTypeEnum.TCP &&
  238 + option?.profileData?.transportConfiguration?.protocol ===
  239 + TCPProtocolTypeEnum.MODBUS_RTU,
226 240 });
227 241 },
228 242 onOptionsChange(options: (DeviceProfileModel & Record<'label' | 'value', string>)[]) {
229 243 const deviceProfileId = formModel[FormFieldsEnum.DEVICE_PROFILE_ID];
230 244 const res = options.find((item) => item.value === deviceProfileId);
231   - res &&
232   - setFieldsValue({
233   - [FormFieldsEnum.TRANSPORT_TYPE]: res.transportType,
234   - });
  245 + if (!res) return;
  246 + const transportType = res.transportType;
  247 + setFieldsValue({
  248 + [FormFieldsEnum.TRANSPORT_TYPE]: transportType,
  249 + [FormFieldsEnum.IS_TCP_MODBUS_PRODUCT]:
  250 + transportType === TransportTypeEnum.TCP &&
  251 + res?.profileData?.transportConfiguration?.protocol ===
  252 + TCPProtocolTypeEnum.MODBUS_RTU,
  253 + });
235 254 },
236 255 };
237 256 },
... ... @@ -295,12 +314,23 @@ export const getFormSchemas = (hasAlarmNotify: Ref<boolean>): FormSchema[] => {
295 314 ],
296 315 defaultValue: CommandTypeEnum.CUSTOM,
297 316 ifShow: ({ model }) => model[FormFieldsEnum.OUT_TARGET] === ExecutionActionEnum.DEVICE_OUT,
298   - componentProps: ({ formActionType }) => {
  317 + componentProps: ({ formModel, formActionType }) => {
  318 + const getOptions = () => {
  319 + const options = [{ label: CommandTypeNameEnum.CUSTOM, value: CommandTypeEnum.CUSTOM }];
  320 +
  321 + const isTCPModbusProduct = formModel[FormFieldsEnum.IS_TCP_MODBUS_PRODUCT];
  322 + const isTCP = formModel[FormFieldsEnum.TRANSPORT_TYPE] === TransportTypeEnum.TCP;
  323 + const deviceType = formModel[FormFieldsEnum.DEVICE_TYPE];
  324 +
  325 + // TCPModbus设备 TCP自定义网关子设备
  326 + if (isTCPModbusProduct || (isTCP && deviceType === DeviceTypeEnum.SENSOR)) return options;
  327 +
  328 + options.push({ label: CommandTypeNameEnum.SERVICE, value: CommandTypeEnum.SERVICE });
  329 +
  330 + return options;
  331 + };
299 332 return {
300   - options: [
301   - { label: CommandTypeNameEnum.CUSTOM, value: CommandTypeEnum.CUSTOM },
302   - { label: CommandTypeNameEnum.SERVICE, value: CommandTypeEnum.SERVICE },
303   - ],
  333 + options: getOptions(),
304 334 placeholder: `请选择${FormFieldsNameEnum.COMMAND_TYPE}`,
305 335 onChange() {
306 336 const { setFieldsValue } = formActionType;
... ...
... ... @@ -144,6 +144,7 @@
144 144 [oldCategory, category].some((item) => item === PackagesCategoryEnum.CONTROL) &&
145 145 oldCategory !== category &&
146 146 firstEnter;
  147 +
147 148 dataSource.value = unref(dataSource).map((item) => ({
148 149 ...item,
149 150 ...(needReset ? { attribute: null } : {}),
... ...
... ... @@ -9,8 +9,8 @@
9 9 import { useDataFetch } from '../../../hook/socket/useSocket';
10 10 import { useModal } from '/@/components/Modal';
11 11 import PasswordModal from '../component/PasswordModal.vue';
12   - import { useCommandDelivery } from '../../../hook/useCommandDelivery';
13 12 import { useDeviceProfileQueryContext } from '/@/views/visual/palette/hooks/useDeviceProfileQueryContext';
  13 + import { useControlComand } from '../../../hook/useControlCommand';
14 14
15 15 const props = defineProps<{
16 16 config: ComponentPropsConfigType<typeof option>;
... ... @@ -38,12 +38,12 @@
38 38 };
39 39 });
40 40
41   - const { doCommandDeliver, loading } = useCommandDelivery();
  41 + const { loading, doControlSendCommand } = useControlComand();
42 42
43 43 const handleSendCommand = async () => {
44 44 if (props.config.option.mode === ComponentMode.SELECT_PREVIEW) return;
45 45 const { option } = props.config || {};
46   - const result = await doCommandDeliver(option, Number(!unref(currentValue)));
  46 + const result = await doControlSendCommand(option, Number(!unref(currentValue)));
47 47 currentValue.value = result ? !unref(currentValue) : unref(currentValue);
48 48 };
49 49
... ...
... ... @@ -11,8 +11,8 @@
11 11 import { useDataFetch } from '../../../hook/socket/useSocket';
12 12 import { useModal } from '/@/components/Modal';
13 13 import PasswordModal from '../component/PasswordModal.vue';
14   - import { useCommandDelivery } from '../../../hook/useCommandDelivery';
15 14 import { useDeviceProfileQueryContext } from '/@/views/visual/palette/hooks/useDeviceProfileQueryContext';
  15 + import { useControlComand } from '../../../hook/useControlCommand';
16 16
17 17 const props = defineProps<{
18 18 config: ComponentPropsConfigType<typeof option>;
... ... @@ -44,7 +44,7 @@
44 44 };
45 45 });
46 46
47   - const { loading, doCommandDeliver } = useCommandDelivery();
  47 + const { loading, doControlSendCommand } = useControlComand();
48 48
49 49 const handleChange = async () => {
50 50 if (unref(getDesign).password) {
... ... @@ -58,7 +58,7 @@
58 58 const handleSendCommand = async () => {
59 59 if (props.config.option.mode === ComponentMode.SELECT_PREVIEW) return;
60 60 const { option } = props.config || {};
61   - const result = await doCommandDeliver(option, Number(unref(checked)));
  61 + const result = await doControlSendCommand(option, Number(unref(checked)));
62 62 if (!result) checked.value = !unref(checked);
63 63 };
64 64
... ...
... ... @@ -9,8 +9,8 @@
9 9 import { useDataFetch } from '../../../hook/socket/useSocket';
10 10 import PasswordModal from '../component/PasswordModal.vue';
11 11 import { useModal } from '/@/components/Modal';
12   - import { useCommandDelivery } from '../../../hook/useCommandDelivery';
13 12 import { useDeviceProfileQueryContext } from '/@/views/visual/palette/hooks/useDeviceProfileQueryContext';
  13 + import { useControlComand } from '../../../hook/useControlCommand';
14 14
15 15 const props = defineProps<{
16 16 config: ComponentPropsConfigType<typeof option>;
... ... @@ -37,12 +37,13 @@
37 37 fontSize: fontSize || persetFontSize || 14,
38 38 };
39 39 });
40   - const { doCommandDeliver, loading } = useCommandDelivery();
  40 +
  41 + const { loading, doControlSendCommand } = useControlComand();
41 42
42 43 const handleSendCommand = async () => {
43 44 if (props.config.option.mode === ComponentMode.SELECT_PREVIEW) return;
44 45 const { option } = props.config || {};
45   - const result = await doCommandDeliver(option, Number(!unref(currentValue)));
  46 + const result = await doControlSendCommand(option, Number(!unref(currentValue)));
46 47 currentValue.value = result ? !unref(currentValue) : unref(currentValue);
47 48 };
48 49 const handleChange = async (event: Event) => {
... ...
... ... @@ -10,8 +10,8 @@
10 10 import { useDataFetch } from '../../../hook/socket/useSocket';
11 11 import PasswordModal from '../component/PasswordModal.vue';
12 12 import { useModal } from '/@/components/Modal';
13   - import { useCommandDelivery } from '../../../hook/useCommandDelivery';
14 13 import { useDeviceProfileQueryContext } from '/@/views/visual/palette/hooks/useDeviceProfileQueryContext';
  14 + import { useControlComand } from '../../../hook/useControlCommand';
15 15
16 16 const props = defineProps<{
17 17 config: ComponentPropsConfigType<typeof option>;
... ... @@ -67,7 +67,7 @@
67 67 sendValue.value = value;
68 68 };
69 69
70   - const { loading, doCommandDeliver } = useCommandDelivery();
  70 + const { loading, doControlSendCommand } = useControlComand();
71 71
72 72 const handleAfterChange = () => {
73 73 if (unref(getDesign).password) {
... ... @@ -82,7 +82,7 @@
82 82 if (props.config.option.mode === ComponentMode.SELECT_PREVIEW) return;
83 83 const value = unref(sendValue);
84 84 const { option } = props.config || {};
85   - const result = await doCommandDeliver(option, value);
  85 + const result = await doControlSendCommand(option, value);
86 86 unref(sliderElRef)?.blur();
87 87 sliderValue.value = result ? value : unref(sliderValue);
88 88 };
... ...
... ... @@ -13,11 +13,11 @@
13 13 import { useReceiveValue } from '../../../hook/useReceiveValue';
14 14 import { useModal } from '/@/components/Modal';
15 15 import PasswordModal from '../component/PasswordModal.vue';
  16 + import { useDeviceProfileQueryContext } from '/@/views/visual/palette/hooks/useDeviceProfileQueryContext';
16 17 import {
17 18 DoCommandDeliverDataSourceType,
18   - useCommandDelivery,
19   - } from '../../../hook/useCommandDelivery';
20   - import { useDeviceProfileQueryContext } from '/@/views/visual/palette/hooks/useDeviceProfileQueryContext';
  19 + useControlComand,
  20 + } from '../../../hook/useControlCommand';
21 21
22 22 const props = defineProps<{
23 23 config: ComponentPropsConfigType<typeof option>;
... ... @@ -96,10 +96,10 @@
96 96 props.config.option.dataSource ? unref(getDesign).dataSource : DEFAULT_VALUE
97 97 );
98 98
99   - const { loading, doCommandDeliver } = useCommandDelivery();
  99 + const { loading, doControlSendCommand } = useControlComand();
100 100 const handleSendCommand = async (modalData: SwitchItemType) => {
101 101 if (props.config.option.mode === ComponentMode.SELECT_PREVIEW) return;
102   - await doCommandDeliver(
  102 + await doControlSendCommand(
103 103 toRaw(unref(modalData)) as DoCommandDeliverDataSourceType,
104 104 Number(unref(modalData).checked)
105 105 );
... ...
... ... @@ -9,12 +9,11 @@ import { findDictItemByCode } from '/@/api/system/dict';
9 9 import { ApiCascader, FormSchema, useComponentRegister } from '/@/components/Form';
10 10 import { OrgTreeSelect } from '/@/views/common/OrgTreeSelect';
11 11 import { DataActionModeEnum } from '/@/enums/toolEnum';
12   -import { TaskTypeEnum } from '/@/views/task/center/config';
13 12 import { createPickerSearch } from '/@/utils/pickerSearch';
14   -import { DataTypeEnum } from '/@/enums/objectModelEnum';
15   -import { CommandTypeEnum, CommandTypeNameEnum } from '/@/enums/deviceEnum';
  13 +import { DataTypeEnum, ObjectModelAccessModeEnum } from '/@/enums/objectModelEnum';
  14 +import { CommandTypeEnum, CommandTypeNameEnum, TCPProtocolTypeEnum } from '/@/enums/deviceEnum';
16 15 import { TransportTypeEnum } from '/@/enums/deviceEnum';
17   -import { DeviceTypeEnum } from '/@/api/device/model/deviceModel';
  16 +import { DeviceProfileModel, DeviceTypeEnum } from '/@/api/device/model/deviceModel';
18 17 import { ControlComponentEnum } from '../components/Control';
19 18 import { isNullOrUnDef } from '/@/utils/is';
20 19 import { validateTCPCustomCommand } from '/@/components/Form/src/components/ThingsModelForm';
... ... @@ -35,20 +34,16 @@ export interface CommonDataSourceBindValueType extends Record<DataSourceField, s
35 34 }
36 35
37 36 export enum DataSourceField {
38   - // IS_GATEWAY_DEVICE = 'gatewayDevice',
39 37 DEVICE_TYPE = 'deviceType',
40 38 TRANSPORT_TYPE = 'transportType',
41 39 ORIGINATION_ID = 'organizationId',
42 40 DEVICE_ID = 'deviceId',
43   - // DEVICE_CODE = 'deviceCode', //设备地址码
44 41 DEVICE_PROFILE_ID = 'deviceProfileId',
45 42 ATTRIBUTE = 'attribute',
46   - // ATTRIBUTE_NAME = 'attributeName',
47 43 ATTRIBUTE_RENAME = 'attributeRename',
48 44 DEVICE_NAME = 'deviceName',
49 45 DEVICE_RENAME = 'deviceRename',
50 46
51   - CODE_TYPE = 'codeType',
52 47 // COMMAND = 'command',
53 48
54 49 OPEN_COMMAND = 'openCommand',
... ... @@ -78,8 +73,7 @@ export enum DataSourceField {
78 73 */
79 74 LATITUDE_IDENTIFIER = 'latitudeIdentifier',
80 75
81   - // EXTENSION_DESC = 'extensionDesc',
82   - // CALL_TYPE = 'callType',
  76 + IS_TCP_MODBUS_PROFILE = 'isTcpModbusProfile',
83 77 }
84 78
85 79 const isTcpProfile = (transportType: string) => transportType === TransportTypeEnum.TCP;
... ... @@ -131,6 +125,7 @@ export const commonDataSourceSchemas = (): FormSchema[] => {
131 125 [DataSourceField.DEVICE_ID]: null,
132 126 [DataSourceField.ATTRIBUTE]: null,
133 127 [DataSourceField.TRANSPORT_TYPE]: null,
  128 + [DataSourceField.COMMAND_TYPE]: null,
134 129 });
135 130 },
136 131 getPopupContainer: () => document.body,
... ... @@ -144,6 +139,12 @@ export const commonDataSourceSchemas = (): FormSchema[] => {
144 139 ifShow: false,
145 140 },
146 141 {
  142 + field: DataSourceField.IS_TCP_MODBUS_PROFILE,
  143 + component: 'Input',
  144 + label: 'TCP Modbus产品',
  145 + ifShow: false,
  146 + },
  147 + {
147 148 field: DataSourceField.DEVICE_PROFILE_ID,
148 149 component: 'ApiSelect',
149 150 label: '产品',
... ... @@ -166,14 +167,35 @@ export const commonDataSourceSchemas = (): FormSchema[] => {
166 167 labelField: 'name',
167 168 valueField: 'id',
168 169 placeholder: '请选择产品',
169   - onChange: (_, option: Record<'transportType', string>) => {
  170 + onChange: (_, option: DeviceProfileModel) => {
  171 + const transportType = option?.[DataSourceField.TRANSPORT_TYPE];
170 172 setFieldsValue({
171 173 [DataSourceField.DEVICE_ID]: null,
172 174 [DataSourceField.ATTRIBUTE]: null,
173   - [DataSourceField.TRANSPORT_TYPE]: option?.[DataSourceField.TRANSPORT_TYPE],
174 175 [DataSourceField.COMMAND_TYPE]: null,
  176 + [DataSourceField.TRANSPORT_TYPE]: transportType,
  177 + [DataSourceField.IS_TCP_MODBUS_PROFILE]:
  178 + transportType === TransportTypeEnum.TCP &&
  179 + option?.profileData?.transportConfiguration?.protocol ===
  180 + TCPProtocolTypeEnum.MODBUS_RTU,
175 181 });
176 182 },
  183 + onOptionsChange(options: (DeviceProfileModel & Record<'value' | 'label', string>)[]) {
  184 + const currentItem = options.find(
  185 + (item) => item.value === formModel[DataSourceField.DEVICE_PROFILE_ID]
  186 + );
  187 +
  188 + if (currentItem) {
  189 + const transportType = currentItem?.[DataSourceField.TRANSPORT_TYPE];
  190 + setFieldsValue({
  191 + [DataSourceField.TRANSPORT_TYPE]: transportType,
  192 + [DataSourceField.IS_TCP_MODBUS_PROFILE]:
  193 + transportType === TransportTypeEnum.TCP &&
  194 + currentItem?.profileData?.transportConfiguration?.protocol ===
  195 + TCPProtocolTypeEnum.MODBUS_RTU,
  196 + });
  197 + }
  198 + },
177 199 getPopupContainer: () => document.body,
178 200 };
179 201 },
... ... @@ -236,13 +258,13 @@ export const commonDataSourceSchemas = (): FormSchema[] => {
236 258 deviceType: item.deviceType,
237 259 }));
238 260
239   - if (
240   - unref(selectWidgetKeys).componentKey ===
241   - ControlComponentEnum.LATERAL_NUMERICAL_CONTROL &&
242   - isTcpProfile(formModel[DataSourceField.TRANSPORT_TYPE])
243   - ) {
244   - return result.filter((item) => item.codeType === TaskTypeEnum.MODBUS_RTU);
245   - }
  261 + // if (
  262 + // unref(selectWidgetKeys).componentKey ===
  263 + // ControlComponentEnum.LATERAL_NUMERICAL_CONTROL &&
  264 + // isTcpProfile(formModel[DataSourceField.TRANSPORT_TYPE])
  265 + // ) {
  266 + // return result.filter((item) => item.codeType === TaskTypeEnum.MODBUS_RTU);
  267 + // }
246 268
247 269 return result;
248 270 }
... ... @@ -254,7 +276,6 @@ export const commonDataSourceSchemas = (): FormSchema[] => {
254 276 setFieldsValue({
255 277 [DataSourceField.COMMAND_TYPE]: null,
256 278 [DataSourceField.DEVICE_NAME]: options?.label,
257   - [DataSourceField.CODE_TYPE]: options?.codeType,
258 279 });
259 280 },
260 281 placeholder: '请选择设备',
... ... @@ -263,26 +284,19 @@ export const commonDataSourceSchemas = (): FormSchema[] => {
263 284 },
264 285 },
265 286 {
266   - field: DataSourceField.CODE_TYPE,
267   - label: '设备标识符类型',
268   - component: 'Input',
269   - ifShow: false,
270   - },
271   - {
272 287 field: DataSourceField.ATTRIBUTE,
273 288 component: 'ApiSelect',
274 289 label: '属性',
275 290 colProps: { span: 8 },
276 291 rules: [{ required: true, message: '请选择属性' }],
277 292 ifShow: () => category !== CategoryEnum.MAP,
278   - componentProps({ formModel, formActionType }) {
279   - const { setFieldsValue } = formActionType;
  293 + componentProps({ formModel }) {
280 294 const deviceProfileId = formModel[DataSourceField.DEVICE_PROFILE_ID];
281 295 return {
282 296 api: async () => {
283 297 try {
284 298 if (deviceProfileId) {
285   - const option = await getDeviceAttribute({
  299 + let option = await getDeviceAttribute({
286 300 deviceProfileId,
287 301 dataType:
288 302 (isControlComponent(category!) &&
... ... @@ -292,21 +306,26 @@ export const commonDataSourceSchemas = (): FormSchema[] => {
292 306 ? DataTypeEnum.BOOL
293 307 : undefined,
294 308 });
  309 + // 过滤只读属性
  310 + option = option.filter(
  311 + (item) => item.accessMode !== ObjectModelAccessModeEnum.READ
  312 + );
295 313
296 314 // 选择控制组件4的时候只能选择属性且是(int double类型)
297 315 if (
298 316 unref(selectWidgetKeys).componentKey ===
299 317 ControlComponentEnum.LATERAL_NUMERICAL_CONTROL
300 318 ) {
301   - setFieldsValue({
302   - [DataSourceField.COMMAND_TYPE]: CommandTypeEnum.ATTRIBUTE.toString(),
303   - });
  319 + // setFieldsValue({
  320 + // [DataSourceField.COMMAND_TYPE]: CommandTypeEnum.ATTRIBUTE.toString(),
  321 + // });
304 322 return option.filter(
305 323 (item) =>
306 324 item.detail.dataType.type === DataTypeEnum.NUMBER_INT ||
307 325 item.detail.dataType.type == DataTypeEnum.NUMBER_DOUBLE
308 326 );
309 327 }
  328 +
310 329 return option;
311 330 }
312 331 } catch (error) {}
... ... @@ -412,18 +431,19 @@ export const commonDataSourceSchemas = (): FormSchema[] => {
412 431 isControlComponent(category!) &&
413 432 unref(selectWidgetKeys).componentKey !== ControlComponentEnum.LATERAL_NUMERICAL_CONTROL &&
414 433 isTcpProfile(model[DataSourceField.TRANSPORT_TYPE]) &&
415   - model[DataSourceField.CODE_TYPE] === TaskTypeEnum.CUSTOM
  434 + !model[DataSourceField.IS_TCP_MODBUS_PROFILE]
416 435 );
417 436 },
418 437 componentProps: ({ formActionType, formModel }) => {
419 438 const { setFieldsValue } = formActionType;
420 439 const deviceType = formModel[DataSourceField.DEVICE_TYPE];
  440 + const isTCPModbusProfile = formModel[DataSourceField.IS_TCP_MODBUS_PROFILE];
421 441 const options: Record<'label' | 'value', string | number>[] = [
422 442 { label: CommandTypeNameEnum.CUSTOM, value: CommandTypeEnum.CUSTOM },
423 443 ];
424 444
425   - // 网关子设备无服务
426   - if (deviceType !== DeviceTypeEnum.SENSOR)
  445 + // TCP网关子设备无服务 TCP Modbus类型设备无服务
  446 + if (deviceType !== DeviceTypeEnum.SENSOR && !isTCPModbusProfile)
427 447 options.push({ label: CommandTypeNameEnum.SERVICE, value: CommandTypeEnum.SERVICE });
428 448
429 449 return {
... ... @@ -448,9 +468,8 @@ export const commonDataSourceSchemas = (): FormSchema[] => {
448 468 colProps: { span: 8 },
449 469 rules: [{ required: true, message: '请选择开服务' }],
450 470 ifShow: ({ model }) =>
451   - isControlComponent(category!) &&
452   - model[DataSourceField.COMMAND_TYPE] === CommandTypeEnum.SERVICE &&
453   - isTcpProfile(model[DataSourceField.TRANSPORT_TYPE]),
  471 + isTcpProfile(model[DataSourceField.TRANSPORT_TYPE]) &&
  472 + model[DataSourceField.COMMAND_TYPE] === CommandTypeEnum.SERVICE,
454 473 componentProps({ formModel }) {
455 474 const deviceProfileId = formModel[DataSourceField.DEVICE_PROFILE_ID];
456 475 return {
... ... @@ -472,9 +491,8 @@ export const commonDataSourceSchemas = (): FormSchema[] => {
472 491 colProps: { span: 8 },
473 492 rules: [{ required: true, message: '请选择关服务' }],
474 493 ifShow: ({ model }) =>
475   - isControlComponent(category!) &&
476   - model[DataSourceField.COMMAND_TYPE] === CommandTypeEnum.SERVICE &&
477   - isTcpProfile(model[DataSourceField.TRANSPORT_TYPE]),
  494 + isTcpProfile(model[DataSourceField.TRANSPORT_TYPE]) &&
  495 + model[DataSourceField.COMMAND_TYPE] === CommandTypeEnum.SERVICE,
478 496 componentProps({ formModel }) {
479 497 const deviceProfileId = formModel[DataSourceField.DEVICE_PROFILE_ID];
480 498 return {
... ... @@ -497,10 +515,8 @@ export const commonDataSourceSchemas = (): FormSchema[] => {
497 515 rules: [{ validator: validateTCPCustomCommand }],
498 516 // 是控制组件 && 自定义命令 && 传输协议为TCP
499 517 ifShow: ({ model }) =>
500   - isControlComponent(category!) &&
501   - model[DataSourceField.COMMAND_TYPE] === CommandTypeEnum.CUSTOM &&
502   - model[DataSourceField.TRANSPORT_TYPE] &&
503   - isTcpProfile(model[DataSourceField.TRANSPORT_TYPE]),
  518 + isTcpProfile(model[DataSourceField.TRANSPORT_TYPE]) &&
  519 + model[DataSourceField.COMMAND_TYPE] === CommandTypeEnum.CUSTOM,
504 520 componentProps: {
505 521 placeholder: '请输入开下发命令',
506 522 },
... ... @@ -513,10 +529,8 @@ export const commonDataSourceSchemas = (): FormSchema[] => {
513 529 rules: [{ validator: validateTCPCustomCommand }],
514 530 // 是控制组件 && 自定义命令 && 传输协议为TCP
515 531 ifShow: ({ model }) =>
516   - isControlComponent(category!) &&
517   - model[DataSourceField.COMMAND_TYPE] === CommandTypeEnum.CUSTOM &&
518   - model[DataSourceField.TRANSPORT_TYPE] &&
519   - isTcpProfile(model[DataSourceField.TRANSPORT_TYPE]),
  532 + isTcpProfile(model[DataSourceField.TRANSPORT_TYPE]) &&
  533 + model[DataSourceField.COMMAND_TYPE] === CommandTypeEnum.CUSTOM,
520 534 componentProps: {
521 535 placeholder: '请输入关下发命令',
522 536 },
... ...