1
0

service.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547
  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. enumCount: 0,
  172. skippedCount: 0,
  173. structCount: 0,
  174. variableCount: 0
  175. }
  176. }
  177. }
  178. async function saveJsonToChat() {
  179. try {
  180. const result = await saveGroupsToChat(getGroups())
  181. return result && result.count
  182. ? result
  183. : {
  184. count: 0
  185. }
  186. } catch (error) {
  187. const message = error && error.message ? error.message : '保存参数组配置失败'
  188. if (!isCancelError(error)) {
  189. transport.showCommandAlert('参数组保存', message)
  190. }
  191. return {
  192. count: 0
  193. }
  194. }
  195. }
  196. async function syncFromStorageAccessCodeInfo(options = {}) {
  197. const result = await storageAccessCodeInfo.syncCodeInfo(options)
  198. if (!result || !result.ok) return result
  199. setStorageCodeInfo(result.codeInfo)
  200. settingsService.setStorageAccessDefaultEndian(result.codeInfoMemoryEndian || result.codeInfo && result.codeInfo.memoryEndian)
  201. const merged = mergeImportedGroupsIntoState(result.importedGroups || [], {
  202. preserveExistingRemarks: true,
  203. preserveExistingPollEnabled: true,
  204. preserveExistingStructLayout: true
  205. })
  206. return {
  207. ...result,
  208. addedGroups: merged.addedGroupCount,
  209. addedRegisters: merged.addedRegisterCount,
  210. updatedGroups: merged.updatedGroupCount,
  211. updatedRegisters: merged.updatedRegisterCount
  212. }
  213. }
  214. function addGroupFromConfig(config = {}) {
  215. const protocolMode = getActiveProtocolMode()
  216. let groupConfig
  217. try {
  218. groupConfig = normalizeGroupConfig({
  219. ...(isStorageAccessProtocolMode(protocolMode) ? { addressUnit: 'byte', sourceMemoryArea: config.sourceMemoryArea || 'XDATA' } : {}),
  220. ...config
  221. })
  222. } catch (error) {
  223. transport.showCommandAlert('参数组添加', error.message || '寄存器组配置无效')
  224. return null
  225. }
  226. if (isGroupAddressRangeOverflow(groupConfig, protocolMode)) {
  227. transport.showCommandAlert('参数组添加', getAddressOverflowText(protocolMode, groupConfig))
  228. return null
  229. }
  230. const registers = Array.isArray(config.registers) ? config.registers : []
  231. const group = normalizeGroup({
  232. ...groupConfig,
  233. layout: config.layout,
  234. ...(registers.length ? { registers } : {}),
  235. expanded: false
  236. })
  237. if (group.addressOverflow) {
  238. transport.showCommandAlert('参数组添加', getAddressOverflowText(protocolMode, group))
  239. return null
  240. }
  241. setGroups(getGroups().concat(group))
  242. return group
  243. }
  244. function updateGroupConfig(groupId, config = {}) {
  245. const protocolMode = getActiveProtocolMode()
  246. const group = findGroup(groupId)
  247. if (!group) return null
  248. let nextConfig
  249. try {
  250. nextConfig = normalizeGroupConfig({
  251. ...group,
  252. ...config
  253. })
  254. } catch (error) {
  255. transport.showCommandAlert('参数组更新', error.message || '寄存器组配置无效')
  256. return null
  257. }
  258. if (isGroupAddressRangeOverflow(nextConfig, protocolMode)) {
  259. transport.showCommandAlert('参数组更新', getAddressOverflowText(protocolMode, nextConfig))
  260. return null
  261. }
  262. const registers = Array.isArray(config.registers) ? config.registers : group.registers
  263. const updatedGroup = normalizeGroup({
  264. ...group,
  265. ...nextConfig,
  266. registers
  267. })
  268. if (updatedGroup.addressOverflow) {
  269. transport.showCommandAlert('参数组更新', getAddressOverflowText(protocolMode, updatedGroup))
  270. return null
  271. }
  272. setGroups(getGroups().map((item) => (
  273. item.id === groupId ? updatedGroup : item
  274. )))
  275. return updatedGroup
  276. }
  277. function setGroupExpanded(groupId, expanded) {
  278. updateGroups((group) => group.id === groupId
  279. ? {
  280. ...group,
  281. deleteVisible: false,
  282. expanded
  283. }
  284. : group)
  285. }
  286. function setGroupDeleteVisible(groupId, deleteVisible) {
  287. updateGroups((group) => group.id === groupId
  288. ? {
  289. ...group,
  290. deleteVisible
  291. }
  292. : group)
  293. }
  294. function removeGroup(groupId) {
  295. setGroups(getGroups().filter((group) => group.id !== groupId))
  296. }
  297. function reorderRegister(groupId, fromIndex, toIndex) {
  298. const group = findGroup(groupId)
  299. if (!group) return null
  300. if (group.isStructLayout) return group
  301. const registers = group.registers.slice()
  302. const sourceIndex = Number(fromIndex)
  303. const targetIndex = Number(toIndex)
  304. if (!Number.isInteger(sourceIndex) || !Number.isInteger(targetIndex)) return null
  305. if (sourceIndex < 0 || sourceIndex >= registers.length) return null
  306. const safeTargetIndex = Math.min(Math.max(targetIndex, 0), registers.length - 1)
  307. if (safeTargetIndex === sourceIndex) return group
  308. const moved = registers.splice(sourceIndex, 1)[0]
  309. registers.splice(safeTargetIndex, 0, moved)
  310. const updatedGroup = normalizeGroup({
  311. ...group,
  312. quantity: registers.length,
  313. registers
  314. })
  315. setGroups(getGroups().map((item) => (
  316. item.id === groupId ? updatedGroup : item
  317. )))
  318. return updatedGroup
  319. }
  320. function updateRegister(groupId, registerIndex, changedData) {
  321. updateGroups((group) => {
  322. if (group.id !== groupId) return group
  323. const shouldResetReadState = Object.prototype.hasOwnProperty.call(changedData, 'dataType')
  324. || Object.prototype.hasOwnProperty.call(changedData, 'textByteLength')
  325. return {
  326. ...group,
  327. registers: group.registers.map((register, currentIndex) => (
  328. currentIndex === registerIndex
  329. ? {
  330. ...register,
  331. ...(shouldResetReadState ? { rawBytes: [], rawValue: null, rawWords: [] } : {}),
  332. ...changedData
  333. }
  334. : register
  335. ))
  336. }
  337. })
  338. }
  339. function updateRegisterValue(groupId, registerIndex, value) {
  340. updateRegister(groupId, registerIndex, {
  341. inputValue: value,
  342. isDirty: true
  343. })
  344. }
  345. function validateRegisterInputValue(groupId, registerIndex, value) {
  346. const group = findGroup(groupId)
  347. if (!group) return false
  348. const register = group.registers[registerIndex]
  349. if (!register) return false
  350. return validateRegisterValue(register, value)
  351. }
  352. async function readGroup(groupId, options = {}) {
  353. const protocolMode = options.protocolMode || getActiveProtocolMode()
  354. const group = findGroup(groupId, protocolMode)
  355. if (!group) return false
  356. if (group.addressOverflow) {
  357. transport.showCommandAlert('参数组读取', getAddressOverflowText(protocolMode, group))
  358. return false
  359. }
  360. const readResult = await parameterGroupIo.readGroup(group, {
  361. ...options,
  362. useStorageAccess: isStorageAccessProtocolMode(protocolMode)
  363. })
  364. if (!readResult) return false
  365. updateGroups((item) => {
  366. if (item.id !== groupId) return item
  367. return readResult.applyToGroup(item)
  368. }, {
  369. protocolMode
  370. })
  371. return true
  372. }
  373. async function writeRegister(groupId, registerIndex) {
  374. const protocolMode = getActiveProtocolMode()
  375. const group = findGroup(groupId, protocolMode)
  376. const register = group && group.registers ? group.registers[registerIndex] : null
  377. if (!group || !register) return false
  378. if (!group.writable) {
  379. transport.showCommandAlert('参数组写入', '当前变量为只读')
  380. return false
  381. }
  382. if (group.addressOverflow) {
  383. transport.showCommandAlert('参数组写入', getAddressOverflowText(protocolMode, group))
  384. return false
  385. }
  386. if (isUnconfiguredRegister(register)) {
  387. transport.showCommandAlert('参数组写入', `${getUnconfiguredRegisterName(register, registerIndex)} 需要先配置数据类型`)
  388. return false
  389. }
  390. const writeResult = await parameterGroupIo.writeRegister(group, registerIndex, {
  391. useStorageAccess: isStorageAccessProtocolMode(protocolMode)
  392. })
  393. if (!writeResult) return false
  394. updateGroups((item) => {
  395. if (item.id !== groupId) return item
  396. return writeResult.applyToGroup(item)
  397. }, {
  398. protocolMode
  399. })
  400. return true
  401. }
  402. async function writeGroup(groupId, options = {}) {
  403. const protocolMode = options.protocolMode || getActiveProtocolMode()
  404. const group = findGroup(groupId, protocolMode)
  405. if (!group) return false
  406. if (!group.writable) {
  407. transport.showCommandAlert('参数组写入', '当前寄存器组为只读')
  408. return false
  409. }
  410. if (group.addressOverflow) {
  411. transport.showCommandAlert('参数组写入', getAddressOverflowText(protocolMode, group))
  412. return false
  413. }
  414. const unconfigured = findUnconfiguredRegister(group)
  415. if (unconfigured) {
  416. transport.showCommandAlert('参数组写入', `${getUnconfiguredRegisterName(unconfigured.register, unconfigured.index)} 需要先配置数据类型`)
  417. return false
  418. }
  419. const writeResult = await parameterGroupIo.writeGroup(group, {
  420. ...options,
  421. useStorageAccess: isStorageAccessProtocolMode(protocolMode)
  422. })
  423. if (!writeResult) return false
  424. updateGroups((item) => {
  425. if (item.id !== groupId) return item
  426. return writeResult.applyToGroup(item)
  427. }, {
  428. protocolMode
  429. })
  430. return true
  431. }
  432. module.exports = {
  433. DATA_TYPE_OPTIONS,
  434. REGISTER_TYPE_OPTIONS,
  435. addGroupFromConfig,
  436. clearStorageAccessGroups,
  437. completeStructInstanceGroups,
  438. completeStructInstanceGroupsWithStructFile,
  439. completeStructInstanceGroupsWithStructSource,
  440. getState,
  441. importJsonFromMessageFile,
  442. init: initParameterGroups,
  443. mergeImportedGroups: mergeImportedGroupsIntoState,
  444. parseStructDefinition,
  445. readGroup,
  446. removeGroup,
  447. reorderRegister,
  448. saveJsonToChat,
  449. setGroupDeleteVisible,
  450. setGroupExpanded,
  451. subscribe,
  452. syncFromStorageAccessCodeInfo,
  453. updateGroupConfig,
  454. updateRegister,
  455. updateRegisterValue,
  456. validateRegisterInputValue,
  457. writeRegister,
  458. writeGroup
  459. }