service.js 13 KB

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