1
0

service.js 15 KB

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