io.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652
  1. const {
  2. parseHexInteger
  3. } = require('../../utils/base-utils.js')
  4. const {
  5. bytesToWords
  6. } = require('../../utils/binary-utils.js')
  7. const transport = require('../../transport/ble-core.js')
  8. const modbusClient = require('../modbus-rtu/service.js')
  9. const storageAccessService = require('../storage-access/service.js')
  10. const {
  11. decodeRegisterFromByteCache,
  12. decodeRegisterFromWordCache,
  13. decodeRegisterValue,
  14. formatCoilDisplayValue,
  15. formatRegisterValue,
  16. getDataType,
  17. getGroupEncodedBytes,
  18. getGroupEncodedWords,
  19. getRegisterBytesFromByteCache,
  20. getRegisterEncodedBytes,
  21. getRegisterEncodedWords,
  22. getRegisterWordsFromByteCache,
  23. getRegisterWordsFromWordCache,
  24. getRegisterWriteValueText,
  25. isBitRegisterType,
  26. isByteRegister,
  27. normalizeRegister,
  28. parseCoilValue,
  29. registerTypeIsBit,
  30. splitWordSpans
  31. } = require('../../domain/parameter-groups/model.js')
  32. const STORAGE_ACCESS_READ_ONLY_AREAS = [storageAccessService.AREA.CODEINFO, storageAccessService.AREA.CODE]
  33. const MAX_STORAGE_ACCESS_ADDRESS = 0xFFFFFFFF
  34. const MAX_STORAGE_ACCESS_BYTE_LENGTH = 0xFFFFFFFF
  35. function getMemoryType(group = {}) {
  36. const memoryArea = String(group.sourceMemoryArea || '').trim().toUpperCase()
  37. if (memoryArea === 'ADDR32' || memoryArea === 'ADDRESS32') return storageAccessService.AREA.ADDR32
  38. if (memoryArea === 'BIT') return storageAccessService.AREA.DATA
  39. const area = storageAccessService.AREA[memoryArea]
  40. return area === undefined ? null : area
  41. }
  42. function isReadOnlyMemoryType(memoryType) {
  43. return STORAGE_ACCESS_READ_ONLY_AREAS.indexOf(memoryType) >= 0
  44. }
  45. function isMemoryGroup(group = {}) {
  46. return getMemoryType(group) !== null
  47. }
  48. function isByteAddressedGroup(group = {}) {
  49. return group.addressUnit === 'byte' || group.addressUnit === 'bytes' || isMemoryGroup(group)
  50. }
  51. function getMemoryAddress(group = {}) {
  52. const sourceAddress = Number(group.sourceAddress)
  53. if (Number.isFinite(sourceAddress)) return Math.min(MAX_STORAGE_ACCESS_ADDRESS, Math.max(0, Math.floor(sourceAddress)))
  54. return Math.min(MAX_STORAGE_ACCESS_ADDRESS, Math.max(0, Math.floor(Number(group.startAddress) || 0)))
  55. }
  56. function getMemoryByteLength(group = {}) {
  57. const sourceByteLength = Number(group.sourceByteLength)
  58. if (Number.isFinite(sourceByteLength) && sourceByteLength > 0) return Math.min(MAX_STORAGE_ACCESS_BYTE_LENGTH, Math.ceil(sourceByteLength))
  59. const byteLength = Number(group.byteLength)
  60. if (Number.isFinite(byteLength) && byteLength > 0) return Math.min(MAX_STORAGE_ACCESS_BYTE_LENGTH, Math.ceil(byteLength))
  61. return Math.max(1, Math.ceil((Number(group.wordQuantity) || 1) * 2))
  62. }
  63. function shouldUseStorageAccess(options = {}, group = {}) {
  64. return !!options.useStorageAccess && isMemoryGroup(group)
  65. }
  66. function getStorageMaxPacketLength(group = {}, options = {}) {
  67. const configuredMaxPacketLength = storageAccessService.resolveMaxPacketLength(options.maxPacketLength)
  68. const deviceMaxPacketLength = Number(group.codeInfoContext && group.codeInfoContext.maxPacketLength)
  69. if (!Number.isFinite(deviceMaxPacketLength) || deviceMaxPacketLength <= 0) return configuredMaxPacketLength
  70. if (configuredMaxPacketLength === 0) return Math.round(deviceMaxPacketLength)
  71. return Math.min(configuredMaxPacketLength, Math.round(deviceMaxPacketLength))
  72. }
  73. function normalizeNumericCache(source = {}) {
  74. const cache = {}
  75. Object.keys(source || {}).forEach((addressText) => {
  76. const numericAddress = Number(addressText)
  77. if (Number.isFinite(numericAddress)) {
  78. cache[numericAddress] = Number(source[addressText]) & 0xFFFF
  79. }
  80. })
  81. return cache
  82. }
  83. function createRegisterStateFromCache(group, register, wordCache) {
  84. const rawBytes = registerTypeIsBit(register)
  85. ? []
  86. : (isByteAddressedGroup(group) ? getRegisterBytesFromByteCache(register, wordCache) : [])
  87. const rawWords = registerTypeIsBit(register)
  88. ? []
  89. : (isByteAddressedGroup(group)
  90. ? getRegisterWordsFromByteCache(register, wordCache)
  91. : getRegisterWordsFromWordCache(register, wordCache))
  92. const rawValue = registerTypeIsBit(register)
  93. ? decodeRegisterFromWordCache(register, wordCache)
  94. : (isByteAddressedGroup(group)
  95. ? decodeRegisterFromByteCache(register, wordCache)
  96. : (rawWords ? decodeRegisterValue(register, rawWords) : null))
  97. const displayValue = rawValue === null || rawValue === undefined
  98. ? '--'
  99. : (registerTypeIsBit(register)
  100. ? formatCoilDisplayValue(rawValue)
  101. : formatRegisterValue(register, rawValue))
  102. const preNormalizedRegister = {
  103. ...register,
  104. displayValue,
  105. isDirty: false,
  106. rawBytes: rawBytes || [],
  107. rawValue,
  108. rawWords: rawWords || []
  109. }
  110. const nextRegister = normalizeRegister(preNormalizedRegister, group, 0, register.address, register.byteOffset)
  111. return {
  112. ...nextRegister,
  113. id: register.id,
  114. inputValue: group.writable ? displayValue : register.inputValue
  115. }
  116. }
  117. function applyReadCacheToGroup(group, wordCache) {
  118. return {
  119. ...group,
  120. registers: group.registers.map((register) => createRegisterStateFromCache(group, register, wordCache))
  121. }
  122. }
  123. function applyWrittenSnapshotToRegister(register, snapshot = {}) {
  124. const hasDisplayValue = Object.prototype.hasOwnProperty.call(snapshot, 'displayValue')
  125. const hasRawBytes = Object.prototype.hasOwnProperty.call(snapshot, 'rawBytes')
  126. const hasRawValue = Object.prototype.hasOwnProperty.call(snapshot, 'rawValue')
  127. const hasRawWords = Object.prototype.hasOwnProperty.call(snapshot, 'rawWords')
  128. return {
  129. ...register,
  130. displayValue: hasDisplayValue ? snapshot.displayValue : register.displayValue,
  131. inputValue: hasDisplayValue ? snapshot.displayValue : register.inputValue,
  132. isDirty: false,
  133. rawBytes: hasRawBytes ? snapshot.rawBytes : register.rawBytes,
  134. rawValue: hasRawValue ? snapshot.rawValue : register.rawValue,
  135. rawWords: hasRawWords ? snapshot.rawWords : register.rawWords
  136. }
  137. }
  138. function applyWrittenSnapshotsToGroup(group, snapshots = []) {
  139. let writtenIndex = 0
  140. return {
  141. ...group,
  142. registers: group.registers.map((register, index) => {
  143. const snapshot = snapshots[writtenIndex] || {}
  144. writtenIndex += 1
  145. const nextRegister = applyWrittenSnapshotToRegister(register, snapshot)
  146. const normalized = normalizeRegister(nextRegister, group, index, nextRegister.address, nextRegister.byteOffset)
  147. return {
  148. ...normalized,
  149. id: register.id,
  150. inputValue: group.writable ? nextRegister.displayValue : nextRegister.inputValue
  151. }
  152. })
  153. }
  154. }
  155. function applyWrittenSnapshotToGroupRegister(group, registerIndex, snapshot) {
  156. return {
  157. ...group,
  158. registers: group.registers.map((register, currentIndex) => (
  159. currentIndex === registerIndex
  160. ? (() => {
  161. const nextRegister = applyWrittenSnapshotToRegister(register, snapshot || {})
  162. const normalized = normalizeRegister(nextRegister, group, currentIndex, nextRegister.address, nextRegister.byteOffset)
  163. return {
  164. ...normalized,
  165. id: register.id,
  166. inputValue: group.writable ? nextRegister.displayValue : nextRegister.inputValue
  167. }
  168. })()
  169. : register
  170. ))
  171. }
  172. }
  173. function getWriteSpanMaxQuantity(totalQuantity, maxPacketLength) {
  174. if (maxPacketLength === 0) return Math.max(1, totalQuantity)
  175. return Math.max(1, modbusClient.getMaxWriteMultipleRegisterQuantity(maxPacketLength))
  176. }
  177. async function readModbusGroup(group, options = {}) {
  178. const totalQuantity = Math.max(1, group.wordQuantity || group.quantity || 0)
  179. const wordCache = {}
  180. const slaveAddress = modbusClient.getSharedSlaveAddress()
  181. if (slaveAddress === null) return null
  182. const values = await modbusClient.readSpans(
  183. slaveAddress,
  184. group.functionCode,
  185. [{
  186. address: group.startAddress,
  187. quantity: totalQuantity
  188. }],
  189. group.name || '参数组读取',
  190. 'parameter-group-read',
  191. {
  192. maxFrameBytes: options.maxPacketLength,
  193. showModal: options.showModal !== false
  194. }
  195. )
  196. if (!values) return null
  197. if (isBitRegisterType(group.registerType)) {
  198. Object.keys(values.coils || {}).forEach((addressText) => {
  199. wordCache[parseHexInteger(addressText)] = Number(values.coils[addressText]) ? 1 : 0
  200. })
  201. } else {
  202. Object.keys(values.words || {}).forEach((addressText) => {
  203. wordCache[parseHexInteger(addressText)] = Number(values.words[addressText]) & 0xFFFF
  204. })
  205. }
  206. return wordCache
  207. }
  208. function getRegisterWordsForModbusWrite(group) {
  209. const words = group.isStructLayout
  210. ? getGroupEncodedWords(group)
  211. : Array.from({ length: Math.max(1, group.wordQuantity || 1) }, () => 0)
  212. if (group.isStructLayout) return words
  213. for (let index = 0; index < group.registers.length; index += 1) {
  214. const register = group.registers[index]
  215. const registerWords = getRegisterEncodedWords(register)
  216. if (!Array.isArray(registerWords) || !registerWords.length) {
  217. throw new Error(`${register.name || `寄存器 ${index + 1}`} 没有有效写入值`)
  218. }
  219. const dataType = getDataType(register.dataType).key
  220. const relativeAddress = Math.max(0, register.address - group.startAddress)
  221. if (isByteRegister(dataType)) {
  222. const byteValue = Number(registerWords[0]) & 0xFF
  223. const currentWord = words[relativeAddress] || 0
  224. words[relativeAddress] = register.byteOffset === 0
  225. ? (((byteValue << 8) | (currentWord & 0x00FF)) & 0xFFFF)
  226. : (((currentWord & 0xFF00) | byteValue) & 0xFFFF)
  227. } else {
  228. for (let offset = 0; offset < register.registerCount; offset += 1) {
  229. words[relativeAddress + offset] = Number(registerWords[offset]) & 0xFFFF
  230. }
  231. }
  232. }
  233. return words
  234. }
  235. function createModbusWrittenRegisterSnapshots(group, words = []) {
  236. const writtenWordCache = words.reduce((cache, word, offset) => {
  237. cache[group.startAddress + offset] = word
  238. return cache
  239. }, {})
  240. return group.registers.map((register) => {
  241. const rawWords = getRegisterWordsFromWordCache(register, writtenWordCache) || []
  242. const rawValue = decodeRegisterValue(register, rawWords)
  243. const displayValue = formatRegisterValue(register, rawValue)
  244. return {
  245. rawWords,
  246. rawValue,
  247. displayValue
  248. }
  249. })
  250. }
  251. async function writeModbusCoilGroup(group, options = {}) {
  252. const slaveAddress = modbusClient.getSharedSlaveAddress()
  253. if (slaveAddress === null) return null
  254. const writtenRegisters = []
  255. for (let index = 0; index < group.registers.length; index += 1) {
  256. const register = group.registers[index]
  257. const coilValue = parseCoilValue(getRegisterWriteValueText(register))
  258. if (coilValue === null) {
  259. transport.showCommandAlert('参数组写入', `${register.name || `寄存器 ${index + 1}`} 没有有效写入值`)
  260. return null
  261. }
  262. const response = await modbusClient.writeSingleCoil(
  263. slaveAddress,
  264. group.startAddress + index,
  265. !!coilValue,
  266. register.name || group.name || '参数组写入',
  267. 'parameter-group-coil-write',
  268. {
  269. maxFrameBytes: options.maxPacketLength
  270. }
  271. )
  272. if (!response) return null
  273. writtenRegisters.push({
  274. rawBytes: [],
  275. rawValue: coilValue,
  276. rawWords: [],
  277. displayValue: formatCoilDisplayValue(coilValue)
  278. })
  279. }
  280. return writtenRegisters
  281. }
  282. async function writeModbusRegisterGroup(group, options = {}) {
  283. const slaveAddress = modbusClient.getSharedSlaveAddress()
  284. if (slaveAddress === null) return null
  285. let words
  286. try {
  287. words = getRegisterWordsForModbusWrite(group)
  288. } catch (error) {
  289. transport.showCommandAlert('参数组写入', error.message || '寄存器组没有有效写入值')
  290. return null
  291. }
  292. const writtenRegisters = createModbusWrittenRegisterSnapshots(group, words)
  293. const maxWriteQuantity = getWriteSpanMaxQuantity(words.length, options.maxPacketLength)
  294. const spans = splitWordSpans(group.startAddress, words.length, maxWriteQuantity)
  295. let cursor = 0
  296. for (const span of spans) {
  297. const spanWords = words.slice(cursor, cursor + span.quantity)
  298. cursor += span.quantity
  299. const response = await modbusClient.writeMultipleRegisters(
  300. slaveAddress,
  301. span.address,
  302. spanWords,
  303. group.name || '参数组写入',
  304. 'parameter-group-write',
  305. {
  306. maxFrameBytes: options.maxPacketLength
  307. }
  308. )
  309. if (!response) return null
  310. }
  311. return writtenRegisters
  312. }
  313. async function writeModbusGroup(group, options = {}) {
  314. return group.registerType === 'coil'
  315. ? writeModbusCoilGroup(group, options)
  316. : writeModbusRegisterGroup(group, options)
  317. }
  318. function bytesToPaddedWords(bytes = []) {
  319. return bytesToWords(bytes.length % 2 === 0 ? bytes : bytes.concat(0))
  320. }
  321. function fillByteCacheFromBytes(byteCache, startAddress, bytes = []) {
  322. bytes.forEach((byte, offset) => {
  323. byteCache[startAddress + offset] = Number(byte) & 0xFF
  324. })
  325. }
  326. function fillWordCacheFromBytes(wordCache, startAddress, bytes = []) {
  327. const words = bytesToPaddedWords(bytes)
  328. words.forEach((word, offset) => {
  329. wordCache[startAddress + offset] = Number(word) & 0xFFFF
  330. })
  331. }
  332. function createStorageWrittenRegisterSnapshots(group, wordCache) {
  333. return group.registers.map((register) => {
  334. const rawBytes = isByteAddressedGroup(group)
  335. ? (getRegisterBytesFromByteCache(register, wordCache) || [])
  336. : []
  337. const rawWords = isByteAddressedGroup(group)
  338. ? (getRegisterWordsFromByteCache(register, wordCache) || [])
  339. : (getRegisterWordsFromWordCache(register, wordCache) || [])
  340. const rawValue = isByteAddressedGroup(group)
  341. ? decodeRegisterFromByteCache(register, wordCache)
  342. : decodeRegisterValue(register, rawWords)
  343. return {
  344. rawBytes,
  345. rawWords,
  346. rawValue,
  347. displayValue: formatRegisterValue(register, rawValue)
  348. }
  349. })
  350. }
  351. function createStorageWrittenRegisterSnapshot(group, register, byteCache) {
  352. const snapshots = createStorageWrittenRegisterSnapshots({
  353. ...group,
  354. registers: [register]
  355. }, byteCache)
  356. return snapshots[0] || null
  357. }
  358. function groupHasBitFields(group = {}) {
  359. return (Array.isArray(group.registers) ? group.registers : []).some((register) => !!register.isBitField)
  360. }
  361. async function writeMemoryRegister(group, register, options = {}) {
  362. const memoryType = getMemoryType(group)
  363. const maxPacketLength = getStorageMaxPacketLength(group, options)
  364. const byteLength = Math.max(1, Number(register.byteLength) || 1)
  365. const address = Math.min(MAX_STORAGE_ACCESS_ADDRESS, Math.max(0, Math.floor(Number(register.address) || getMemoryAddress(group))))
  366. let bytes
  367. if (memoryType === null) {
  368. transport.showCommandAlert('内存写入', `暂不支持 ${group.sourceMemoryArea || '未知'} 内存区域`)
  369. return null
  370. }
  371. if (isReadOnlyMemoryType(memoryType)) {
  372. transport.showCommandAlert('内存写入', `${group.sourceMemoryArea || '该'} 区不可写`)
  373. return null
  374. }
  375. try {
  376. if (register.isBitField) {
  377. const baseBytes = await storageAccessService.readMemory(
  378. memoryType,
  379. address,
  380. byteLength,
  381. register.name ? `${register.name} 读改写` : '变量读改写',
  382. 'parameter-group-memory-register-rmw-read',
  383. {
  384. maxFrameBytes: maxPacketLength
  385. }
  386. )
  387. if (!baseBytes) return null
  388. bytes = getGroupEncodedBytes({
  389. ...group,
  390. paddedByteLength: byteLength,
  391. registers: [{
  392. ...register,
  393. address,
  394. byteStart: 0
  395. }]
  396. }, baseBytes).slice(0, byteLength)
  397. } else {
  398. bytes = getRegisterEncodedBytes(register)
  399. }
  400. } catch (error) {
  401. transport.showCommandAlert('内存写入', error.message || '变量没有有效写入值')
  402. return null
  403. }
  404. if (!Array.isArray(bytes) || !bytes.length) {
  405. transport.showCommandAlert('内存写入', `${register.name || '变量'} 没有有效写入值`)
  406. return null
  407. }
  408. bytes = bytes.slice(0, byteLength)
  409. while (bytes.length < byteLength) bytes.push(0)
  410. const ok = await storageAccessService.writeMemory(
  411. memoryType,
  412. address,
  413. bytes,
  414. register.name || group.name || '变量写入',
  415. 'parameter-group-memory-register-write',
  416. {
  417. maxFrameBytes: maxPacketLength
  418. }
  419. )
  420. if (!ok) return null
  421. const byteCache = {}
  422. fillByteCacheFromBytes(byteCache, address, bytes)
  423. return createStorageWrittenRegisterSnapshot(group, register, byteCache)
  424. }
  425. async function readMemoryGroup(group, options = {}) {
  426. const memoryType = getMemoryType(group)
  427. const address = getMemoryAddress(group)
  428. const byteLength = getMemoryByteLength(group)
  429. const maxPacketLength = getStorageMaxPacketLength(group, options)
  430. if (memoryType === null) {
  431. transport.showCommandAlert('内存读取', `暂不支持 ${group.sourceMemoryArea || '未知'} 内存区域`)
  432. return null
  433. }
  434. const bytes = await storageAccessService.readMemory(
  435. memoryType,
  436. address,
  437. byteLength,
  438. group.name || '内存读取',
  439. 'parameter-group-memory-read',
  440. {
  441. maxFrameBytes: maxPacketLength,
  442. showModal: options.showModal !== false
  443. }
  444. )
  445. if (!bytes) return null
  446. const wordCache = {}
  447. if (isByteAddressedGroup(group)) {
  448. fillByteCacheFromBytes(wordCache, address, bytes)
  449. } else {
  450. fillWordCacheFromBytes(wordCache, address, bytes)
  451. }
  452. return wordCache
  453. }
  454. async function writeMemoryGroup(group, options = {}) {
  455. const memoryType = getMemoryType(group)
  456. const address = getMemoryAddress(group)
  457. const byteLength = getMemoryByteLength(group)
  458. const maxPacketLength = getStorageMaxPacketLength(group, options)
  459. let bytes
  460. if (memoryType === null) {
  461. transport.showCommandAlert('内存写入', `暂不支持 ${group.sourceMemoryArea || '未知'} 内存区域`)
  462. return null
  463. }
  464. if (isReadOnlyMemoryType(memoryType)) {
  465. transport.showCommandAlert('内存写入', `${group.sourceMemoryArea || '该'} 区不可写`)
  466. return null
  467. }
  468. try {
  469. let baseBytes = null
  470. if (groupHasBitFields(group)) {
  471. baseBytes = await storageAccessService.readMemory(
  472. memoryType,
  473. address,
  474. byteLength,
  475. group.name ? `${group.name} 读改写` : '内存读改写',
  476. 'parameter-group-memory-rmw-read',
  477. {
  478. maxFrameBytes: maxPacketLength
  479. }
  480. )
  481. if (!baseBytes) return null
  482. }
  483. bytes = getGroupEncodedBytes(group, baseBytes)
  484. } catch (error) {
  485. transport.showCommandAlert('内存写入', error.message || '寄存器组没有有效写入值')
  486. return null
  487. }
  488. bytes = bytes.slice(0, byteLength)
  489. const ok = await storageAccessService.writeMemory(
  490. memoryType,
  491. address,
  492. bytes,
  493. group.name || '内存写入',
  494. 'parameter-group-memory-write',
  495. {
  496. maxFrameBytes: maxPacketLength
  497. }
  498. )
  499. if (!ok) return null
  500. const wordCache = {}
  501. if (isByteAddressedGroup(group)) {
  502. fillByteCacheFromBytes(wordCache, address, bytes)
  503. } else {
  504. fillWordCacheFromBytes(wordCache, address, bytes)
  505. }
  506. return createStorageWrittenRegisterSnapshots(group, wordCache)
  507. }
  508. async function readGroup(group, options = {}) {
  509. const wordCache = shouldUseStorageAccess(options, group)
  510. ? await readMemoryGroup(group, options)
  511. : await readModbusGroup(group, options)
  512. if (!wordCache) return null
  513. const normalizedCache = normalizeNumericCache(wordCache)
  514. return {
  515. applyToGroup(currentGroup) {
  516. return applyReadCacheToGroup(currentGroup, normalizedCache)
  517. }
  518. }
  519. }
  520. async function writeRegister(group, registerIndex, options = {}) {
  521. const register = group && Array.isArray(group.registers) ? group.registers[registerIndex] : null
  522. if (!register) return null
  523. if (shouldUseStorageAccess(options, group)) {
  524. const written = await writeMemoryRegister(group, register, options)
  525. return written
  526. ? {
  527. applyToGroup(currentGroup) {
  528. return applyWrittenSnapshotToGroupRegister(currentGroup, registerIndex, written)
  529. }
  530. }
  531. : null
  532. }
  533. return writeGroup(group, options)
  534. }
  535. async function writeGroup(group, options = {}) {
  536. const snapshots = shouldUseStorageAccess(options, group)
  537. ? await writeMemoryGroup(group, options)
  538. : await writeModbusGroup(group, options)
  539. if (!snapshots) return null
  540. return {
  541. applyToGroup(currentGroup) {
  542. return applyWrittenSnapshotsToGroup(currentGroup, snapshots)
  543. }
  544. }
  545. }
  546. module.exports = {
  547. getMemoryAddress,
  548. getMemoryByteLength,
  549. getMemoryType,
  550. isByteAddressedGroup,
  551. isMemoryGroup,
  552. readGroup,
  553. writeGroup,
  554. writeRegister
  555. }