service.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  1. const {
  2. isCancelError,
  3. loadGroupsFromMessageFile,
  4. saveGroupsToChat
  5. } = require('./persistence.js')
  6. const {
  7. mergeImportedGroups
  8. } = require('./import-merge.js')
  9. const {
  10. completeStructInstanceGroups,
  11. parseStructDefinition
  12. } = require('./struct-completion.js')
  13. const storageAccessService = require('../storage-access/service.js')
  14. const storageAccessIo = require('./storage-access-io.js')
  15. const modbusIo = require('./modbus-io.js')
  16. const settingsService = require('../../store/settings-store.js')
  17. const store = require('./store.js')
  18. const stateMappers = require('./state-mappers.js')
  19. const {
  20. loadSelectedFile
  21. } = require('../../repositories/file.js')
  22. const transport = require('../../transport/ble-core.js')
  23. const {
  24. DATA_TYPE_OPTIONS,
  25. REGISTER_TYPE_OPTIONS,
  26. cloneImportedGroup,
  27. isAddressRangeOverflow,
  28. normalizeGroup,
  29. normalizeGroupConfig,
  30. validateRegisterValue
  31. } = require('../../domain/parameter-groups/model.js')
  32. const {
  33. findGroup,
  34. getGroups,
  35. getState,
  36. init,
  37. setGroups,
  38. setStorageCodeInfo,
  39. switchProtocolMode,
  40. subscribe,
  41. updateGroups
  42. } = store
  43. let initialized = false
  44. function getActiveProtocolMode() {
  45. return settingsService.getState().protocolMode
  46. }
  47. function isStorageAccessProtocolMode(protocolMode) {
  48. return settingsService.isStorageAccessProtocol(protocolMode)
  49. }
  50. function initParameterGroups() {
  51. settingsService.init()
  52. init(getActiveProtocolMode())
  53. switchProtocolMode(getActiveProtocolMode(), {
  54. notify: false
  55. })
  56. if (initialized) return
  57. settingsService.subscribe((settingsState) => {
  58. switchProtocolMode(settingsState.protocolMode)
  59. })
  60. initialized = true
  61. }
  62. async function importJsonFromMessageFile() {
  63. try {
  64. const importedGroups = (await loadGroupsFromMessageFile()).map(normalizeGroup)
  65. if (!importedGroups.length) throw new Error('JSON 中没有可导入的寄存器组')
  66. const merged = mergeImportedGroups(getGroups(), importedGroups)
  67. setGroups(merged.groups)
  68. return merged.changedCount || 0
  69. } catch (error) {
  70. const message = error && error.message ? error.message : '导入参数组配置失败'
  71. transport.showCommandAlert('参数组导入', message)
  72. return 0
  73. }
  74. }
  75. function completeStructInstanceGroupsWithStructSource(sourceText, options = {}) {
  76. const completed = completeStructInstanceGroups(getGroups(), sourceText, options)
  77. if (!completed.completedCount) {
  78. throw new Error('没有找到可匹配的结构体实例')
  79. }
  80. setGroups(completed.groups)
  81. return completed
  82. }
  83. function mergeImportedGroupsIntoState(importedGroups = [], options = {}) {
  84. const normalizedGroups = importedGroups.map(cloneImportedGroup).map(normalizeGroup)
  85. const merged = mergeImportedGroups(getGroups(), normalizedGroups, options)
  86. if (normalizedGroups.length) {
  87. setGroups(merged.groups)
  88. }
  89. return merged
  90. }
  91. async function completeStructInstanceGroupsWithStructFile(options = {}) {
  92. try {
  93. const file = await loadSelectedFile('message', {
  94. encoding: 'utf8',
  95. extensionMessage: '请选择 .h 或 .c 结构体定义文件',
  96. extensions: ['h', 'c', 'txt'],
  97. fallbackName: 'structs.h'
  98. })
  99. return completeStructInstanceGroupsWithStructSource(file.text, options)
  100. } catch (error) {
  101. const message = error && error.message ? error.message : '结构体补全失败'
  102. transport.showCommandAlert('结构体补全', message)
  103. return {
  104. completedCount: 0,
  105. skippedCount: 0,
  106. structCount: 0,
  107. variableCount: 0
  108. }
  109. }
  110. }
  111. async function saveJsonToChat() {
  112. try {
  113. return saveGroupsToChat(getGroups())
  114. } catch (error) {
  115. const message = error && error.message ? error.message : '保存参数组配置失败'
  116. if (!isCancelError(error)) {
  117. transport.showCommandAlert('参数组保存', message)
  118. }
  119. return 0
  120. }
  121. }
  122. async function syncFromStorageAccessCodeInfo(options = {}) {
  123. const result = await storageAccessService.syncCodeInfo(options)
  124. if (!result || !result.ok) return result
  125. setStorageCodeInfo(result.codeInfo)
  126. const merged = mergeImportedGroupsIntoState(result.importedGroups || [], {
  127. preserveExistingRemarks: true,
  128. preserveExistingStructLayout: true
  129. })
  130. return {
  131. ...result,
  132. addedGroups: merged.addedGroupCount,
  133. addedRegisters: merged.addedRegisterCount,
  134. updatedGroups: merged.updatedGroupCount,
  135. updatedRegisters: merged.updatedRegisterCount
  136. }
  137. }
  138. function addGroupFromConfig(config = {}) {
  139. let groupConfig
  140. try {
  141. groupConfig = normalizeGroupConfig(config)
  142. } catch (error) {
  143. transport.showCommandAlert('参数组添加', error.message || '寄存器组配置无效')
  144. return null
  145. }
  146. if (isAddressRangeOverflow(groupConfig.startAddress, groupConfig.quantity)) {
  147. transport.showCommandAlert('参数组添加', '地址范围超出 0xFFFF')
  148. return null
  149. }
  150. const registers = Array.isArray(config.registers) ? config.registers : []
  151. const group = normalizeGroup({
  152. ...groupConfig,
  153. layout: config.layout,
  154. ...(registers.length ? { registers } : {}),
  155. expanded: false
  156. })
  157. if (group.addressOverflow) {
  158. transport.showCommandAlert('参数组添加', '地址范围超出 0xFFFF')
  159. return null
  160. }
  161. setGroups(getGroups().concat(group))
  162. return group
  163. }
  164. function updateGroupConfig(groupId, config = {}) {
  165. const group = findGroup(groupId)
  166. if (!group) return null
  167. let nextConfig
  168. try {
  169. nextConfig = normalizeGroupConfig({
  170. ...group,
  171. ...config
  172. })
  173. } catch (error) {
  174. transport.showCommandAlert('参数组更新', error.message || '寄存器组配置无效')
  175. return null
  176. }
  177. if (isAddressRangeOverflow(nextConfig.startAddress, nextConfig.quantity)) {
  178. transport.showCommandAlert('参数组更新', '地址范围超出 0xFFFF')
  179. return null
  180. }
  181. const registers = Array.isArray(config.registers) ? config.registers : group.registers
  182. const updatedGroup = normalizeGroup({
  183. ...group,
  184. ...nextConfig,
  185. registers
  186. })
  187. if (updatedGroup.addressOverflow) {
  188. transport.showCommandAlert('参数组更新', '地址范围超出 0xFFFF')
  189. return null
  190. }
  191. setGroups(getGroups().map((item) => (
  192. item.id === groupId ? updatedGroup : item
  193. )))
  194. return updatedGroup
  195. }
  196. function setGroupExpanded(groupId, expanded) {
  197. updateGroups((group) => group.id === groupId
  198. ? {
  199. ...group,
  200. deleteVisible: false,
  201. expanded
  202. }
  203. : group)
  204. }
  205. function setGroupDeleteVisible(groupId, deleteVisible) {
  206. updateGroups((group) => group.id === groupId
  207. ? {
  208. ...group,
  209. deleteVisible
  210. }
  211. : group)
  212. }
  213. function removeGroup(groupId) {
  214. setGroups(getGroups().filter((group) => group.id !== groupId))
  215. }
  216. function reorderRegister(groupId, fromIndex, toIndex) {
  217. const group = findGroup(groupId)
  218. if (!group) return null
  219. if (group.isStructLayout) return group
  220. const registers = group.registers.slice()
  221. const sourceIndex = Number(fromIndex)
  222. const targetIndex = Number(toIndex)
  223. if (!Number.isInteger(sourceIndex) || !Number.isInteger(targetIndex)) return null
  224. if (sourceIndex < 0 || sourceIndex >= registers.length) return null
  225. const safeTargetIndex = Math.min(Math.max(targetIndex, 0), registers.length - 1)
  226. if (safeTargetIndex === sourceIndex) return group
  227. const moved = registers.splice(sourceIndex, 1)[0]
  228. registers.splice(safeTargetIndex, 0, moved)
  229. const updatedGroup = normalizeGroup({
  230. ...group,
  231. quantity: registers.length,
  232. registers
  233. })
  234. setGroups(getGroups().map((item) => (
  235. item.id === groupId ? updatedGroup : item
  236. )))
  237. return updatedGroup
  238. }
  239. function updateRegister(groupId, registerIndex, changedData) {
  240. updateGroups((group) => {
  241. if (group.id !== groupId) return group
  242. const shouldResetReadState = Object.prototype.hasOwnProperty.call(changedData, 'dataType')
  243. || Object.prototype.hasOwnProperty.call(changedData, 'textByteLength')
  244. return {
  245. ...group,
  246. registers: group.registers.map((register, currentIndex) => (
  247. currentIndex === registerIndex
  248. ? {
  249. ...register,
  250. ...(shouldResetReadState ? { rawValue: null, rawWords: [] } : {}),
  251. ...changedData
  252. }
  253. : register
  254. ))
  255. }
  256. })
  257. }
  258. function updateRegisterValue(groupId, registerIndex, value) {
  259. updateRegister(groupId, registerIndex, {
  260. inputValue: value,
  261. isDirty: true
  262. })
  263. }
  264. function validateRegisterInputValue(groupId, registerIndex, value) {
  265. const group = findGroup(groupId)
  266. if (!group) return false
  267. const register = group.registers[registerIndex]
  268. if (!register) return false
  269. return validateRegisterValue(register, value)
  270. }
  271. async function readGroup(groupId, options = {}) {
  272. const protocolMode = options.protocolMode || getActiveProtocolMode()
  273. const group = findGroup(groupId, protocolMode)
  274. if (!group) return false
  275. if (group.addressOverflow) {
  276. transport.showCommandAlert('参数组读取', '地址范围超出 0xFFFF')
  277. return false
  278. }
  279. let wordCache = {}
  280. if (isStorageAccessProtocolMode(protocolMode) && storageAccessIo.isMemoryGroup(group)) {
  281. const memoryWordCache = await storageAccessIo.readMemoryGroup(group, options)
  282. if (!memoryWordCache) return false
  283. wordCache = stateMappers.normalizeNumericCache(memoryWordCache)
  284. } else {
  285. const modbusWordCache = await modbusIo.readGroup(group, options)
  286. if (!modbusWordCache) return false
  287. wordCache = stateMappers.normalizeNumericCache(modbusWordCache)
  288. }
  289. updateGroups((item) => {
  290. if (item.id !== groupId) return item
  291. return stateMappers.applyReadCacheToGroup(item, wordCache)
  292. }, {
  293. protocolMode
  294. })
  295. return true
  296. }
  297. async function writeRegister(groupId, registerIndex) {
  298. const protocolMode = getActiveProtocolMode()
  299. const group = findGroup(groupId, protocolMode)
  300. const register = group && group.registers ? group.registers[registerIndex] : null
  301. if (!group || !register) return false
  302. if (!group.writable) {
  303. transport.showCommandAlert('参数组写入', '当前变量为只读')
  304. return false
  305. }
  306. if (group.addressOverflow) {
  307. transport.showCommandAlert('参数组写入', '地址范围超出 0xFFFF')
  308. return false
  309. }
  310. const written = isStorageAccessProtocolMode(protocolMode) && storageAccessIo.isMemoryGroup(group)
  311. ? await storageAccessIo.writeMemoryRegister(group, register)
  312. : null
  313. if (!written) {
  314. if (!isStorageAccessProtocolMode(protocolMode) || !storageAccessIo.isMemoryGroup(group)) {
  315. return writeGroup(groupId, {
  316. protocolMode
  317. })
  318. }
  319. return false
  320. }
  321. updateGroups((item) => {
  322. if (item.id !== groupId) return item
  323. return stateMappers.applyWrittenSnapshotToGroupRegister(item, registerIndex, written)
  324. }, {
  325. protocolMode
  326. })
  327. return true
  328. }
  329. async function writeGroup(groupId, options = {}) {
  330. const protocolMode = options.protocolMode || getActiveProtocolMode()
  331. const group = findGroup(groupId, protocolMode)
  332. if (!group) return false
  333. if (!group.writable) {
  334. transport.showCommandAlert('参数组写入', '当前寄存器组为只读')
  335. return false
  336. }
  337. if (group.addressOverflow) {
  338. transport.showCommandAlert('参数组写入', '地址范围超出 0xFFFF')
  339. return false
  340. }
  341. const writtenRegisters = []
  342. if (isStorageAccessProtocolMode(protocolMode) && storageAccessIo.isMemoryGroup(group)) {
  343. const snapshots = await storageAccessIo.writeMemoryGroup(group)
  344. if (!snapshots) return false
  345. snapshots.forEach((snapshot) => {
  346. writtenRegisters.push(snapshot)
  347. })
  348. } else {
  349. const snapshots = await modbusIo.writeGroup(group, options)
  350. if (!snapshots) return false
  351. snapshots.forEach((snapshot) => {
  352. writtenRegisters.push(snapshot)
  353. })
  354. }
  355. updateGroups((item) => {
  356. if (item.id !== groupId) return item
  357. return stateMappers.applyWrittenSnapshotsToGroup(item, writtenRegisters)
  358. }, {
  359. protocolMode
  360. })
  361. return true
  362. }
  363. module.exports = {
  364. DATA_TYPE_OPTIONS,
  365. REGISTER_TYPE_OPTIONS,
  366. addGroupFromConfig,
  367. completeStructInstanceGroups,
  368. completeStructInstanceGroupsWithStructFile,
  369. completeStructInstanceGroupsWithStructSource,
  370. getState,
  371. importJsonFromMessageFile,
  372. init: initParameterGroups,
  373. mergeImportedGroups: mergeImportedGroupsIntoState,
  374. parseStructDefinition,
  375. readGroup,
  376. removeGroup,
  377. reorderRegister,
  378. saveJsonToChat,
  379. setGroupDeleteVisible,
  380. setGroupExpanded,
  381. subscribe,
  382. syncFromStorageAccessCodeInfo,
  383. updateGroupConfig,
  384. updateRegister,
  385. updateRegisterValue,
  386. validateRegisterInputValue,
  387. writeRegister,
  388. writeGroup
  389. }