1
0

service.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655
  1. const {
  2. isCancelError,
  3. loadGroupsFromMessageFile,
  4. saveGroupsToChat
  5. } = require('./persistence.js')
  6. const {
  7. completeStructInstanceGroups,
  8. mergeImportedGroups,
  9. parseStructDefinition
  10. } = require('./imports.js')
  11. const storageAccessCodeInfo = require('../storage-access/code-info-sync.js')
  12. const parameterGroupIo = require('./io.js')
  13. const settingsService = require('../../store/settings-store.js')
  14. const store = require('./store.js')
  15. const {
  16. loadSelectedFile
  17. } = require('../../repositories/file.js')
  18. const transport = require('../../transport/ble-core.js')
  19. const {
  20. DATA_TYPE_OPTIONS,
  21. MAX_MODBUS_ADDRESS,
  22. MAX_STORAGE_ADDRESS,
  23. REGISTER_TYPE_OPTIONS,
  24. cloneImportedGroup,
  25. getDataType,
  26. getRegisterWriteValueText,
  27. isAddressRangeOverflow,
  28. isStorageSourceLockedGroup,
  29. isStorageSourceLockedRegister,
  30. normalizeGroup,
  31. normalizeGroupConfig,
  32. validateRegisterValue
  33. } = require('../../domain/parameter-groups/model.js')
  34. const {
  35. normalizeEnumOptions,
  36. parseEnumValueText,
  37. parseNumberText
  38. } = require('../../domain/parameter-groups/value-codec.js')
  39. const {
  40. findGroup,
  41. getGroups,
  42. getState,
  43. init,
  44. setGroups,
  45. setStorageCodeInfo,
  46. switchProtocolMode,
  47. subscribe,
  48. updateGroups
  49. } = store
  50. let initialized = false
  51. function getActiveProtocolMode() {
  52. return settingsService.getState().protocolMode
  53. }
  54. function isStorageAccessProtocolMode(protocolMode) {
  55. return settingsService.isStorageAccessProtocol(protocolMode)
  56. }
  57. function getGroupStorageAddressWidth(group = {}) {
  58. const codeInfoContext = group.codeInfoContext || {}
  59. const explicitWidth = group.sourceAddressWidth
  60. || group.storageAddressWidth
  61. || group.addressWidth
  62. || codeInfoContext.storageAddressWidth
  63. || codeInfoContext.addressWidth
  64. || codeInfoContext.codeInfoAddressWidth
  65. const explicitByteLength = group.sourceAddressByteLength
  66. || group.addressByteLength
  67. || codeInfoContext.sourceAddressByteLength
  68. || codeInfoContext.addressByteLength
  69. const width = Number(explicitWidth)
  70. if (width === 16 || width === 2) return 16
  71. if (width === 32 || width === 4) return 32
  72. const byteLength = Number(explicitByteLength)
  73. if (byteLength === 2) return 16
  74. if (byteLength === 4) return 32
  75. const memoryArea = String(group.sourceMemoryArea || '').trim().toUpperCase()
  76. if (memoryArea === 'ADDR32' || memoryArea === 'ADDRESS32') return 32
  77. return Math.floor(Number(group.startAddress) || 0) > MAX_MODBUS_ADDRESS ? 32 : 16
  78. }
  79. function getGroupStorageAddressMax(group = {}) {
  80. return getGroupStorageAddressWidth(group) === 16
  81. ? MAX_MODBUS_ADDRESS
  82. : MAX_STORAGE_ADDRESS
  83. }
  84. function isGroupAddressRangeOverflow(group = {}, protocolMode = getActiveProtocolMode()) {
  85. if (!isStorageAccessProtocolMode(protocolMode)) {
  86. return isAddressRangeOverflow(group.startAddress, group.quantity)
  87. }
  88. const startAddress = Math.max(0, Math.floor(Number(group.startAddress) || 0))
  89. const addressSpan = Math.max(1, Math.floor(Number(group.byteLength || group.sourceByteLength || group.quantity) || 1))
  90. const maxAddress = getGroupStorageAddressMax(group)
  91. return startAddress + addressSpan - 1 > maxAddress
  92. }
  93. function getAddressOverflowText(protocolMode = getActiveProtocolMode(), group = {}) {
  94. if (isStorageAccessProtocolMode(protocolMode)) {
  95. return getGroupStorageAddressMax(group) === MAX_MODBUS_ADDRESS
  96. ? '地址范围超出 0xFFFF'
  97. : '地址范围超出 0xFFFFFFFF'
  98. }
  99. return '地址范围超出 0xFFFF'
  100. }
  101. function isUnconfiguredRegister(register = {}) {
  102. return getDataType(register.dataType).key === 'raw'
  103. }
  104. function getUnconfiguredRegisterName(register = {}, index = 0) {
  105. return register.displayName || register.name || `变量 ${index + 1}`
  106. }
  107. function findUnconfiguredRegister(group = {}) {
  108. const registers = Array.isArray(group.registers) ? group.registers : []
  109. const index = registers.findIndex(isUnconfiguredRegister)
  110. return index >= 0
  111. ? {
  112. index,
  113. register: registers[index]
  114. }
  115. : null
  116. }
  117. function isEnumSourceText(value) {
  118. const text = String(value || '').trim().toLowerCase()
  119. return text === 'enum' || /^enum\b/.test(text) || /\benum\b/.test(text)
  120. }
  121. function isEnumLikeRegister(group = {}, register = {}) {
  122. if (String(register.enumName || '').trim()) return true
  123. if (normalizeEnumOptions(register).length) return true
  124. return [
  125. group.sourceEntryKind,
  126. group.sourceElementType,
  127. group.sourceValueType,
  128. register.sourceEntryKind,
  129. register.sourceElementType,
  130. register.sourceValueType
  131. ].some(isEnumSourceText)
  132. }
  133. function enumOptionMatchesValue(option, value) {
  134. if (value === null || value === undefined) return false
  135. if (typeof value === 'bigint') {
  136. const optionValue = Number(option && option.value)
  137. if (!Number.isFinite(optionValue)) return false
  138. return BigInt(Math.trunc(optionValue)) === value
  139. }
  140. return Number(option && option.value) === Number(value)
  141. }
  142. function validateEnumWriteRegister(group = {}, register = {}, context = {}) {
  143. if (!isEnumLikeRegister(group, register)) return true
  144. const enumOptions = normalizeEnumOptions(register)
  145. if (!enumOptions.length) {
  146. if (!context.missingEnumWarningShown) {
  147. transport.showCommandAlert('枚举写入', '未加载结构体/枚举定义,无法校验枚举值')
  148. context.missingEnumWarningShown = true
  149. }
  150. return true
  151. }
  152. const valueText = getRegisterWriteValueText(register).trim()
  153. if (!valueText || valueText === '--') return true
  154. const enumValue = parseEnumValueText(register, valueText)
  155. const parsedValue = enumValue === null
  156. ? parseNumberText(valueText, register.dataType)
  157. : enumValue
  158. const exists = enumOptions.some((option) => enumOptionMatchesValue(option, parsedValue))
  159. if (exists) return true
  160. transport.showCommandAlert('枚举写入', '写入值不在枚举定义中,已取消写入')
  161. return false
  162. }
  163. function validateEnumWriteGroup(group = {}) {
  164. const context = {
  165. missingEnumWarningShown: false
  166. }
  167. const registers = Array.isArray(group.registers) ? group.registers : []
  168. return registers.every((register) => validateEnumWriteRegister(group, register, context))
  169. }
  170. function initParameterGroups() {
  171. settingsService.init()
  172. init(getActiveProtocolMode())
  173. switchProtocolMode(getActiveProtocolMode(), {
  174. notify: false
  175. })
  176. if (initialized) return
  177. settingsService.subscribe((settingsState) => {
  178. switchProtocolMode(settingsState.protocolMode)
  179. })
  180. initialized = true
  181. }
  182. async function importJsonFromMessageFile() {
  183. try {
  184. const importedGroups = (await loadGroupsFromMessageFile()).map(normalizeGroup)
  185. if (!importedGroups.length) throw new Error('JSON 中没有可导入的寄存器组')
  186. const merged = mergeImportedGroups(getGroups(), importedGroups)
  187. setGroups(merged.groups)
  188. return merged.changedCount || 0
  189. } catch (error) {
  190. const message = error && error.message ? error.message : '导入参数组配置失败'
  191. transport.showCommandAlert('参数组导入', message)
  192. return 0
  193. }
  194. }
  195. function completeStructInstanceGroupsWithStructSource(sourceText, options = {}) {
  196. const completed = completeStructInstanceGroups(getGroups(), sourceText, options)
  197. if (!completed.completedCount) {
  198. throw new Error('没有找到可匹配的结构体实例')
  199. }
  200. setGroups(completed.groups)
  201. return completed
  202. }
  203. function mergeImportedGroupsIntoState(importedGroups = [], options = {}) {
  204. const normalizedGroups = importedGroups.map(cloneImportedGroup).map(normalizeGroup)
  205. const merged = mergeImportedGroups(getGroups(), normalizedGroups, options)
  206. if (normalizedGroups.length) {
  207. setGroups(merged.groups)
  208. }
  209. return merged
  210. }
  211. function clearStorageAccessGroups() {
  212. setGroups([], {
  213. protocolMode: settingsService.PROTOCOL_MODE.STORAGE_ACCESS
  214. })
  215. setStorageCodeInfo(null)
  216. return true
  217. }
  218. async function completeStructInstanceGroupsWithStructFile(options = {}) {
  219. try {
  220. const file = await loadSelectedFile('auto', {
  221. encoding: 'utf8',
  222. extensionMessage: '请选择 .h 或 .c 结构体/枚举定义文件',
  223. extensions: ['h', 'c', 'txt'],
  224. fallbackName: 'structs.h'
  225. })
  226. return completeStructInstanceGroupsWithStructSource(file.text, options)
  227. } catch (error) {
  228. const message = error && error.message ? error.message : '结构体/枚举补全失败'
  229. transport.showCommandAlert('结构体/枚举补全', message)
  230. return {
  231. completedCount: 0,
  232. enumCount: 0,
  233. skippedCount: 0,
  234. structCount: 0,
  235. variableCount: 0
  236. }
  237. }
  238. }
  239. async function saveJsonToChat() {
  240. try {
  241. const result = await saveGroupsToChat(getGroups())
  242. return result && result.count
  243. ? result
  244. : {
  245. count: 0
  246. }
  247. } catch (error) {
  248. const message = error && error.message ? error.message : '保存参数组配置失败'
  249. if (!isCancelError(error)) {
  250. transport.showCommandAlert('参数组保存', message)
  251. }
  252. return {
  253. count: 0
  254. }
  255. }
  256. }
  257. async function syncFromStorageAccessCodeInfo(options = {}) {
  258. const result = await storageAccessCodeInfo.syncCodeInfo(options)
  259. if (!result || !result.ok) return result
  260. setStorageCodeInfo(result.codeInfo)
  261. settingsService.setStorageAccessDefaultEndian(result.codeInfoMemoryEndian || result.codeInfo && result.codeInfo.memoryEndian)
  262. const merged = mergeImportedGroupsIntoState(result.importedGroups || [], {
  263. preserveExistingRemarks: true,
  264. preserveExistingPollEnabled: true,
  265. preserveExistingStructLayout: true
  266. })
  267. return {
  268. ...result,
  269. addedGroups: merged.addedGroupCount,
  270. addedRegisters: merged.addedRegisterCount,
  271. updatedGroups: merged.updatedGroupCount,
  272. updatedRegisters: merged.updatedRegisterCount
  273. }
  274. }
  275. function addGroupFromConfig(config = {}) {
  276. const protocolMode = getActiveProtocolMode()
  277. let groupConfig
  278. try {
  279. groupConfig = normalizeGroupConfig({
  280. ...(isStorageAccessProtocolMode(protocolMode) ? { addressUnit: 'byte', sourceMemoryArea: config.sourceMemoryArea || 'XDATA' } : {}),
  281. ...config
  282. })
  283. } catch (error) {
  284. transport.showCommandAlert('参数组添加', error.message || '寄存器组配置无效')
  285. return null
  286. }
  287. if (isGroupAddressRangeOverflow(groupConfig, protocolMode)) {
  288. transport.showCommandAlert('参数组添加', getAddressOverflowText(protocolMode, groupConfig))
  289. return null
  290. }
  291. const registers = Array.isArray(config.registers) ? config.registers : []
  292. const group = normalizeGroup({
  293. ...groupConfig,
  294. layout: config.layout,
  295. ...(registers.length ? { registers } : {}),
  296. expanded: false
  297. })
  298. if (group.addressOverflow) {
  299. transport.showCommandAlert('参数组添加', getAddressOverflowText(protocolMode, group))
  300. return null
  301. }
  302. setGroups(getGroups().concat(group))
  303. return group
  304. }
  305. function updateGroupConfig(groupId, config = {}) {
  306. const protocolMode = getActiveProtocolMode()
  307. const group = findGroup(groupId)
  308. if (!group) return null
  309. if (isStorageSourceLockedGroup(group)) {
  310. return updateGroupPollEnabled(groupId, config.pollEnabled)
  311. }
  312. let nextConfig
  313. try {
  314. nextConfig = normalizeGroupConfig({
  315. ...group,
  316. ...config
  317. })
  318. } catch (error) {
  319. transport.showCommandAlert('参数组更新', error.message || '寄存器组配置无效')
  320. return null
  321. }
  322. if (isGroupAddressRangeOverflow(nextConfig, protocolMode)) {
  323. transport.showCommandAlert('参数组更新', getAddressOverflowText(protocolMode, nextConfig))
  324. return null
  325. }
  326. const registers = Array.isArray(config.registers) ? config.registers : group.registers
  327. const updatedGroup = normalizeGroup({
  328. ...group,
  329. ...nextConfig,
  330. registers
  331. })
  332. if (updatedGroup.addressOverflow) {
  333. transport.showCommandAlert('参数组更新', getAddressOverflowText(protocolMode, updatedGroup))
  334. return null
  335. }
  336. setGroups(getGroups().map((item) => (
  337. item.id === groupId ? updatedGroup : item
  338. )))
  339. return updatedGroup
  340. }
  341. function updateGroupPollEnabled(groupId, pollEnabled) {
  342. let updatedGroup = null
  343. updateGroups((group) => {
  344. if (group.id !== groupId) return group
  345. updatedGroup = normalizeGroup({
  346. ...group,
  347. pollEnabled: pollEnabled === false ? false : true
  348. })
  349. return updatedGroup
  350. })
  351. return updatedGroup
  352. }
  353. function setGroupExpanded(groupId, expanded) {
  354. updateGroups((group) => group.id === groupId
  355. ? {
  356. ...group,
  357. deleteVisible: false,
  358. expanded
  359. }
  360. : group)
  361. }
  362. function setGroupDeleteVisible(groupId, deleteVisible) {
  363. updateGroups((group) => group.id === groupId
  364. ? {
  365. ...group,
  366. deleteVisible
  367. }
  368. : group)
  369. }
  370. function removeGroup(groupId) {
  371. setGroups(getGroups().filter((group) => group.id !== groupId))
  372. }
  373. function reorderRegister(groupId, fromIndex, toIndex) {
  374. const group = findGroup(groupId)
  375. if (!group) return null
  376. if (group.isStructLayout || group.sourceMetadataLocked) return group
  377. const registers = group.registers.slice()
  378. const sourceIndex = Number(fromIndex)
  379. const targetIndex = Number(toIndex)
  380. if (!Number.isInteger(sourceIndex) || !Number.isInteger(targetIndex)) return null
  381. if (sourceIndex < 0 || sourceIndex >= registers.length) return null
  382. const safeTargetIndex = Math.min(Math.max(targetIndex, 0), registers.length - 1)
  383. if (safeTargetIndex === sourceIndex) return group
  384. const moved = registers.splice(sourceIndex, 1)[0]
  385. registers.splice(safeTargetIndex, 0, moved)
  386. const updatedGroup = normalizeGroup({
  387. ...group,
  388. quantity: registers.length,
  389. registers
  390. })
  391. setGroups(getGroups().map((item) => (
  392. item.id === groupId ? updatedGroup : item
  393. )))
  394. return updatedGroup
  395. }
  396. function updateRegister(groupId, registerIndex, changedData) {
  397. updateGroups((group) => {
  398. if (group.id !== groupId) return group
  399. const register = group.registers[registerIndex]
  400. const safeChangedData = isStorageSourceLockedRegister(group, register)
  401. ? Object.keys(changedData || {}).reduce((data, key) => {
  402. if (key !== 'name' && key !== 'dataType' && key !== 'textByteLength') {
  403. data[key] = changedData[key]
  404. }
  405. return data
  406. }, {})
  407. : changedData
  408. const shouldResetReadState = Object.prototype.hasOwnProperty.call(safeChangedData, 'dataType')
  409. || Object.prototype.hasOwnProperty.call(safeChangedData, 'textByteLength')
  410. return {
  411. ...group,
  412. registers: group.registers.map((register, currentIndex) => (
  413. currentIndex === registerIndex
  414. ? {
  415. ...register,
  416. ...(shouldResetReadState ? { rawBytes: [], rawValue: null, rawWords: [] } : {}),
  417. ...safeChangedData
  418. }
  419. : register
  420. ))
  421. }
  422. })
  423. }
  424. function updateRegisterValue(groupId, registerIndex, value) {
  425. updateRegister(groupId, registerIndex, {
  426. inputValue: value,
  427. isDirty: true
  428. })
  429. }
  430. function validateRegisterInputValue(groupId, registerIndex, value) {
  431. const group = findGroup(groupId)
  432. if (!group) return false
  433. const register = group.registers[registerIndex]
  434. if (!register) return false
  435. return validateRegisterValue(register, value)
  436. }
  437. async function readGroup(groupId, options = {}) {
  438. const protocolMode = options.protocolMode || getActiveProtocolMode()
  439. const group = findGroup(groupId, protocolMode)
  440. if (!group) return false
  441. if (group.addressOverflow) {
  442. transport.showCommandAlert('参数组读取', getAddressOverflowText(protocolMode, group))
  443. return false
  444. }
  445. const readResult = await parameterGroupIo.readGroup(group, {
  446. ...options,
  447. useStorageAccess: isStorageAccessProtocolMode(protocolMode)
  448. })
  449. if (!readResult) return false
  450. updateGroups((item) => {
  451. if (item.id !== groupId) return item
  452. return readResult.applyToGroup(item)
  453. }, {
  454. protocolMode
  455. })
  456. return true
  457. }
  458. async function writeRegister(groupId, registerIndex) {
  459. const protocolMode = getActiveProtocolMode()
  460. const group = findGroup(groupId, protocolMode)
  461. const register = group && group.registers ? group.registers[registerIndex] : null
  462. if (!group || !register) return false
  463. if (!group.writable) {
  464. transport.showCommandAlert('参数组写入', '当前变量为只读')
  465. return false
  466. }
  467. if (group.addressOverflow) {
  468. transport.showCommandAlert('参数组写入', getAddressOverflowText(protocolMode, group))
  469. return false
  470. }
  471. if (isUnconfiguredRegister(register)) {
  472. transport.showCommandAlert('参数组写入', `${getUnconfiguredRegisterName(register, registerIndex)} 需要先配置数据类型`)
  473. return false
  474. }
  475. if (!validateEnumWriteRegister(group, register)) return false
  476. const writeResult = await parameterGroupIo.writeRegister(group, registerIndex, {
  477. useStorageAccess: isStorageAccessProtocolMode(protocolMode)
  478. })
  479. if (!writeResult) return false
  480. updateGroups((item) => {
  481. if (item.id !== groupId) return item
  482. return writeResult.applyToGroup(item)
  483. }, {
  484. protocolMode
  485. })
  486. return true
  487. }
  488. async function writeGroup(groupId, options = {}) {
  489. const protocolMode = options.protocolMode || getActiveProtocolMode()
  490. const group = findGroup(groupId, protocolMode)
  491. if (!group) return false
  492. if (!group.writable) {
  493. transport.showCommandAlert('参数组写入', '当前寄存器组为只读')
  494. return false
  495. }
  496. if (group.addressOverflow) {
  497. transport.showCommandAlert('参数组写入', getAddressOverflowText(protocolMode, group))
  498. return false
  499. }
  500. const unconfigured = findUnconfiguredRegister(group)
  501. if (unconfigured) {
  502. transport.showCommandAlert('参数组写入', `${getUnconfiguredRegisterName(unconfigured.register, unconfigured.index)} 需要先配置数据类型`)
  503. return false
  504. }
  505. if (!validateEnumWriteGroup(group)) return false
  506. const writeResult = await parameterGroupIo.writeGroup(group, {
  507. ...options,
  508. useStorageAccess: isStorageAccessProtocolMode(protocolMode)
  509. })
  510. if (!writeResult) return false
  511. updateGroups((item) => {
  512. if (item.id !== groupId) return item
  513. return writeResult.applyToGroup(item)
  514. }, {
  515. protocolMode
  516. })
  517. return true
  518. }
  519. module.exports = {
  520. DATA_TYPE_OPTIONS,
  521. REGISTER_TYPE_OPTIONS,
  522. addGroupFromConfig,
  523. clearStorageAccessGroups,
  524. completeStructInstanceGroups,
  525. completeStructInstanceGroupsWithStructFile,
  526. completeStructInstanceGroupsWithStructSource,
  527. getState,
  528. importJsonFromMessageFile,
  529. init: initParameterGroups,
  530. mergeImportedGroups: mergeImportedGroupsIntoState,
  531. parseStructDefinition,
  532. readGroup,
  533. removeGroup,
  534. reorderRegister,
  535. saveJsonToChat,
  536. setGroupDeleteVisible,
  537. setGroupExpanded,
  538. subscribe,
  539. syncFromStorageAccessCodeInfo,
  540. updateGroupConfig,
  541. updateGroupPollEnabled,
  542. updateRegister,
  543. updateRegisterValue,
  544. validateRegisterInputValue,
  545. writeRegister,
  546. writeGroup
  547. }