params-service.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. const {
  2. buildReadFrame,
  3. buildWriteMultipleRegistersFrame,
  4. buildWriteSingleCoilFrame,
  5. MAX_READ_COIL_QUANTITY,
  6. MAX_READ_REGISTER_QUANTITY
  7. } = require('./modbus-rtu')
  8. const paramsPageState = require('./params-page-state')
  9. const transport = require('./ble-transport')
  10. const {
  11. addCoilReadValues,
  12. addWordReadValues,
  13. floatToWords
  14. } = require('./register-value-utils')
  15. function getSharedSlaveAddress() {
  16. try {
  17. return transport.getSlaveAddress()
  18. } catch (error) {
  19. transport.showCommandAlert('从机地址错误', error.message)
  20. return null
  21. }
  22. }
  23. function parseAddress(address) {
  24. return parseInt(String(address || '0'), 16)
  25. }
  26. function getAreaKey(item) {
  27. return (item.area && item.area.key) || item.areaKey || 'holding'
  28. }
  29. function getRegisterCount(item) {
  30. return item.registerCount || (item.type === 'float' ? 2 : 1)
  31. }
  32. function hasWriteValue(value) {
  33. return value !== '' && value !== undefined && value !== null && value !== '--'
  34. }
  35. function toWriteNumber(value) {
  36. if (!hasWriteValue(value)) return null
  37. const numberValue = Number(value)
  38. if (!Number.isFinite(numberValue)) return null
  39. return numberValue
  40. }
  41. function toWord(value) {
  42. const numberValue = Number(value)
  43. if (!Number.isFinite(numberValue)) return null
  44. const word = Math.round(numberValue)
  45. return word >= 0 && word <= 0xFFFF ? word : null
  46. }
  47. function getGroupItems(data, groupKey) {
  48. if (groupKey === 'vsp') return data.vspCurveRegisters.concat([data.speedSlopeRegister])
  49. if (groupKey === 'speedLoop') {
  50. return data.speedLoopInputDisplayRegisters
  51. .concat(data.speedLoopCalculatedDisplayRegisters, data.speedLoopExtraDisplayRegisters)
  52. }
  53. if (groupKey === 'estimator') {
  54. return data.estimatorCalculatedDisplayRegisters.concat(data.atoBandwidthDisplayRegisters)
  55. }
  56. if (groupKey === 'tailwind') {
  57. return data.tailwindControlRegisters
  58. .concat(data.tailwindCalculatedDisplayRegisters, data.tailwindAtoBandwidthDisplayRegisters)
  59. }
  60. if (groupKey === 'preposition') return data.prepositionSwitchRegisters.concat(data.prepositionParameterDisplayRegisters)
  61. if (groupKey === 'oil') return data.oilParameterInputRegisters
  62. if (groupKey === 'dq') return data.dqGainDisplayRegisters
  63. if (groupKey === 'protectionSwitch') return data.protectionSwitchRegisters
  64. if (groupKey === 'protection') return data.protectionDisplayRegisters
  65. return []
  66. }
  67. function expandAtoItems(item) {
  68. if (!item.kpAddress || !item.kiAddress) return [item]
  69. return [
  70. {
  71. address: item.kpAddress,
  72. areaKey: 'holding',
  73. name: `${item.name} KP`,
  74. type: 'uint16_t',
  75. writeValue: item.kpWriteValue
  76. },
  77. {
  78. address: item.kiAddress,
  79. areaKey: 'holding',
  80. name: `${item.name} KI`,
  81. type: 'uint16_t',
  82. writeValue: item.kiWriteValue
  83. }
  84. ]
  85. }
  86. function expandItems(items) {
  87. return items.reduce((result, item) => result.concat(expandAtoItems(item)), [])
  88. }
  89. function makeReadSpans(entries) {
  90. const sortedEntries = entries
  91. .map((item) => ({
  92. address: parseAddress(item.address),
  93. count: getRegisterCount(item)
  94. }))
  95. .filter((item) => Number.isFinite(item.address) && item.count > 0)
  96. .sort((left, right) => left.address - right.address)
  97. const spans = []
  98. sortedEntries.forEach((entry) => {
  99. const last = spans[spans.length - 1]
  100. if (last && entry.address <= last.address + last.quantity) {
  101. const end = Math.max(last.address + last.quantity, entry.address + entry.count)
  102. last.quantity = end - last.address
  103. return
  104. }
  105. spans.push({
  106. address: entry.address,
  107. quantity: entry.count
  108. })
  109. })
  110. return spans
  111. }
  112. function splitReadSpans(spans, maxQuantity) {
  113. return spans.reduce((result, span) => {
  114. let address = span.address
  115. let remaining = span.quantity
  116. while (remaining > 0) {
  117. const quantity = Math.min(remaining, maxQuantity)
  118. result.push({
  119. address,
  120. quantity
  121. })
  122. address += quantity
  123. remaining -= quantity
  124. }
  125. return result
  126. }, [])
  127. }
  128. async function readGroup(data, groupKey) {
  129. const slaveAddress = getSharedSlaveAddress()
  130. if (slaveAddress === null) return false
  131. const items = expandItems(getGroupItems(data, groupKey))
  132. const coilItems = items.filter((item) => getAreaKey(item) === 'coil')
  133. const holdingItems = items.filter((item) => getAreaKey(item) === 'holding')
  134. const inputItems = items.filter((item) => getAreaKey(item) === 'input')
  135. const readValues = {
  136. coils: {},
  137. words: {}
  138. }
  139. let sent = false
  140. for (const span of splitReadSpans(makeReadSpans(coilItems), MAX_READ_COIL_QUANTITY)) {
  141. sent = true
  142. const response = await transport.sendManagedFrame(
  143. buildReadFrame(slaveAddress, 0x01, span.address, span.quantity),
  144. '参数读取',
  145. {
  146. address: span.address,
  147. functionCode: 0x01,
  148. kind: 'params-read',
  149. quantity: span.quantity,
  150. slaveAddress
  151. }
  152. )
  153. addCoilReadValues(readValues, span.address, span.quantity, response)
  154. }
  155. for (const span of splitReadSpans(makeReadSpans(holdingItems), MAX_READ_REGISTER_QUANTITY)) {
  156. sent = true
  157. const response = await transport.sendManagedFrame(
  158. buildReadFrame(slaveAddress, 0x03, span.address, span.quantity),
  159. '参数读取',
  160. {
  161. address: span.address,
  162. functionCode: 0x03,
  163. kind: 'params-read',
  164. quantity: span.quantity,
  165. slaveAddress
  166. }
  167. )
  168. addWordReadValues(readValues, span.address, response)
  169. }
  170. for (const span of splitReadSpans(makeReadSpans(inputItems), MAX_READ_REGISTER_QUANTITY)) {
  171. sent = true
  172. const response = await transport.sendManagedFrame(
  173. buildReadFrame(slaveAddress, 0x04, span.address, span.quantity),
  174. '参数读取',
  175. {
  176. address: span.address,
  177. functionCode: 0x04,
  178. kind: 'params-read',
  179. quantity: span.quantity,
  180. slaveAddress
  181. }
  182. )
  183. addWordReadValues(readValues, span.address, response)
  184. }
  185. if (!sent) {
  186. transport.showCommandAlert('参数读取', '当前分组没有可读取的寄存器')
  187. return false
  188. }
  189. if (!Object.keys(readValues.coils).length && !Object.keys(readValues.words).length) {
  190. return false
  191. }
  192. return paramsPageState.applyReadValues(data, readValues)
  193. }
  194. async function readSingleHoldingWord(slaveAddress, address) {
  195. const response = await transport.sendManagedFrame(
  196. buildReadFrame(slaveAddress, 0x03, address, 1),
  197. '读取配对寄存器',
  198. {
  199. address,
  200. functionCode: 0x03,
  201. kind: 'params-pair-read',
  202. quantity: 1,
  203. slaveAddress
  204. },
  205. {}
  206. )
  207. if (!response || !Array.isArray(response.words) || response.words.length < 1) return null
  208. return response.words[0] & 0xFFFF
  209. }
  210. async function buildHoldingWriteEntries(slaveAddress, items) {
  211. const normalEntries = []
  212. const byteGroups = {}
  213. items.forEach((item) => {
  214. if (getAreaKey(item) !== 'holding') return
  215. if (item.type === 'uint8_t' && item.bytePosition) {
  216. const address = parseAddress(item.address)
  217. const group = byteGroups[address] || {
  218. address,
  219. high: null,
  220. low: null
  221. }
  222. group[item.bytePosition] = item
  223. byteGroups[address] = group
  224. return
  225. }
  226. const writeNumber = toWriteNumber(item.writeValue)
  227. if (writeNumber === null) return
  228. const words = item.type === 'float'
  229. ? floatToWords(writeNumber)
  230. : [toWord(writeNumber)]
  231. if (!words || words.some((word) => word === null)) return
  232. normalEntries.push({
  233. address: parseAddress(item.address),
  234. label: item.name,
  235. values: words
  236. })
  237. })
  238. for (const addressText of Object.keys(byteGroups)) {
  239. const group = byteGroups[addressText]
  240. const highValue = group.high ? toWord(group.high.writeValue) : null
  241. const lowValue = group.low ? toWord(group.low.writeValue) : null
  242. if (highValue === null && lowValue === null) continue
  243. let baseWord = 0
  244. if (highValue === null || lowValue === null) {
  245. const readWord = await readSingleHoldingWord(slaveAddress, group.address)
  246. if (!Number.isInteger(readWord)) continue
  247. baseWord = readWord
  248. }
  249. const nextHigh = highValue === null ? ((baseWord >> 8) & 0xFF) : highValue
  250. const nextLow = lowValue === null ? (baseWord & 0xFF) : lowValue
  251. if (nextHigh > 0xFF || nextLow > 0xFF) continue
  252. normalEntries.push({
  253. address: group.address,
  254. label: '8位参数',
  255. values: [(nextHigh << 8) | nextLow]
  256. })
  257. }
  258. return normalEntries.sort((left, right) => left.address - right.address)
  259. }
  260. async function writeGroup(data, groupKey) {
  261. const slaveAddress = getSharedSlaveAddress()
  262. if (slaveAddress === null) return false
  263. const items = expandItems(getGroupItems(data, groupKey))
  264. const coilItems = items.filter((item) => getAreaKey(item) === 'coil')
  265. const holdingItems = items.filter((item) => getAreaKey(item) === 'holding')
  266. let sent = false
  267. for (const item of coilItems) {
  268. const checked = Number(item.writeValue) !== 0
  269. const address = parseAddress(item.address)
  270. sent = true
  271. const response = await transport.sendManagedFrame(
  272. buildWriteSingleCoilFrame(slaveAddress, address, checked),
  273. item.name,
  274. {
  275. address,
  276. functionCode: 0x05,
  277. kind: 'params-coil-write',
  278. quantity: 1,
  279. value: checked ? 0xFF00 : 0x0000,
  280. slaveAddress
  281. }
  282. )
  283. if (!response) return false
  284. }
  285. const holdingEntries = await buildHoldingWriteEntries(slaveAddress, holdingItems)
  286. for (const entry of holdingEntries) {
  287. sent = true
  288. const response = await transport.sendManagedFrame(
  289. buildWriteMultipleRegistersFrame(slaveAddress, entry.address, entry.values),
  290. entry.label,
  291. {
  292. address: entry.address,
  293. functionCode: 0x10,
  294. kind: 'params-holding-write',
  295. quantity: entry.values.length,
  296. slaveAddress
  297. }
  298. )
  299. if (!response) return false
  300. }
  301. if (!sent) {
  302. transport.showCommandAlert('参数写入', '当前分组没有可写入的参数')
  303. }
  304. return sent
  305. }
  306. async function writeSwitchRegister(item) {
  307. const slaveAddress = getSharedSlaveAddress()
  308. if (slaveAddress === null || !item) return false
  309. const address = parseAddress(item.address)
  310. const checked = Number(item.writeValue) !== 0
  311. return transport.sendManagedFrame(
  312. buildWriteSingleCoilFrame(slaveAddress, address, checked),
  313. item.name,
  314. {
  315. address,
  316. functionCode: 0x05,
  317. kind: 'params-switch-write',
  318. quantity: 1,
  319. value: checked ? 0xFF00 : 0x0000,
  320. slaveAddress
  321. }
  322. )
  323. }
  324. module.exports = {
  325. readGroup,
  326. writeGroup,
  327. writeSwitchRegister
  328. }