-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.ts
369 lines (318 loc) · 15.5 KB
/
main.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
namespace WashingMachine {
/**
* *****************************************************************************************************************************************
* This extension provides the control block for a custom made washing machine toy. The toy has been reworked so that it is entirely controlled
* by BBC:microbit. This extension operates on Programs and Patterns, what is explained down below.
*
* Each Program should be defined within a separate function in the main project.
* Each Program can consist of forward (CW) and backward (CCW) movement phases with braking in between if spinning direction has changed.
* Including braking is a User responsibility. Not braking when spinning direction changes may lead to hardware damage.
*
* A special spinning Patterns like step, pulse and pyramid patterns are provided as blocks for auto execution - no need to define a function.
* Each spinning Pattern (similarly to Programs) auto updates the timer and reacts to stop button.
* Aborting the program will abort the countdown timer and clear the display. At the moment a User can only abort a single phase of Program/Pattern.
* As a consequence pressing a stop button once, will only abort the current phase. To abort the whole program, User has to press stop as many times as there are phases.
*
* Exposed blocks:
* readButton(button: buttonsNames): number
* SpinMe(direction: dirOpt, speed: number, spinTime: number, stopCmd?: brakeOpt): void
* createPattern(mode: modOpt, noSteps: number, stepTime: number, direction?: dirOpt, speedA?: number, speedB?: number): SpinPattern
* executePattern(): void
* *****************************************************************************************************************************************
* Left to do:
* () Use doxygen style comments - explore github actions and doxygen generation
* (OK) Dependencies: DF-Driver added to dependencies in 'Project Settings'
* () Explore control lib: background tasks scheduling and events
* () Modify enums to directly link button ports - there's bunch of weird enums defined on top of runtime, to be checked
* () At the moment all functions/APIs are blocking - can be rewritten to return a state variable that will allow User to add extra code after calling APIs
* (OK) Callbacks - added for button presses, only one added at the moment, but the general scheme is clear and tested
* (-) Auto created variable - no such need in this library
* (OK) Test.ts - Ok for very rough testing or passing through program checkpoints, debug mode is though not available
* () Explore shadowing - used for custom value pickers
* () Explore static variables
* () Pyramid peak has double the segment time
* () Add slider where speeds are selected - not always work, the same as for defaults - works for SpinMe, not for creatting Pattern
* () Should Patterns be coded as classes? Can functions do the same? Any benefits from classes here? Try to refactor to take advantage of OOP
* check this thread https://stackoverflow.com/questions/6480676/why-use-classes-instead-of-functions
* (OK) doorButton may have reversed logic, what would require a unification in readButton - Done
* () Add pause/resume button - this one should use events
* (OK) Buttons mappings to be checked
* () Convert old WM project to use this extension
* () Add abort function e.x. by long-press of stopButton - to stop complex sequences
* (OK) Bit sizes (int8, int16 ..) are not supported inside class methods. No other restrictions are known.
*
*/
/**
* *****************************************************************************************************************************************
* Global variables section
*/
let lastDigit: number = 0; //!<This variable is used to store last display value of countdown timer to prevent too quick display refreshing
let userStop: boolean = false; //!<This variable is used to detect a User pressed the stopButton
/**
* *****************************************************************************************************************************************
* Enums section
*/
//This enum lists names of supported buttons
export enum buttonsNames {
//%block="Start"
startButton,
//%block="Stop"
stopButton,
//%block="Pause/Resume"
pauseButton,
//%block="Doors"
doorButton
}
//This enum lists braking options
export enum brakeOpt {
//%block="brake"
brake,
//%block="don't brake"
nobrake
}
//This enum lists the washing machine spinning direction
export enum dirOpt {
//%block="clockwise"
clockwise = 1,
//%block="counter clockwise"
cclockwise = -1
}
//This enum lists Pattern execution options
export enum modOpt {
//%block="pulse"
pulse,
//%block="steps"
steps,
//%block="pyramid"
pyramid
}
enum digitalPinButtons {
DigitalButtonStop = DigitalPin.P15
}
/**
* *****************************************************************************************************************************************
* Interface section
*/
let startButton = pins.P8;
let stopButton = pins.P15;
let pauseButton = pins.P12;
let doorButton = pins.P16;
startButton.setPull(PinPullMode.PullNone);
stopButton.setPull( PinPullMode.PullNone);
pauseButton.setPull(PinPullMode.PullNone);
doorButton.setPull( PinPullMode.PullNone);
//pins.setEvents(DigitalPin.P8, PinEventType.Edge);
pins.setEvents(DigitalPin.P15, PinEventType.None);
//pins.setEvents(DigitalPin.P12, PinEventType.Edge);
//pins.setEvents(DigitalPin.P16, PinEventType.Edge);
function stopButton_h() {
userStop = true;
}
function pauseButton_h() {
//TO DO
}
function doorButton_h() {
//userStop = true; //!<With this it becomes undistinguishable if it's a button press or door opening that stopped the cycle.
}
//control.onEvent(EventBusSource.MICROBIT_ID_IO_P8, DAL.MICROBIT_PIN_EVT_RISE, startButton_h);
//control.onEvent(EventBusSource.MICROBIT_ID_IO_P12, DAL.MICROBIT_PIN_EVT_RISE, pauseButton_h);
//control.onEvent(EventBusSource.MICROBIT_ID_IO_P16, DAL.MICROBIT_PIN_EVT_RISE, doorButton_h );
control.onEvent(EventBusSource.MICROBIT_ID_IO_P15, DAL.MICROBIT_PIN_EVT_RISE, stopButton_h);
/**
* *****************************************************************************************************************************************
* Functions section
*/
/**
* @function readButton Returns selected button state through digital pin reading.
* @param button Name of the button
* @returns State of the button which corresponds to its logical state i.e. 1 or 0 (pressed). Doors button has reverse logic,
* though this function alignes its with the other buttons
*/
//%block = "Check the %button button"
export function readButton(button: buttonsNames): number {
switch (button) {
case buttonsNames.startButton:
return pins.digitalReadPin(DigitalPin.P8);
break;
case buttonsNames.stopButton:
return pins.digitalReadPin(DigitalPin.P15);
break;
case buttonsNames.doorButton:
if (pins.digitalReadPin(DigitalPin.P16) == 1)
return 0;
else
return 1;
break;
default:
return 1; //!< could be missleading as 0 is one of the possible pin state, though using NaN could result in unknown behavior/exception
}
}
/**
* @function SpinMe Spins the motor in selected direction with given speed and for given time;
* After the time has elapsed motor stops with or without braking phase.
* @param direction Clockwise or counterclockwise selector
* @param speed Value of speed <0, 255>
* @param spinTime Value in [s]
* @param stopCmd Brake or nobrake selector. This parameter is optional with default value = brake (in case not given explicitly)
*/
//% block="Spin %direction with speed %speed for %spintime seconds.|| At the end: %stopCmd"
//% inlineInputMode=inline
//% stopCmd.defl=brakeOpt.brake
//% speed.min=0 speed.max=255
export function SpinMe(direction: dirOpt, speed: number, spinTime: number, stopCmd?: brakeOpt): void {
basic.showNumber(spinTime);
runMotor(direction, speed);
let startTstamp = control.millis();
pins.setEvents(digitalPinButtons.DigitalButtonStop, PinEventType.Edge);//<!Sets the listener for the Stop button
while ( (!monitorUserStop()) && runCountdown(startTstamp, spinTime));
pins.setEvents(digitalPinButtons.DigitalButtonStop, PinEventType.None);//<!Deregisters Stop button callback
motor.motorStop(motor.Motors.M1); //!<Not sure if this is needed or should be included in the if statement below
if (stopCmd == brakeOpt.brake) {
basic.pause(1800); //An arbitrary time interval to fully stop the motor.
}
basic.clearScreen();
basic.pause(200); //Check if needed
}
/**
* @function runMotor Start a motor in specified direction and speed
* @param direction Either CW or CCW
* @param speed Speed as a number in <0, 255> range
*/
function runMotor(direction: dirOpt, speed: number): void {
userStop = false; //!<This is needed as it is unknown if a User did pressed a button or not
switch (direction) {
case dirOpt.clockwise:
motor.MotorRun(motor.Motors.M1, motor.Dir.CW, speed);
break;
case dirOpt.cclockwise:
motor.MotorRun(motor.Motors.M1, motor.Dir.CCW, speed);
break;
default:
basic.showString("Err");
}
}
/**
* @function runCountdown Function counts the ms elapsed since the start_tstamp, compares it with time and update the display.
* Display is only updated on the counter change (in seconds)
* @param start_tstamp The moment in time [ms] with respect to which the counter is downcounting
* @param time The amount of time [s] for downcounting
*/
function runCountdown(start_tstamp: number, time: number): boolean {
let timeLeft: number;
time *= 1000;
if (lastDigit == 0)
lastDigit = time;
let current_tstamp = control.millis();
timeLeft = Math.trunc((time - (current_tstamp - start_tstamp)) / 1000);
if (timeLeft != lastDigit)
basic.showNumber(timeLeft);
if (timeLeft > 0) {
lastDigit = timeLeft;
return true;
}
else {
lastDigit = 0;
return false;
}
}
/**
* @function monitorUserStop Function that returns the boolean that corresponds to logical state of stopButton
*/
function monitorUserStop(): boolean {
return userStop;
}
/**
* *****************************************************************************************************************************************
* Classes section
*/
export class SpinPattern {
mode: modOpt;
noSteps: number;
stepTime: number;
direction: dirOpt;
speedA: number;
speedB: number;
constructor(mode: modOpt, noSteps: number, stepTime: number, direction: dirOpt, speedA: number, speedB: number){
this.mode = mode;
this.noSteps = noSteps;
this.stepTime = stepTime;
this.direction = direction;
this.speedA = speedA;
this.speedB = speedB;
}
/**
* @function executePattern This function execute created Pattern, at the moment three pre-defined patterns are available:
* Pulse, Step and Pyramid.
*/
//%block="Execute $this program"
//%this.defl=myPattern
public executePattern(): void {
switch(this.mode){
case modOpt.pulse:
this.executePulse();
break;
case modOpt.steps:
this.executeSteps();
break;
case modOpt.pyramid:
this.executePyramid();
break;
}
motor.motorStop(motor.Motors.M1);
basic.clearScreen();
}
executePyramid(){
this.executeSteps();
this.switchSpeeds();
this.executeSteps(); //!<The flaw of this simple apporach is that at peak of the pyramid segment time is doubled
this.switchSpeeds();
}
executePulse(){
for (let i = this.noSteps; i>0; i--) {
basic.showString("X" + i);
//runMotor( (this.direction = dirOpt.clockwise ? dirOpt.clockwise : dirOpt.cclockwise), this.speedA);
runMotor(this.direction, this.speedA);
this.completeSegment();
//runMotor( (this.direction = dirOpt.clockwise ? dirOpt.clockwise : dirOpt.cclockwise), this.speedB);
runMotor(this.direction, this.speedB);
this.completeSegment();
}
}
executeSteps(){
let speed: number;
let deltaSpeed: number = (this.speedB - this.speedA) / this.noSteps; //!<deltaSpeed can be positive and negative
for (let j = this.noSteps; j > 0; j--) {
speed = (this.noSteps - j) * deltaSpeed + this.speedA;
//runMotor( (this.direction = dirOpt.clockwise? dirOpt.clockwise : dirOpt.cclockwise), speed);
runMotor(this.direction, speed);
this.completeSegment();
}
}
completeSegment(): void {
let startTstamp = control.millis();
basic.showNumber(this.stepTime); //!< Check if no interference occured after having added it
pins.setEvents(digitalPinButtons.DigitalButtonStop, PinEventType.Edge);//<!Sets the listener for the Stop button
while ( (!monitorUserStop()) && runCountdown(startTstamp, this.stepTime));
pins.setEvents(digitalPinButtons.DigitalButtonStop, PinEventType.None);
}
switchSpeeds(): void {
let temp = this.speedA;
this.speedA = this.speedB;
this.speedB = temp;
}
}
/**
* Factory function exposing User Pattern constructor
*/
//% block="%mode pattern with %noSteps segments, each segment %stepTime [s] long. || Spin %direction with speeds between %speedA and %speedB"
//% direction.defl = dirOpt.clockwise
//% speedA.defl = 0
//% speedB.defl = 255
//% speedA.min=0 speedA.max=255
//% speedB.min=0 speedB.max=255
//% blockSetVariable=myPattern
//% inlineInputMode=inline
export function createPattern(mode: modOpt, noSteps: number, stepTime: number, direction?: dirOpt, speedA?: number, speedB?: number): SpinPattern {
return new SpinPattern(mode, noSteps, stepTime, direction, speedA, speedB);
}
}