| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389 |
- const {
- buildReadFrame,
- buildWriteMultipleRegistersFrame,
- buildWriteSingleCoilFrame,
- MAX_READ_COIL_QUANTITY,
- MAX_READ_REGISTER_QUANTITY
- } = require('./modbus-rtu')
- const paramsPageState = require('./params-page-state')
- const transport = require('./ble-transport')
- const {
- addCoilReadValues,
- addWordReadValues,
- floatToWords
- } = require('./register-value-utils')
- function getSharedSlaveAddress() {
- try {
- return transport.getSlaveAddress()
- } catch (error) {
- transport.showCommandAlert('从机地址错误', error.message)
- return null
- }
- }
- function parseAddress(address) {
- return parseInt(String(address || '0'), 16)
- }
- function getAreaKey(item) {
- return (item.area && item.area.key) || item.areaKey || 'holding'
- }
- function getRegisterCount(item) {
- return item.registerCount || (item.type === 'float' ? 2 : 1)
- }
- function hasWriteValue(value) {
- return value !== '' && value !== undefined && value !== null && value !== '--'
- }
- function toWriteNumber(value) {
- if (!hasWriteValue(value)) return null
- const numberValue = Number(value)
- if (!Number.isFinite(numberValue)) return null
- return numberValue
- }
- function toWord(value) {
- const numberValue = Number(value)
- if (!Number.isFinite(numberValue)) return null
- const word = Math.round(numberValue)
- return word >= 0 && word <= 0xFFFF ? word : null
- }
- function getGroupItems(data, groupKey) {
- if (groupKey === 'vsp') return data.vspCurveRegisters.concat([data.speedSlopeRegister])
- if (groupKey === 'speedLoop') {
- return data.speedLoopInputDisplayRegisters
- .concat(data.speedLoopCalculatedDisplayRegisters, data.speedLoopExtraDisplayRegisters)
- }
- if (groupKey === 'estimator') {
- return data.estimatorCalculatedDisplayRegisters.concat(data.atoBandwidthDisplayRegisters)
- }
- if (groupKey === 'tailwind') {
- return data.tailwindControlRegisters
- .concat(data.tailwindCalculatedDisplayRegisters, data.tailwindAtoBandwidthDisplayRegisters)
- }
- if (groupKey === 'preposition') return data.prepositionSwitchRegisters.concat(data.prepositionParameterDisplayRegisters)
- if (groupKey === 'oil') return data.oilParameterInputRegisters
- if (groupKey === 'dq') return data.dqGainDisplayRegisters
- if (groupKey === 'protectionSwitch') return data.protectionSwitchRegisters
- if (groupKey === 'protection') return data.protectionDisplayRegisters
- return []
- }
- function expandAtoItems(item) {
- if (!item.kpAddress || !item.kiAddress) return [item]
- return [
- {
- address: item.kpAddress,
- areaKey: 'holding',
- name: `${item.name} KP`,
- type: 'uint16_t',
- writeValue: item.kpWriteValue
- },
- {
- address: item.kiAddress,
- areaKey: 'holding',
- name: `${item.name} KI`,
- type: 'uint16_t',
- writeValue: item.kiWriteValue
- }
- ]
- }
- function expandItems(items) {
- return items.reduce((result, item) => result.concat(expandAtoItems(item)), [])
- }
- function makeReadSpans(entries) {
- const sortedEntries = entries
- .map((item) => ({
- address: parseAddress(item.address),
- count: getRegisterCount(item)
- }))
- .filter((item) => Number.isFinite(item.address) && item.count > 0)
- .sort((left, right) => left.address - right.address)
- const spans = []
- sortedEntries.forEach((entry) => {
- const last = spans[spans.length - 1]
- if (last && entry.address <= last.address + last.quantity) {
- const end = Math.max(last.address + last.quantity, entry.address + entry.count)
- last.quantity = end - last.address
- return
- }
- spans.push({
- address: entry.address,
- quantity: entry.count
- })
- })
- return spans
- }
- function splitReadSpans(spans, maxQuantity) {
- return spans.reduce((result, span) => {
- let address = span.address
- let remaining = span.quantity
- while (remaining > 0) {
- const quantity = Math.min(remaining, maxQuantity)
- result.push({
- address,
- quantity
- })
- address += quantity
- remaining -= quantity
- }
- return result
- }, [])
- }
- async function readGroup(data, groupKey) {
- const slaveAddress = getSharedSlaveAddress()
- if (slaveAddress === null) return false
- const items = expandItems(getGroupItems(data, groupKey))
- const coilItems = items.filter((item) => getAreaKey(item) === 'coil')
- const holdingItems = items.filter((item) => getAreaKey(item) === 'holding')
- const inputItems = items.filter((item) => getAreaKey(item) === 'input')
- const readValues = {
- coils: {},
- words: {}
- }
- let sent = false
- for (const span of splitReadSpans(makeReadSpans(coilItems), MAX_READ_COIL_QUANTITY)) {
- sent = true
- const response = await transport.sendManagedFrame(
- buildReadFrame(slaveAddress, 0x01, span.address, span.quantity),
- '参数读取',
- {
- address: span.address,
- functionCode: 0x01,
- kind: 'params-read',
- quantity: span.quantity,
- slaveAddress
- }
- )
- addCoilReadValues(readValues, span.address, span.quantity, response)
- }
- for (const span of splitReadSpans(makeReadSpans(holdingItems), MAX_READ_REGISTER_QUANTITY)) {
- sent = true
- const response = await transport.sendManagedFrame(
- buildReadFrame(slaveAddress, 0x03, span.address, span.quantity),
- '参数读取',
- {
- address: span.address,
- functionCode: 0x03,
- kind: 'params-read',
- quantity: span.quantity,
- slaveAddress
- }
- )
- addWordReadValues(readValues, span.address, response)
- }
- for (const span of splitReadSpans(makeReadSpans(inputItems), MAX_READ_REGISTER_QUANTITY)) {
- sent = true
- const response = await transport.sendManagedFrame(
- buildReadFrame(slaveAddress, 0x04, span.address, span.quantity),
- '参数读取',
- {
- address: span.address,
- functionCode: 0x04,
- kind: 'params-read',
- quantity: span.quantity,
- slaveAddress
- }
- )
- addWordReadValues(readValues, span.address, response)
- }
- if (!sent) {
- transport.showCommandAlert('参数读取', '当前分组没有可读取的寄存器')
- return false
- }
- if (!Object.keys(readValues.coils).length && !Object.keys(readValues.words).length) {
- return false
- }
- return paramsPageState.applyReadValues(data, readValues)
- }
- async function readSingleHoldingWord(slaveAddress, address) {
- const response = await transport.sendManagedFrame(
- buildReadFrame(slaveAddress, 0x03, address, 1),
- '读取配对寄存器',
- {
- address,
- functionCode: 0x03,
- kind: 'params-pair-read',
- quantity: 1,
- slaveAddress
- },
- {}
- )
- if (!response || !Array.isArray(response.words) || response.words.length < 1) return null
- return response.words[0] & 0xFFFF
- }
- async function buildHoldingWriteEntries(slaveAddress, items) {
- const normalEntries = []
- const byteGroups = {}
- items.forEach((item) => {
- if (getAreaKey(item) !== 'holding') return
- if (item.type === 'uint8_t' && item.bytePosition) {
- const address = parseAddress(item.address)
- const group = byteGroups[address] || {
- address,
- high: null,
- low: null
- }
- group[item.bytePosition] = item
- byteGroups[address] = group
- return
- }
- const writeNumber = toWriteNumber(item.writeValue)
- if (writeNumber === null) return
- const words = item.type === 'float'
- ? floatToWords(writeNumber)
- : [toWord(writeNumber)]
- if (!words || words.some((word) => word === null)) return
- normalEntries.push({
- address: parseAddress(item.address),
- label: item.name,
- values: words
- })
- })
- for (const addressText of Object.keys(byteGroups)) {
- const group = byteGroups[addressText]
- const highValue = group.high ? toWord(group.high.writeValue) : null
- const lowValue = group.low ? toWord(group.low.writeValue) : null
- if (highValue === null && lowValue === null) continue
- let baseWord = 0
- if (highValue === null || lowValue === null) {
- const readWord = await readSingleHoldingWord(slaveAddress, group.address)
- if (!Number.isInteger(readWord)) continue
- baseWord = readWord
- }
- const nextHigh = highValue === null ? ((baseWord >> 8) & 0xFF) : highValue
- const nextLow = lowValue === null ? (baseWord & 0xFF) : lowValue
- if (nextHigh > 0xFF || nextLow > 0xFF) continue
- normalEntries.push({
- address: group.address,
- label: '8位参数',
- values: [(nextHigh << 8) | nextLow]
- })
- }
- return normalEntries.sort((left, right) => left.address - right.address)
- }
- async function writeGroup(data, groupKey) {
- const slaveAddress = getSharedSlaveAddress()
- if (slaveAddress === null) return false
- const items = expandItems(getGroupItems(data, groupKey))
- const coilItems = items.filter((item) => getAreaKey(item) === 'coil')
- const holdingItems = items.filter((item) => getAreaKey(item) === 'holding')
- let sent = false
- for (const item of coilItems) {
- const checked = Number(item.writeValue) !== 0
- const address = parseAddress(item.address)
- sent = true
- const response = await transport.sendManagedFrame(
- buildWriteSingleCoilFrame(slaveAddress, address, checked),
- item.name,
- {
- address,
- functionCode: 0x05,
- kind: 'params-coil-write',
- quantity: 1,
- value: checked ? 0xFF00 : 0x0000,
- slaveAddress
- }
- )
- if (!response) return false
- }
- const holdingEntries = await buildHoldingWriteEntries(slaveAddress, holdingItems)
- for (const entry of holdingEntries) {
- sent = true
- const response = await transport.sendManagedFrame(
- buildWriteMultipleRegistersFrame(slaveAddress, entry.address, entry.values),
- entry.label,
- {
- address: entry.address,
- functionCode: 0x10,
- kind: 'params-holding-write',
- quantity: entry.values.length,
- slaveAddress
- }
- )
- if (!response) return false
- }
- if (!sent) {
- transport.showCommandAlert('参数写入', '当前分组没有可写入的参数')
- }
- return sent
- }
- async function writeSwitchRegister(item) {
- const slaveAddress = getSharedSlaveAddress()
- if (slaveAddress === null || !item) return false
- const address = parseAddress(item.address)
- const checked = Number(item.writeValue) !== 0
- return transport.sendManagedFrame(
- buildWriteSingleCoilFrame(slaveAddress, address, checked),
- item.name,
- {
- address,
- functionCode: 0x05,
- kind: 'params-switch-write',
- quantity: 1,
- value: checked ? 0xFF00 : 0x0000,
- slaveAddress
- }
- )
- }
- module.exports = {
- readGroup,
- writeGroup,
- writeSwitchRegister
- }
|