mirror of
https://github.com/jwetzell/free-d-js.git
synced 2026-07-26 10:28:44 +00:00
initial commit
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
module.exports = {
|
||||
env: {
|
||||
node: true,
|
||||
commonjs: true,
|
||||
es2021: true,
|
||||
},
|
||||
extends: ['airbnb', 'prettier'],
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
},
|
||||
ignorePatterns: ['**/node_modules', '**/dist'],
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
# These are supported funding model platforms
|
||||
github: [jwetzell]
|
||||
@@ -0,0 +1,6 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: 'npm'
|
||||
directory: '/'
|
||||
schedule:
|
||||
interval: 'weekly'
|
||||
@@ -0,0 +1,25 @@
|
||||
name: Check @jwetzell/free-d builds
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'packages/free-d/**'
|
||||
jobs:
|
||||
build-webui:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: 'package-lock.json'
|
||||
|
||||
- name: Install Node.js dependencies
|
||||
run: npm ci --workspace @jwetzell/free-d
|
||||
|
||||
- run: npm run build --workspace @jwetzell/free-d
|
||||
@@ -0,0 +1,26 @@
|
||||
name: Publish @jwetzell/free-d to npmjs
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'free-d/*'
|
||||
jobs:
|
||||
publish-lib:
|
||||
permissions:
|
||||
id-token: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
- uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: 'package-lock.json'
|
||||
|
||||
- name: Install Node.js dependencies
|
||||
run: npm ci --workspace @jwetzell/free-d
|
||||
|
||||
- name: Publish to NPM
|
||||
run: npm publish --provenance --access=public --workspace @jwetzell/free-d
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
@@ -0,0 +1 @@
|
||||
node_modules
|
||||
@@ -0,0 +1,3 @@
|
||||
*.md
|
||||
*.min.*
|
||||
dist
|
||||
@@ -0,0 +1,8 @@
|
||||
module.exports = {
|
||||
trailingComma: 'es5',
|
||||
tabWidth: 2,
|
||||
semi: true,
|
||||
singleQuote: true,
|
||||
bracketSameLine: true,
|
||||
printWidth: 120,
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Joel Wetzell
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,4 @@
|
||||
# free-d-js
|
||||
Collection of free-d things written in JS/TS
|
||||
|
||||
- [free-d encoding/decoding library](./packages/free-d/)
|
||||
Generated
+3894
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "free-d",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"author": {
|
||||
"name": "Joel Wetzell",
|
||||
"email": "me@jwetzell.com",
|
||||
"url": "https://jwetzell.com"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/jwetzell/free-d-js.git"
|
||||
},
|
||||
"scripts": {
|
||||
"lint:check": "eslint ./",
|
||||
"format:check": "prettier ./ --check",
|
||||
"format:write": "prettier ./ --write"
|
||||
},
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"devDependencies": {
|
||||
"prettier": "3.3.3",
|
||||
"eslint": "^8.48.0",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"eslint-config-prettier": "^9.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
dist
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "@jwetzell/free-d",
|
||||
"version": "0.1.0",
|
||||
"description": "libray for encoding/decoding free-d messages",
|
||||
"main": "./dist/cjs/index.js",
|
||||
"module": "./dist/esm/index.js",
|
||||
"types": "./dist/types/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/esm/index.js",
|
||||
"require": "./dist/cjs/index.js",
|
||||
"types": "./dist/types/index.d.ts"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"prebuild": "rimraf dist",
|
||||
"build": "npm run build:cjs && npm run build:esm && npm run build:types",
|
||||
"build:cjs": "tsc -p tsconfig.cjs.json",
|
||||
"build:esm": "tsc -p tsconfig.esm.json",
|
||||
"build:types": "tsc -p tsconfig.types.json",
|
||||
"prepublishOnly": "npm run build",
|
||||
"pretest": "npm run build",
|
||||
"test": "node --test --experimental-test-coverage"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"author": {
|
||||
"name": "Joel Wetzell",
|
||||
"email": "me@jwetzell.com",
|
||||
"url": "https://jwetzell.com"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/jwetzell/free-d-js.git"
|
||||
},
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "22.8.2",
|
||||
"rimraf": "6.0.1",
|
||||
"typescript": "5.6.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import { FreeDPosition, FreeDMessageType } from './types';
|
||||
import { checksum, freeDUnitsToPosition, freeDUnitsToRotation } from './utils';
|
||||
|
||||
function bytesToPositionMessage(bytes: Uint8Array): FreeDPosition {
|
||||
const id = bytes[1];
|
||||
const pan = freeDUnitsToRotation(bytes[2], bytes[3], bytes[4]);
|
||||
const tilt = freeDUnitsToRotation(bytes[5], bytes[6], bytes[7]);
|
||||
const roll = freeDUnitsToRotation(bytes[8], bytes[9], bytes[10]);
|
||||
|
||||
const posX = freeDUnitsToPosition(bytes[11], bytes[12], bytes[13]);
|
||||
const posY = freeDUnitsToPosition(bytes[14], bytes[15], bytes[16]);
|
||||
const posZ = freeDUnitsToPosition(bytes[17], bytes[18], bytes[19]);
|
||||
|
||||
const zoom = bytes[20] * 65536 + bytes[21] * 256 + bytes[22];
|
||||
const focus = bytes[23] * 65536 + bytes[24] * 256 + bytes[25];
|
||||
|
||||
const spare = bytes[26] * 256 + bytes[27];
|
||||
|
||||
return {
|
||||
type: FreeDMessageType.Position,
|
||||
id,
|
||||
pan,
|
||||
tilt,
|
||||
roll,
|
||||
posX,
|
||||
posY,
|
||||
posZ,
|
||||
zoom,
|
||||
focus,
|
||||
spare,
|
||||
};
|
||||
}
|
||||
|
||||
export function decode(bytes: Uint8Array): FreeDPosition | undefined {
|
||||
if (bytes.length === 0) {
|
||||
throw new Error('cannot decode 0 bytes');
|
||||
}
|
||||
|
||||
const messageType = bytes[0];
|
||||
|
||||
switch (messageType) {
|
||||
case FreeDMessageType.Position:
|
||||
if (bytes.length !== 29) {
|
||||
throw new Error('freeD position message should be 29 bytes long');
|
||||
}
|
||||
|
||||
if (checksum(bytes.subarray(0, bytes.length - 1)) !== bytes[bytes.length - 1]) {
|
||||
throw new Error('freeD message failed checksum');
|
||||
}
|
||||
|
||||
return bytesToPositionMessage(bytes);
|
||||
default:
|
||||
throw new Error('unsupported freeD message type');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { FreeDPosition, FreeDMessageType } from './types';
|
||||
import { checksum, positionToFreeDUnits, rotationToFreeDUnits } from './utils';
|
||||
|
||||
function positionMessageToBytes(message: FreeDPosition): Uint8Array {
|
||||
const bytes = new Uint8Array(29);
|
||||
|
||||
bytes[0] = FreeDMessageType.Position;
|
||||
bytes[1] = message.id;
|
||||
|
||||
bytes.set(rotationToFreeDUnits(message.pan), 2);
|
||||
bytes.set(rotationToFreeDUnits(message.tilt), 5);
|
||||
bytes.set(rotationToFreeDUnits(message.roll), 8);
|
||||
|
||||
bytes.set(positionToFreeDUnits(message.posX), 11);
|
||||
bytes.set(positionToFreeDUnits(message.posY), 14);
|
||||
bytes.set(positionToFreeDUnits(message.posZ), 17);
|
||||
|
||||
bytes[20] = Math.trunc(message.zoom / 65536);
|
||||
bytes[21] = Math.trunc((message.zoom / 256) % 256);
|
||||
bytes[22] = message.zoom % 256;
|
||||
|
||||
bytes[23] = Math.trunc(message.focus / 65536);
|
||||
bytes[24] = Math.trunc((message.focus / 256) % 256);
|
||||
bytes[25] = message.focus % 256;
|
||||
|
||||
if (message.spare !== undefined) {
|
||||
bytes[26] = message.spare >> 8;
|
||||
bytes[27] = message.spare % 256;
|
||||
}
|
||||
|
||||
bytes[28] = checksum(bytes.subarray(0, 28));
|
||||
return bytes;
|
||||
}
|
||||
export function encode(message: FreeDPosition): Uint8Array {
|
||||
switch (message.type) {
|
||||
case FreeDMessageType.Position:
|
||||
return positionMessageToBytes(message);
|
||||
default:
|
||||
throw new Error('unsupported freeD message type');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './decode';
|
||||
export * from './encode';
|
||||
export * from './types';
|
||||
@@ -0,0 +1,17 @@
|
||||
export enum FreeDMessageType {
|
||||
Position = 0xd1
|
||||
}
|
||||
|
||||
export type FreeDPosition = {
|
||||
type: FreeDMessageType.Position;
|
||||
id: number;
|
||||
pan: number;
|
||||
tilt: number;
|
||||
roll: number;
|
||||
posZ: number;
|
||||
posX: number;
|
||||
posY: number;
|
||||
zoom: number;
|
||||
focus: number;
|
||||
spare?: number;
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
export function checksum(bytes: Uint8Array): number {
|
||||
let checksum = 0x40;
|
||||
|
||||
bytes.forEach((byte) => {
|
||||
checksum -= byte;
|
||||
if (checksum < 0) {
|
||||
checksum += 256;
|
||||
}
|
||||
});
|
||||
return checksum;
|
||||
}
|
||||
|
||||
export function rotationToFreeDUnits(rotation: number): Uint8Array {
|
||||
const view = new DataView(new ArrayBuffer(4));
|
||||
const units = rotation * 32768 * 256;
|
||||
view.setInt32(0, units);
|
||||
return new Uint8Array(view.buffer, 0, 3);
|
||||
}
|
||||
|
||||
export function positionToFreeDUnits(position: number): Uint8Array {
|
||||
const view = new DataView(new ArrayBuffer(4));
|
||||
const units = position * 64 * 256;
|
||||
view.setInt32(0, units);
|
||||
return new Uint8Array(view.buffer, 0, 3);
|
||||
}
|
||||
|
||||
export function freeDUnitsToRotation(upper: number, middle: number, lower: number): number {
|
||||
const padded = new Uint8Array([upper, middle, lower, 0]);
|
||||
const view = new DataView(padded.buffer, padded.byteOffset, padded.byteLength);
|
||||
return view.getInt32(0) / 256 / 32768;
|
||||
}
|
||||
|
||||
export function freeDUnitsToPosition(upper: number, middle: number, lower: number): number {
|
||||
const padded = new Uint8Array([upper, middle, lower, 0]);
|
||||
const view = new DataView(padded.buffer, padded.byteOffset, padded.byteLength);
|
||||
return view.getInt32(0) / 256 / 64;
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
const { deepEqual, throws } = require('assert');
|
||||
const { describe, it } = require('node:test');
|
||||
const { decode } = require('../dist/cjs');
|
||||
|
||||
const goodTests = [
|
||||
{
|
||||
description: 'simple position message',
|
||||
bytes: new Uint8Array([
|
||||
0xd1, 0x01, 0x5a, 0x00, 0x00, 0x2d, 0x00, 0x00, 0xa6, 0x00, 0x00, 0x7f, 0xff, 0x40, 0x7f, 0xff, 0x80, 0x7f, 0xff,
|
||||
0xc0, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x00, 0x00, 50,
|
||||
]),
|
||||
expected: {
|
||||
type: 0xd1,
|
||||
id: 1,
|
||||
pan: 180,
|
||||
tilt: 90,
|
||||
roll: -180,
|
||||
posX: 131069,
|
||||
posY: 131070,
|
||||
posZ: 131071,
|
||||
zoom: 66051,
|
||||
focus: 263430,
|
||||
spare: 0,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const badTests = [
|
||||
{
|
||||
description: 'position - bad checksum',
|
||||
bytes: new Uint8Array([
|
||||
0xd1, 0x01, 0x5a, 0x00, 0x00, 0x2d, 0x00, 0x00, 0xa6, 0x00, 0x00, 0x7f, 0xff, 0x40, 0x7f, 0xff, 0x80, 0x7f, 0xff,
|
||||
0xc0, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x00, 0x00, 100,
|
||||
]),
|
||||
throwsMessage: { name: /^Error$/, message: /failed checksum/ },
|
||||
},
|
||||
{
|
||||
description: 'position - incomplete',
|
||||
bytes: new Uint8Array([
|
||||
0xd1, 0x01, 0x5a, 0x00, 0x00, 0x2d, 0x00, 0x00, 0xa6, 0x00, 0x00, 0x7f, 0xff, 0x40, 0x7f, 0xff, 0x80, 0x7f, 0xff,
|
||||
0xc0, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x00, 0x00,
|
||||
]),
|
||||
throwsMessage: { name: /^Error$/, message: /should be 29 bytes/ },
|
||||
},
|
||||
{
|
||||
description: 'empty bytes',
|
||||
bytes: new Uint8Array([]),
|
||||
throwsMessage: { name: /^Error$/, message: /cannot decode 0 bytes/ },
|
||||
},
|
||||
{
|
||||
description: 'bad message type',
|
||||
bytes: new Uint8Array([0xff]),
|
||||
throwsMessage: { name: /^Error$/, message: /unsupported freeD/ },
|
||||
},
|
||||
];
|
||||
|
||||
describe('FreeD Bytes Decoding', () => {
|
||||
goodTests.forEach((bytesTest) => {
|
||||
it(bytesTest.description, () => {
|
||||
const decoded = decode(bytesTest.bytes);
|
||||
deepEqual(decoded, bytesTest.expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('FreeD Bytes Decoding Throws', () => {
|
||||
badTests.forEach((bytesTest) => {
|
||||
it(bytesTest.description, () => {
|
||||
throws(() => {
|
||||
decode(bytesTest.bytes);
|
||||
}, bytesTest.throwsMessage);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
const { deepEqual, throws } = require('assert');
|
||||
const { describe, it } = require('node:test');
|
||||
const { encode } = require('../dist/cjs');
|
||||
|
||||
const goodTests = [
|
||||
{
|
||||
description: 'simple position message',
|
||||
message: {
|
||||
type: 0xd1,
|
||||
id: 1,
|
||||
pan: 180,
|
||||
tilt: 90,
|
||||
roll: -180,
|
||||
posX: 131069,
|
||||
posY: 131070,
|
||||
posZ: 131071,
|
||||
zoom: 66051,
|
||||
focus: 263430,
|
||||
spare: 0,
|
||||
},
|
||||
expected: new Uint8Array([
|
||||
0xd1, 0x01, 0x5a, 0x00, 0x00, 0x2d, 0x00, 0x00, 0xa6, 0x00, 0x00, 0x7f, 0xff, 0x40, 0x7f, 0xff, 0x80, 0x7f, 0xff,
|
||||
0xc0, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x00, 0x00, 50,
|
||||
]),
|
||||
},
|
||||
];
|
||||
|
||||
const badTests = [
|
||||
{
|
||||
description: 'unsupported message',
|
||||
message: {
|
||||
type: 0xff,
|
||||
},
|
||||
throwsMessage: { name: /^Error$/, message: /unsupported freeD message type/ },
|
||||
},
|
||||
];
|
||||
|
||||
describe('FreeD Message Encoding', () => {
|
||||
goodTests.forEach((messageTest) => {
|
||||
it(messageTest.description, () => {
|
||||
const decoded = encode(messageTest.message);
|
||||
deepEqual(decoded, messageTest.expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('FreeD Message Encoding Throws', () => {
|
||||
badTests.forEach((messageTest) => {
|
||||
it(messageTest.description, () => {
|
||||
throws(() => {
|
||||
encode(messageTest.message);
|
||||
}, messageTest.throwsMessage);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"module": "CommonJS",
|
||||
"declaration": false,
|
||||
"outDir": "./dist/cjs"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"module": "ESNext",
|
||||
"declaration": false,
|
||||
"outDir": "./dist/esm"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "Node",
|
||||
"lib": ["ESNext"],
|
||||
"target": "ESNext",
|
||||
"esModuleInterop": true,
|
||||
"isolatedModules": true,
|
||||
"rootDir": "src",
|
||||
"strict": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"emitDeclarationOnly": true,
|
||||
"declaration": true,
|
||||
"outDir": "./dist/types"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user