| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518 |
- const {
- buildReadFrame,
- buildWriteMultipleRegistersFrame,
- buildWriteSingleCoilFrame,
- buildWriteSingleRegisterFrame,
- formatHex
- } = require('../../protocols/modbus-rtu/frame.js')
- const transport = require('../../transport/ble-core.js')
- const {
- DATA_TYPE_OPTIONS,
- getDataType,
- getRegisterEncodedWords,
- isByteRegister,
- isTextRegister,
- normalizeRegister,
- validateRegisterValue
- } = require('../../domain/generic-modbus/model.js')
- const MODBUS_COMMANDS = [
- { key: 'readCoils', label: '01 读取线圈', functionCode: 0x01, inputMode: 'quantity' },
- { key: 'readDiscreteInputs', label: '02 读取离散输入', functionCode: 0x02, inputMode: 'quantity' },
- { key: 'readHolding', label: '03 读取保持寄存器', functionCode: 0x03, inputMode: 'quantity' },
- { key: 'readInput', label: '04 读取输入寄存器', functionCode: 0x04, inputMode: 'quantity' },
- { key: 'writeCoil', label: '05 写单线圈', functionCode: 0x05, inputMode: 'coil' },
- { key: 'writeRegister', label: '06 写单寄存器', functionCode: 0x06, inputMode: 'single' },
- { key: 'writeRegisters', label: '10 写多寄存器', functionCode: 0x10, inputMode: 'multiple' }
- ]
- const state = {
- commandIndex: 2,
- commandRegisterQuantity: '0001',
- commandValue: '0001',
- commandValueLabel: '读取数量',
- coilEnabled: true,
- generatedHex: '',
- protocolCommands: MODBUS_COMMANDS,
- protocolDataTypeOptions: DATA_TYPE_OPTIONS,
- protocolErrorText: '',
- protocolMultipleDialog: {
- visible: false
- },
- protocolMultipleValues: [],
- registerAddress: '0000',
- showCoilValue: false,
- showCommandValue: true,
- showRegisterQuantity: false,
- slaveAddress: 'F0'
- }
- const subscribers = []
- function setState(changedData) {
- Object.assign(state, changedData)
- subscribers.slice().forEach((subscriber) => {
- subscriber(getState())
- })
- }
- function getState() {
- return {
- ...state,
- protocolCommands: state.protocolCommands.slice(),
- protocolDataTypeOptions: state.protocolDataTypeOptions.slice(),
- protocolMultipleDialog: {
- ...state.protocolMultipleDialog
- },
- protocolMultipleValues: state.protocolMultipleValues.map((item) => ({
- ...item
- }))
- }
- }
- function subscribe(subscriber) {
- if (typeof subscriber !== 'function') return () => {}
- subscribers.push(subscriber)
- subscriber(getState())
- return () => {
- const index = subscribers.indexOf(subscriber)
- if (index >= 0) subscribers.splice(index, 1)
- }
- }
- function parseHexNumber(value, label, maxValue) {
- const text = String(value || '').trim().replace(/^0x/i, '')
- if (!text || !/^[0-9a-fA-F]+$/.test(text)) {
- throw new Error(`${label}请输入十六进制数值`)
- }
- const parsedValue = parseInt(text, 16)
- if (parsedValue > maxValue) {
- throw new Error(`${label}超出范围`)
- }
- return parsedValue
- }
- function parseRegisterValues(value) {
- const text = String(value || '').trim()
- if (!text) throw new Error('请输入寄存器写入值')
- return text.split(/[\s,;]+/)
- .filter(Boolean)
- .map((item) => parseHexNumber(item, '写入值', 0xFFFF))
- }
- function getCommand(index) {
- return MODBUS_COMMANDS[index] || MODBUS_COMMANDS[0]
- }
- function getDefaultCommandValue(command) {
- if (command.inputMode === 'quantity') return '0001'
- if (command.inputMode === 'coil') return 'ON'
- if (command.inputMode === 'multiple') return '0000'
- return '0000'
- }
- function normalizeManualMultipleQuantity(value) {
- const text = String(value === undefined || value === null ? '' : value).trim()
- if (!text) return 1
- if (/^[0-9a-fA-F]+$/.test(text)) return Math.max(1, Math.min(parseInt(text, 16), 0x007B))
- const numberValue = Number(text)
- return Number.isFinite(numberValue) ? Math.max(1, Math.min(Math.round(numberValue), 0x007B)) : 1
- }
- function formatManualMultipleQuantity(quantity) {
- return Number(quantity || 1).toString(16).toUpperCase().padStart(4, '0')
- }
- function createManualMultipleRegister(index, value = {}) {
- const dataType = getDataType(value.dataType || 'hex')
- const register = normalizeRegister({
- dataType: dataType.key,
- inputValue: value.inputValue === undefined ? '' : value.inputValue,
- name: `寄存器 ${index + 1}`,
- textByteLength: value.textByteLength || (isTextRegister(dataType.key) ? '32' : '')
- }, {
- registerType: 'holding'
- }, index, Number(value.address || 0), 0)
- return {
- ...register,
- dataTypeIndex: DATA_TYPE_OPTIONS.findIndex((item) => item.key === register.dataType),
- inputValue: value.inputValue === undefined ? '' : value.inputValue
- }
- }
- function getManualRegisterWordCount(register) {
- return Math.max(1, Number(register && register.registerCount) || 1)
- }
- function normalizeManualMultipleValues(wordQuantity, values = [], startAddress = 0) {
- const result = []
- let address = Number(startAddress) || 0
- const endAddress = address + Math.max(1, Number(wordQuantity) || 1)
- let sourceIndex = 0
- while (address < endAddress) {
- const current = values[sourceIndex] || {}
- let register = createManualMultipleRegister(result.length, {
- ...current,
- address
- })
- const remainingWords = endAddress - address
- if (getManualRegisterWordCount(register) > remainingWords) {
- register = createManualMultipleRegister(result.length, {
- ...current,
- address,
- dataType: 'hex',
- inputValue: ''
- })
- }
- result.push(register)
- address += getManualRegisterWordCount(register)
- sourceIndex += 1
- }
- return result
- }
- function getManualMultipleWords(values = []) {
- const words = []
- values.forEach((register) => {
- if (isByteRegister(register.dataType)) {
- const registerWords = getRegisterEncodedWords(register)
- if (!Array.isArray(registerWords) || !registerWords.length) throw new Error(`${register.name} 输入值无效`)
- words.push(Number(registerWords[0]) & 0x00FF)
- return
- }
- const registerWords = getRegisterEncodedWords(register)
- if (!Array.isArray(registerWords) || !registerWords.length) throw new Error(`${register.name} 输入值无效`)
- registerWords.forEach((word) => words.push(Number(word) & 0xFFFF))
- })
- return words
- }
- function getManualMultipleValueText(values = []) {
- try {
- return getManualMultipleWords(values).map((word) => word.toString(16).toUpperCase().padStart(4, '0')).join(' ')
- } catch (error) {
- return ''
- }
- }
- function generateModbusFrame(command, slaveAddress, registerAddress, commandValue, coilEnabled, commandRegisterQuantity) {
- const slave = parseHexNumber(slaveAddress, '从站地址', 0xFF)
- const address = parseHexNumber(registerAddress, '起始地址', 0xFFFF)
- if (command.inputMode === 'quantity') {
- const quantity = parseHexNumber(commandValue, '读取数量', 0xFFFF)
- return buildReadFrame(slave, command.functionCode, address, quantity)
- }
- if (command.inputMode === 'coil') {
- return buildWriteSingleCoilFrame(slave, address, coilEnabled)
- }
- if (command.inputMode === 'single') {
- return buildWriteSingleRegisterFrame(slave, address, parseHexNumber(commandValue, '写入值', 0xFFFF))
- }
- const words = parseRegisterValues(commandValue)
- const quantity = parseHexNumber(commandRegisterQuantity, '寄存器个数', 0xFFFF)
- if (quantity !== words.length) {
- throw new Error(`写入值数量应为 ${quantity} 个寄存器`)
- }
- return buildWriteMultipleRegistersFrame(slave, address, words)
- }
- function createProtocolState(commandIndex, slaveAddress, registerAddress, commandValue, coilEnabled, commandRegisterQuantity) {
- const command = getCommand(commandIndex)
- const commandValueLabel = command.inputMode === 'quantity' ? '读取数量' : '写入值'
- try {
- return {
- commandValueLabel,
- generatedHex: formatHex(generateModbusFrame(command, slaveAddress, registerAddress, commandValue, coilEnabled, commandRegisterQuantity)),
- protocolErrorText: '',
- showCoilValue: command.inputMode === 'coil',
- showRegisterQuantity: command.inputMode === 'multiple',
- showCommandValue: command.inputMode !== 'coil'
- }
- } catch (error) {
- return {
- commandValueLabel,
- generatedHex: '',
- protocolErrorText: error.message,
- showCoilValue: command.inputMode === 'coil',
- showRegisterQuantity: command.inputMode === 'multiple',
- showCommandValue: command.inputMode !== 'coil'
- }
- }
- }
- function setProtocolInput(changedData) {
- const command = getCommand(changedData.commandIndex === undefined ? state.commandIndex : changedData.commandIndex)
- let nextMultipleValues = changedData.protocolMultipleValues
- let nextCommandValue = changedData.commandValue
- if (
- command.inputMode === 'multiple'
- && Object.prototype.hasOwnProperty.call(changedData, 'registerAddress')
- && !Object.prototype.hasOwnProperty.call(changedData, 'protocolMultipleValues')
- ) {
- try {
- const startAddress = parseHexNumber(changedData.registerAddress, '起始地址', 0xFFFF)
- nextMultipleValues = normalizeManualMultipleValues(
- normalizeManualMultipleQuantity(state.commandRegisterQuantity),
- state.protocolMultipleValues,
- startAddress
- )
- nextCommandValue = getManualMultipleValueText(nextMultipleValues)
- } catch (error) {}
- }
- const nextState = {
- commandIndex: state.commandIndex,
- commandRegisterQuantity: state.commandRegisterQuantity,
- slaveAddress: state.slaveAddress,
- registerAddress: state.registerAddress,
- commandValue: state.commandValue,
- coilEnabled: state.coilEnabled,
- ...changedData,
- ...(nextMultipleValues ? { protocolMultipleValues: nextMultipleValues } : {}),
- ...(nextCommandValue !== undefined ? { commandValue: nextCommandValue } : {})
- }
- setState({
- ...changedData,
- ...(nextMultipleValues ? { protocolMultipleValues: nextMultipleValues } : {}),
- ...(nextCommandValue !== undefined ? { commandValue: nextCommandValue } : {}),
- ...createProtocolState(
- nextState.commandIndex,
- nextState.slaveAddress,
- nextState.registerAddress,
- nextState.commandValue,
- nextState.coilEnabled,
- nextState.commandRegisterQuantity
- )
- })
- }
- function setCommandIndex(index) {
- const commandIndex = Number(index)
- const command = getCommand(commandIndex)
- const commandValue = command.inputMode === 'multiple'
- ? getManualMultipleValueText(state.protocolMultipleValues)
- : getDefaultCommandValue(command)
- setProtocolInput({
- commandIndex,
- commandValue,
- coilEnabled: true,
- commandRegisterQuantity: state.commandRegisterQuantity
- })
- }
- function openProtocolMultipleDialog() {
- let startAddress = 0
- try {
- startAddress = parseHexNumber(state.registerAddress, '起始地址', 0xFFFF)
- } catch (error) {
- setState({
- protocolErrorText: error.message || '起始地址无效'
- })
- return
- }
- const quantity = normalizeManualMultipleQuantity(state.commandRegisterQuantity)
- const values = normalizeManualMultipleValues(quantity, state.protocolMultipleValues, startAddress)
- setState({
- commandRegisterQuantity: formatManualMultipleQuantity(quantity),
- protocolMultipleDialog: {
- title: '写多个寄存器',
- visible: true
- },
- protocolMultipleValues: values
- })
- }
- function closeProtocolMultipleDialog() {
- setState({
- protocolMultipleDialog: {
- visible: false
- }
- })
- }
- function setProtocolMultipleQuantity(value) {
- const quantity = normalizeManualMultipleQuantity(value)
- let startAddress = 0
- try {
- startAddress = parseHexNumber(state.registerAddress, '起始地址', 0xFFFF)
- } catch (error) {
- setState({
- commandRegisterQuantity: value,
- protocolErrorText: error.message || '起始地址无效'
- })
- return
- }
- const values = normalizeManualMultipleValues(quantity, state.protocolMultipleValues, startAddress)
- const commandValue = getManualMultipleValueText(values)
- setProtocolInput({
- commandRegisterQuantity: value,
- commandValue,
- protocolMultipleValues: values
- })
- }
- function setProtocolMultipleValue(index, value) {
- const registerIndex = Number(index)
- const values = state.protocolMultipleValues.map((register, currentIndex) => (
- currentIndex === registerIndex
- ? {
- ...register,
- inputValue: value
- }
- : register
- ))
- let startAddress = 0
- try {
- startAddress = parseHexNumber(state.registerAddress, '起始地址', 0xFFFF)
- } catch (error) {
- setState({ protocolErrorText: error.message || '起始地址无效' })
- return
- }
- const normalizedValues = normalizeManualMultipleValues(normalizeManualMultipleQuantity(state.commandRegisterQuantity), values, startAddress)
- const commandValue = getManualMultipleValueText(normalizedValues)
- setProtocolInput({
- commandValue,
- protocolMultipleValues: normalizedValues
- })
- }
- function setProtocolMultipleType(index, dataTypeIndex) {
- const registerIndex = Number(index)
- const dataType = DATA_TYPE_OPTIONS[Number(dataTypeIndex)] || DATA_TYPE_OPTIONS[0]
- const changedValues = state.protocolMultipleValues.map((register, currentIndex) => (
- currentIndex === registerIndex
- ? createManualMultipleRegister(currentIndex, {
- ...register,
- dataType: dataType.key,
- inputValue: '',
- textByteLength: isTextRegister(dataType.key) ? (register.textByteLength || '32') : ''
- })
- : register
- ))
- let startAddress = 0
- try {
- startAddress = parseHexNumber(state.registerAddress, '起始地址', 0xFFFF)
- } catch (error) {
- setState({ protocolErrorText: error.message || '起始地址无效' })
- return
- }
- const values = normalizeManualMultipleValues(normalizeManualMultipleQuantity(state.commandRegisterQuantity), changedValues, startAddress)
- setProtocolInput({
- commandValue: getManualMultipleValueText(values),
- protocolMultipleValues: values
- })
- }
- function setProtocolMultipleTextLength(index, value) {
- const registerIndex = Number(index)
- const changedValues = state.protocolMultipleValues.map((register, currentIndex) => (
- currentIndex === registerIndex
- ? createManualMultipleRegister(currentIndex, {
- ...register,
- textByteLength: value
- })
- : register
- ))
- let startAddress = 0
- try {
- startAddress = parseHexNumber(state.registerAddress, '起始地址', 0xFFFF)
- } catch (error) {
- setState({ protocolErrorText: error.message || '起始地址无效' })
- return
- }
- const values = normalizeManualMultipleValues(normalizeManualMultipleQuantity(state.commandRegisterQuantity), changedValues, startAddress)
- setProtocolInput({
- commandValue: getManualMultipleValueText(values),
- protocolMultipleValues: values
- })
- }
- function validateProtocolMultipleValue(index, value) {
- const register = state.protocolMultipleValues[Number(index)]
- if (!register) return false
- return validateRegisterValue(register, value)
- }
- function buildGeneratedExpectedResponse() {
- try {
- const command = getCommand(state.commandIndex)
- const address = parseHexNumber(state.registerAddress, '起始地址', 0xFFFF)
- const slaveAddress = parseHexNumber(state.slaveAddress, '从站地址', 0xFF)
- const quantity = command.inputMode === 'quantity'
- ? parseHexNumber(state.commandValue, '读取数量', 0xFFFF)
- : (command.inputMode === 'multiple' ? parseHexNumber(state.commandRegisterQuantity, '寄存器个数', 0xFFFF) : 1)
- const value = command.inputMode === 'coil'
- ? (state.coilEnabled ? 0xFF00 : 0x0000)
- : (command.inputMode === 'single' ? parseHexNumber(state.commandValue, '写入值', 0xFFFF) : undefined)
- return {
- address,
- functionCode: command.functionCode,
- kind: 'manual-rtu',
- quantity,
- value,
- slaveAddress
- }
- } catch (error) {
- return null
- }
- }
- function sendGeneratedFrame() {
- if (!state.generatedHex) return false
- const expected = buildGeneratedExpectedResponse()
- return transport.enqueueSendFrame(state.generatedHex, 'RTU', expected ? {
- expected
- } : {})
- }
- setState(createProtocolState(
- state.commandIndex,
- state.slaveAddress,
- state.registerAddress,
- state.commandValue,
- state.coilEnabled,
- state.commandRegisterQuantity
- ))
- module.exports = {
- buildGeneratedExpectedResponse,
- closeProtocolMultipleDialog,
- getState,
- openProtocolMultipleDialog,
- sendGeneratedFrame,
- setCommandIndex,
- setProtocolInput,
- setProtocolMultipleQuantity,
- setProtocolMultipleTextLength,
- setProtocolMultipleType,
- setProtocolMultipleValue,
- subscribe,
- validateProtocolMultipleValue
- }
|