From 90c6d9baecf2f06dd49464a7083f849e37d60f0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcone=20Ten=C3=B3rio=20da=20Silva=20Filho?= Date: Tue, 21 Apr 2026 14:45:33 +0200 Subject: [PATCH] refactor(ethercat): extract concrete EthercatConfig and EtherCATMasterConfig types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the inline anonymous shape of PLCRemoteDevice.ethercatConfig with two named interfaces in the shared ports layer, so consumers can reference them directly instead of duplicating the shape or widening to Record. Drops the `as unknown as ConfiguredEtherCATDevice[]` casts in the EtherCAT editor components and the `as EthercatConfig` casts around masterConfig syncing in the project slice. The updateEthercatConfig action now receives an EthercatConfig directly rather than the Record fallback. Stacked on feat/ethercat-esi-backend (PR #731) — the EtherCAT base shape only lives on that branch until #731 lands in development. Mirrored in openplc-web on development directly (that side already carries the base shape). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../ethercat/ethercat-device-editor.tsx | 2 +- .../editor/device/ethercat/index.tsx | 2 +- src/frontend/store/slices/project/slice.ts | 7 ++-- src/frontend/store/slices/project/types.ts | 3 +- src/middleware/shared/ports/types.ts | 37 +++++++++---------- 5 files changed, 24 insertions(+), 27 deletions(-) diff --git a/src/frontend/components/_features/[workspace]/editor/device/ethercat/ethercat-device-editor.tsx b/src/frontend/components/_features/[workspace]/editor/device/ethercat/ethercat-device-editor.tsx index 14fbf77e7..b5de3d12d 100644 --- a/src/frontend/components/_features/[workspace]/editor/device/ethercat/ethercat-device-editor.tsx +++ b/src/frontend/components/_features/[workspace]/editor/device/ethercat/ethercat-device-editor.tsx @@ -66,7 +66,7 @@ const EtherCATDeviceEditor = () => { }, [project.data.remoteDevices, busName]) const configuredDevices = useMemo(() => { - return (remoteDevice?.ethercatConfig?.devices ?? []) as unknown as ConfiguredEtherCATDevice[] + return remoteDevice?.ethercatConfig?.devices ?? [] }, [remoteDevice]) const device = useMemo(() => { diff --git a/src/frontend/components/_features/[workspace]/editor/device/ethercat/index.tsx b/src/frontend/components/_features/[workspace]/editor/device/ethercat/index.tsx index a6b377de6..3e5031602 100644 --- a/src/frontend/components/_features/[workspace]/editor/device/ethercat/index.tsx +++ b/src/frontend/components/_features/[workspace]/editor/device/ethercat/index.tsx @@ -98,7 +98,7 @@ const EtherCATEditor = () => { }, [project.data.remoteDevices, deviceName]) const configuredDevices = useMemo(() => { - return (remoteDevice?.ethercatConfig?.devices ?? []) as unknown as ConfiguredEtherCATDevice[] + return remoteDevice?.ethercatConfig?.devices ?? [] }, [remoteDevice]) const masterConfig = useMemo(() => { diff --git a/src/frontend/store/slices/project/slice.ts b/src/frontend/store/slices/project/slice.ts index f1ff583e6..e5381dbed 100644 --- a/src/frontend/store/slices/project/slice.ts +++ b/src/frontend/store/slices/project/slice.ts @@ -1,5 +1,4 @@ import { cycleTimeUsToIecInterval, ethercatTaskName } from '@root/backend/shared/ethercat/ethercat-task-helpers' -import type { EthercatConfig } from '@root/backend/shared/types/PLC/open-plc' import { produce } from 'immer' import { StateCreator } from 'zustand' @@ -1184,7 +1183,7 @@ const createProjectSlice: StateCreator = (se ) return ok() }, - updateEthercatConfig: (deviceName: string, ethercatConfig: EthercatConfig | Record) => { + updateEthercatConfig: (deviceName, ethercatConfig) => { let response = ok() setState( produce((slice: ProjectSlice) => { @@ -1201,10 +1200,10 @@ const createProjectSlice: StateCreator = (se response = { ok: false, message: 'Device is not an EtherCAT device' } return } - device.ethercatConfig = ethercatConfig as typeof device.ethercatConfig + device.ethercatConfig = ethercatConfig // Sync master config fields to the associated system task - const masterCfg = (ethercatConfig as EthercatConfig).masterConfig + const masterCfg = ethercatConfig.masterConfig if (masterCfg) { const systemTask = slice.project.data.configurations.resource.tasks.find( (t) => t.isSystemTask && t.associatedDevice === deviceName, diff --git a/src/frontend/store/slices/project/types.ts b/src/frontend/store/slices/project/types.ts index 105dfbc25..627d58c94 100644 --- a/src/frontend/store/slices/project/types.ts +++ b/src/frontend/store/slices/project/types.ts @@ -1,4 +1,5 @@ import type { + EthercatConfig, ModbusBufferMapping, ModbusIOGroup, OpcUaNodeConfig, @@ -239,7 +240,7 @@ export type ProjectActions = { ) => ProjectResponse deleteIOGroup: (deviceName: string, groupId: string) => ProjectResponse updateIOPointAlias: (deviceName: string, groupId: string, pointId: string, alias: string) => ProjectResponse - updateEthercatConfig: (deviceName: string, ethercatConfig: Record) => ProjectResponse + updateEthercatConfig: (deviceName: string, ethercatConfig: EthercatConfig) => ProjectResponse } // --------------------------------------------------------------------------- diff --git a/src/middleware/shared/ports/types.ts b/src/middleware/shared/ports/types.ts index 404123765..7e7d5b7d3 100644 --- a/src/middleware/shared/ports/types.ts +++ b/src/middleware/shared/ports/types.ts @@ -7,6 +7,8 @@ * implement the ports that use them. */ +import type { ConfiguredEtherCATDevice } from './esi-types' + // --------------------------------------------------------------------------- // Result wrappers // --------------------------------------------------------------------------- @@ -384,31 +386,26 @@ export interface PLCServer { opcuaServerConfig?: OpcUaServerConfig } +// EtherCAT +export interface EtherCATMasterConfig { + enabled?: boolean + networkInterface: string + cycleTimeUs: number + watchdogTimeoutCycles?: number + taskPriority?: number +} + +export interface EthercatConfig { + masterConfig?: EtherCATMasterConfig + devices: ConfiguredEtherCATDevice[] +} + // PLCRemoteDevice export interface PLCRemoteDevice { name: string protocol: RemoteDeviceProtocol modbusTcpConfig?: ModbusRemoteTcpConfig - ethercatConfig?: { - masterConfig?: { - enabled?: boolean - networkInterface: string - cycleTimeUs: number - watchdogTimeoutCycles?: number - taskPriority?: number - } - devices: Array<{ - id: string - name: string - channelMappings: Array<{ - channelId: string - iecLocation: string - userEdited: boolean - alias?: string - }> - [key: string]: unknown - }> - } + ethercatConfig?: EthercatConfig } // ---------------------------------------------------------------------------