The RGBLed module has been moved to a separate repository

Changelog-entry: The RGBLed module has been moved to a separate repository
Change-type: patch
This commit is contained in:
Alexis Svinartchouk 2020-02-13 11:15:39 +01:00
parent ed90f21188
commit 94d262263c
3 changed files with 19 additions and 127 deletions

View File

@ -14,131 +14,17 @@
* limitations under the License. * limitations under the License.
*/ */
import { delay } from 'bluebird'; import {
import { promises as fs } from 'fs'; AnimationFunction,
blinkWhite,
breatheGreen,
Color,
RGBLed,
} from 'sys-class-rgb-led';
import * as settings from './settings'; import * as settings from './settings';
import { observe } from './store'; import { observe } from './store';
class Led {
private handle: fs.FileHandle;
private lastValue?: number;
constructor(private path: string) {}
private async open() {
if (this.handle === undefined) {
this.handle = await fs.open(this.path, 'w');
}
}
public async setIntensity(intensity: number) {
if (intensity < 0 || intensity > 1) {
throw new Error('Led intensity must be between 0 and 1');
}
const value = Math.round(intensity * 255);
if (value !== this.lastValue) {
await this.open();
await this.handle.write(value.toString(), 0);
// On a regular file we would also need to truncate to the written value length but it looks like it's not the case on sysfs files
this.lastValue = value;
}
}
}
export type Color = [number, number, number];
export type AnimationFunction = (t: number) => Color;
function delay1(duration: number): Promise<void> {
// delay that accepts Infinity
if (duration === Infinity) {
return new Promise(() => {
// Never resolve
});
} else {
return delay(duration);
}
}
function cancellableDelay(
duration: number,
): { promise: Promise<void>; cancel: () => void } {
let maybeCancel: () => void;
const cancel = () => {
if (maybeCancel !== undefined) {
maybeCancel();
}
};
const cancelPromise: Promise<void> = new Promise(resolve => {
maybeCancel = resolve;
});
const promise = Promise.race([delay1(duration), cancelPromise]);
return { promise, cancel };
}
export class RGBLed {
private leds: [Led, Led, Led];
private animation: AnimationFunction;
private period: number; // in ms
private wakeUp = () => {
// noop until this.loop() is called
};
constructor(paths: [string, string, string]) {
this.leds = paths.map(path => new Led(path)) as [Led, Led, Led];
this.setStaticColor([0, 0, 0]);
this.loop();
}
private setFrequency(frequency: number) {
if (frequency < 0) {
throw new Error('frequency must be greater or equal to 0');
}
this.period = 1000 / frequency;
this.wakeUp();
}
private async loop() {
while (true) {
const start = new Date().getTime();
await this.setColor(this.animation(start));
const end = new Date().getTime();
const duration = end - start;
const { promise, cancel } = cancellableDelay(this.period - duration);
this.wakeUp = cancel;
await promise;
}
}
public setAnimation(animation: AnimationFunction, frequency = 10) {
this.animation = animation;
this.setFrequency(frequency);
}
public setStaticColor(color: Color) {
this.setAnimation(() => color, 0);
}
private async setColor(color: Color) {
await Promise.all([
this.leds[0].setIntensity(color[0]),
this.leds[1].setIntensity(color[1]),
this.leds[2].setIntensity(color[2]),
]);
}
}
// Animations:
function breatheGreen(t: number): Color {
const intensity = (1 + Math.sin(t / 1000)) / 2;
return [0, intensity, 0];
}
function blinkWhite(t: number): Color {
const intensity = Math.floor(t / 1000) % 2;
return [intensity, intensity, intensity];
}
const leds: Map<string, RGBLed> = new Map(); const leds: Map<string, RGBLed> = new Map();
function setLeds( function setLeds(
@ -184,16 +70,16 @@ export function init() {
// ledsMapping is something like: // ledsMapping is something like:
// { // {
// 'platform-xhci-hcd.0.auto-usb-0:1.1.1:1.0-scsi-0:0:0:0': [ // 'platform-xhci-hcd.0.auto-usb-0:1.1.1:1.0-scsi-0:0:0:0': [
// '/sys/class/leds/led1_r', // 'led1_r',
// '/sys/class/leds/led1_g', // 'led1_g',
// '/sys/class/leds/led1_b', // 'led1_b',
// ], // ],
// ... // ...
// } // }
const ledsMapping: _.Dictionary<[string, string, string]> = const ledsMapping: _.Dictionary<[string, string, string]> =
settings.get('ledsMapping') || {}; settings.get('ledsMapping') || {};
for (const [drivePath, ledsPaths] of Object.entries(ledsMapping)) { for (const [drivePath, ledsNames] of Object.entries(ledsMapping)) {
leds.set('/dev/disk/by-path/' + drivePath, new RGBLed(ledsPaths)); leds.set('/dev/disk/by-path/' + drivePath, new RGBLed(ledsNames));
} }
observe(state => { observe(state => {
const availableDrives = state const availableDrives = state

7
npm-shrinkwrap.json generated
View File

@ -12694,6 +12694,11 @@
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
"integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==" "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ=="
}, },
"sys-class-rgb-led": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/sys-class-rgb-led/-/sys-class-rgb-led-2.1.0.tgz",
"integrity": "sha512-ckjrMCWWwg1J4d+B3xlTPLhgK6U/2qpW1TYzCLBSULKoLk2tFchis7nDEOmcbiLfpR/8GQK1Q2CrDNleF0USHA=="
},
"tapable": { "tapable": {
"version": "1.1.3", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz",
@ -14457,4 +14462,4 @@
} }
} }
} }
} }

View File

@ -82,6 +82,7 @@
"styled-components": "^4.2.0", "styled-components": "^4.2.0",
"styled-system": "^4.1.0", "styled-system": "^4.1.0",
"sudo-prompt": "^9.0.0", "sudo-prompt": "^9.0.0",
"sys-class-rgb-led": "^2.1.0",
"tmp": "^0.1.0", "tmp": "^0.1.0",
"uuid": "^3.0.1" "uuid": "^3.0.1"
}, },