forked from KierPalin/MicroData
-
Notifications
You must be signed in to change notification settings - Fork 0
/
noArcadeShieldModeForMakecode.ts
463 lines (388 loc) · 17.1 KB
/
noArcadeShieldModeForMakecode.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
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
/**
* THIS FILE IS NOT USED BY MICRODATA.
*
* This is the same as noArcadeShieldMode.ts but it has no dependencies.
*
* This code may be copied directly into makecode to demo noArcadeShieldMode without requiring sensors.ts etc
*/
/**
* Sensor Selection cycles between 'animations' of the options; animations are an LED loop specific to that sensor.
* Mutated by the A & B button
*/
const enum UI_MODE {
SENSOR_SELECTION,
LOGGING
};
/**
* Represents the internal state of the UI diplay when in SENSOR_SELECTION UI_MODE;
*
* Mutated by the A & B button & .dynamicSensorSelectionLoop()
*
* Which LED should be shown and what Sensor object should it be converted to when complete.
* see .uiSelectionToSensor()
*
* Notice the RADIO element; which is not a sensor; see .uiSelectionToSensor() since it is handled differently.
*/
const enum UI_SENSOR_SELECT_STATE {
ACCELERATION,
TEMPERATURE,
LIGHT,
MAGNET,
RADIO
};
/** For module inside of B button. */
const SENSOR_SELECTION_SIZE = 5;
/** How long should each LED picture be shown for? Series of pictures divide this by how many there are. */
const SHOW_EACH_SENSOR_FOR_MS: number = 1000;
/**
* Simple class to enable the use of MicroData w/o an Arcade Shield for recording data for the sensors listed in UI_SENSOR_SELECT_STATE.
* Invoked if an arcade shield is not detected from app.ts
* The LED is used to represent sensor options, the user can press A to select one; which starts logging.
* Or press B to move onto the next one.
*
* Logging happens every second and is indefinite. The user may cancel the logging via the B button.
*
* Whilst the sensors are cycled between the sensor being displayed may dynamically update if the readings from that sensor are in excess.
* See .dynamicSensorSelectionLoop()
* It checks all sensors inside UI_SENSOR_SELECT_STATE; if there is one that has a reading beyond the threshold then it will switch this.uiSensorSelectState to that sensor.
* This allows the user to cycle between UI elements phsyically - by shining light on or shaking the microbit.
*
* Fibers and special waiting functions .waitUntilSensorSelectStateChange & .waitUntilUIModeChanges are required to maintain low-latency and the dynamic behaviour described above.
*/
class NoArcadeShieldMode {
/** Mutated by the A & B button */
private uiMode: UI_MODE;
/** Mutated by the B button & .dynamicSensorSelectionLoop() */
private uiSensorSelectState: UI_SENSOR_SELECT_STATE;
constructor() {
this.uiMode = UI_MODE.SENSOR_SELECTION;
this.uiSensorSelectState = UI_SENSOR_SELECT_STATE.ACCELERATION;
// A Button
input.onButtonPressed(1, () => {
if (this.uiMode == UI_MODE.SENSOR_SELECTION) {
this.uiMode = UI_MODE.LOGGING;
this.log();
}
})
// B Button
input.onButtonPressed(2, () => {
if (this.uiMode == UI_MODE.SENSOR_SELECTION)
this.uiSensorSelectState = (this.uiSensorSelectState + 1) % SENSOR_SELECTION_SIZE
else if (this.uiMode == UI_MODE.LOGGING) {
this.uiMode = UI_MODE.SENSOR_SELECTION;
this.dynamicSensorSelectionLoop();
this.showSensorIcon();
}
})
this.dynamicSensorSelectionLoop();
this.showSensorIcon();
}
/**
* Runs in background fiber.
* Polls all UI_SENSOR_SELECT_STATE except RADIO for abormally high readings.
* If the reading is beyond the threshold then this.uiSensorSelectState is mutated.
*
* Turned off if not in UI_MODE.SENSOR_SELECTION
*
* Invoked at start and when moving back from logging via pressing the B button.
*/
private dynamicSensorSelectionLoop() {
const dynamicInfo = [
{ sensor: new AccelerometerXSensor(), uiState: UI_SENSOR_SELECT_STATE.ACCELERATION, threshold: 0.25 },
{ sensor: new AccelerometerYSensor(), uiState: UI_SENSOR_SELECT_STATE.ACCELERATION, threshold: 0.25 },
{ sensor: new AccelerometerZSensor(), uiState: UI_SENSOR_SELECT_STATE.ACCELERATION, threshold: 0.25 },
{ sensor: new LightSensor(), uiState: UI_SENSOR_SELECT_STATE.LIGHT, threshold: 0.75 },
{ sensor: new MagnetXSensor(), uiState: UI_SENSOR_SELECT_STATE.MAGNET, threshold: 0.80 },
];
// Don't trigger the same sensor selection twice in a row:
let ignore: boolean[] = dynamicInfo.map(_ => false);
control.inBackground(() => {
while (this.uiMode == UI_MODE.SENSOR_SELECTION) {
dynamicInfo.forEach((info, idx) => {
if (!ignore[idx] && info.sensor.getNormalisedReading() > info.threshold) {
this.uiSensorSelectState = info.uiState;
ignore = dynamicInfo.map(_ => false);
ignore[idx] = true;
basic.pause(1000)
}
basic.pause(100)
})
basic.pause(100)
}
return;
})
}
//-------------------------
// Special Waiting Methods:
//-------------------------
/**
* Wait time number of milliseconds but in increments of check_n_times. Exit if initialState changes.
* To show led animations you need to wait inbetween each frame. But you need to switch to another state if a button is pressed immediately.
* used by .showSensorIcon()
*
* @param time milliseconds
* @param check_n_times period = time / check_n_times
* @param initialState this.uiSensorSelectState != causes pre-mature exit; returning false.
* @returns true if neither this.uiSensorSelectState nor this.uiMode changed; meaning that the full time was waited.
*/
private waitUntilSensorSelectStateChange(time: number, check_n_times: number, initialState: UI_SENSOR_SELECT_STATE): boolean {
const period = time / check_n_times;
for (let n = 0; n < check_n_times; n++) {
if (this.uiSensorSelectState != initialState || this.uiMode != UI_MODE.SENSOR_SELECTION)
return false;
basic.pause(period)
}
return true;
}
/**
* Wait time number of milliseconds but in increments of check_n_times. Exit if initialState changes.
* To show led animations you need to wait inbetween each frame. But you need to switch to another state if a button is pressed immediately.
* used by .log()
*
* @param time milliseconds
* @param check_n_times period = time / check_n_times
* @param initialState this.uiMode != causes pre-mature exit; returning false.
* @returns true if this.uiMode did not change; meaning that the full time was waited.
*/
private waitUntilUIModeChanges(time: number, check_n_times: number, initialState: UI_MODE): boolean {
const period = time / check_n_times;
for (let n = 0; n < check_n_times; n++) {
if (this.uiMode != initialState) {
return false;
}
basic.pause(period)
}
return true;
}
//-----------------
// Display Methods:
//-----------------
/**
* Starts a fiber that loops through UI_SENSOR_SELECT_STATE:
* Showing each as an animation, checking for this.uiMode & this.uiSensorSelectState changes whilst waiting.
* Invoked at start & by the B button if re-entering SENSOR_SELECTION UI_MODE from the LOGGING UI_MODE
*/
private showSensorIcon() {
control.inBackground(() => {
while (this.uiMode == UI_MODE.SENSOR_SELECTION) {
switch (this.uiSensorSelectState) {
case UI_SENSOR_SELECT_STATE.ACCELERATION: {
// basic.showLeds() requires a '' literal; thus the following is un-loopable:
basic.showLeds(`
# # # . .
# # . . .
# . # . .
. . . # .
. . . . .
`);
if (!this.waitUntilSensorSelectStateChange((SHOW_EACH_SENSOR_FOR_MS / 3), 10, UI_SENSOR_SELECT_STATE.ACCELERATION)) break;
basic.showLeds(`
. . # . .
. . # . .
# # # # #
. # # # .
. . # . .
`);
if (!this.waitUntilSensorSelectStateChange((SHOW_EACH_SENSOR_FOR_MS / 3), 10, UI_SENSOR_SELECT_STATE.ACCELERATION)) break;
basic.showLeds(`
. . # . .
. . # # .
# # # # #
. . # # .
. . # . .
`);
if (!this.waitUntilSensorSelectStateChange((SHOW_EACH_SENSOR_FOR_MS / 3), 10, UI_SENSOR_SELECT_STATE.ACCELERATION)) break;
break;
}
case UI_SENSOR_SELECT_STATE.TEMPERATURE: {
basic.showLeds(`
# . . . .
. . # # .
. # . . .
. # . . .
. . # # .
`);
if (!this.waitUntilSensorSelectStateChange((SHOW_EACH_SENSOR_FOR_MS), 50, UI_SENSOR_SELECT_STATE.TEMPERATURE)) break;
break;
}
case UI_SENSOR_SELECT_STATE.LIGHT: {
basic.showLeds(`
. . . . .
. # # # .
. . # . .
. . . . .
. . # . .
`);
if (!this.waitUntilSensorSelectStateChange((SHOW_EACH_SENSOR_FOR_MS>> 1), 50, UI_SENSOR_SELECT_STATE.LIGHT)) break;
basic.showLeds(`
. # # # .
. # # # .
. # # # .
. . . . .
. . # . .
`);
if (!this.waitUntilSensorSelectStateChange((SHOW_EACH_SENSOR_FOR_MS>> 1), 50, UI_SENSOR_SELECT_STATE.LIGHT)) break;
break;
}
case UI_SENSOR_SELECT_STATE.MAGNET: {
basic.showLeds(`
. # # # .
# # # # #
# # . # #
. . . . .
. . . . .
`)
if (!this.waitUntilSensorSelectStateChange((SHOW_EACH_SENSOR_FOR_MS>> 1), 50, UI_SENSOR_SELECT_STATE.MAGNET)) break;
basic.showLeds(`
. # # # .
# # # # #
# # . # #
. . . . .
# # . # #
`)
if (!this.waitUntilSensorSelectStateChange((SHOW_EACH_SENSOR_FOR_MS>> 1), 50, UI_SENSOR_SELECT_STATE.MAGNET)) break;
break;
}
case UI_SENSOR_SELECT_STATE.RADIO: {
basic.showLeds(`
. . . . .
. . . . .
. # # # .
# . . . #
. . # . .
`);
if (!this.waitUntilSensorSelectStateChange((SHOW_EACH_SENSOR_FOR_MS>> 1), 50, UI_SENSOR_SELECT_STATE.RADIO)) break;
basic.showLeds(`
. # # # .
# . . . #
. # # # .
# . . . #
. . # . .
`);
if (!this.waitUntilSensorSelectStateChange((SHOW_EACH_SENSOR_FOR_MS>> 1), 50, UI_SENSOR_SELECT_STATE.RADIO)) break;
break;
}
default:
break;
}
}
});
}
/**
* Get the sensor(s) that the user selected and start logging them.
* Exit if the UI_MODE changes back to SENSOR_SELECTION (upon the user pressing B)
*/
private log() {
const sensors = this.uiSelectionToSensors();
let time = 0;
control.inBackground(() => {
while (this.uiMode == UI_MODE.LOGGING) {
let start = input.runningTime();
sensors.forEach(sensor => {
datalogger.log(
datalogger.createCV("Sensor", sensor.getName()),
datalogger.createCV("Time (ms)", time),
datalogger.createCV("Reading", sensor.getReading()),
datalogger.createCV("Event", "N/A")
);
});
if (this.uiMode == UI_MODE.LOGGING)
basic.showNumber((time / 1000));
if (!this.waitUntilUIModeChanges(Math.max(0, 1000 - (input.runningTime() - start)), 80, UI_MODE.LOGGING)) break;
time += 1000;
}
return;
});
}
/**
* this.uiSensorSelectState -> relevant sensors
* Most are only 1 sensor, but UI_SENSOR_SELECT_STATE.ACCELERATION gives all X,Y,Z sensors.
*
* Special note to UI_SENSOR_SELECT_STATE.RADIO which leaves NoArcadeShieldMode & starts the DistributedLoggingProtocol().
*
* @returns sensors used by .log()
*/
private uiSelectionToSensors(): Sensor[] {
switch (this.uiSensorSelectState) {
case UI_SENSOR_SELECT_STATE.ACCELERATION:
return [new AccelerometerXSensor(), new AccelerometerYSensor(), new AccelerometerZSensor()]
case UI_SENSOR_SELECT_STATE.TEMPERATURE:
return [new TemperatureSensor()]
case UI_SENSOR_SELECT_STATE.LIGHT:
return [new LightSensor()]
case UI_SENSOR_SELECT_STATE.MAGNET:
return [new MagnetXSensor()]
case UI_SENSOR_SELECT_STATE.RADIO:
// new DistributedLoggingProtocol(this.app, false);
return []
default:
return []
}
}
}
class Sensor {
constructor() {}
getNormalisedReading(): number {
return Math.abs(this.getReading()) / (Math.abs(this.getMinimum()) + this.getMaximum())
}
getName(): string {return "abstract"}
getReading(): number {return undefined}
getMinimum(): number {return 0;}
getMaximum(): number {return 100;}
}
class AccelerometerXSensor extends Sensor {
constructor() {
super()
input.setAccelerometerRange(AcceleratorRange.OneG)
}
public static getName(): string { return "Accel. X" }
public static getRadioName(): string { return "AX" }
public static getReading(): number { return input.acceleration(Dimension.X) }
public static getMinimum(): number { return -2048; }
public static getMaximum(): number { return 2048; }
}
class AccelerometerYSensor extends Sensor {
constructor() {
super()
input.setAccelerometerRange(AcceleratorRange.OneG)
}
public static getName(): string { return "Accel. Y" }
public static getRadioName(): string { return "AY" }
public static getReading(): number { return input.acceleration(Dimension.Y) }
public static getMinimum(): number { return -2048; }
public static getMaximum(): number { return 2048; }
}
class AccelerometerZSensor extends Sensor {
constructor() {
super()
input.setAccelerometerRange(AcceleratorRange.OneG)
}
public static getName(): string { return "Accel. Z" }
public static getRadioName(): string { return "AZ" }
public static getReading(): number { return input.acceleration(Dimension.Z) }
public static getMinimum(): number { return -2048; }
public static getMaximum(): number { return 2048; }
}
class TemperatureSensor extends Sensor {
constructor() { super() }
public static getName(): string { return "Temp." }
public static getRadioName(): string { return "T" }
public static getReading(): number { return input.temperature() }
}
class LightSensor extends Sensor {
constructor() { super() }
public static getName(): string { return "Light" }
public static getRadioName(): string { return "L" }
public static getReading(): number { return input.lightLevel() }
public static getMinimum(): number { return 0; }
public static getMaximum(): number { return 255; }
}
class MagnetXSensor extends Sensor {
constructor() { super() }
public static getName(): string { return "Magnet" }
public static getRadioName(): string { return "M" }
public static getReading(): number { return input.magneticForce(Dimension.Strength) }
}
// Start:
datalogger.includeTimestamp(FlashLogTimeStampFormat.None)
new NoArcadeShieldMode();