1
0

io.js 20 KB

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