@tigerpython/robotics-libraries 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/CHANGELOG +68 -0
  2. package/LICENSE +373 -0
  3. package/README.md +81 -0
  4. package/calliope/README.md +15 -0
  5. package/calliope/callibot.py +176 -0
  6. package/calliope/callibotmot.py +46 -0
  7. package/calliope/callimk.py +143 -0
  8. package/calliope/cbalarm.py +14 -0
  9. package/calliope/cpglow.py +76 -0
  10. package/calliope/cpmike.py +16 -0
  11. package/calliope/cprover.py +33 -0
  12. package/calliope/cputils.py +23 -0
  13. package/calliope/libraries.json +10 -0
  14. package/calliope/libraries.raw.json +10 -0
  15. package/calliope/min/callibot.py +97 -0
  16. package/calliope/min/callibotmot.py +23 -0
  17. package/calliope/min/callimk.py +47 -0
  18. package/calliope/min/cbalarm.py +6 -0
  19. package/calliope/min/cpglow.py +29 -0
  20. package/calliope/min/cpmike.py +8 -0
  21. package/calliope/min/cprover.py +9 -0
  22. package/calliope/min/cputils.py +7 -0
  23. package/dist/index.d.mts +124 -0
  24. package/dist/index.d.ts +124 -0
  25. package/dist/index.js +2896 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/index.mjs +2862 -0
  28. package/dist/index.mjs.map +1 -0
  29. package/microbit/README.md +48 -0
  30. package/microbit/controller.py +212 -0
  31. package/microbit/huskylens.py +479 -0
  32. package/microbit/libraries.json +19 -0
  33. package/microbit/libraries.raw.json +19 -0
  34. package/microbit/mbalarm.py +15 -0
  35. package/microbit/mbbitbot.py +127 -0
  36. package/microbit/mbglow.py +77 -0
  37. package/microbit/mbled.py +56 -0
  38. package/microbit/mbmarsrover.py +216 -0
  39. package/microbit/mbminibit.py +139 -0
  40. package/microbit/mbrobot.py +401 -0
  41. package/microbit/mbrobot_legacy.py +90 -0
  42. package/microbit/mbrobot_plus.py +179 -0
  43. package/microbit/mbrobot_plusV2.py +490 -0
  44. package/microbit/mbrobot_plusV3.py +427 -0
  45. package/microbit/mbrobotmot.py +49 -0
  46. package/microbit/mbthetabot.py +167 -0
  47. package/microbit/mbwait.py +50 -0
  48. package/microbit/mbxgo.py +173 -0
  49. package/microbit/min/controller.py +42 -0
  50. package/microbit/min/huskylens.py +145 -0
  51. package/microbit/min/mbalarm.py +6 -0
  52. package/microbit/min/mbbitbot.py +40 -0
  53. package/microbit/min/mbglow.py +29 -0
  54. package/microbit/min/mbled.py +18 -0
  55. package/microbit/min/mbmarsrover.py +62 -0
  56. package/microbit/min/mbminibit.py +50 -0
  57. package/microbit/min/mbrobot.py +82 -0
  58. package/microbit/min/mbrobot_legacy.py +45 -0
  59. package/microbit/min/mbrobot_plus.py +75 -0
  60. package/microbit/min/mbrobot_plusV2.py +102 -0
  61. package/microbit/min/mbrobot_plusV3.py +194 -0
  62. package/microbit/min/mbrobotmot.py +25 -0
  63. package/microbit/min/mbthetabot.py +47 -0
  64. package/microbit/min/mbwait.py +23 -0
  65. package/microbit/min/mbxgo.py +37 -0
  66. package/package.json +54 -0
@@ -0,0 +1,490 @@
1
+ # mbrobot_plusV2.py
2
+ # Date 10/09/24
3
+
4
+ from microbit import i2c, pin0, pin1, pin2, pin13, pin14, pin15, sleep
5
+ import gc
6
+ import machine
7
+ import music
8
+ import neopixel
9
+
10
+ # Motor state
11
+ _speedPercent = 50
12
+ _powerByteL = 50
13
+ _powerByteR = 50
14
+ _motorState = bytearray(5)
15
+ _powerBytesLUT = bytes(b'\x00\x0f\x10\x10\x11\x12\x12\x13\x13\x14\x15\x15\x16\x16\x17\x18\x18\x19\x19\x1a\x1b\x1b\x1c\x1c\x1d\x1e\x1e\x1f\x1f\x20\x21\x21\x22\x22\x23\x24\x24\x25\x25\x26\x27\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3b\x3c\x3e\x3f\x41\x43\x45\x47\x49\x4b\x4d\x50\x52\x55\x58\x5b\x5e\x61\x65\x69\x6d\x71\x75\x7a\x7f\x84\x89\x8f\x95\x9b\xa2\xa9\xb1\xb9\xc1\xca\xd3\xdd\xe8\xf3\xff')
16
+
17
+ # Calibration data
18
+ _powerOffset = 0
19
+ _powerDifferential = 0
20
+ _arcScaling = 0
21
+ _servoMinPulse = 25
22
+ _servoMaxPulse = 131
23
+
24
+ # Signaling objects and buffers
25
+ _ledState = bytearray(b'\x0B\0\0')
26
+ _underglowNP = neopixel.NeoPixel(pin15, 4)
27
+ np_rgb_pixels = _underglowNP
28
+ _alarmSequence = ['c5:1', 'r', 'c5,1', 'r:3']
29
+
30
+ _UNCONNECTEDERRORMSG = "Please connect to Maqueen robot and switch it on."
31
+
32
+ # Utility functions
33
+
34
+
35
+ def _setMotors(dirL, powerL, dirR, powerR):
36
+ # """Write Motor State via i2c
37
+
38
+ # Parameters:
39
+ # dirL (0/1): Direction of left Wheel. 0=forward, 1=backward
40
+ # powerL (int): Power of left Wheel in range [0,255].
41
+ # dirR (0/1): Direction of right Wheel. 0=forward, 1=backward
42
+ # powerR (int): Power of right Wheel in range [0,255].
43
+ #
44
+ # raises:
45
+ # RuntimeError: if Robot is switched off or unconnected. (replaces ENODEV error)
46
+ # """
47
+ global _motorState
48
+ _motorState[1] = dirL
49
+ _motorState[2] = powerL
50
+ _motorState[3] = dirR
51
+ _motorState[4] = powerR
52
+ try:
53
+ i2c.write(0x10, _motorState)
54
+ except:
55
+ raise RuntimeError(_UNCONNECTEDERRORMSG)
56
+
57
+
58
+ def _setSingleMotor(side, dir, power):
59
+ # """Write Motor State of a single Motor via i2c
60
+
61
+ # Parameters:
62
+ # side (0/2): Selection of the Wheel. 0=left, 2=right
63
+ # dir (0/1): Direction for that Wheel. 0=forward, 1=backward
64
+ # power (int): Power for that Wheel in range [0,255].
65
+ #
66
+ # raises:
67
+ # RuntimeError: if Robot is switched off or unconnected. (replaces ENODEV error)
68
+ # """
69
+ global _motorState
70
+ _motorState[1 + side] = dir
71
+ _motorState[2 + side] = power
72
+ try:
73
+ i2c.write(0x10, _motorState)
74
+ except:
75
+ raise RuntimeError(_UNCONNECTEDERRORMSG)
76
+
77
+ # _getPowerByte was used to create LookUpTable (_powerBytesLUT) used in _getPowerByteLUT to reduce Memory Leaks.
78
+ # def _getPowerByte(speed, offset):
79
+ # # """Computes the power value for a given speed in %.
80
+ # # It accouts for the nonlinearity of motor strength.
81
+ # # This is the original function that generates the LUT.
82
+ #
83
+ # # Parameter:
84
+ # # speed (number): Desired speed of the Robot in %. Range [0,100].
85
+ # # offset (int): basic power offset to add to the function.
86
+ # # Returns:
87
+ # # int: Power in range [0,255] to write to the motors via i2c.
88
+ # # """
89
+ # if speed <= 0:
90
+ # return 0
91
+ # elif speed < 40:
92
+ # return int(0.6 * speed + 15 + offset)
93
+ # elif speed < 60:
94
+ # return int(speed + offset)
95
+ # elif speed < 100:
96
+ # return int(min(1.05546 ** speed + 34 + offset, 255))
97
+ # else:
98
+ # return 255
99
+
100
+
101
+ def _getPowerByteLUT(speed, offset):
102
+ # """Lookup Table version of _getPowerByte"""
103
+ return min(_powerBytesLUT[speed] + offset, 255)
104
+
105
+
106
+ def _getArcBytes(r):
107
+ # """Computes the power bytes to drive an arc.
108
+
109
+ # Parameter:
110
+ # r (float): Radius in meters of the desired Arc.
111
+ # Measured from the center of the axle.
112
+ # Returns:
113
+ # (int, int): Power byte values for each motor.
114
+ # First the outer Wheels byte [0,255],
115
+ # then the inner Wheels byte [0,255].
116
+ # """
117
+ rmm = int(r * 100) # radius in mm
118
+ outerSpeed = _speedPercent
119
+ # adjust outer speed for unhealthy values
120
+ if outerSpeed < 25:
121
+ outerSpeed = 25
122
+ speedFix = min(abs(outerSpeed - 70), 20) / 20
123
+ reducedSpeed = 0
124
+ if rmm > 5:
125
+ # formula derived from data and simplified
126
+ n = outerSpeed * (3 * _arcScaling - outerSpeed - 9 * rmm + 220)
127
+ d = -14 * _arcScaling + outerSpeed - 200 + 3 * outerSpeed - 10 * rmm - 290
128
+ reducedSpeed = int(n/d)
129
+ if reducedSpeed < 2: # fix values at low radii (negative values too)
130
+ reducedSpeed = 2 if rmm > 15 else 1
131
+ innerByte = _getPowerByteLUT(int(reducedSpeed), 0)
132
+ outerByte = _getPowerByteLUT(int(outerSpeed), 0)
133
+ return (innerByte, outerByte)
134
+
135
+ # Movement functions
136
+
137
+
138
+ def calibrate(offset, differential=0, arcScaling=0):
139
+ # """Adjust the driving behaviour of the robots
140
+
141
+ # Parameters:
142
+ # offset (int): Offsets the minimal power of the motors.
143
+ # Range [-10,50]. Highly affected by battery level.
144
+ # Adjust this value until it starts moving at speed 1%.
145
+ # differential (int, optional): Adjusts power difference
146
+ # of left and right Wheel. Range [-150, 150].
147
+ # Varies unpredictably with different speeds.
148
+ # If a Robot steers left when driving forward: negative value
149
+ # If a Robot steers right when driving forward: positive value
150
+ # Perfectly straight driving Robots can leave this at 0.
151
+ # arcScaling (int, optional): Adjusts the radius
152
+ # driven by leftArc/rightArc. Valid range [-50, 50].
153
+ # If the Robots radius is too large: positive value
154
+ # If the Robots radius is too small: negative value
155
+ # This then adjusts all radii for this Robot, by
156
+ # scaling it's internal function to the new range.
157
+ # """
158
+ global _powerDifferential
159
+ global _powerOffset
160
+ global _arcScaling
161
+ _powerOffset = max(min(int(offset), 50), -14)
162
+ _powerDifferential = max(min(int(differential), 150), -150)
163
+ _arcScaling = max(min(arcScaling, 50), -50)
164
+ setSpeed(_speedPercent)
165
+
166
+
167
+ def setSpeed(speed):
168
+ # """sets the speed for future motion
169
+
170
+ # Parameter:
171
+ # speed (int): in Range [0,100] as % of desired velocity.
172
+ # """
173
+ global _speedPercent
174
+ global _powerByteL
175
+ global _powerByteR
176
+ _speedPercent = int(min(max(speed, 0), 100))
177
+ powerByte = _getPowerByteLUT(_speedPercent, _powerOffset)
178
+ boost = round((1 - _speedPercent / 100) *
179
+ abs(_powerDifferential)) if _speedPercent > 0 else 0
180
+ reduction = round((_speedPercent / 100) * abs(_powerDifferential))
181
+ if _powerDifferential > 0:
182
+ _powerByteL = powerByte - reduction
183
+ _powerByteR = powerByte + boost
184
+ else:
185
+ _powerByteL = powerByte + boost
186
+ _powerByteR = powerByte - reduction
187
+
188
+
189
+ def resetSpeed():
190
+ setSpeed(50)
191
+
192
+
193
+ def stop():
194
+ _setMotors(0, 0, 0, 0)
195
+
196
+
197
+ def forward():
198
+ _setMotors(0, _powerByteL, 0, _powerByteR)
199
+
200
+
201
+ def backward():
202
+ _setMotors(1, _powerByteL, 1, _powerByteR)
203
+
204
+
205
+ def left():
206
+ _setMotors(1, _powerByteL, 0, _powerByteR)
207
+
208
+
209
+ def right():
210
+ _setMotors(0, _powerByteL, 1, _powerByteR)
211
+
212
+
213
+ def rightArc(radius):
214
+ # """radius must be given in meters."""
215
+ inner, outer = _getArcBytes(radius)
216
+ _setMotors(0, outer, 0, inner)
217
+
218
+
219
+ def leftArc(radius):
220
+ # """radius must be given in meters."""
221
+ inner, outer = _getArcBytes(radius)
222
+ _setMotors(0, inner, 0, outer)
223
+
224
+
225
+ class Motor:
226
+ def __init__(self, side):
227
+ # """Create a single motor.
228
+
229
+ # Parameter:
230
+ # side (0/2): 0=left, 2=right
231
+ # """
232
+ self._side = side
233
+
234
+ def rotate(self, speed):
235
+ # """Controls rotation of this motor.
236
+
237
+ # Parameters:
238
+ # speed (int): Desired speed in %.
239
+ # Valid range [-100,100].
240
+ # Negative values are for backward turning.
241
+ # """
242
+ speedClamped = int(min(max(abs(speed), 0), 100))
243
+ power = _getPowerByteLUT(speedClamped, _powerOffset)
244
+ direction = 0 if speed > 0 else 1
245
+ _setSingleMotor(self._side, direction, power)
246
+
247
+
248
+ def setServo(servo, angle):
249
+ # """Moves the Servo to position angle.
250
+ # Servos must be connected to the Maqueen Plus V2's "P" connectors.
251
+ # They are located at the back of the robot.
252
+
253
+ # Parameters:
254
+ # servo (str): Desired Servo Port. Either 'P0', 'P1' or 'P2'.
255
+ # angle (int): Desired angle in degrees. Range [0,180].
256
+
257
+ # raises:
258
+ # ValueError: if arguments are out of valid range
259
+ # """
260
+ if servo == "P0" or servo == 'S1':
261
+ pin = pin0
262
+ elif servo == "P1" or servo == 'S2':
263
+ pin = pin1
264
+ elif servo == "P2":
265
+ pin = pin2
266
+ else:
267
+ raise ValueError("Unknown Servo. Please use 'P0', 'P1' or 'P2'.")
268
+
269
+ if angle < 0 or angle > 180:
270
+ raise ValueError("Invalid angle. Must be between 0 and 180")
271
+
272
+ frac = (_servoMaxPulse - _servoMinPulse) * int(angle)
273
+ offset = (frac >> 8) + (frac >> 10) + (frac >> 11) + (frac >> 12) # / 180
274
+ usPulseTime = _servoMinPulse + offset # min + (max-min) * (angle / 180)
275
+ pin.set_analog_period(20)
276
+ pin.write_analog(usPulseTime)
277
+
278
+
279
+ def setMinAngleVal(duty):
280
+ # """ Sets the minimal pulse duty cycle for the servo.
281
+ # It should match 1ms of a 20ms period, where the duty is in range [0,1024].
282
+ # Theoretically: 1/20*1024 = 51. Practically: Default is 25. adjust carefully in steps of 1.
283
+ #
284
+ # Parameter:
285
+ # duty (int): The minimal duty amount for a write_analog signal with 20ms pulse.
286
+ # It should approximate 1ms. 1ms/20ms*1024 = min duty = 0-degree position for the servo.
287
+ # """
288
+ global _servoMinPulse
289
+ _servoMinPulse = int(duty)
290
+
291
+
292
+ def setMaxAngleVal(duty):
293
+ # """ Sets the maximal pulse duty cycle for the servo.
294
+ # It should match 2ms of a 20ms period, where the duty is in range [0,1024].
295
+ # Theoretically: 2/20*1024 = 102. Practically: Default is 131. adjust carefully to increase range of the servo.
296
+ #
297
+ # Parameter:
298
+ # duty (int): The maximal duty amount for a write_analog signal with 20ms pulse.
299
+ # It should approximate 2ms. 2ms/20ms*1024 = max duty = 180-degree position for the servo.
300
+ # """
301
+ global _servoMaxPulse
302
+ _servoMaxPulse = int(duty)
303
+
304
+ # Sensor functions
305
+
306
+
307
+ class IRSensor:
308
+ _address = bytes(b'\x1D')
309
+
310
+ def __init__(self, index):
311
+ # """Create a new IR sensor.
312
+
313
+ # Parameter:
314
+ # index (int): 0=R2, 1=R1, 2=M, 3=L1, 4=L2
315
+ # """
316
+ self._index = index
317
+
318
+ def read_digital(self):
319
+ # """Returns if the surface below is dark or bright.
320
+ # Result can be adjusted by putting the sensor on the dark
321
+ # surface and pressing the LineKey calibration button on the
322
+ # Robot for a few seconds, until the LED's blink.
323
+
324
+ # Returns:
325
+ # 0 if the surface is dark. No light was reflected (in Air).
326
+ # 1 if the surface is bright. A lot of light was reflected.
327
+ #
328
+ # raises:
329
+ # RuntimeError: if Robot is switched off or unconnected. (replaces ENODEV error)
330
+ # """
331
+ try:
332
+ i2c.write(0x10, IRSensor._address)
333
+ except:
334
+ raise RuntimeError(_UNCONNECTEDERRORMSG)
335
+ byte = ~i2c.read(0x10, 1)[0]
336
+ # mask out corresponding bit, from returned byte.
337
+ return (byte & (2 ** self._index)) >> self._index
338
+
339
+ def read_analog(self):
340
+ # """Returns the brightness of the surface as a byte.
341
+
342
+ # Returns:
343
+ # int: amount of reflection of light in range [0,255].
344
+ #
345
+ # raises:
346
+ # RuntimeError: if Robot is switched off or unconnected. (replaces ENODEV error)
347
+ # """
348
+ try:
349
+ i2c.write(0x10, IRSensor._address)
350
+ except:
351
+ raise RuntimeError(_UNCONNECTEDERRORMSG)
352
+ # Buffer structure:
353
+ # 1 Byte Bitmask (for digital redout),
354
+ # then 10 Bytes analog byte values (every second entry is a value).
355
+ buffer = i2c.read(0x10, 11)
356
+ return buffer[1 + self._index * 2]
357
+
358
+
359
+ def ir_read_values_as_byte():
360
+ # """get single byte with bit encoding of each Infrared sensor state
361
+ # Bits: 0=R2, 1=R1, 2=M, 3=L1, 4=L2
362
+ # """
363
+ i2c.write(0x10, bytearray([0x1D]))
364
+ buf = i2c.read(0x10, 1)
365
+ return ~buf[0]
366
+
367
+
368
+ def getDistance():
369
+ # """uses the ultrasonic sensor to measure distance
370
+
371
+ # Returns:
372
+ # int: valid Distance as cm in range [0,500].
373
+ # For measurement errors or larger distances: 255.
374
+ # """
375
+ pin13.write_digital(1)
376
+ pin13.write_digital(0)
377
+ p = machine.time_pulse_us(pin14, 1, 50000)
378
+ # approximate division: p / 58.2 - 0.5
379
+ cm = (p >> 6) + (p >> 10) + (p >> 11) + (p >> 12) + 1
380
+ return max(min(cm, 500), 0) if cm > 0 else 255
381
+
382
+ # Signaling functions
383
+
384
+
385
+ def setLED(state, stateR=None):
386
+ # """Set the front red LED's.
387
+
388
+ # Parameters:
389
+ # state (0/1): Sets the state of the left LED.
390
+ # if stateR is omitted, then both LEDS.
391
+ # 0=Off, 1=On
392
+ # stateR (0/1/None, optional): Sets the right LED state.
393
+ # 0=Off, 1=On, Default=None uses "state" for right LED.
394
+ # """
395
+ global _ledState
396
+ stateR = stateR if stateR != None else state
397
+ _ledState[1] = state
398
+ _ledState[2] = stateR
399
+ i2c.write(0x10, _ledState)
400
+
401
+
402
+ def setLEDLeft(state):
403
+ # """state: 0=Off, 1=On"""
404
+ global _ledState
405
+ _ledState[1] = state
406
+ i2c.write(0x10, _ledState)
407
+
408
+
409
+ def setLEDRight(state):
410
+ # """state: 0=Off, 1=On"""
411
+ global _ledState
412
+ _ledState[2] = state
413
+ i2c.write(0x10, _ledState)
414
+
415
+
416
+ def fillRGB(red, green, blue):
417
+ # """Uses Neopixel to set all 4 bottom RGB LEDs color.
418
+ # Parameters (red,green,blue) are each a byte in Range [0,255].
419
+ # """
420
+ for i in range(4):
421
+ _underglowNP[i] = (red, green, blue)
422
+ _underglowNP.show()
423
+ setRGB=fillRGB
424
+
425
+
426
+ def clearRGB():
427
+ _underglowNP.clear()
428
+
429
+ def posRGB(position, red, green, blue):
430
+ # """Uses Neopixel to set a single RGB LED of the robot.
431
+
432
+ # Parameters:
433
+ # position (int): position of the targeted LED.
434
+ # Numbers are visible at underside of Robot.
435
+ # 0=front left
436
+ # 1=back left
437
+ # 2=back right
438
+ # 3=front right
439
+ # red, green, blue (int): color byte value.
440
+ # each in range [0,255].
441
+
442
+ # raises:
443
+ # ValueError: if position argument is out of valid range
444
+ # """
445
+ if position < 0 or position > 3:
446
+ raise ValueError("invalid RGB-LED position. Must be 0,1,2 or 3.")
447
+ _underglowNP[position] = (red, green, blue)
448
+ _underglowNP.show()
449
+
450
+
451
+ def setAlarm(state):
452
+ if state:
453
+ music.play(_alarmSequence, wait=False, loop=True)
454
+ else:
455
+ music.stop()
456
+
457
+
458
+ def beep():
459
+ music.pitch(440, 200, wait=False)
460
+
461
+ # Class constants (for compatibilty)
462
+
463
+
464
+ class LEDState:
465
+ ON = 1
466
+ OFF = 0
467
+ RED = 1
468
+
469
+
470
+ class IR:
471
+ R2 = 0
472
+ R1 = 1
473
+ M = 2
474
+ L1 = 3
475
+ L2 = 4
476
+ masks = [0x01, 0x02, 0x04, 0x08, 0x10]
477
+
478
+
479
+ # Default instances
480
+ pin2.set_pull(pin2.NO_PULL)
481
+ delay = sleep
482
+ irR2 = IRSensor(0)
483
+ irR1 = IRSensor(1)
484
+ irRight = irR1
485
+ irM = IRSensor(2)
486
+ irL1 = IRSensor(3)
487
+ irLeft = irL1
488
+ irL2 = IRSensor(4)
489
+ motL = Motor(0)
490
+ motR = Motor(2)