This commit is contained in:
peli 2020-06-23 22:35:37 -07:00
Родитель 7db6446465
Коммит 39f1cdff4c
13 изменённых файлов: 527 добавлений и 262 удалений

Просмотреть файл

@ -22,3 +22,5 @@ A list of small tools to operate on JACDAC devices, built with this library.
* [packets](./tools/packets), sniff all packets on the bus
* [devices](./tools/devices), list of connected devices
* [console](./tools/console), turns on verbose console and display messages

75
docs/tools/console.html Normal file
Просмотреть файл

@ -0,0 +1,75 @@
<html>
<head>
<style>
body {
font-family: monospace;
}
.segment {
padding: 0.5rem;
margin: 0.5rem;
}
#log {
margin-top: 1rem;
}
#log>div {
margin: 0.25rem;
}
</style>
</head>
<body>
<h1>JACDAC/Logger</h1>
<div>
<button id="connect">connect to jacdac device</button>
</div>
<div id="devices" class="segment"></div>
<div id="log" class="segment">
<div>waiting for message...</div>
</div>
<script src="/dist/jacdac.umd.js"></script>
<script>
const logDiv = document.getElementById("log");
const devicesDiv = document.getElementById("devices");
function log(msg) {
const line = document.createElement("div");
line.innerText = "" + msg;
logDiv.insertBefore(line, logDiv.firstElementChild);
if (logDiv.childElementCount > 100)
logDiv.lastElementChild.remove();
}
async function sniff() {
log('starting')
let bus;
try {
bus = await jacdac.requestUSBBus();
bus.minConsolePriority = jacdac.ConsolePriority.Debug;
bus.on('deviceconnect', d => {
devicesDiv.innerText = bus.devices().map(d => d.toString()).join(', ')
});
bus.on('packet', pkt => {
const decoded = jacdac.decodePacketData(pkt);
console.debug(jacdac.printPacket(pkt))
if (decoded)
log(decoded)
})
log('started')
} catch (err) {
log(err)
await bus.disconnect()
}
}
const connect = document.getElementById("connect");
connect.onclick = sniff;
</script>
</body>
</html>

132
src/buffer.ts Normal file
Просмотреть файл

@ -0,0 +1,132 @@
import { read16, read32 } from "./utils";
export const enum NumberFormat {
Int8LE = 1,
UInt8LE = 2,
Int16LE = 3,
UInt16LE = 4,
Int32LE = 5,
Int8BE = 6,
UInt8BE = 7,
Int16BE = 8,
UInt16BE = 9,
Int32BE = 10,
UInt32LE = 11,
UInt32BE = 12,
Float32LE = 13,
Float64LE = 14,
Float32BE = 15,
Float64BE = 16,
}
function fmtInfoCore(fmt: NumberFormat) {
switch (fmt) {
case NumberFormat.Int8LE: return -1;
case NumberFormat.UInt8LE: return 1;
case NumberFormat.Int16LE: return -2;
case NumberFormat.UInt16LE: return 2;
case NumberFormat.Int32LE: return -4;
case NumberFormat.UInt32LE: return 4;
case NumberFormat.Int8BE: return -10;
case NumberFormat.UInt8BE: return 10;
case NumberFormat.Int16BE: return -20;
case NumberFormat.UInt16BE: return 20;
case NumberFormat.Int32BE: return -40;
case NumberFormat.UInt32BE: return 40;
case NumberFormat.Float32LE: return 4;
case NumberFormat.Float32BE: return 40;
case NumberFormat.Float64LE: return 8;
case NumberFormat.Float64BE: return 80;
default: throw new Error("unknown format");
}
}
function fmtInfo(fmt: NumberFormat) {
let size = fmtInfoCore(fmt)
let signed = false
if (size < 0) {
signed = true
size = -size
}
let swap = false
if (size >= 10) {
swap = true
size /= 10
}
let isFloat = fmt >= NumberFormat.Float32LE
return { size, signed, swap, isFloat }
}
/**
* Get the size in bytes of specified number format.
*/
export function sizeOfNumberFormat(format: NumberFormat) {
switch (format) {
case NumberFormat.Int8LE:
case NumberFormat.UInt8LE:
case NumberFormat.Int8BE:
case NumberFormat.UInt8BE:
return 1;
case NumberFormat.Int16LE:
case NumberFormat.UInt16LE:
case NumberFormat.Int16BE:
case NumberFormat.UInt16BE:
return 2;
case NumberFormat.Int32LE:
case NumberFormat.Int32BE:
case NumberFormat.UInt32BE:
case NumberFormat.UInt32LE:
case NumberFormat.Float32BE:
case NumberFormat.Float32LE:
return 4;
case NumberFormat.Float64BE:
case NumberFormat.Float64LE:
return 8;
}
return 0;
}
export function getNumber(buf: ArrayLike<number>, fmt: NumberFormat, offset: number) {
switch (fmt) {
case NumberFormat.UInt8BE:
case NumberFormat.UInt8LE:
return buf[offset]
case NumberFormat.Int8BE:
case NumberFormat.Int8LE:
return (buf[offset] << 24) >> 24
case NumberFormat.UInt16LE:
return read16(buf, offset)
case NumberFormat.Int16LE:
return (read16(buf, offset) << 16) >> 16
case NumberFormat.UInt32LE:
return read32(buf, offset)
case NumberFormat.Int32LE:
return read32(buf, offset) >> 0
default:
throw new Error("unsupported fmt:" + fmt)
}
}
export function setNumber(buf: Uint8Array, fmt: NumberFormat, offset: number, r: number) {
let inf = fmtInfo(fmt)
if (inf.isFloat) {
let arr = new Uint8Array(inf.size)
if (inf.size == 4)
new Float32Array(arr.buffer)[0] = r
else
new Float64Array(arr.buffer)[0] = r
if (inf.swap)
arr.reverse()
for (let i = 0; i < inf.size; ++i) {
buf[offset + i] = arr[i]
}
return
}
for (let i = 0; i < inf.size; ++i) {
let off = !inf.swap ? offset + i : offset + inf.size - i - 1
buf[off] = (r & 0xff)
r >>= 8
}
}

Просмотреть файл

@ -1,175 +0,0 @@
import { Device, Bus } from "./device";
import { Packet } from "./packet";
import { NumberFormat, getNumber } from "./utils";
import { CMD_SET_REG, CMD_ADVERTISEMENT_DATA, CMD_EVENT } from "./constants";
import { EventEmitter } from "./eventemitter";
class ClientPacketQueue {
private pkts: Uint8Array[] = []
constructor(public parent: Client) { }
private updateQueue(pkt: Packet) {
const cmd = pkt.service_command
for (let i = 0; i < this.pkts.length; ++i) {
if (getNumber(this.pkts[i], NumberFormat.UInt16LE, 2) == cmd) {
this.pkts[i] = pkt.withFrameStripped()
return
}
}
this.pkts.push(pkt.withFrameStripped())
}
clear() {
this.pkts = []
}
send(pkt: Packet) {
if (pkt.is_reg_set || this.parent.serviceNumber == null)
this.updateQueue(pkt)
this.parent.sendCommand(pkt)
}
resend() {
const sn = this.parent.serviceNumber
if (sn == null || this.pkts.length == 0)
return
let hasNonSet = false
for (let p of this.pkts) {
p[1] = sn
if ((p[3] >> 4) != (CMD_SET_REG >> 12))
hasNonSet = true
}
const pkt = Packet.onlyHeader(0)
pkt.compress(this.pkts)
this.parent.sendCommand(pkt)
// after re-sending only leave set_reg packets
if (hasNonSet)
this.pkts = this.pkts.filter(p => (p[3] >> 4) == (CMD_SET_REG >> 12))
}
}
export class Client extends EventEmitter {
device: Device
eventId: number
broadcast: boolean // when true, this.device is never set
serviceNumber: number;
protected supressLog: boolean;
started: boolean;
advertisementData: Uint8Array;
private config: ClientPacketQueue
constructor(
public bus: Bus,
public name: string,
public serviceClass: number,
public requiredDeviceName: string
) {
super();
this.config = new ClientPacketQueue(this)
}
broadcastDevices() {
return this.bus.devices().filter(d => d.hasService(this.serviceClass))
}
isConnected() {
return !!this.device
}
requestAdvertisementData() {
this.sendCommand(Packet.onlyHeader(CMD_ADVERTISEMENT_DATA))
}
handlePacketOuter(pkt: Packet) {
if (pkt.service_command == CMD_ADVERTISEMENT_DATA)
this.advertisementData = pkt.data
if (pkt.service_command == CMD_EVENT)
control.raiseEvent(this.eventId, pkt.intData)
this.handlePacket(pkt)
}
handlePacket(pkt: Packet) { }
_attach(dev: Device, serviceNum: number) {
if (this.device) throw new Error("Invalid attach")
if (!this.broadcast) {
if (this.requiredDeviceName && this.requiredDeviceName != dev.name && this.requiredDeviceName != dev.deviceId)
return false // don't attach
this.device = dev
this.serviceNumber = serviceNum
_unattachedClients.removeElement(this)
}
console.log(`attached ${dev.toString()}/${serviceNum} to client ${this.name}`)
dev.clients.push(this)
this.onAttach()
this.config.resend()
return true
}
_detach() {
this.log(`dettached ${this.name}`)
this.serviceNumber = null
if (!this.broadcast) {
if (!this.device) throw new Error("Invalid detach")
this.device = null
_unattachedClients.push(this)
clearAttachCache()
}
this.onDetach()
}
protected onAttach() { }
protected onDetach() { }
async sendCommand(pkt: Packet) {
this.start()
if (this.serviceNumber == null)
return
pkt.service_number = this.serviceNumber
await pkt.sendCmdAsync(this.device)
}
async sendCommandWithAck(pkt: Packet) {
this.start()
if (this.serviceNumber == null)
return
pkt.service_number = this.serviceNumber
if (!pkt._sendWithAck(this.device))
throw new Error("No ACK")
}
setRegBuffer(reg: number, value: Uint8Array) {
this.start()
this.config.send(Packet.from(CMD_SET_REG | reg, value))
}
/*
protected registerEvent(value: number, handler: () => void) {
this.start()
control.onEvent(this.eventId, value, handler);
}
*/
start() {
if (this.started) return
this.started = true
jacdac.start()
_unattachedClients.push(this)
_allClients.push(this)
clearAttachCache()
}
destroy() {
if (this.device)
this.device.clients.removeElement(this)
_unattachedClients.removeElement(this)
_allClients.removeElement(this)
this.serviceNumber = null
this.device = null
clearAttachCache()
}
}

Просмотреть файл

@ -50,6 +50,10 @@ export const CMD_CTRL_IDENTIFY = 0x81
// reset device
export const CMD_CTRL_RESET = 0x82
export const CMD_CONSOLE_REG = 0x80
export const CMD_CONSOLE_MESSAGE_DBG = 0x80
export const CMD_CONSOLE_SET_MIN_PRIORITY = 0x2000 | CMD_CONSOLE_REG
export const STREAM_PORT_SHIFT = 7
export const STREAM_COUNTER_MASK = 0x001f
export const STREAM_CLOSE_MASK = 0x0020
@ -72,10 +76,48 @@ export const JD_FRAME_FLAG_ACK_REQUESTED = 0x02
// the device_identifier contains target service class number
export const JD_FRAME_FLAG_IDENTIFIER_IS_SERVICE_CLASS = 0x04
export const enum ConsolePriority {
export enum ConsolePriority {
Debug = 0,
Log = 1,
Warning = 2,
Error = 3,
Silent = 4
}
}
/** known service numbers */
export const JD_SERVICE_CTRL = 0;
export const JD_SERVICE_LOGGER = 0x12dc1fca;
export const JD_SERVICE_BATTERY = 0x1d2a2acd;
export const JD_SERVICE_ACCELEROMETER = 0x1f140409;
export const JD_SERVICE_BUTTON = 0x1473a263;
export const JD_SERVICE_TOUCHBUTTON = 0x130cf5be;
export const JD_SERVICE_LIGHT_SENSOR = 0x15e7a0ff;
export const JD_SERVICE_MICROPHONE = 0x1a5c5866;
export const JD_SERVICE_THERMOMETER = 0x1421bac7;
export const JD_SERVICE_SWITCH = 0x14218172;
export const JD_SERVICE_PIXEL = 0x1768fbbf;
export const JD_SERVICE_HAPTIC = 0x116b14a3;
export const JD_SERVICE_LIGHT = 0x126f00e0;
export const JD_SERVICE_KEYBOARD = 0x1ae4812d;
export const JD_SERVICE_MOUSE = 0x14bc97bf;
export const JD_SERVICE_GAMEPAD = 0x100527e8;
export const JD_SERVICE_MUSIC = 0x1b57b1d7;
export const JD_SERVICE_SERVO = 0x12fc9103;
export const JD_SERVICE_CONTROLLER = 0x188ae4b8;
export const JD_SERVICE_LCD = 0x18d5284c;
export const JD_SERVICE_MESSAGE_BUS = 0x115cabf5;
export const JD_SERVICE_COLOR_SENSOR = 0x14d6dda2;
export const JD_SERVICE_LIGHT_SPECTRUM_SENSOR = 0x16fa0c0d;
export const JD_SERVICE_PROXIMITY = 0x14c1791b;
export const JD_SERVICE_TOUCH_BUTTONS = 0x1acb49d5;
export const JD_SERVICE_SERVOS = 0x182988d8;
export const JD_SERVICE_ROTARY_ENCODER = 0x10fa29c9;
export const JD_SERVICE_DNS = 0x117729bd;
export const JD_SERVICE_PWM_LIGHT = 0x1fb57453;
export const JD_SERVICE_BOOTLOADER = 0x1ffa9948;
export const JD_SERVICE_ARCADE_CONTROLS = 0x1deaa06e;
export const JD_SERVICE_POWER = 0x1fa4c95a;
export const JD_SERVICE_SLIDER = 0x1f274746;
export const JD_SERVICE_MOTOR = 0x17004cd8;
export const JD_SERVICE_TCP = 0x1b43b70b;
export const JD_SERVICE_WIFI = 0x18aae1fa;

Просмотреть файл

@ -1,7 +1,8 @@
import { Packet } from "./packet"
import { JD_SERVICE_NUMBER_CTRL, CMD_ADVERTISEMENT_DATA, ConsolePriority } from "./constants"
import { hash, fromHex, idiv, getNumber, NumberFormat, read32, SMap, bufferEq } from "./utils"
import { JD_SERVICE_NUMBER_CTRL, CMD_ADVERTISEMENT_DATA, ConsolePriority, CMD_CONSOLE_SET_MIN_PRIORITY, JD_SERVICE_LOGGER, CMD_EVENT } from "./constants"
import { hash, fromHex, idiv, read32, SMap, bufferEq } from "./utils"
import { EventEmitter } from "./eventemitter"
import { getNumber, NumberFormat } from "./buffer";
export interface BusOptions {
sendPacket: (p: Packet) => Promise<void>;
@ -23,6 +24,13 @@ export interface PacketEventEmitter {
*/
on(event: 'packet', listener: (packet: Packet) => void): boolean;
/**
* Event containing an event from a sensor
* @param event
* @param listener
*/
on(event: 'packetevent', listener: (packet: Packet) => void): boolean;
/**
* Event emitted when a device is detected on the bus. The information on the device might not be fully populated yet.
* @param event
@ -53,7 +61,7 @@ export class Bus extends EventEmitter implements PacketEventEmitter {
private _deviceNames: SMap<string> = {};
private _startTime: number;
private _gcInterval: any;
consolePriority = ConsolePriority.Log;
private _minConsolePriority = ConsolePriority.Log;
/**
* Creates the bus with the given transport
@ -62,12 +70,32 @@ export class Bus extends EventEmitter implements PacketEventEmitter {
constructor(public options: BusOptions) {
super();
this._startTime = Date.now();
this.on('announce', () => this.pingLoggers());
}
get timestamp() {
return Date.now() - this._startTime;
}
get minConsolePriority(): ConsolePriority {
return this._minConsolePriority;
}
set minConsolePriority(priority: ConsolePriority) {
if (priority !== this._minConsolePriority) {
this._minConsolePriority = priority;
}
}
private pingLoggers() {
if (this._minConsolePriority < ConsolePriority.Silent) {
this.log(`ping loggers`)
const pkt = Packet.packed(CMD_CONSOLE_SET_MIN_PRIORITY, "i", [this._minConsolePriority]);
pkt.sendAsMultiCommandAsync(this, JD_SERVICE_LOGGER);
}
}
sendPacket(p: Packet) {
return this.options.sendPacket(p);
}
@ -139,7 +167,10 @@ export class Bus extends EventEmitter implements PacketEventEmitter {
}
}
}
this.emit('packet', pkt)
// don't spam with duplicate advertisement events
if (pkt.service_command !== CMD_ADVERTISEMENT_DATA) {
this.emit('packet', pkt)
}
}
/**
@ -150,14 +181,13 @@ export class Bus extends EventEmitter implements PacketEventEmitter {
return this._deviceNames[id];
}
log(msg: any, ...optionalArgs: any[]) {
if (this.consolePriority > ConsolePriority.Log)
log(msg: any) {
if (this._minConsolePriority > ConsolePriority.Log)
return
console.log(msg, optionalArgs);
console.log(msg);
}
}
export class Device {
connected: boolean;
services: Uint8Array

Просмотреть файл

@ -1,6 +1,9 @@
export * from './constants'
export * from './eventemitter'
export * from './utils'
export * from './buffer'
export * from './msgpack'
export * from './struct'
export * from './eventemitter'
export * from './packet'
export * from './device'
export * from './hf2'

94
src/msgpack.ts Normal file
Просмотреть файл

@ -0,0 +1,94 @@
import { NumberFormat, getNumber, sizeOfNumberFormat, setNumber } from "./buffer"
// see http://msgpack.org/ for the spec
// it currently only implements numbers and their sequances
// once we handle any type and typeof expressions we can do more
function tagFormat(tag: number) {
switch (tag) {
case 0xCB: return NumberFormat.Float64BE
case 0xCC: return NumberFormat.UInt8BE
case 0xCD: return NumberFormat.UInt16BE
case 0xCE: return NumberFormat.UInt32BE
case 0xD0: return NumberFormat.Int8BE
case 0xD1: return NumberFormat.Int16BE
case 0xD2: return NumberFormat.Int32BE
default:
return null
}
}
function packNumberCore(buf: Uint8Array, offset: number, num: number) {
let tag = 0xCB
if (num == (num << 0) || num == (num >>> 0)) {
if (-31 <= num && num <= 127) {
if (buf) buf[offset] = num
return 1
} else if (0 <= num) {
if (num <= 0xff) {
tag = 0xCC
} else if (num <= 0xffff) {
tag = 0xCD
} else {
tag = 0xCE
}
} else {
if (-0x7f <= num) {
tag = 0xD0
} else if (-0x7fff <= num) {
tag = 0xD1
} else {
tag = 0xD2
}
}
}
let fmt = tagFormat(tag)
if (buf) {
buf[offset] = tag
setNumber(buf, fmt, offset + 1, num)
}
return sizeOfNumberFormat(fmt) + 1
}
/**
* Unpacks a buffer into a number array.
*/
export function unpackNumberArray(buf: Uint8Array, offset = 0): number[] {
let res: number[] = []
while (offset < buf.length) {
let fmt = tagFormat(buf[offset++])
if (fmt === null) {
let v = getNumber(buf, NumberFormat.Int8BE, offset - 1)
if (-31 <= v && v <= 127)
res.push(v)
else
return null
} else {
res.push(getNumber(buf, fmt, offset))
offset += sizeOfNumberFormat(fmt)
}
// padding at the end
while (buf[offset] === 0xc1) offset++;
}
return res
}
/**
* Pack a number array into a buffer.
* @param nums the numbers to be packed
*/
export function packNumberArray(nums: number[]): Uint8Array {
let off = 0
for (let n of nums) {
off += packNumberCore(null, off, n)
}
let buf = new Uint8Array(off)
off = 0
for (let n of nums) {
off += packNumberCore(buf, off, n)
}
return buf
}

Просмотреть файл

@ -1,4 +1,4 @@
import { warn, crc, ALIGN, write16, bufferConcat, toHex, fromHex, error, read32, read16, NumberFormat, getNumber, write32 } from "./utils";
import { warn, crc, ALIGN, write16, bufferConcat, toHex, fromHex, error, read32, read16, write32 } from "./utils";
import {
JD_FRAME_FLAG_COMMAND,
JD_FRAME_FLAG_IDENTIFIER_IS_SERVICE_CLASS,
@ -10,6 +10,8 @@ import {
JD_SERIAL_MAX_PAYLOAD_SIZE,
} from "./constants";
import { Device, Bus } from "./device";
import { NumberFormat, getNumber } from "./buffer";
import { pack } from "./struct";
export class Packet {
_header: Uint8Array;
@ -217,6 +219,10 @@ export class Packet {
static fromFrame(frame: Uint8Array, timestamp: number) {
return frameToPackets(frame, timestamp)
}
static packed(service_command: number, fmt: string, nums: number[]) {
return Packet.from(service_command, pack(fmt, nums))
}
}

Просмотреть файл

@ -6,41 +6,41 @@ import { Device } from "./device"
const service_classes: U.SMap<number> = {
"<disabled>": -1,
CTRL: 0,
LOGGER: 0x12dc1fca,
BATTERY: 0x1d2a2acd,
ACCELEROMETER: 0x1f140409,
BUTTON: 0x1473a263,
TOUCHBUTTON: 0x130cf5be,
LIGHT_SENSOR: 0x15e7a0ff,
MICROPHONE: 0x1a5c5866,
THERMOMETER: 0x1421bac7,
SWITCH: 0x14218172,
PIXEL: 0x1768fbbf,
HAPTIC: 0x116b14a3,
LIGHT: 0x126f00e0,
KEYBOARD: 0x1ae4812d,
MOUSE: 0x14bc97bf,
GAMEPAD: 0x100527e8,
MUSIC: 0x1b57b1d7,
SERVO: 0x12fc9103,
CONTROLLER: 0x188ae4b8,
LCD: 0x18d5284c,
MESSAGE_BUS: 0x115cabf5,
COLOR_SENSOR: 0x14d6dda2,
LIGHT_SPECTRUM_SENSOR: 0x16fa0c0d,
PROXIMITY: 0x14c1791b,
TOUCH_BUTTONS: 0x1acb49d5,
SERVOS: 0x182988d8,
ROTARY_ENCODER: 0x10fa29c9,
DNS: 0x117729bd,
PWM_LIGHT: 0x1fb57453,
BOOTLOADER: 0x1ffa9948,
ARCADE_CONTROLS: 0x1deaa06e,
POWER: 0x1fa4c95a,
SLIDER: 0x1f274746,
MOTOR: 0x17004cd8,
TCP: 0x1b43b70b,
WIFI: 0x18aae1fa,
LOGGER: jd.JD_SERVICE_LOGGER,
BATTERY: jd.JD_SERVICE_BATTERY,
ACCELEROMETER: jd.JD_SERVICE_ACCELEROMETER,
BUTTON: jd.JD_SERVICE_BUTTON,
TOUCHBUTTON: jd.JD_SERVICE_TOUCHBUTTON,
LIGHT_SENSOR: jd.JD_SERVICE_LIGHT_SENSOR,
MICROPHONE: jd.JD_SERVICE_MICROPHONE,
THERMOMETER: jd.JD_SERVICE_THERMOMETER,
SWITCH: jd.JD_SERVICE_SWITCH,
PIXEL: jd.JD_SERVICE_PIXEL,
HAPTIC: jd.JD_SERVICE_HAPTIC,
LIGHT: jd.JD_SERVICE_LIGHT,
KEYBOARD: jd.JD_SERVICE_KEYBOARD,
MOUSE: jd.JD_SERVICE_MOUSE,
GAMEPAD: jd.JD_SERVICE_GAMEPAD,
MUSIC: jd.JD_SERVICE_MUSIC,
SERVO: jd.JD_SERVICE_SERVO,
CONTROLLER: jd.JD_SERVICE_CONTROLLER,
LCD: jd.JD_SERVICE_LCD,
MESSAGE_BUS: jd.JD_SERVICE_MESSAGE_BUS,
COLOR_SENSOR: jd.JD_SERVICE_COLOR_SENSOR,
LIGHT_SPECTRUM_SENSOR: jd.JD_SERVICE_LIGHT_SPECTRUM_SENSOR,
PROXIMITY: jd.JD_SERVICE_PROXIMITY,
TOUCH_BUTTONS: jd.JD_SERVICE_TOUCH_BUTTONS,
SERVOS: jd.JD_SERVICE_SERVOS,
ROTARY_ENCODER: jd.JD_SERVICE_ROTARY_ENCODER,
DNS: jd.JD_SERVICE_DNS,
PWM_LIGHT: jd.JD_SERVICE_PWM_LIGHT,
BOOTLOADER: jd.JD_SERVICE_BOOTLOADER,
ARCADE_CONTROLS: jd.JD_SERVICE_ARCADE_CONTROLS,
POWER: jd.JD_SERVICE_POWER,
SLIDER: jd.JD_SERVICE_SLIDER,
MOTOR: jd.JD_SERVICE_MOTOR,
TCP: jd.JD_SERVICE_TCP,
WIFI: jd.JD_SERVICE_WIFI,
}
const generic_commands: U.SMap<number> = {
@ -84,6 +84,13 @@ const serv_decoders: U.SMap<(p: Packet) => string> = {
}
}
export function decodePacketData(pkt: Packet): string {
const serv_id = serviceName(pkt?.dev?.serviceClassAt(pkt.service_number));
const decoder = serv_decoders[serv_id];
const decoded = decoder ? decoder(pkt) : null
return decoded;
}
function reverseLookup(map: U.SMap<number>, n: number) {
for (let k of Object.keys(map)) {
if (map[k] == n)

88
src/struct.ts Normal file
Просмотреть файл

@ -0,0 +1,88 @@
// Python-like packing, see https://docs.python.org/3/library/struct.html
import { NumberFormat, setNumber, getNumber, sizeOfNumberFormat } from "./buffer"
export function packedSize(format: string) {
return packUnpackCore(format, null, null, true)
}
export function pack(format: string, nums: number[]) {
let buf = new Uint8Array(packedSize(format))
packUnpackCore(format, nums, buf, true)
return buf
}
function getFormat(pychar: string, isBig: boolean) {
switch (pychar) {
case 'B':
return NumberFormat.UInt8LE
case 'b':
return NumberFormat.Int8LE
case 'H':
return isBig ? NumberFormat.UInt16BE : NumberFormat.UInt16LE
case 'h':
return isBig ? NumberFormat.Int16BE : NumberFormat.Int16LE
case 'I':
case 'L':
return isBig ? NumberFormat.UInt32BE : NumberFormat.UInt32LE
case 'i':
case 'l':
return isBig ? NumberFormat.Int32BE : NumberFormat.Int32LE
case 'f':
return isBig ? NumberFormat.Float32BE : NumberFormat.Float32LE
case 'd':
return isBig ? NumberFormat.Float64BE : NumberFormat.Float64LE
default:
return null as NumberFormat
}
}
function isDigit(ch: string) {
const code = ch.charCodeAt(0)
return 0x30 <= code && code <= 0x39
}
function packUnpackCore(format: string, nums: number[], buf: Uint8Array, isPack: boolean, off = 0) {
let isBig = false
let idx = 0
for (let i = 0; i < format.length; ++i) {
switch (format[i]) {
case ' ':
case '<':
case '=':
isBig = false
break
case '>':
case '!':
isBig = true
break
case 'x':
off++
break
default:
const i0 = i
while (isDigit(format[i])) i++
let reps = 1
if (i0 != i)
reps = parseInt(format.slice(i0, i))
while (reps--) {
let fmt = getFormat(format[i], isBig)
if (fmt === null) {
throw new Error("Unsupported format character: " + format[i])
} else {
if (buf) {
if (isPack)
setNumber(buf, fmt, off, nums[idx++])
else
nums.push(getNumber(buf, fmt, off))
}
off += sizeOfNumberFormat(fmt)
}
}
break
}
}
return off
}

Просмотреть файл

@ -276,46 +276,6 @@ export function decodeU32LE(buf: Uint8Array) {
return res
}
export const enum NumberFormat {
Int8LE = 1,
UInt8LE = 2,
Int16LE = 3,
UInt16LE = 4,
Int32LE = 5,
Int8BE = 6,
UInt8BE = 7,
Int16BE = 8,
UInt16BE = 9,
Int32BE = 10,
UInt32LE = 11,
UInt32BE = 12,
Float32LE = 13,
Float64LE = 14,
Float32BE = 15,
Float64BE = 16,
}
export function getNumber(buf: ArrayLike<number>, fmt: NumberFormat, offset: number) {
switch (fmt) {
case NumberFormat.UInt8BE:
case NumberFormat.UInt8LE:
return buf[offset]
case NumberFormat.Int8BE:
case NumberFormat.Int8LE:
return (buf[offset] << 24) >> 24
case NumberFormat.UInt16LE:
return read16(buf, offset)
case NumberFormat.Int16LE:
return (read16(buf, offset) << 16) >> 16
case NumberFormat.UInt32LE:
return read32(buf, offset)
case NumberFormat.Int32LE:
return read32(buf, offset) >> 0
default:
throw new Error("unsupported fmt:" + fmt)
}
}
export function bufferToString(buf: Uint8Array) {
return fromUTF8(uint8ArrayToString(buf))
}

Просмотреть файл

@ -10,6 +10,7 @@
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"preserveConstEnums": true,
"declarationDir": "dist/types",
"outDir": "dist/lib",
"typeRoots": [