445 lines
10 KiB
JavaScript
445 lines
10 KiB
JavaScript
// third party modules
|
|
import bits from 'bitwise/bits/index.js'
|
|
import { toBits } from 'bitwise/string/index.js'
|
|
import { readUInt } from 'bitwise/buffer/index.js'
|
|
import { create as createBuffer } from 'bitwise/buffer/index.js'
|
|
import { read as readByte, write as writeByte } from 'bitwise/byte/index.js'
|
|
//local
|
|
import * as array from './array.js'
|
|
|
|
import logger from '@uci-utils/logger'
|
|
|
|
let log = logger({package:'@uci-utils/byte'})
|
|
|
|
// Classes
|
|
|
|
// Byte Class is a single byte value with an attached format designation which allows easy
|
|
// conversion of the byte to any of the other formats
|
|
// of ['STR', 'BUF', 'BUF', 'DEC', 'HEX', 'ARY', 'PLC'] like this
|
|
// examples:
|
|
// '1100101', 'STR' will be left padded to 8 chars with 0
|
|
// Buffer.from([0x65]), 'BUF'
|
|
// 101, 'DEC'
|
|
// 65, 'HEX'
|
|
// [1,1,0,0,1,0,1], 'ARY' will be left padded/unshifted to 8 elements, with 0
|
|
// [7,3,1,6], 'PLC' any order, the number of the bit that is "on" or "1" from 1-8, 1=rightmost LSB
|
|
|
|
/**
|
|
* Byte - A class to hold and manipulate a bitwise byte
|
|
*/
|
|
class Byte {
|
|
/**
|
|
* constructor - Description
|
|
*
|
|
* @param {number} [byte=0] Description
|
|
* @param {string} [format=DEC] Description
|
|
*
|
|
* @returns {Object} instance of Byte Class
|
|
*/
|
|
|
|
constructor(byte = 0, format = 'DEC') {
|
|
this.cur = byte
|
|
this.prv = byte
|
|
this.format = format
|
|
}
|
|
|
|
static validateFmt(fmt) {
|
|
return ['STR', 'BUF', 'DEC', 'HEX', 'ARY', 'PLC'].indexOf(fmt) === -1
|
|
? false
|
|
: true
|
|
}
|
|
|
|
// getter/setters
|
|
get value() {
|
|
return this.cur
|
|
}
|
|
set value(byte) {
|
|
this.prv = this.cur
|
|
this.cur = byte
|
|
}
|
|
|
|
get fmt() {
|
|
return this.format
|
|
}
|
|
|
|
set fmt(f) {
|
|
this.cur = this.toFmt(f)
|
|
this.prv = byteFormat(this.prv, { in: this.format, out: f })
|
|
this.format = f
|
|
}
|
|
|
|
get prev() {
|
|
return this.prv
|
|
}
|
|
|
|
toFmt(fmt) {
|
|
return byteFormat(this.cur, { in: this.format, out: fmt })
|
|
}
|
|
|
|
toFmtPrev(fmt) {
|
|
return byteFormat(this.prv, { in: this.format, out: fmt })
|
|
}
|
|
|
|
reset(byte, fmt) {
|
|
this.cur = byte
|
|
this.prv = byte
|
|
this.format = fmt ? fmt : 'DEC'
|
|
}
|
|
|
|
// used for setting bytes when passed byte format needs to be adapted to current instance's format
|
|
adaptFmt(byte, format) {
|
|
return byteFormat(byte, { in: format, out: this.format })
|
|
}
|
|
|
|
bwOp(byte, op, format = { in: 'DEC', out: 'DEC' }) {
|
|
let bwop
|
|
switch (op) {
|
|
case 'toggle':
|
|
bwop = bits.xor
|
|
break
|
|
case 'off': // NOT mask then AND which is not the same as NAND = AND then NOT result
|
|
bwop = (byte, mask) => {
|
|
return bits.and(byte, bits.not(mask))
|
|
}
|
|
break
|
|
case 'on':
|
|
bwop = bits.or
|
|
break
|
|
case 'check':
|
|
bwop = bits.and
|
|
break
|
|
}
|
|
// console.log({msg:'byte and state as passed', op:op, byte:byteFormat(byte, { in: format.in, out: 'ARY' }), value:this.toFmt('ARY')})
|
|
log.debug({msg:'byte and state as passed', byte:byte, value:this.value, })
|
|
let opbyte = bwop(
|
|
this.toFmt('ARY'),
|
|
byteFormat(byte, { in: format.in, out: 'ARY' })
|
|
)
|
|
// console.log('>>>>',opbyte)
|
|
return byteFormat(opbyte, { in: 'ARY', out: format.out })
|
|
}
|
|
|
|
changes() {
|
|
return bitChanges(this.toFmt('ARY'), this.toFmtPrev('ARY'))
|
|
}
|
|
|
|
stateChanges() {
|
|
return stateChanges(this.toFmt('ARY'), this.toFmtPrev('ARY'))
|
|
}
|
|
} // end Byte Class
|
|
|
|
// Library functions and objects
|
|
|
|
const BYTE_FORMATS = ['STR', 'BUF', 'DEC', 'HEX', 'ARY', 'PLC']
|
|
|
|
//can be used with the BYTE Class or stand alone if you supply by and fmt object
|
|
//can accept single multiple byte input
|
|
// multiple bytes stored in array with most significant byte with index 0
|
|
function byteFormat(bytes, fmt) {
|
|
// TODO support STR and ARY formats that a long string or single flat array rather than array of each
|
|
|
|
if (fmt.in === 'PLC') {
|
|
bytes = plc2bytes(bytes)
|
|
return byteFormat(bytes.length === 1 ? bytes[0] : bytes, {
|
|
in: 'DEC',
|
|
out: fmt.out
|
|
})
|
|
}
|
|
|
|
if (fmt.in === 'BUF') {
|
|
bytes = buf2ary(bytes)
|
|
return byteFormat(bytes.length === 1 ? bytes[0] : bytes, {
|
|
in: 'DEC',
|
|
out: fmt.out
|
|
})
|
|
}
|
|
|
|
// It's a single byte
|
|
if (
|
|
(fmt.in === 'ARY' && !Array.isArray(bytes[0])) ||
|
|
(fmt.in !== 'ARY' && !Array.isArray(bytes))
|
|
) {
|
|
return format(bytes, fmt)
|
|
}
|
|
|
|
// process multiple bytes
|
|
|
|
if (fmt.out === 'PLC') {
|
|
return bytes2plc(byteFormat(bytes, { in: fmt.in, out: 'DEC' }))
|
|
}
|
|
if (fmt.out === 'BUF') {
|
|
if (fmt.in !== 'DEC') {
|
|
bytes = byteFormat(bytes, { in: fmt.in, out: 'DEC' })
|
|
}
|
|
return Buffer.from(bytes)
|
|
}
|
|
// everything else
|
|
return bytes.map(byte => {
|
|
return format(byte, fmt)
|
|
})
|
|
}
|
|
|
|
// curr and prev need to be arrays of bits as arrays ARY
|
|
// returns changes if any otherwise false
|
|
function bitChanges(curr, prev) {
|
|
let changes = bits.xor(curr, prev)
|
|
|
|
// nothing changed
|
|
if (!array.sum(changes)) {
|
|
return false
|
|
}
|
|
// something changed
|
|
return changes
|
|
}
|
|
|
|
// all bit changes
|
|
// changes and curr need to be an array of arrays of 8 bits/byte in ARY format
|
|
// returns array of array pairs
|
|
// where each pair is the bit that changed and it's current state (1=on,0=off)
|
|
function stateChanges(curr, prev) {
|
|
let bitchanges = bitChanges(curr, prev)
|
|
if (!bitchanges) {
|
|
return false
|
|
}
|
|
log.debug({prev:prev, cur:curr, bitchanges:bitchanges, msg:'state changes'})
|
|
|
|
let nowon = curr.map((bit, i) => {
|
|
log.debug({bit:bit, bitchangesi:bitchanges[i], msg:'now on state '})
|
|
return bit & bitchanges[i]
|
|
})
|
|
nowon = byteFormat(nowon, {
|
|
in: 'ARY',
|
|
out: 'PLC'
|
|
})
|
|
|
|
log.debug({nowon:nowon, msg:'now on state '})
|
|
|
|
return byteFormat(bitchanges, {
|
|
in: 'ARY',
|
|
out: 'PLC'
|
|
}).map(swtch => {
|
|
return [swtch, nowon.indexOf(swtch) === -1 ? 'off' : 'on']
|
|
})
|
|
}
|
|
|
|
function bitsReverse(byte) {
|
|
// in DEC out DEC
|
|
return writeByte(readByte(byte).reverse())
|
|
}
|
|
|
|
// exports here
|
|
export default Byte
|
|
export { Byte, byteFormat, BYTE_FORMATS, bitsReverse, bitChanges, stateChanges }
|
|
|
|
|
|
///////// module scoped not exported functions /////
|
|
|
|
/*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*/
|
|
// Takes a SINGLE byte in one format and returns it in another,
|
|
// byteFormat function above allows multiple bytes and uses this
|
|
const format = function(byte, fmt) {
|
|
let ifmt = fmt.in || 'STR' // default STR
|
|
let ofmt = fmt.out || 'DEC' // default DEC
|
|
log.debug('input: ', byte, ' input format: ', ifmt, ' output format:', ofmt)
|
|
|
|
let decimal = 0
|
|
switch (ifmt) {
|
|
case 'STR':
|
|
decimal = writeByte(toBits(byte.padStart(8,'0')))
|
|
break
|
|
case 'BUF':
|
|
decimal = readUInt(byte, 0, 8)
|
|
break
|
|
case 'DEC':
|
|
decimal = +byte // make decimal as string is a number
|
|
break
|
|
case 'HEX':
|
|
decimal = parseInt(byte, 16)
|
|
break
|
|
case 'ARY':
|
|
decimal = writeByte(array.padStart(byte))
|
|
break
|
|
case 'PLC':
|
|
decimal = plc2dec(byte)
|
|
break
|
|
default:
|
|
ofmt = false
|
|
break
|
|
}
|
|
if ( decimal<0 || decimal >255 || isNaN(decimal)) {
|
|
log.warn ({decimal:decimal, byte:byte, fmt:fmt, msg:'error - invalid byte - not able to convert to decmial, converting to 0'})
|
|
decimal = 0
|
|
}
|
|
|
|
let output
|
|
//TODO only BIT and DEC are working
|
|
switch (ofmt) {
|
|
case 'STR':
|
|
output = bits.toString(readByte(decimal))
|
|
break
|
|
case 'BUF':
|
|
output = createBuffer(readByte(decimal))
|
|
break
|
|
case 'DEC':
|
|
output = decimal
|
|
break
|
|
case 'HEX':
|
|
output = decimal.toString(16)
|
|
break
|
|
case 'ARY':
|
|
output = readByte(decimal)
|
|
break
|
|
case 'PLC':
|
|
output = dec2plc(decimal)
|
|
break
|
|
default:
|
|
log.warn({byte:byte, fmt:fmt, msg:'error - unkown output format'})
|
|
output = false
|
|
break
|
|
}
|
|
|
|
return output
|
|
}
|
|
|
|
/*
|
|
*
|
|
*
|
|
*
|
|
*/
|
|
function bytes2plc(bytes) {
|
|
// bytes must be in 'DEC' format first
|
|
|
|
let plc = bytes.map((byte, bytenum) => {
|
|
return format(byte, {
|
|
in: 'DEC',
|
|
out: 'PLC'
|
|
}).map(cmpt => {
|
|
return bytenum * 8 + cmpt
|
|
})
|
|
})
|
|
|
|
plc = plc.reduce(function(a, b) {
|
|
// combine plc arrays
|
|
return a.concat(b)
|
|
}, [])
|
|
plc.sort((a, b) => a - b)
|
|
return plc
|
|
}
|
|
|
|
/*
|
|
*
|
|
*
|
|
*
|
|
*/
|
|
function plc2bytes(cmpts) {
|
|
// cmpts is an array of bit numbers that can be of any value (beyond 8 ok)
|
|
// returns an object of pairs, each pair a byte number (bank) and the value for that byte(bank) in
|
|
// in 'DEC' format.
|
|
|
|
let UCI_ENDIAN = process.env.UCI_ENDIAN || 'little'
|
|
|
|
let byte = 0,
|
|
cmpt = 0,
|
|
maxbyte = 0,
|
|
bytes = {}
|
|
|
|
for (let i in cmpts) {
|
|
cmpt = cmpts[i]
|
|
byte = Math.floor((cmpt - 1) / 8) + 1
|
|
if (byte > maxbyte) {
|
|
maxbyte = byte
|
|
}
|
|
if (bytes[byte] === undefined) {
|
|
bytes[byte] = []
|
|
}
|
|
bytes[byte].push(cmpt % 8 === 0 ? 8 : cmpt % 8)
|
|
}
|
|
let bytearr = []
|
|
// bytes index 0 with be least signficant, need to reverse that
|
|
for (byte in bytes) {
|
|
bytes[byte] = format(bytes[byte], {
|
|
in: 'PLC',
|
|
out: 'DEC'
|
|
})
|
|
if (UCI_ENDIAN==='little') bytearr[byte-1] = bytes[byte]
|
|
else bytearr[maxbyte - byte] = bytes[byte]
|
|
}
|
|
|
|
// pad in "zero" for missing byte elements
|
|
for (let i = 0; i < maxbyte-1; i++) {
|
|
if (!bytearr[i]) {
|
|
bytearr[i] = 0
|
|
}
|
|
}
|
|
// log.debug({places:cmpts, bytes:bytearr, endian:UCI_ENDIAN, msg:'plc to bytes conversion')
|
|
return bytearr
|
|
}
|
|
|
|
// can't just do split map because split doesn't work on single number
|
|
function numStr2Arr(str, del) {
|
|
del = del || ' ' //default delimimter is a space, see string.protoype.split
|
|
// let arr = []
|
|
// TODO change to single line
|
|
if (typeof str === 'number') {
|
|
// just a single number
|
|
// arr[0] = parseInt(str, 10)
|
|
// return arr;
|
|
return parseInt(str, 10)
|
|
} else {
|
|
//it's a string of numbers
|
|
return str.split(del).map(Number)
|
|
}
|
|
}
|
|
|
|
function bits2dec(bits) {
|
|
let decimal = 0
|
|
let place = bits.length
|
|
bits.split('').forEach(function(bit) {
|
|
decimal += Math.pow(2, bit * (place - 1))
|
|
--place
|
|
})
|
|
return decimal
|
|
}
|
|
|
|
function plc2dec(nums, string) {
|
|
let decimal = 0
|
|
// log.debug('nums', nums)
|
|
|
|
if (string) {
|
|
nums = numStr2Arr(nums)
|
|
}
|
|
nums.forEach(function(num) {
|
|
decimal += Math.pow(2, num - 1)
|
|
// log.debug('place', num, 'dec ', decimal);
|
|
})
|
|
return decimal
|
|
}
|
|
|
|
function dec2plc(decimal, toStr) {
|
|
//outputs an array unless str is true
|
|
let place = 8
|
|
let plc = []
|
|
readByte(decimal).forEach(function(bit) {
|
|
if (bit) {
|
|
plc.push(place)
|
|
}
|
|
--place
|
|
})
|
|
if (toStr) {
|
|
return plc.join(' ')
|
|
} else {
|
|
return plc
|
|
}
|
|
}
|
|
|
|
function buf2ary(buf) {
|
|
return Array.from(new Uint8Array(buf))
|
|
}
|