forked from KierPalin/MicroData
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathheadlessMode.ts
More file actions
301 lines (260 loc) · 10.6 KB
/
headlessMode.ts
File metadata and controls
301 lines (260 loc) · 10.6 KB
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
namespace microdata {
/**
* 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
};
const sensorEventThresholds: { [id: number]: number } = {
[UI_SENSOR_SELECT_STATE.ACCELERATION]: 300, // in milli-g for 2g (-2048 to 2047)
[UI_SENSOR_SELECT_STATE.TEMPERATURE]: 1,
[UI_SENSOR_SELECT_STATE.LIGHT]: 25,
[UI_SENSOR_SELECT_STATE.MAGNET]: 100,
}
/** For module inside of B button. */
const UI_SENSOR_SELECT_STATE_LEN = 4;
/** 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.
*/
export class HeadlessMode {
/** 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;
datalogger.deleteLog(datalogger.DeleteType.Fast)
// A Button
input.onButtonPressed(1, () => {
if (this.uiMode == UI_MODE.SENSOR_SELECTION) {
this.uiMode = UI_MODE.LOGGING;
} else if (this.uiMode == UI_MODE.LOGGING) {
this.uiMode = UI_MODE.SENSOR_SELECTION;
}
})
// B Button
input.onButtonPressed(2, () => {
if (this.uiMode == UI_MODE.SENSOR_SELECTION)
this.uiSensorSelectState = (this.uiSensorSelectState + 1) % UI_SENSOR_SELECT_STATE_LEN
else if (this.uiMode == UI_MODE.LOGGING) {
this.uiMode = UI_MODE.SENSOR_SELECTION;
}
})
this.loop();
}
private loop() {
while (1) {
if (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;
}
default:
break;
}
} else if (this.uiMode == UI_MODE.LOGGING) {
const sensors = this.uiSelectionToSensors();
let time = 0;
basic.showLeds(`
. . . . .
. . . . .
. . . . .
. . . . .
. # # # .
`)
// control.inBackground(() => {
const WAIT_TIME_MS = 30;
let start = input.runningTime();
const threshold = this.uiSelectionToSensorEventThresholds();
let priorReadings: number[] = sensors.map(sensor => sensor.getReading());
while (this.uiMode == UI_MODE.LOGGING) {
sensors.forEach((sensor, index) => {
// datalogger.log(
// datalogger.createCV("Sensor", sensor.getName()),
// datalogger.createCV("Time (ms)", time),
// datalogger.createCV("Reading", sensor.getReading()),
// datalogger.createCV("Event", "N/A")
// );
const reading = sensor.getReading();
if (Math.abs(reading - priorReadings[index]) > threshold) {
datalogger.log(
datalogger.createCV("Sensor", sensor.getName()),
datalogger.createCV("Time (ms)", time),
datalogger.createCV("Reading", reading),
datalogger.createCV("Event", "delta")
);
}
priorReadings[index] = reading;
});
time += WAIT_TIME_MS;
const loop = input.runningTime();
basic.pause(WAIT_TIME_MS - (loop - start));
start = loop;
}
basic.showLeds(`
. . . . .
. # . # .
. . . . .
# . . . #
. # # # .
`)
basic.pause(1000)
}
}
}
//-------------------------
// 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;
}
/**
* 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 [Sensor.getFromName("Accel. X"), Sensor.getFromName("Accel. Y"), Sensor.getFromName("Accel. Z")]
case UI_SENSOR_SELECT_STATE.TEMPERATURE:
return [Sensor.getFromName("Temp.")]
case UI_SENSOR_SELECT_STATE.LIGHT:
return [Sensor.getFromName("Light")]
case UI_SENSOR_SELECT_STATE.MAGNET:
return [Sensor.getFromName("Magnet")]
default:
return []
}
}
private uiSelectionToSensorEventThresholds(): number {
return sensorEventThresholds[this.uiSensorSelectState];
}
}
}