-
Notifications
You must be signed in to change notification settings - Fork 0
/
mod.ts
254 lines (233 loc) · 6.82 KB
/
mod.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
import singleton from "https://raw.githubusercontent.com/grevend/singleton/main/mod.ts";
export type VPinNumber =
| 2
| 3
| 4
| 17
| 27
| 22
| 10
| 9
| 11
| 0
| 5
| 6
| 13
| 19
| 26
| 14
| 15
| 18
| 23
| 24
| 25
| 8
| 7
| 1
| 12
| 16
| 20
| 21;
export enum PinDirection {
IN = "in",
OUT = "out",
}
/**
* Not adding sudo might misbehave on repeated runs.
*/
// deno-lint-ignore prefer-const
export let FORCE_SUDO = true;
export class InstructionsQueue {
private cmd: string[] = [];
add(c: string) {
this.cmd.push(c);
}
async execute() {
await Deno.run({
cmd: [
...(FORCE_SUDO ? ["sudo"] : []),
"bash",
"-c",
`${this.cmd.join(";")}`,
],
}).status();
this.cmd = [];
}
}
export const instructionsQueue = singleton(() => new InstructionsQueue());
/**
* executes the instructions added above or within the method (i.e. executes the instruction queue).
* > Executing instructions will clear the instruction queue.
* @example ```TypeScript
const led = new Pin(24, PinDirection.OUT, 1);
sleep(5);
led.setValue(0);
led.unexport();
executeInstructions(); // executes the above instructions.
// OR
const led = new Pin(24, PinDirection.OUT, 1);
executeInstructions(
sleep(5),
led.setValue(0),
led.unexport()
); // executes the above instructions.
* ```
* @param _ This parameter does nothing. It is used to help structure code
* @returns a promise that resolves when all the instructions finished executing
*/
export async function executeInstructions(..._: unknown[]) {
return await instructionsQueue.getInstance().execute();
}
type PinValue = 1 | 0;
/**
* Queues up an instruction to sleeps in between commands for the given seconds. milliseconds, micro and nano seconds are supported.
* > to sleep for 40 microseconds you may use ``` sleep(4e-5); ```
* @param seconds the seconds to sleep for.
*/
export function sleep(seconds: number) {
instructionsQueue.getInstance().add(`sleep ${seconds}`);
}
export class Pin {
readonly number: VPinNumber;
/**
* Instantiates a new pin for usage.
* > This new pin will queue up an export instruction. you must execute these manually using the executeInstructions method
* @param number The GPIO pin number (e.g. GPIO23 -> 23)
* @param direction The current flow direction. in being to receive and out being to send out
* @param initialState optional property to automatically set the state as soon as pin is exported
* @param options further configuration.
*/
constructor(
number: VPinNumber,
direction: PinDirection,
initialState?: PinValue,
) {
this.number = number;
Pin.export(this);
this.setDirection(direction);
if (initialState !== undefined) this.setValue(initialState);
}
/**
* Reads the value of this pin.
* > NOTE: this method is not very precise (timing wise). use pipeValue to queue up an instruction and execute with precise timing.
* @returns the value of the pin (1 or 0)
*/
readValue(): number {
return Deno.readFileSync(`/sys/class/gpio/gpio${this.number}/value`)[0] -
48;
}
/**
* Queues up an instruction that waits until the pin is of the given value before continuing execution.
* > This instruction blocks all further scripts until the pin is of the given value.
* @param pinValue the pin value to wait for. defaults to 1 (HIGH)
*/
waitForValue(pinValue: PinValue = 1){
instructionsQueue.getInstance().add(
`while true; do if grep -q -m 1 ${pinValue} /sys/class/gpio/gpio${this.number}/value; then break; fi; done`
)
}
/**
* Queues up an instruction to set the pin value.
* > to be used when the direction is set to out.
* @param value 0 for low, 1 for high
*/
setValue(value: PinValue) {
instructionsQueue.getInstance().add(
`echo ${value} > /sys/class/gpio/gpio${this.number}/value`,
);
}
/**
* Queues up an instruction to set the pin direction (in or out).
* @param d the direction
*/
setDirection(d: PinDirection) {
instructionsQueue.getInstance().add(
`echo ${d} > /sys/class/gpio/gpio${this.number}/direction`,
);
}
/**
* Fetches the current pin direction from the system.
* > Useful if other programs will be changing the pin direction.
* @returns the pin direction
*/
getDirection(): PinDirection {
return Deno.readTextFileSync(`/sys/class/gpio/gpio${this.number}/direction`)
.includes("out")
? PinDirection.OUT
: PinDirection.IN;
}
/**
* Queues up an instruction to export the provided pin.
* This is required before you operate with the pin.
* @param pin the pin to export
*/
static export(pin: Pin) {
instructionsQueue.getInstance().add(
`echo ${pin.number.toString()} > /sys/class/gpio/export`,
);
}
/**
* Queues up an instruction to unexport this pin.
*/
unexport() {
return Pin.unexport(this);
}
/**
* Unexport the pin immediately after queening up the instruction.
* > This method calls ```executeInstructions```
*/
async unexportImmediate(){
Pin.unexport(this);
await executeInstructions();
}
/**
* Queues up an instruction to unexport the provided pin
* @param pin
*/
static unexport(pin: Pin | number) {
const pinNumber: string = (typeof pin === "number" ? pin : pin.number)
.toString();
instructionsQueue.getInstance().add(
`echo ${pinNumber} > /sys/class/gpio/unexport`,
);
}
/**
* Checks if this pin is already exported.
* @returns true if the pin is exported, false otherwise.
*/
isExported() {
return Pin.isExported(this);
}
/**
* Checks if the given pin is exported or not
* @param pin the pin number
* @returns
*/
static isExported(pin: Pin): boolean {
for (const { name } of Deno.readDirSync("/sys/class/gpio")) {
if (name.includes(`gpio${pin.number}`)) return true;
}
return false;
}
/**
* Queues up an instruction to pipe the pin value to a buffer file which can later be read from.
* This method can be used when micro or nanosecond precision is needed in between reads.
* > This method appends to the file each time a value is read. (note: appends on a new line each time)
*/
pipeValue(filePath: string) {
instructionsQueue.getInstance().add(
`head -n 1 /sys/class/gpio/gpio${this.number}/value >> ${filePath}`,
);
}
/**
* queues up an instruction to request a number of bits while sleeping in between each fetch.
* @param n number of bits to read
* @param toFile file to pipe the bits to
* @param sleep how much to sleep in between each bit fetch.
*/
readNBits(n: number, toFile: string, sleep = 0){
instructionsQueue.getInstance().add(
`for ((i = 0 ; i < ${n} ; i++)); do head -n 1 /sys/class/gpio/gpio${this.number}/value >> ${toFile}; sleep ${sleep}; done`
);
}
}