зеркало из https://github.com/microsoft/jacdac-ts.git
working on console
This commit is contained in:
Родитель
7db6446465
Коммит
39f1cdff4c
|
@ -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
|
||||
|
||||
|
|
|
@ -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>
|
|
@ -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'
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
40
src/utils.ts
40
src/utils.ts
|
@ -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": [
|
||||
|
|
Загрузка…
Ссылка в новой задаче