tjbot-ce 3.0.1
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/LICENSE +202 -0
- package/README.md +382 -0
- package/dist/camera/camera.d.ts +62 -0
- package/dist/camera/camera.d.ts.map +1 -0
- package/dist/camera/camera.js +155 -0
- package/dist/camera/camera.js.map +1 -0
- package/dist/camera/index.d.ts +18 -0
- package/dist/camera/index.d.ts.map +1 -0
- package/dist/camera/index.js +18 -0
- package/dist/camera/index.js.map +1 -0
- package/dist/config/config-types.d.ts +75 -0
- package/dist/config/config-types.d.ts.map +1 -0
- package/dist/config/config-types.generated.d.ts +495 -0
- package/dist/config/config-types.generated.d.ts.map +1 -0
- package/dist/config/config-types.generated.js +2 -0
- package/dist/config/config-types.generated.js.map +1 -0
- package/dist/config/config-types.js +175 -0
- package/dist/config/config-types.js.map +1 -0
- package/dist/config/index.d.ts +20 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +19 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/tjbot-config.d.ts +98 -0
- package/dist/config/tjbot-config.d.ts.map +1 -0
- package/dist/config/tjbot-config.js +309 -0
- package/dist/config/tjbot-config.js.map +1 -0
- package/dist/config/vendor/colors.yaml +61 -0
- package/dist/config/vendor/model-registry.yaml +275 -0
- package/dist/config/vendor/tjbot-config.schema.yaml +792 -0
- package/dist/config/vendor/tjbot.default.toml +452 -0
- package/dist/led/index.d.ts +20 -0
- package/dist/led/index.d.ts.map +1 -0
- package/dist/led/index.js +20 -0
- package/dist/led/index.js.map +1 -0
- package/dist/led/led-common-anode.d.ts +38 -0
- package/dist/led/led-common-anode.d.ts.map +1 -0
- package/dist/led/led-common-anode.js +79 -0
- package/dist/led/led-common-anode.js.map +1 -0
- package/dist/led/led-neopixel-spi.d.ts +60 -0
- package/dist/led/led-neopixel-spi.d.ts.map +1 -0
- package/dist/led/led-neopixel-spi.js +216 -0
- package/dist/led/led-neopixel-spi.js.map +1 -0
- package/dist/led/led-neopixel-ws281x.js +186 -0
- package/dist/led/led-neopixel.d.ts +57 -0
- package/dist/led/led-neopixel.d.ts.map +1 -0
- package/dist/led/led-neopixel.js +235 -0
- package/dist/led/led-neopixel.js.map +1 -0
- package/dist/microphone/index.d.ts +18 -0
- package/dist/microphone/index.d.ts.map +1 -0
- package/dist/microphone/index.js +18 -0
- package/dist/microphone/index.js.map +1 -0
- package/dist/microphone/microphone.d.ts +65 -0
- package/dist/microphone/microphone.d.ts.map +1 -0
- package/dist/microphone/microphone.js +179 -0
- package/dist/microphone/microphone.js.map +1 -0
- package/dist/rpi-drivers/index.d.ts +22 -0
- package/dist/rpi-drivers/index.d.ts.map +1 -0
- package/dist/rpi-drivers/index.js +22 -0
- package/dist/rpi-drivers/index.js.map +1 -0
- package/dist/rpi-drivers/rpi-detect.d.ts +24 -0
- package/dist/rpi-drivers/rpi-detect.d.ts.map +1 -0
- package/dist/rpi-drivers/rpi-detect.js +49 -0
- package/dist/rpi-drivers/rpi-detect.js.map +1 -0
- package/dist/rpi-drivers/rpi-driver.d.ts +116 -0
- package/dist/rpi-drivers/rpi-driver.d.ts.map +1 -0
- package/dist/rpi-drivers/rpi-driver.js +261 -0
- package/dist/rpi-drivers/rpi-driver.js.map +1 -0
- package/dist/rpi-drivers/rpi3-driver.d.ts +47 -0
- package/dist/rpi-drivers/rpi3-driver.d.ts.map +1 -0
- package/dist/rpi-drivers/rpi3-driver.js +145 -0
- package/dist/rpi-drivers/rpi3-driver.js.map +1 -0
- package/dist/rpi-drivers/rpi4-driver.d.ts +35 -0
- package/dist/rpi-drivers/rpi4-driver.d.ts.map +1 -0
- package/dist/rpi-drivers/rpi4-driver.js +101 -0
- package/dist/rpi-drivers/rpi4-driver.js.map +1 -0
- package/dist/rpi-drivers/rpi5-driver.d.ts +33 -0
- package/dist/rpi-drivers/rpi5-driver.d.ts.map +1 -0
- package/dist/rpi-drivers/rpi5-driver.js +78 -0
- package/dist/rpi-drivers/rpi5-driver.js.map +1 -0
- package/dist/servo/index.d.ts +19 -0
- package/dist/servo/index.d.ts.map +1 -0
- package/dist/servo/index.js +19 -0
- package/dist/servo/index.js.map +1 -0
- package/dist/servo/servo-constants.d.ts +33 -0
- package/dist/servo/servo-constants.d.ts.map +1 -0
- package/dist/servo/servo-constants.js +34 -0
- package/dist/servo/servo-constants.js.map +1 -0
- package/dist/servo/servo-lgpio.d.ts +82 -0
- package/dist/servo/servo-lgpio.d.ts.map +1 -0
- package/dist/servo/servo-lgpio.js +178 -0
- package/dist/servo/servo-lgpio.js.map +1 -0
- package/dist/speaker/audio-player.d.ts +30 -0
- package/dist/speaker/audio-player.d.ts.map +1 -0
- package/dist/speaker/audio-player.js +68 -0
- package/dist/speaker/audio-player.js.map +1 -0
- package/dist/speaker/index.d.ts +18 -0
- package/dist/speaker/index.d.ts.map +1 -0
- package/dist/speaker/index.js +18 -0
- package/dist/speaker/index.js.map +1 -0
- package/dist/speaker/speaker.d.ts +53 -0
- package/dist/speaker/speaker.d.ts.map +1 -0
- package/dist/speaker/speaker.js +125 -0
- package/dist/speaker/speaker.js.map +1 -0
- package/dist/stt/backends/azure-stt.d.ts +32 -0
- package/dist/stt/backends/azure-stt.d.ts.map +1 -0
- package/dist/stt/backends/azure-stt.js +227 -0
- package/dist/stt/backends/azure-stt.js.map +1 -0
- package/dist/stt/backends/google-cloud-stt.d.ts +31 -0
- package/dist/stt/backends/google-cloud-stt.d.ts.map +1 -0
- package/dist/stt/backends/google-cloud-stt.js +371 -0
- package/dist/stt/backends/google-cloud-stt.js.map +1 -0
- package/dist/stt/backends/ibm-watson-stt.d.ts +32 -0
- package/dist/stt/backends/ibm-watson-stt.d.ts.map +1 -0
- package/dist/stt/backends/ibm-watson-stt.js +190 -0
- package/dist/stt/backends/ibm-watson-stt.js.map +1 -0
- package/dist/stt/backends/sherpa-onnx-stt.d.ts +117 -0
- package/dist/stt/backends/sherpa-onnx-stt.d.ts.map +1 -0
- package/dist/stt/backends/sherpa-onnx-stt.js +694 -0
- package/dist/stt/backends/sherpa-onnx-stt.js.map +1 -0
- package/dist/stt/index.d.ts +20 -0
- package/dist/stt/index.d.ts.map +1 -0
- package/dist/stt/index.js +21 -0
- package/dist/stt/index.js.map +1 -0
- package/dist/stt/stt-engine.d.ts +68 -0
- package/dist/stt/stt-engine.d.ts.map +1 -0
- package/dist/stt/stt-engine.js +99 -0
- package/dist/stt/stt-engine.js.map +1 -0
- package/dist/stt/stt-utils.d.ts +36 -0
- package/dist/stt/stt-utils.d.ts.map +1 -0
- package/dist/stt/stt-utils.js +112 -0
- package/dist/stt/stt-utils.js.map +1 -0
- package/dist/stt/stt.d.ts +52 -0
- package/dist/stt/stt.d.ts.map +1 -0
- package/dist/stt/stt.js +100 -0
- package/dist/stt/stt.js.map +1 -0
- package/dist/tjbot.d.ts +317 -0
- package/dist/tjbot.d.ts.map +1 -0
- package/dist/tjbot.js +736 -0
- package/dist/tjbot.js.map +1 -0
- package/dist/tts/backends/azure-tts.d.ts +30 -0
- package/dist/tts/backends/azure-tts.d.ts.map +1 -0
- package/dist/tts/backends/azure-tts.js +92 -0
- package/dist/tts/backends/azure-tts.js.map +1 -0
- package/dist/tts/backends/google-cloud-tts.d.ts +38 -0
- package/dist/tts/backends/google-cloud-tts.d.ts.map +1 -0
- package/dist/tts/backends/google-cloud-tts.js +116 -0
- package/dist/tts/backends/google-cloud-tts.js.map +1 -0
- package/dist/tts/backends/ibm-watson-tts.d.ts +42 -0
- package/dist/tts/backends/ibm-watson-tts.d.ts.map +1 -0
- package/dist/tts/backends/ibm-watson-tts.js +99 -0
- package/dist/tts/backends/ibm-watson-tts.js.map +1 -0
- package/dist/tts/backends/sherpa-onnx-tts.d.ts +80 -0
- package/dist/tts/backends/sherpa-onnx-tts.d.ts.map +1 -0
- package/dist/tts/backends/sherpa-onnx-tts.js +237 -0
- package/dist/tts/backends/sherpa-onnx-tts.js.map +1 -0
- package/dist/tts/index.d.ts +19 -0
- package/dist/tts/index.d.ts.map +1 -0
- package/dist/tts/index.js +20 -0
- package/dist/tts/index.js.map +1 -0
- package/dist/tts/tts-engine.d.ts +67 -0
- package/dist/tts/tts-engine.d.ts.map +1 -0
- package/dist/tts/tts-engine.js +109 -0
- package/dist/tts/tts-engine.js.map +1 -0
- package/dist/tts/tts.d.ts +47 -0
- package/dist/tts/tts.d.ts.map +1 -0
- package/dist/tts/tts.js +101 -0
- package/dist/tts/tts.js.map +1 -0
- package/dist/utils/colors.d.ts +39 -0
- package/dist/utils/colors.d.ts.map +1 -0
- package/dist/utils/colors.js +155 -0
- package/dist/utils/colors.js.map +1 -0
- package/dist/utils/constants.d.ts +41 -0
- package/dist/utils/constants.d.ts.map +1 -0
- package/dist/utils/constants.js +43 -0
- package/dist/utils/constants.js.map +1 -0
- package/dist/utils/credentials.d.ts +43 -0
- package/dist/utils/credentials.d.ts.map +1 -0
- package/dist/utils/credentials.js +121 -0
- package/dist/utils/credentials.js.map +1 -0
- package/dist/utils/errors.d.ts +26 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +32 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/index.d.ts +25 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +23 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/logging.d.ts +44 -0
- package/dist/utils/logging.d.ts.map +1 -0
- package/dist/utils/logging.js +113 -0
- package/dist/utils/logging.js.map +1 -0
- package/dist/utils/model-registry.d.ts +142 -0
- package/dist/utils/model-registry.d.ts.map +1 -0
- package/dist/utils/model-registry.js +391 -0
- package/dist/utils/model-registry.js.map +1 -0
- package/dist/utils/utils.d.ts +33 -0
- package/dist/utils/utils.d.ts.map +1 -0
- package/dist/utils/utils.js +50 -0
- package/dist/utils/utils.js.map +1 -0
- package/dist/vision/backends/azure-vision.d.ts +33 -0
- package/dist/vision/backends/azure-vision.d.ts.map +1 -0
- package/dist/vision/backends/azure-vision.js +151 -0
- package/dist/vision/backends/azure-vision.js.map +1 -0
- package/dist/vision/backends/google-cloud-vision.d.ts +32 -0
- package/dist/vision/backends/google-cloud-vision.d.ts.map +1 -0
- package/dist/vision/backends/google-cloud-vision.js +193 -0
- package/dist/vision/backends/google-cloud-vision.js.map +1 -0
- package/dist/vision/backends/onnx.d.ts +116 -0
- package/dist/vision/backends/onnx.d.ts.map +1 -0
- package/dist/vision/backends/onnx.js +781 -0
- package/dist/vision/backends/onnx.js.map +1 -0
- package/dist/vision/index.d.ts +19 -0
- package/dist/vision/index.d.ts.map +1 -0
- package/dist/vision/index.js +20 -0
- package/dist/vision/index.js.map +1 -0
- package/dist/vision/vision-engine.d.ts +131 -0
- package/dist/vision/vision-engine.d.ts.map +1 -0
- package/dist/vision/vision-engine.js +97 -0
- package/dist/vision/vision-engine.js.map +1 -0
- package/dist/vision/vision.d.ts +48 -0
- package/dist/vision/vision.d.ts.map +1 -0
- package/dist/vision/vision.js +83 -0
- package/dist/vision/vision.js.map +1 -0
- package/package.json +124 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2025 IBM Corp. All Rights Reserved.
|
|
3
|
+
* Copyright 2026-present TJBot Contributors. All Rights Reserved.
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import SPI from 'pi-spi';
|
|
18
|
+
/**
|
|
19
|
+
* LED controller for SPI-based NeoPixel LEDs (Raspberry Pi 5)
|
|
20
|
+
* This is based on pi5neo.py:
|
|
21
|
+
* https://github.com/vanshksingh/Pi5Neo/blob/main/pi5neo/pi5neo.py
|
|
22
|
+
*/
|
|
23
|
+
export declare class LEDNeopixelSPI {
|
|
24
|
+
spi: SPI.SPI;
|
|
25
|
+
useGRBFormat: boolean;
|
|
26
|
+
private isPrimed;
|
|
27
|
+
private static readonly HIGH;
|
|
28
|
+
private static readonly LOW;
|
|
29
|
+
private static readonly FREQ;
|
|
30
|
+
private static readonly RESET_BYTES;
|
|
31
|
+
private static readonly FRAME_REPEATS;
|
|
32
|
+
private static readonly PRIME_OFF_FRAMES;
|
|
33
|
+
private static readonly INTER_FRAME_DELAY_MS;
|
|
34
|
+
private static readonly PRIME_DELAY_MS;
|
|
35
|
+
private static isSPIEnabledFromConfig;
|
|
36
|
+
private static assertSPIEnabled;
|
|
37
|
+
private static isSPIEnabledInFirmwareConfig;
|
|
38
|
+
private static isGpio10MuxedForSPI;
|
|
39
|
+
private static assertSPIPreconditions;
|
|
40
|
+
constructor(spiInterface: string, useGRB?: boolean);
|
|
41
|
+
private static sleep;
|
|
42
|
+
private primeLink;
|
|
43
|
+
private transferFrame;
|
|
44
|
+
private buildFramedBitstream;
|
|
45
|
+
static bitMask(byte: number, index: number): boolean;
|
|
46
|
+
static byteToBitstream(byte: number): number[];
|
|
47
|
+
static rgbToSpiBitstream(red: number, green: number, blue: number, useGRB: boolean): Buffer;
|
|
48
|
+
/**
|
|
49
|
+
* Render the LED to a specified color.
|
|
50
|
+
* @param color The color to render, specified as a string of hexadecimal digits
|
|
51
|
+
* with no leading '0x' or '#' in RRGGBB format.
|
|
52
|
+
* @returns A promise that resolves when the SPI transfer completes.
|
|
53
|
+
*/
|
|
54
|
+
render(color: string): Promise<void>;
|
|
55
|
+
/**
|
|
56
|
+
* Clean up resources
|
|
57
|
+
*/
|
|
58
|
+
cleanup(): void;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=led-neopixel-spi.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"led-neopixel-spi.d.ts","sourceRoot":"","sources":["../../src/led/led-neopixel-spi.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,OAAO,GAAG,MAAM,QAAQ,CAAC;AAMzB;;;;GAIG;AACH,qBAAa,cAAc;IACvB,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC;IACb,YAAY,EAAE,OAAO,CAAC;IACtB,OAAO,CAAC,QAAQ,CAAU;IAE1B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAgB;IAC5C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAgB;IAC3C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAmB;IAC/C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAe;IAClD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAa;IAClD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAa;IACrD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,oBAAoB,CAAa;IACzD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAa;IAEnD,OAAO,CAAC,MAAM,CAAC,sBAAsB;IAarC,OAAO,CAAC,MAAM,CAAC,gBAAgB;IAuB/B,OAAO,CAAC,MAAM,CAAC,4BAA4B;IAS3C,OAAO,CAAC,MAAM,CAAC,mBAAmB;IAoBlC,OAAO,CAAC,MAAM,CAAC,sBAAsB;gBAkBzB,YAAY,EAAE,MAAM,EAAE,MAAM,GAAE,OAAe;IAazD,OAAO,CAAC,MAAM,CAAC,KAAK;YAIN,SAAS;YAUT,aAAa;IAiB3B,OAAO,CAAC,oBAAoB;IAW5B,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO;IAIpD,MAAM,CAAC,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE;IAY9C,MAAM,CAAC,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM;IAU3F;;;;;OAKG;IACG,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA0B1C;;OAEG;IACH,OAAO,IAAI,IAAI;CAGlB"}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2025 IBM Corp. All Rights Reserved.
|
|
3
|
+
* Copyright 2026-present TJBot Contributors. All Rights Reserved.
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import { execSync } from 'child_process';
|
|
18
|
+
import { existsSync, readFileSync } from 'fs';
|
|
19
|
+
import SPI from 'pi-spi';
|
|
20
|
+
import { TJBotError } from '../utils/errors.js';
|
|
21
|
+
import { getLogger } from '../utils/logging.js';
|
|
22
|
+
const logger = getLogger(import.meta.url);
|
|
23
|
+
/**
|
|
24
|
+
* LED controller for SPI-based NeoPixel LEDs (Raspberry Pi 5)
|
|
25
|
+
* This is based on pi5neo.py:
|
|
26
|
+
* https://github.com/vanshksingh/Pi5Neo/blob/main/pi5neo/pi5neo.py
|
|
27
|
+
*/
|
|
28
|
+
export class LEDNeopixelSPI {
|
|
29
|
+
spi;
|
|
30
|
+
useGRBFormat;
|
|
31
|
+
isPrimed;
|
|
32
|
+
static HIGH = 0xf8; // possibles: F0, F8, FC
|
|
33
|
+
static LOW = 0xc0; // possibles: C0
|
|
34
|
+
static FREQ = 6400000;
|
|
35
|
+
static RESET_BYTES = 100;
|
|
36
|
+
static FRAME_REPEATS = 2;
|
|
37
|
+
static PRIME_OFF_FRAMES = 6;
|
|
38
|
+
static INTER_FRAME_DELAY_MS = 1;
|
|
39
|
+
static PRIME_DELAY_MS = 2;
|
|
40
|
+
static isSPIEnabledFromConfig(configPath) {
|
|
41
|
+
if (!existsSync(configPath)) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
const config = readFileSync(configPath, 'utf8');
|
|
46
|
+
return /^\s*dtparam\s*=\s*spi\s*=\s*on\s*$/m.test(config);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
static assertSPIEnabled(spiInterface) {
|
|
53
|
+
const firmwareConfig = '/boot/firmware/config.txt';
|
|
54
|
+
const legacyConfig = '/boot/config.txt';
|
|
55
|
+
const fwEnabled = LEDNeopixelSPI.isSPIEnabledFromConfig(firmwareConfig);
|
|
56
|
+
const legacyEnabled = LEDNeopixelSPI.isSPIEnabledFromConfig(legacyConfig);
|
|
57
|
+
// If either known config explicitly enables SPI, accept.
|
|
58
|
+
if (fwEnabled === true || legacyEnabled === true) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
// If a matching spidev node exists, SPI is effectively available.
|
|
62
|
+
if (existsSync(spiInterface)) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
throw new TJBotError(`SPI appears to be disabled or unavailable (missing ${spiInterface}). ` +
|
|
66
|
+
'To enable SPI, edit /boot/firmware/config.txt and set dtparam=spi=on, then reboot your Raspberry Pi.');
|
|
67
|
+
}
|
|
68
|
+
static isSPIEnabledInFirmwareConfig() {
|
|
69
|
+
try {
|
|
70
|
+
const config = readFileSync('/boot/firmware/config.txt', 'utf8');
|
|
71
|
+
return /^\s*dtparam\s*=\s*spi\s*=\s*on\s*$/m.test(config);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
static isGpio10MuxedForSPI() {
|
|
78
|
+
try {
|
|
79
|
+
const output = execSync('pinctrl get 10', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
|
|
80
|
+
const lower = output.toLowerCase();
|
|
81
|
+
if (lower.includes('= none')) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
if (lower.includes('spi')) {
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
// On Raspberry Pi 5, SPI0 MOSI on GPIO10 is often ALT3 (shown as a3).
|
|
88
|
+
if (lower.includes(' a3 ') || lower.includes(' a3|') || lower.includes(' a3\t') || lower.includes(': a3')) {
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
static assertSPIPreconditions(spiInterface) {
|
|
98
|
+
LEDNeopixelSPI.assertSPIEnabled(spiInterface);
|
|
99
|
+
if (!LEDNeopixelSPI.isSPIEnabledInFirmwareConfig()) {
|
|
100
|
+
throw new TJBotError('SPI appears disabled in /boot/firmware/config.txt (dtparam=spi=on not set). ' +
|
|
101
|
+
'Enable SPI in /boot/firmware/config.txt, reboot, then retry.');
|
|
102
|
+
}
|
|
103
|
+
if (!LEDNeopixelSPI.isGpio10MuxedForSPI()) {
|
|
104
|
+
throw new TJBotError('GPIO10 is not currently muxed for SPI (pinctrl reports none). ' +
|
|
105
|
+
'Enable SPI in /boot/firmware/config.txt (dtparam=spi=on), reboot, then retry.');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
constructor(spiInterface, useGRB = false) {
|
|
109
|
+
const i = spiInterface || '/dev/spidev0.0';
|
|
110
|
+
LEDNeopixelSPI.assertSPIPreconditions(i);
|
|
111
|
+
this.spi = SPI.initialize(i);
|
|
112
|
+
this.spi.clockSpeed(LEDNeopixelSPI.FREQ);
|
|
113
|
+
this.spi.dataMode(0);
|
|
114
|
+
this.spi.bitOrder(SPI.order.MSB_FIRST);
|
|
115
|
+
this.useGRBFormat = useGRB;
|
|
116
|
+
this.isPrimed = false;
|
|
117
|
+
logger.verbose(`Initialized NeoPixel SPI LED on interface ${i} with GRB format: ${useGRB}`);
|
|
118
|
+
}
|
|
119
|
+
static sleep(ms) {
|
|
120
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
121
|
+
}
|
|
122
|
+
async primeLink() {
|
|
123
|
+
logger.debug('Priming NeoPixel SPI link with OFF frames');
|
|
124
|
+
const offFrame = this.buildFramedBitstream('000000');
|
|
125
|
+
for (let i = 0; i < LEDNeopixelSPI.PRIME_OFF_FRAMES; i++) {
|
|
126
|
+
await this.transferFrame(offFrame);
|
|
127
|
+
await LEDNeopixelSPI.sleep(LEDNeopixelSPI.PRIME_DELAY_MS);
|
|
128
|
+
}
|
|
129
|
+
logger.debug('NeoPixel SPI link prime complete');
|
|
130
|
+
}
|
|
131
|
+
async transferFrame(bitstream) {
|
|
132
|
+
for (let i = 0; i < LEDNeopixelSPI.FRAME_REPEATS; i++) {
|
|
133
|
+
await new Promise((resolve, reject) => {
|
|
134
|
+
this.spi.transfer(bitstream, (err) => {
|
|
135
|
+
if (err) {
|
|
136
|
+
logger.error('SPI transfer error:', err);
|
|
137
|
+
reject(new TJBotError('SPI transfer failed', { cause: err }));
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
logger.debug('LEDNeopixelSPI.render spi.transfer success');
|
|
141
|
+
resolve();
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
await LEDNeopixelSPI.sleep(LEDNeopixelSPI.INTER_FRAME_DELAY_MS);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
buildFramedBitstream(color) {
|
|
149
|
+
const c = parseInt(color, 16);
|
|
150
|
+
const r = (c & 0xff0000) >> 16;
|
|
151
|
+
const g = (c & 0x00ff00) >> 8;
|
|
152
|
+
const b = c & 0x0000ff;
|
|
153
|
+
const colorFrame = LEDNeopixelSPI.rgbToSpiBitstream(r, g, b, this.useGRBFormat);
|
|
154
|
+
const resetBuf = Buffer.alloc(LEDNeopixelSPI.RESET_BYTES, 0x00);
|
|
155
|
+
return Buffer.concat([resetBuf, colorFrame, resetBuf]);
|
|
156
|
+
}
|
|
157
|
+
static bitMask(byte, index) {
|
|
158
|
+
return (byte & (1 << (7 - index))) !== 0;
|
|
159
|
+
}
|
|
160
|
+
static byteToBitstream(byte) {
|
|
161
|
+
// Initialize with low bits
|
|
162
|
+
const bitstream = Array(8).fill(LEDNeopixelSPI.LOW);
|
|
163
|
+
for (let i = 0; i < 8; i++) {
|
|
164
|
+
if (LEDNeopixelSPI.bitMask(byte, i)) {
|
|
165
|
+
// Set high bits for '1'
|
|
166
|
+
bitstream[i] = LEDNeopixelSPI.HIGH;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return bitstream;
|
|
170
|
+
}
|
|
171
|
+
static rgbToSpiBitstream(red, green, blue, useGRB) {
|
|
172
|
+
const red_bits = LEDNeopixelSPI.byteToBitstream(red);
|
|
173
|
+
const green_bits = LEDNeopixelSPI.byteToBitstream(green);
|
|
174
|
+
const blue_bits = LEDNeopixelSPI.byteToBitstream(blue);
|
|
175
|
+
const bitstream = useGRB
|
|
176
|
+
? Buffer.from(green_bits.concat(red_bits).concat(blue_bits))
|
|
177
|
+
: Buffer.from(red_bits.concat(green_bits).concat(blue_bits));
|
|
178
|
+
return bitstream;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Render the LED to a specified color.
|
|
182
|
+
* @param color The color to render, specified as a string of hexadecimal digits
|
|
183
|
+
* with no leading '0x' or '#' in RRGGBB format.
|
|
184
|
+
* @returns A promise that resolves when the SPI transfer completes.
|
|
185
|
+
*/
|
|
186
|
+
async render(color) {
|
|
187
|
+
logger.debug(`Rendering NeoPixel LED (SPI) with color: ${color}`);
|
|
188
|
+
try {
|
|
189
|
+
if (!this.isPrimed) {
|
|
190
|
+
await this.primeLink();
|
|
191
|
+
this.isPrimed = true;
|
|
192
|
+
}
|
|
193
|
+
const bitstream = this.buildFramedBitstream(color);
|
|
194
|
+
// Transfer data via SPI to update the LED
|
|
195
|
+
// Wait for the transfer to complete before returning
|
|
196
|
+
logger.debug('LEDNeopixelSPI.render about to call spi.transfer');
|
|
197
|
+
await this.transferFrame(bitstream);
|
|
198
|
+
logger.debug('LEDNeopixelSPI.render completed normally');
|
|
199
|
+
}
|
|
200
|
+
catch (e) {
|
|
201
|
+
logger.error('Exception in LEDNeopixelSPI.render:', e);
|
|
202
|
+
// Print stack trace if available
|
|
203
|
+
if (e instanceof Error && e.stack) {
|
|
204
|
+
logger.error(e.stack);
|
|
205
|
+
}
|
|
206
|
+
throw e;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Clean up resources
|
|
211
|
+
*/
|
|
212
|
+
cleanup() {
|
|
213
|
+
logger.debug('LEDNeopixelSPI cleanup (no-op)');
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
//# sourceMappingURL=led-neopixel-spi.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"led-neopixel-spi.js","sourceRoot":"","sources":["../../src/led/led-neopixel-spi.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Copyright 2026-present TJBot Contributors. All Rights Reserved.
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* NeoPixel root helper process.
|
|
21
|
+
*
|
|
22
|
+
* This process is launched as root (via sudo) by LEDNeopixel to gain access to
|
|
23
|
+
* rpi-ws281x-native, which requires root privileges on RPi 3/4.
|
|
24
|
+
*
|
|
25
|
+
* Protocol: newline-delimited JSON on stdin/stdout.
|
|
26
|
+
* Requests:
|
|
27
|
+
* { id, cmd: "init", pin, numLeds }
|
|
28
|
+
* { id, cmd: "render", color } -- color is a 32-bit integer (0xRRGGBB)
|
|
29
|
+
* { id, cmd: "reset" }
|
|
30
|
+
* { id, cmd: "shutdown" }
|
|
31
|
+
* Responses:
|
|
32
|
+
* { id, ok: true }
|
|
33
|
+
* { id, ok: false, error: "<message>" }
|
|
34
|
+
*
|
|
35
|
+
* stderr is for human-readable diagnostics only and does not affect the protocol.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
import { createRequire } from 'module';
|
|
39
|
+
|
|
40
|
+
// rpi-ws281x-native is a native CJS addon; load it via createRequire so this
|
|
41
|
+
// file can remain an ES module (.js with "type":"module" in package.json).
|
|
42
|
+
const require = createRequire(import.meta.url);
|
|
43
|
+
const ws281x = require('rpi-ws281x-native');
|
|
44
|
+
|
|
45
|
+
const LED_DMA = 10;
|
|
46
|
+
const LED_FREQ_HZ = 800000;
|
|
47
|
+
const LED_BRIGHTNESS = 255;
|
|
48
|
+
const LED_INVERT = false;
|
|
49
|
+
const LED_STRIP_TYPE = ws281x.stripType.WS2812;
|
|
50
|
+
|
|
51
|
+
let initialized = false;
|
|
52
|
+
let channel;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Reply with a structured JSON response on stdout.
|
|
56
|
+
* @param {number|string} id
|
|
57
|
+
* @param {boolean} ok
|
|
58
|
+
* @param {string|undefined} error
|
|
59
|
+
*/
|
|
60
|
+
function reply(id, ok, error) {
|
|
61
|
+
const msg = ok ? { id, ok: true } : { id, ok: false, error: String(error ?? 'unknown error') };
|
|
62
|
+
process.stdout.write(JSON.stringify(msg) + '\n');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Handle a single parsed command object.
|
|
67
|
+
* @param {{ id: number|string, cmd: string, pin?: number, numLeds?: number, color?: number }} req
|
|
68
|
+
*/
|
|
69
|
+
function handle(req) {
|
|
70
|
+
const { id, cmd } = req;
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
switch (cmd) {
|
|
74
|
+
case 'init': {
|
|
75
|
+
const pin = Number(req.pin);
|
|
76
|
+
const numLeds = Number(req.numLeds ?? 1);
|
|
77
|
+
if (!Number.isInteger(pin) || pin < 0 || pin > 40) {
|
|
78
|
+
reply(id, false, `invalid pin: ${req.pin}`);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
channel = ws281x(numLeds, {
|
|
82
|
+
gpio: pin,
|
|
83
|
+
dma: LED_DMA,
|
|
84
|
+
freq: LED_FREQ_HZ,
|
|
85
|
+
invert: LED_INVERT,
|
|
86
|
+
brightness: LED_BRIGHTNESS,
|
|
87
|
+
stripType: LED_STRIP_TYPE,
|
|
88
|
+
});
|
|
89
|
+
initialized = true;
|
|
90
|
+
reply(id, true);
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
case 'render': {
|
|
95
|
+
if (!initialized) {
|
|
96
|
+
reply(id, false, 'not initialized');
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const color = Number(req.color);
|
|
100
|
+
if (!Number.isInteger(color) || color < 0 || color > 0xffffff) {
|
|
101
|
+
reply(id, false, `invalid color: ${req.color}`);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
channel.array[0] = color;
|
|
105
|
+
ws281x.render();
|
|
106
|
+
reply(id, true);
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
case 'reset': {
|
|
111
|
+
if (initialized) {
|
|
112
|
+
ws281x.reset();
|
|
113
|
+
}
|
|
114
|
+
reply(id, true);
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
case 'shutdown': {
|
|
119
|
+
if (initialized) {
|
|
120
|
+
ws281x.reset();
|
|
121
|
+
ws281x.finalize();
|
|
122
|
+
initialized = false;
|
|
123
|
+
channel = undefined;
|
|
124
|
+
}
|
|
125
|
+
reply(id, true);
|
|
126
|
+
// Give stdout a chance to flush before exiting.
|
|
127
|
+
process.stdout.once('drain', () => process.exit(0));
|
|
128
|
+
// Force exit if drain takes too long (e.g. pipe already closed).
|
|
129
|
+
setTimeout(() => process.exit(0), 200).unref();
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
default:
|
|
134
|
+
reply(id, false, `unknown command: ${cmd}`);
|
|
135
|
+
}
|
|
136
|
+
} catch (err) {
|
|
137
|
+
reply(id, false, String(err));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// --- Stdin line reader ---
|
|
142
|
+
let buffer = '';
|
|
143
|
+
|
|
144
|
+
process.stdin.setEncoding('utf8');
|
|
145
|
+
|
|
146
|
+
process.stdin.on('data', (chunk) => {
|
|
147
|
+
buffer += chunk;
|
|
148
|
+
let newline;
|
|
149
|
+
while ((newline = buffer.indexOf('\n')) !== -1) {
|
|
150
|
+
const line = buffer.slice(0, newline).trim();
|
|
151
|
+
buffer = buffer.slice(newline + 1);
|
|
152
|
+
if (!line) continue;
|
|
153
|
+
try {
|
|
154
|
+
const req = JSON.parse(line);
|
|
155
|
+
handle(req);
|
|
156
|
+
} catch (_) {
|
|
157
|
+
// Malformed JSON — no id to reply to, so silently ignore.
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
process.stdin.on('end', () => {
|
|
163
|
+
// Parent closed the pipe — clean up and exit.
|
|
164
|
+
if (initialized) {
|
|
165
|
+
try {
|
|
166
|
+
ws281x.reset();
|
|
167
|
+
ws281x.finalize();
|
|
168
|
+
} catch (_) {
|
|
169
|
+
/* best effort */
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
process.exit(0);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// Ensure the helper does not keep a stale process alive if the parent dies.
|
|
176
|
+
process.on('SIGTERM', () => {
|
|
177
|
+
if (initialized) {
|
|
178
|
+
try {
|
|
179
|
+
ws281x.reset();
|
|
180
|
+
ws281x.finalize();
|
|
181
|
+
} catch (_) {
|
|
182
|
+
/* best effort */
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
process.exit(0);
|
|
186
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2025 IBM Corp. All Rights Reserved.
|
|
3
|
+
* Copyright 2026-present TJBot Contributors. All Rights Reserved.
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* LED controller for NeoPixel (WS281x) LEDs on Raspberry Pi 3/4.
|
|
19
|
+
*
|
|
20
|
+
* rpi-ws281x-native requires root privileges. Rather than launching every
|
|
21
|
+
* TJBot recipe that uses the LED as root, this class spawns a small, long-lived
|
|
22
|
+
* helper process (in led-neopixel-ws281x.js) using sudo and communicates with it
|
|
23
|
+
* over a newline-delimited JSON IPC channel on stdin/stdout.
|
|
24
|
+
*
|
|
25
|
+
* Sudo authentication is performed once at construction time (either
|
|
26
|
+
* passwordless or via an interactive prompt). Subsequent render() calls are
|
|
27
|
+
* cheap IPC messages with no additional privilege escalation.
|
|
28
|
+
*/
|
|
29
|
+
export declare class LEDNeopixel {
|
|
30
|
+
private helper;
|
|
31
|
+
private reader?;
|
|
32
|
+
private helperStderrTail;
|
|
33
|
+
private _ready;
|
|
34
|
+
private _pendingById;
|
|
35
|
+
private _nextId;
|
|
36
|
+
private _helperDead;
|
|
37
|
+
constructor(pin: number);
|
|
38
|
+
/**
|
|
39
|
+
* Wait for the NeoPixel helper to be fully initialized and ready.
|
|
40
|
+
* Call this before loading long-running tasks if the LED needs to be available early.
|
|
41
|
+
*/
|
|
42
|
+
initialize(): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Render the NeoPixel to a specific color.
|
|
45
|
+
* @param color Color as a 32-bit integer in RGB format (0xRRGGBB)
|
|
46
|
+
*/
|
|
47
|
+
render(color: number): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Send a reset command and terminate the helper process.
|
|
50
|
+
*/
|
|
51
|
+
cleanup(): Promise<void>;
|
|
52
|
+
private _handleLine;
|
|
53
|
+
private _send;
|
|
54
|
+
private _setHelperHandleRefState;
|
|
55
|
+
private _killHelper;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=led-neopixel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"led-neopixel.d.ts","sourceRoot":"","sources":["../../src/led/led-neopixel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAwBH;;;;;;;;;;;GAWG;AACH,qBAAa,WAAW;IACpB,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,MAAM,CAAC,CAAY;IAC3B,OAAO,CAAC,gBAAgB,CAAgB;IACxC,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,YAAY,CAA0C;IAC9D,OAAO,CAAC,OAAO,CAAK;IACpB,OAAO,CAAC,WAAW,CAAsB;gBAE7B,GAAG,EAAE,MAAM;IA+FvB;;;OAGG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAIjC;;;OAGG;IACG,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAM1C;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAY9B,OAAO,CAAC,WAAW;IA4BnB,OAAO,CAAC,KAAK;IA0Bb,OAAO,CAAC,wBAAwB;IAqBhC,OAAO,CAAC,WAAW;CAYtB"}
|