@kelnishi/satmouse-client 0.9.4 → 0.9.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +118 -0
- package/dist/react/index.cjs +66 -3
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +31 -1
- package/dist/react/index.d.ts +31 -1
- package/dist/react/index.js +66 -3
- package/dist/react/index.js.map +1 -1
- package/dist/utils/index.cjs +80 -3
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.d.cts +43 -2
- package/dist/utils/index.d.ts +43 -2
- package/dist/utils/index.js +77 -4
- package/dist/utils/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# @kelnishi/satmouse-client
|
|
2
|
+
|
|
3
|
+
Client SDK for [SatMouse](https://kelnishi.github.io/SatMouse/) — stream 6DOF spatial input from SpaceMouse and other devices to web apps and PWAs.
|
|
4
|
+
|
|
5
|
+
Three tree-shakeable modules:
|
|
6
|
+
|
|
7
|
+
| Module | Import | Purpose |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| **core** | `@kelnishi/satmouse-client` | Connection, discovery, binary decode. Zero dependencies. |
|
|
10
|
+
| **utils** | `@kelnishi/satmouse-client/utils` | InputManager, transforms, per-device config, action mapping, persistence |
|
|
11
|
+
| **react** | `@kelnishi/satmouse-client/react` | Provider, hooks, headless components |
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @kelnishi/satmouse-client
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Vanilla
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { SatMouseConnection } from "@kelnishi/satmouse-client";
|
|
23
|
+
import { InputManager } from "@kelnishi/satmouse-client/utils";
|
|
24
|
+
|
|
25
|
+
const connection = new SatMouseConnection();
|
|
26
|
+
const manager = new InputManager();
|
|
27
|
+
manager.addConnection(connection);
|
|
28
|
+
|
|
29
|
+
manager.onSpatialData((data) => {
|
|
30
|
+
console.log(data.translation, data.rotation);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
await connection.connect();
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### React
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
import { SatMouseProvider, useSpatialData } from "@kelnishi/satmouse-client/react";
|
|
40
|
+
|
|
41
|
+
function App() {
|
|
42
|
+
return (
|
|
43
|
+
<SatMouseProvider>
|
|
44
|
+
<Scene />
|
|
45
|
+
</SatMouseProvider>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function Scene() {
|
|
50
|
+
const data = useSpatialData();
|
|
51
|
+
// data.translation.x/y/z, data.rotation.x/y/z
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Per-Device Configuration
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
const manager = new InputManager({
|
|
59
|
+
sensitivity: { translation: 0.001, rotation: 0.001 },
|
|
60
|
+
devices: {
|
|
61
|
+
"hid-054c-*": { sensitivity: { translation: 0.002 } },
|
|
62
|
+
"spacemouse-c635": { flip: { rz: false } },
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Query devices and their resolved config
|
|
67
|
+
const devices = manager.getDevicesWithConfig();
|
|
68
|
+
|
|
69
|
+
// Update per-device
|
|
70
|
+
manager.updateDeviceConfig("spacemouse-c635", {
|
|
71
|
+
sensitivity: { rotation: 0.005 },
|
|
72
|
+
});
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Action Mapping
|
|
76
|
+
|
|
77
|
+
Remap input axes to named actions. Default passes through 1:1.
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import { InputManager, swapActions, DEFAULT_ACTION_MAP } from "@kelnishi/satmouse-client/utils";
|
|
81
|
+
|
|
82
|
+
// Swap ty and tz
|
|
83
|
+
const manager = new InputManager({
|
|
84
|
+
actionMap: swapActions(DEFAULT_ACTION_MAP, "ty", "tz"),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Per-device remapping
|
|
88
|
+
manager.updateDeviceConfig("hid-054c-*", {
|
|
89
|
+
actionMap: {
|
|
90
|
+
tx: { source: "tx" },
|
|
91
|
+
tz: { source: "ty", invert: true },
|
|
92
|
+
ry: { source: "rx", scale: 2.0 },
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Named action values
|
|
97
|
+
manager.onActionValues((values) => {
|
|
98
|
+
scene.pan(values.tx, values.ty);
|
|
99
|
+
scene.zoom(values.tz);
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Connection Options
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
// Auto-discover via Thing Description
|
|
107
|
+
new SatMouseConnection();
|
|
108
|
+
|
|
109
|
+
// Direct URL
|
|
110
|
+
new SatMouseConnection({ wsUrl: "ws://192.168.1.42:18945/spatial" });
|
|
111
|
+
|
|
112
|
+
// Via satmouse:// URI
|
|
113
|
+
new SatMouseConnection({ uri: "satmouse://connect?host=192.168.1.42" });
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## License
|
|
117
|
+
|
|
118
|
+
MIT — [GitHub](https://github.com/kelnishi/SatMouse)
|
package/dist/react/index.cjs
CHANGED
|
@@ -427,6 +427,57 @@ function launchSatMouse(options) {
|
|
|
427
427
|
});
|
|
428
428
|
}
|
|
429
429
|
|
|
430
|
+
// src/utils/action-map.ts
|
|
431
|
+
var DEFAULT_ACTION_MAP = {
|
|
432
|
+
tx: { source: "tx" },
|
|
433
|
+
ty: { source: "ty" },
|
|
434
|
+
tz: { source: "tz" },
|
|
435
|
+
rx: { source: "rx" },
|
|
436
|
+
ry: { source: "ry" },
|
|
437
|
+
rz: { source: "rz" }
|
|
438
|
+
};
|
|
439
|
+
function readAxis(data, axis) {
|
|
440
|
+
switch (axis) {
|
|
441
|
+
case "tx":
|
|
442
|
+
return data.translation.x;
|
|
443
|
+
case "ty":
|
|
444
|
+
return data.translation.y;
|
|
445
|
+
case "tz":
|
|
446
|
+
return data.translation.z;
|
|
447
|
+
case "rx":
|
|
448
|
+
return data.rotation.x;
|
|
449
|
+
case "ry":
|
|
450
|
+
return data.rotation.y;
|
|
451
|
+
case "rz":
|
|
452
|
+
return data.rotation.z;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
function applyActionMap(data, map) {
|
|
456
|
+
const result = {};
|
|
457
|
+
for (const [action, binding] of Object.entries(map)) {
|
|
458
|
+
let value = readAxis(data, binding.source);
|
|
459
|
+
if (binding.invert) value = -value;
|
|
460
|
+
value *= binding.scale ?? 1;
|
|
461
|
+
result[action] = value;
|
|
462
|
+
}
|
|
463
|
+
return result;
|
|
464
|
+
}
|
|
465
|
+
function actionValuesToSpatialData(values, timestamp) {
|
|
466
|
+
return {
|
|
467
|
+
translation: {
|
|
468
|
+
x: values.tx ?? 0,
|
|
469
|
+
y: values.ty ?? 0,
|
|
470
|
+
z: values.tz ?? 0
|
|
471
|
+
},
|
|
472
|
+
rotation: {
|
|
473
|
+
x: values.rx ?? 0,
|
|
474
|
+
y: values.ry ?? 0,
|
|
475
|
+
z: values.rz ?? 0
|
|
476
|
+
},
|
|
477
|
+
timestamp
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
|
|
430
481
|
// src/utils/config.ts
|
|
431
482
|
var DEFAULT_CONFIG = {
|
|
432
483
|
sensitivity: { translation: 1e-3, rotation: 1e-3 },
|
|
@@ -436,6 +487,7 @@ var DEFAULT_CONFIG = {
|
|
|
436
487
|
axisRemap: { tx: "x", ty: "y", tz: "z", rx: "x", ry: "y", rz: "z" },
|
|
437
488
|
lockPosition: false,
|
|
438
489
|
lockRotation: false,
|
|
490
|
+
actionMap: { ...DEFAULT_ACTION_MAP },
|
|
439
491
|
devices: {}
|
|
440
492
|
};
|
|
441
493
|
function mergeConfig(base, partial) {
|
|
@@ -445,6 +497,7 @@ function mergeConfig(base, partial) {
|
|
|
445
497
|
sensitivity: { ...base.sensitivity, ...partial.sensitivity },
|
|
446
498
|
flip: { ...base.flip, ...partial.flip },
|
|
447
499
|
axisRemap: { ...base.axisRemap, ...partial.axisRemap },
|
|
500
|
+
actionMap: partial.actionMap ? { ...base.actionMap, ...partial.actionMap } : { ...base.actionMap },
|
|
448
501
|
devices: { ...base.devices }
|
|
449
502
|
};
|
|
450
503
|
if (partial.devices) {
|
|
@@ -484,6 +537,7 @@ function resolveDeviceConfig(config, deviceId) {
|
|
|
484
537
|
deadZone: deviceOverride.deadZone ?? config.deadZone,
|
|
485
538
|
dominant: deviceOverride.dominant ?? config.dominant,
|
|
486
539
|
axisRemap: { ...config.axisRemap, ...deviceOverride.axisRemap },
|
|
540
|
+
actionMap: deviceOverride.actionMap ? { ...config.actionMap, ...deviceOverride.actionMap } : config.actionMap,
|
|
487
541
|
lockPosition: deviceOverride.lockPosition ?? config.lockPosition,
|
|
488
542
|
lockRotation: deviceOverride.lockRotation ?? config.lockRotation
|
|
489
543
|
};
|
|
@@ -650,6 +704,7 @@ var InputManager = class extends TypedEmitter {
|
|
|
650
704
|
deadZone: resolved.deadZone,
|
|
651
705
|
dominant: resolved.dominant,
|
|
652
706
|
axisRemap: resolved.axisRemap,
|
|
707
|
+
actionMap: resolved.actionMap,
|
|
653
708
|
lockPosition: resolved.lockPosition,
|
|
654
709
|
lockRotation: resolved.lockRotation
|
|
655
710
|
};
|
|
@@ -679,11 +734,17 @@ var InputManager = class extends TypedEmitter {
|
|
|
679
734
|
this.on("buttonEvent", callback);
|
|
680
735
|
return () => this.off("buttonEvent", callback);
|
|
681
736
|
}
|
|
737
|
+
/** Register a callback for action values. Returns unsubscribe function. */
|
|
738
|
+
onActionValues(callback) {
|
|
739
|
+
this.on("actionValues", callback);
|
|
740
|
+
return () => this.off("actionValues", callback);
|
|
741
|
+
}
|
|
682
742
|
wireConnection(connection) {
|
|
683
743
|
connection.on("spatialData", (raw) => {
|
|
684
744
|
this.emit("rawSpatialData", raw);
|
|
685
|
-
const
|
|
686
|
-
if (
|
|
745
|
+
const { spatial, actions } = this.processSpatialData(raw);
|
|
746
|
+
if (spatial) this.emit("spatialData", spatial);
|
|
747
|
+
if (actions) this.emit("actionValues", actions);
|
|
687
748
|
});
|
|
688
749
|
connection.on("buttonEvent", (event) => this.emit("buttonEvent", event));
|
|
689
750
|
connection.on("stateChange", (state, proto) => this.emit("stateChange", state, proto));
|
|
@@ -707,7 +768,9 @@ var InputManager = class extends TypedEmitter {
|
|
|
707
768
|
if (cfg.lockRotation) {
|
|
708
769
|
data = { ...data, rotation: { x: 0, y: 0, z: 0 } };
|
|
709
770
|
}
|
|
710
|
-
|
|
771
|
+
const actions = applyActionMap(data, cfg.actionMap);
|
|
772
|
+
const spatial = actionValuesToSpatialData(actions, data.timestamp);
|
|
773
|
+
return { spatial, actions };
|
|
711
774
|
}
|
|
712
775
|
};
|
|
713
776
|
var SatMouseContext = react.createContext(null);
|