io.js 20 KB

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