| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733 |
- const {
- padHex
- } = require('../../utils/base-utils.js')
- const {
- bytesToWords,
- toByteArray
- } = require('../../utils/binary-utils.js')
- const {
- BYTE_ORDER_HIGH,
- appendCrc16Ccitt,
- crc16Ccitt,
- hasValidCrc16Ccitt
- } = require('../../utils/crc.js')
- const transport = require('../../transport/ble-core.js')
- const PROTOCOL_NAME = 'storage-access'
- const CMD_ERR_MASK = 0x80
- const CMD_WRITE_MASK = 0x40
- const CMD_AREA_MASK = 0x3F
- const CMD_INFO = 0x0F
- const INFO_DATA_BYTE_LENGTH = 4
- const AREA = {
- DATA: 0x01,
- IDATA: 0x02,
- XDATA: 0x03,
- CODE: 0x04,
- INFO: 0x0F
- }
- const AREA_NAMES = {
- [AREA.DATA]: 'DATA',
- [AREA.IDATA]: 'IDATA',
- [AREA.XDATA]: 'XDATA',
- [AREA.CODE]: 'CODE',
- [AREA.INFO]: 'INFO'
- }
- const AREA_BY_NAME = {
- DATA: AREA.DATA,
- IDATA: AREA.IDATA,
- XDATA: AREA.XDATA,
- CODE: AREA.CODE,
- INFO: AREA.INFO,
- SYNC: AREA.INFO
- }
- const EXCEPTION_MESSAGES = {
- 0x01: '非法命令',
- 0x02: '非法区域',
- 0x03: '非法地址',
- 0x04: '非法长度',
- 0x05: '写保护',
- 0x06: '设备忙',
- 0x07: '格式错误',
- 0x08: '访问被拒绝',
- 0x09: '内部错误',
- 0x0A: '对齐错误',
- 0x0B: '范围溢出',
- 0x0C: '不支持的操作'
- }
- const DEFAULT_MAX_FRAME_BYTES = 64
- const MAX_PAYLOAD_BYTES = 256
- const UNLIMITED_FRAME_BYTES = 0
- const READ_REQUEST_LENGTH = 7
- const WRITE_REQUEST_OVERHEAD = 7
- const READ_RESPONSE_OVERHEAD = 7
- const WRITE_RESPONSE_LENGTH = 7
- const EXCEPTION_RESPONSE_LENGTH = 4
- const INFO_REQUEST_LENGTH = READ_REQUEST_LENGTH
- const INFO_RESPONSE_LENGTH = READ_RESPONSE_OVERHEAD + INFO_DATA_BYTE_LENGTH
- const STORAGE_CRC_OPTIONS = {
- byteOrder: BYTE_ORDER_HIGH
- }
- const VALID_AREAS = [AREA.DATA, AREA.IDATA, AREA.XDATA, AREA.CODE, AREA.INFO]
- const MEMORY_AREAS = [AREA.DATA, AREA.IDATA, AREA.XDATA, AREA.CODE]
- function toByte(value, label) {
- if (!Number.isInteger(value) || value < 0 || value > 0xFF) {
- throw new Error(`${label}必须在 0x00 至 0xFF 之间`)
- }
- return value
- }
- function toWord(value, label) {
- if (!Number.isInteger(value) || value < 0 || value > 0xFFFF) {
- throw new Error(`${label}必须在 0x0000 至 0xFFFF 之间`)
- }
- return value
- }
- function normalizeArea(value) {
- if (typeof value === 'string') {
- const area = AREA_BY_NAME[value.trim().toUpperCase()]
- if (area) return area
- }
- const area = toByte(Number(value), '存储区域')
- if (VALID_AREAS.indexOf(area) < 0) {
- throw new Error('存储区域必须为 data/idata/xdata/code/info')
- }
- return area
- }
- function normalizeMemoryArea(value) {
- const area = normalizeArea(value)
- if (MEMORY_AREAS.indexOf(area) < 0) {
- throw new Error('存储读写区域必须为 data/idata/xdata/code')
- }
- return area
- }
- function toByteLength(value, label = '字节长度', maxPayload = MAX_PAYLOAD_BYTES) {
- const byteLength = toWord(Number(value), label)
- if (byteLength === 0) {
- throw new Error(`${label}必须大于 0`)
- }
- if (maxPayload > 0 && byteLength > maxPayload) {
- throw new Error(`单帧最多访问 ${maxPayload} 字节`)
- }
- return byteLength
- }
- function splitWord(value) {
- return [(value >> 8) & 0xFF, value & 0xFF]
- }
- function readWord(bytes, offset) {
- return (((bytes[offset] || 0) << 8) | (bytes[offset + 1] || 0)) & 0xFFFF
- }
- function normalizeMaxFrameBytes(maxFrameBytes = DEFAULT_MAX_FRAME_BYTES) {
- const numberValue = Number(maxFrameBytes)
- if (Number.isFinite(numberValue) && Math.round(numberValue) === UNLIMITED_FRAME_BYTES) return UNLIMITED_FRAME_BYTES
- if (Number.isFinite(numberValue) && numberValue > 0) return Math.round(numberValue)
- return DEFAULT_MAX_FRAME_BYTES
- }
- function getPayloadLimitFromFrame(maxFrameBytes, overhead) {
- const frameBytes = normalizeMaxFrameBytes(maxFrameBytes)
- if (frameBytes === UNLIMITED_FRAME_BYTES) return MAX_PAYLOAD_BYTES
- return Math.max(0, Math.min(MAX_PAYLOAD_BYTES, frameBytes - overhead))
- }
- function getMaxReadByteLength(maxFrameBytes = DEFAULT_MAX_FRAME_BYTES) {
- return getPayloadLimitFromFrame(maxFrameBytes, READ_RESPONSE_OVERHEAD)
- }
- function getMaxWriteByteLength(maxFrameBytes = DEFAULT_MAX_FRAME_BYTES) {
- return getPayloadLimitFromFrame(maxFrameBytes, WRITE_REQUEST_OVERHEAD)
- }
- function buildCommand(area, isWrite = false) {
- const normalizedArea = isWrite ? normalizeMemoryArea(area) : normalizeArea(area)
- return (isWrite ? CMD_WRITE_MASK : 0x00) | normalizedArea
- }
- function decodeCommand(command) {
- const cmd = toByte(Number(command), '命令字')
- return {
- area: cmd & CMD_AREA_MASK,
- command: cmd,
- hasError: !!(cmd & CMD_ERR_MASK),
- isWrite: !!(cmd & CMD_WRITE_MASK)
- }
- }
- function hasValidStorageCrc(bytes) {
- const frame = toByteArray(bytes)
- if (frame.length < 3) return false
- if (frame.length >= 4) return hasValidCrc16Ccitt(frame, STORAGE_CRC_OPTIONS)
- const expected = crc16Ccitt(frame.slice(0, -2), STORAGE_CRC_OPTIONS)
- const received = (((frame[frame.length - 2] || 0) << 8) | (frame[frame.length - 1] || 0)) & 0xFFFF
- return expected === received
- }
- function appendStorageCrc(bytes) {
- return appendCrc16Ccitt(bytes, STORAGE_CRC_OPTIONS)
- }
- function buildReadFrame(area, address, byteLength, options = {}) {
- const command = buildCommand(area, false)
- const startAddress = toWord(Number(address), '内存地址')
- const maxByteLength = getMaxReadByteLength(options.maxFrameBytes)
- const length = toByteLength(Number(byteLength), '读取字节长度', maxByteLength || MAX_PAYLOAD_BYTES)
- return appendStorageCrc([command].concat(splitWord(startAddress), splitWord(length)))
- }
- function buildInfoFrame(address = 0, byteLength = INFO_DATA_BYTE_LENGTH) {
- return buildReadFrame(AREA.INFO, address, byteLength)
- }
- function buildWriteFrame(area, address, bytes, options = {}) {
- const normalizedArea = normalizeMemoryArea(area)
- if (normalizedArea === AREA.CODE) {
- throw new Error('code 区暂不支持写入')
- }
- const command = buildCommand(normalizedArea, true)
- const startAddress = toWord(Number(address), '内存地址')
- const dataBytes = toByteArray(bytes).map((byte) => toByte(Number(byte), '写入字节'))
- const maxByteLength = getMaxWriteByteLength(options.maxFrameBytes)
- const length = toByteLength(dataBytes.length, '写入字节长度', maxByteLength || MAX_PAYLOAD_BYTES)
- return appendStorageCrc([command].concat(splitWord(startAddress), splitWord(length), dataBytes))
- }
- function formatHex(bytes) {
- return toByteArray(bytes).map((byte) => byte.toString(16).padStart(2, '0').toUpperCase()).join(' ')
- }
- function parseStorageAccessResponse(bytes) {
- const frame = toByteArray(bytes)
- if (frame.length < EXCEPTION_RESPONSE_LENGTH || !hasValidStorageCrc(frame)) return null
- const command = frame[0] & 0xFF
- const decoded = decodeCommand(command)
- if (decoded.hasError) {
- if (frame.length !== EXCEPTION_RESPONSE_LENGTH) return null
- return {
- area: decoded.area,
- areaName: AREA_NAMES[decoded.area] || 'UNKNOWN',
- command,
- exceptionCode: frame[1] & 0xFF,
- isException: true,
- isWrite: decoded.isWrite,
- protocol: PROTOCOL_NAME,
- sourceCommand: command & ~CMD_ERR_MASK
- }
- }
- if (!AREA_NAMES[decoded.area]) return null
- if (decoded.isWrite) {
- if (decoded.area === AREA.INFO) return null
- if (frame.length !== WRITE_RESPONSE_LENGTH) return null
- return {
- address: readWord(frame, 1),
- area: decoded.area,
- areaName: AREA_NAMES[decoded.area] || 'UNKNOWN',
- byteLength: readWord(frame, 3),
- command,
- dataBytes: [],
- isException: false,
- isWrite: true,
- protocol: PROTOCOL_NAME
- }
- }
- if (frame.length < READ_RESPONSE_OVERHEAD) return null
- const byteLength = readWord(frame, 3)
- const dataStart = 5
- const dataEnd = dataStart + byteLength
- if (frame.length !== dataEnd + 2) return null
- const dataBytes = frame.slice(dataStart, dataEnd)
- return {
- address: readWord(frame, 1),
- area: decoded.area,
- areaName: AREA_NAMES[decoded.area] || 'UNKNOWN',
- byteLength,
- command,
- dataBytes,
- isException: false,
- isInfo: decoded.area === AREA.INFO,
- isWrite: false,
- protocol: PROTOCOL_NAME,
- words: bytesToWords(dataBytes.length % 2 === 0 ? dataBytes : dataBytes.concat(0)),
- ...(decoded.area === AREA.INFO && dataBytes.length >= INFO_DATA_BYTE_LENGTH
- ? {
- codeInfoAddress: readWord(frame, 5),
- codeInfoByteLength: readWord(frame, 7),
- infoBytes: dataBytes.slice(0, INFO_DATA_BYTE_LENGTH)
- }
- : {})
- }
- }
- function parseStorageAccessRequest(bytes) {
- const frame = toByteArray(bytes)
- if (frame.length < INFO_REQUEST_LENGTH || !hasValidStorageCrc(frame)) return null
- const command = frame[0] & 0xFF
- if (frame.length < READ_REQUEST_LENGTH) return null
- const decoded = decodeCommand(command)
- if (decoded.hasError) return null
- if (!AREA_NAMES[decoded.area]) return null
- if (decoded.area === AREA.INFO && decoded.isWrite) return null
- const address = readWord(frame, 1)
- const byteLength = readWord(frame, 3)
- const expectedLength = decoded.isWrite
- ? WRITE_REQUEST_OVERHEAD + byteLength
- : READ_REQUEST_LENGTH
- if (byteLength <= 0 || frame.length !== expectedLength) return null
- return {
- address,
- area: decoded.area,
- areaName: AREA_NAMES[decoded.area] || 'UNKNOWN',
- byteLength,
- command,
- dataBytes: decoded.isWrite ? frame.slice(5, 5 + byteLength) : [],
- isInfo: decoded.area === AREA.INFO,
- isWrite: decoded.isWrite,
- kind: 'raw-hex',
- operation: decoded.isWrite ? 'write' : 'read',
- protocol: PROTOCOL_NAME,
- quantity: byteLength
- }
- }
- function getExpectedResponseLength(expected, responseCommand, responseBytes = []) {
- if (!expected) return 0
- const command = Number(responseCommand) & 0xFF
- if (command === (expected.command | CMD_ERR_MASK)) return EXCEPTION_RESPONSE_LENGTH
- if (command !== expected.command) return 0
- if (expected.operation === 'write' || expected.isWrite) {
- return WRITE_RESPONSE_LENGTH
- }
- if (responseBytes.length < 5) return 0
- return READ_RESPONSE_OVERHEAD + readWord(responseBytes, 3)
- }
- function isExpectedResponse(response, expected) {
- if (!response || !expected) return false
- const sourceCommand = response.isException ? response.sourceCommand : response.command
- if (sourceCommand !== expected.command) return false
- if (!response.isException && response.area !== expected.area) return false
- if (response.isException) return true
- if (response.address !== expected.address) return false
- if (response.byteLength !== expected.byteLength) return false
- if (!response.isWrite && (!Array.isArray(response.dataBytes) || response.dataBytes.length !== expected.byteLength)) return false
- return true
- }
- function getExceptionText(code) {
- return EXCEPTION_MESSAGES[code] || '未知异常'
- }
- function formatExceptionMessage(response) {
- const sourceCommand = response && response.sourceCommand
- const exceptionCode = response && response.exceptionCode
- const exceptionText = getExceptionText(exceptionCode)
- return `设备返回异常帧:命令 0x${padHex(sourceCommand, 2)},异常码 0x${padHex(exceptionCode, 2)}(${exceptionText})`
- }
- function getReadBufferHint(expected) {
- if (!expected) return 0
- if (expected.operation === 'write' || expected.isWrite) return WRITE_RESPONSE_LENGTH
- return READ_RESPONSE_OVERHEAD + Number(expected.byteLength || expected.quantity || 0)
- }
- function alignResponseBuffer(buffer, expected) {
- if (!Array.isArray(buffer) || !buffer.length || !expected) return
- const expectedCommands = [expected.command, expected.command | CMD_ERR_MASK]
- let matchIndex = -1
- for (let index = 0; index < buffer.length; index += 1) {
- if (expectedCommands.indexOf(buffer[index]) < 0) continue
- matchIndex = index
- break
- }
- if (matchIndex > 0) {
- buffer.splice(0, matchIndex)
- } else if (matchIndex < 0 && buffer.length > 1) {
- buffer.splice(0, buffer.length - 1)
- }
- }
- function readResponseFromBuffer(buffer, expected, options = {}) {
- if (!Array.isArray(buffer) || !buffer.length || !expected) {
- return {
- status: 'pending'
- }
- }
- alignResponseBuffer(buffer, expected)
- while (buffer.length >= 1) {
- const responseCommand = buffer[0]
- const responseLength = getExpectedResponseLength(expected, responseCommand, buffer)
- if (!responseLength) {
- return {
- status: 'pending'
- }
- }
- const frameLimit = normalizeMaxFrameBytes(
- options.maxFrameBytes === undefined ? expected.maxFrameBytes : options.maxFrameBytes
- )
- if (frameLimit > 0 && responseLength > frameLimit) {
- return {
- frameLimit,
- responseLength,
- status: 'frame-too-long'
- }
- }
- if (buffer.length < responseLength) {
- return {
- status: 'pending'
- }
- }
- const frameBytes = buffer.slice(0, responseLength)
- const response = parseStorageAccessResponse(frameBytes)
- if (!response) {
- return {
- frameBytes,
- status: 'invalid'
- }
- }
- if (!isExpectedResponse(response, expected)) {
- buffer.shift()
- alignResponseBuffer(buffer, expected)
- continue
- }
- if (response.isException) {
- return {
- message: formatExceptionMessage(response),
- response,
- status: 'exception'
- }
- }
- buffer.splice(0, responseLength)
- return {
- response,
- status: 'complete'
- }
- }
- return {
- status: 'pending'
- }
- }
- function createExpected(area, address, byteLength, isWrite, kind) {
- const normalizedArea = normalizeMemoryArea(area)
- const command = buildCommand(normalizedArea, isWrite)
- return {
- address,
- area: normalizedArea,
- byteLength,
- command,
- isWrite,
- kind,
- operation: isWrite ? 'write' : 'read',
- protocol: PROTOCOL_NAME,
- quantity: byteLength
- }
- }
- function createInfoExpected(kind = 'storage-info-read') {
- return {
- address: 0,
- area: AREA.INFO,
- byteLength: INFO_DATA_BYTE_LENGTH,
- command: buildCommand(AREA.INFO, false),
- frame: buildInfoFrame(0, INFO_DATA_BYTE_LENGTH),
- isInfo: true,
- isWrite: false,
- kind,
- operation: 'read',
- protocol: PROTOCOL_NAME
- }
- }
- function formatAddress(value) {
- return Number(value || 0).toString(16).toUpperCase()
- }
- function getChunkLabel(label, chunks, chunk) {
- if (!label || chunks.length <= 1) return label
- return `${label} ${formatAddress(chunk.address)}-${formatAddress(chunk.address + chunk.quantity - 1)}`
- }
- function splitQuantity(startAddress, quantity, maxQuantity) {
- const chunks = []
- let address = Number(startAddress) || 0
- let remaining = Math.max(0, Math.floor(Number(quantity) || 0))
- const chunkLimit = Math.max(1, Math.floor(Number(maxQuantity) || remaining || 1))
- while (remaining > 0) {
- const chunkQuantity = Math.min(remaining, chunkLimit)
- chunks.push({
- address,
- quantity: chunkQuantity
- })
- address += chunkQuantity
- remaining -= chunkQuantity
- }
- return chunks
- }
- function getReadChunks(startAddress, byteLength, options = {}) {
- const maxByteLength = getMaxReadByteLength(options.maxFrameBytes)
- return splitQuantity(startAddress, byteLength, maxByteLength || byteLength)
- }
- function getWriteChunks(startAddress, bytes, options = {}) {
- const sourceBytes = Array.prototype.slice.call(bytes || []).map((byte) => Number(byte) & 0xFF)
- const maxByteLength = getMaxWriteByteLength(options.maxFrameBytes)
- const chunks = splitQuantity(startAddress, sourceBytes.length, maxByteLength || sourceBytes.length)
- let offset = 0
- return chunks.map((chunk) => {
- const dataBytes = sourceBytes.slice(offset, offset + chunk.quantity)
- offset += chunk.quantity
- return {
- ...chunk,
- dataBytes
- }
- })
- }
- function sendReadChunk(area, chunk, label, kind, options = {}) {
- const normalizedArea = normalizeMemoryArea(area)
- return transport.sendManagedFrame(
- buildReadFrame(normalizedArea, chunk.address, chunk.quantity, {
- maxFrameBytes: options.maxFrameBytes
- }),
- label,
- createExpected(normalizedArea, chunk.address, chunk.quantity, false, kind),
- {
- maxFrameBytes: options.maxFrameBytes,
- showModal: options.showModal
- }
- )
- }
- async function readMemory(area, startAddress, byteLength, label, kind = 'storage-memory-read', options = {}) {
- const normalizedArea = normalizeMemoryArea(area)
- const bytes = []
- const chunks = getReadChunks(startAddress, byteLength, options)
- for (const chunk of chunks) {
- const response = await sendReadChunk(
- normalizedArea,
- chunk,
- getChunkLabel(label, chunks, chunk),
- kind,
- options
- )
- if (!response) return null
- const dataBytes = Array.isArray(response.dataBytes) ? response.dataBytes : []
- dataBytes.forEach((byte, index) => {
- bytes[chunk.address - startAddress + index] = Number(byte) & 0xFF
- })
- if (typeof options.onChunk === 'function') {
- options.onChunk(response, chunk)
- }
- }
- return bytes
- }
- async function writeMemory(area, startAddress, bytes, label, kind = 'storage-memory-write', options = {}) {
- const normalizedArea = normalizeMemoryArea(area)
- const chunks = getWriteChunks(startAddress, bytes, options)
- for (const chunk of chunks) {
- const response = await transport.sendManagedFrame(
- buildWriteFrame(normalizedArea, chunk.address, chunk.dataBytes, {
- maxFrameBytes: options.maxFrameBytes
- }),
- getChunkLabel(label, chunks, chunk),
- createExpected(normalizedArea, chunk.address, chunk.quantity, true, kind),
- {
- maxFrameBytes: options.maxFrameBytes,
- showModal: options.showModal
- }
- )
- if (!response) return false
- if (typeof options.onChunk === 'function') {
- options.onChunk(response, chunk)
- }
- }
- return true
- }
- async function readCodeInfoBlock(label = '同步info', kind = 'storage-info-read', options = {}) {
- const infoResponse = await transport.sendManagedFrame(
- buildInfoFrame(0, INFO_DATA_BYTE_LENGTH),
- label,
- createInfoExpected(`${kind}-info`),
- {
- maxFrameBytes: options.maxFrameBytes,
- showModal: options.showModal
- }
- )
- if (!infoResponse) return null
- const codeInfoAddress = Number(infoResponse.codeInfoAddress || 0)
- const codeInfoByteLength = Number(infoResponse.codeInfoByteLength || 0)
- if (!codeInfoByteLength || codeInfoByteLength > 0xFFFF) {
- transport.showCommandAlert(label, 'info 信息块长度无效')
- return null
- }
- const bytes = await readMemory(
- AREA.CODE,
- codeInfoAddress,
- codeInfoByteLength,
- label,
- kind,
- options
- )
- if (!bytes) return null
- return {
- codeInfoAddress,
- codeInfoByteLength,
- codeInfoBytes: bytes,
- infoBytes: Array.isArray(infoResponse.infoBytes)
- ? infoResponse.infoBytes
- : [],
- codeInfoMemoryType: AREA.CODE
- }
- }
- const response = {
- createExpected,
- createInfoExpected,
- formatExceptionMessage,
- getExceptionText,
- getExpectedResponseLength,
- getReadBufferHint,
- isExpectedResponse,
- parseStorageAccessRequest,
- parseStorageAccessResponse,
- readResponseFromBuffer
- }
- const client = {
- AREA,
- getMaxReadByteLength,
- getMaxWriteByteLength,
- getReadChunks,
- getWriteChunks,
- readCodeInfoBlock,
- readMemory,
- splitQuantity,
- writeMemory
- }
- module.exports = {
- AREA,
- AREA_BY_NAME,
- AREA_NAMES,
- CMD_AREA_MASK,
- CMD_ERR_MASK,
- CMD_INFO,
- CMD_WRITE_MASK,
- DEFAULT_MAX_FRAME_BYTES,
- EXCEPTION_MESSAGES,
- EXCEPTION_RESPONSE_LENGTH,
- INFO_REQUEST_LENGTH,
- INFO_RESPONSE_LENGTH,
- INFO_DATA_BYTE_LENGTH,
- MAX_PAYLOAD_BYTES,
- PROTOCOL_NAME,
- READ_REQUEST_LENGTH,
- READ_RESPONSE_OVERHEAD,
- STORAGE_CRC_OPTIONS,
- UNLIMITED_FRAME_BYTES,
- WRITE_REQUEST_OVERHEAD,
- WRITE_RESPONSE_LENGTH,
- appendStorageCrc,
- buildCommand,
- buildInfoFrame,
- buildReadFrame,
- buildWriteFrame,
- client,
- decodeCommand,
- formatHex,
- getMaxReadByteLength,
- getMaxWriteByteLength,
- hasValidStorageCrc,
- normalizeArea,
- normalizeMaxFrameBytes,
- normalizeMemoryArea,
- response,
- splitWord,
- toByte,
- toByteLength,
- toWord
- }
|