GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Push — master ( d74eb3...fdd290 )
by Virantha
01:35
created

bricknil.sensor.ExternalMotor.set_pos()   A

Complexity

Conditions 1

Size

Total Lines 17
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nop 3
dl 0
loc 17
rs 10
c 0
b 0
f 0
1
# Copyright 2019 Virantha N. Ekanayake 
2
# 
3
# Licensed under the Apache License, Version 2.0 (the "License");
4
# you may not use this file except in compliance with the License.
5
# You may obtain a copy of the License at
6
# 
7
# http://www.apache.org/licenses/LICENSE-2.0
8
# 
9
# Unless required by applicable law or agreed to in writing, software
10
# distributed under the License is distributed on an "AS IS" BASIS,
11
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
# See the License for the specific language governing permissions and
13
# limitations under the License.
14
15
"""Actual sensor and motor peripheral definitions from Boost and PoweredUp
16
"""
17
from curio import sleep, current_task, spawn  # Needed for motor speed ramp
18
19
from enum import Enum, IntEnum
20
from struct import pack
21
from .const import Color
22
23
from .peripheral import Peripheral, Motor, TachoMotor
24
25
26
class InternalMotor(TachoMotor):
27
    """ Access the internal motor(s) in the Boost Move Hub.
28
29
        Unlike the train motors, these motors (as well as the stand-alone Boost
30
        motors :class:`ExternalMotor`) have a built-in sensor/tachometer for sending back
31
        the motor's current speed and position.  However, you don't need to use the
32
        sensors, and can treat this motor strictly as an output device.
33
34
        Examples::
35
36
            # Basic connection to the motor on Port A
37
            @attach(InternalMotor, name='left_motor', port=InternalMotor.Port.A)
38
39
            # Basic connection to both motors at the same time (virtual I/O port).
40
            # Any speed command will cause both motors to rotate at the same speed
41
            @attach(InternalMotor, name='motors', port=InternalMotor.Port.AB)
42
43
            # Report back when motor speed changes. You must have a motor_change method defined 
44
            @attach(InternalMotor, name='motor', port=InternalMotor.Port.A, capabilities=['sense_speed'])
45
46
            # Only report back when speed change exceeds 5 units
47
            @attach(InternalMotor, name='motors', port=InternalMotor.Port.A, capabilities=[('sense_speed', 5)])
48
49
        And within the run body you can control the motor output::
50
            await self.motor.set_speed(50)   # Setting the speed
51
            await self.motor.ramp_speed(80, 2000)  # Ramp speed to 80 over 2 seconds
52
            await self.motor.set_pos(90, speed=20) # Turn clockwise to 90 degree position
53
54
        See Also:
55
            * :class:`TrainMotor` for connecting to a train motor
56
            * :class:`ExternalMotor` for connecting to a train motor
57
58
    """
59
    _sensor_id = 0x0027
60
    _DEFAULT_THRESHOLD=2
61
    """Set to 2 to avoid a lot of updates since the speed seems to oscillate a lot"""
62
63
    Port = Enum('Port', 'A B AB', start=0)
64
    """Address either motor A or Motor B, or both AB at the same time"""
65
66
    def __init__(self, name, port=None, capabilities=[]):
67
        """Maps the port names `A`, `B`, `AB` to hard-coded port numbers"""
68
        if port:
69
            port_map = [55, 56, 57]
70
            port = port_map[port.value]
71
        self.speed = 0
72
        super().__init__(name, port, capabilities)
73
    
74
        
75
class ExternalMotor(TachoMotor):
76
    """ Access the stand-alone Boost motors
77
78
        These are similar to the :class:`InternalMotor` with build-in tachometer and
79
        sensor for sending back the motor's current speed and position.  You
80
        don't need to use the sensors, and can treat this as strictly an
81
        output.
82
83
        Examples::
84
85
            # Basic connection to the motor on Port A
86
            @attach(ExternalMotor, name='motor')
87
88
            # Report back when motor speed changes. You must have a motor_change method defined 
89
            @attach(ExternalMotor, name='motor', capabilities=['sense_speed'])
90
91
            # Only report back when speed change exceeds 5 units, and position changes (degrees)
92
            @attach(ExternalMotor, name='motor', capabilities=[('sense_speed', 5), 'sense_pos'])
93
94
        And then within the run body::
95
96
            await self.motor.set_speed(50)   # Setting the speed
97
            await self.motor.ramp_speed(80, 2000)  # Ramp speed to 80 over 2 seconds
98
            await self.motor.set_pos(90, speed=20) # Turn clockwise to 90 degree position
99
100
        See Also:
101
            * :class:`TrainMotor` for connecting to a train motor
102
            * :class:`InternalMotor` for connecting to the Boost hub built-in motors
103
104
    """
105
106
    _sensor_id = 0x26
107
108
109
class VisionSensor(Peripheral):
110
    """ Access the Boost Vision/Distance Sensor
111
112
        Only the sensing capabilities of this sensor is supported right now.
113
114
        - *sense_color*: Returns one of the 10 predefined colors
115
        - *sense_distance*: Distance from 0-7 in roughly inches
116
        - *sense_count*: Running count of waving your hand/item over the sensor (32-bit)
117
        - *sense_reflectivity*: Under distances of one inch, the inverse of the distance
118
        - *sense_ambient*: Distance under one inch (so inverse of the preceeding)
119
        - *sense_rgb*: R, G, B values (3 sets of uint16)
120
121
        Any combination of sense_color, sense_distance, sense_count, sense_reflectivity, 
122
        and sense_rgb is supported.
123
124
        Examples::
125
126
            # Basic distance sensor
127
            @attach(VisionSensor, name='vision', capabilities=['sense_color'])
128
            # Or use the capability Enum
129
            @attach(VisionSensor, name='vision', capabilities=[VisionSensor.capability.sense_color])
130
131
            # Distance and color sensor
132
            @attach(VisionSensor, name='vision', capabilities=['sense_color', 'sense_distance'])
133
134
            # Distance and rgb sensor with different thresholds to trigger updates
135
            @attach(VisionSensor, name='vision', capabilities=[('sense_color', 1), ('sense_rgb', 5)])
136
137
        The values returned by the sensor will always be available in the instance variable
138
        `self.value`.  For example, when the `sense_color` and `sense_rgb` capabilities are 
139
        enabled, the following values will be stored and updated::
140
141
            self.value = { VisionSensor.capability.sense_color:  uint8,
142
                           VisionSensor.capability.sense_rgb: 
143
                                            [ uint16, uint16, uint16 ]
144
                         }
145
146
        Notes:
147
            The actual modes supported by the sensor are as follows:
148
149
            -  0 = color (0-10)
150
            -  1 = IR proximity (0-7)
151
            -  2 = count (32-bit int)
152
            -  3 = Reflt   (inverse of distance when closer than 1")
153
            -  4 = Amb  (distance when closer than 1")
154
            -  5 = COL (output) ?
155
            -  6 = RGB I
156
            -  7 = IR tx (output) ?
157
            -  8 = combined:  Color byte, Distance byte, 0xFF, Reflected light
158
159
    """
160
161
    _sensor_id = 0x0025
162
    capability = Enum("capability", 
163
                      [('sense_color', 0),
164
                       ('sense_distance', 1),
165
                       ('sense_count', 2),
166
                       ('sense_reflectivity', 3),
167
                       ('sense_ambient', 4),
168
                       ('sense_rgb', 6),
169
                       ])
170
171
    datasets = { capability.sense_color: (1, 1),
172
                 capability.sense_distance: (1, 1),
173
                 capability.sense_count: (1, 4),  # 4-bytes (32-bit)
174
                 capability.sense_reflectivity: (1, 1),
175
                 capability.sense_ambient: (1, 1),
176
                 capability.sense_rgb: (3, 2)   # 3 16-bit values
177
                }
178
179
    allowed_combo = [ capability.sense_color,
180
                      capability.sense_distance,
181
                      capability.sense_count,
182
                      capability.sense_reflectivity,
183
                      capability.sense_rgb,
184
                    ]
185
186
class InternalTiltSensor(Peripheral):
187
    """
188
        Access the internal tilt sensor in the Boost Move Hub.
189
        
190
        The various modes are:
191
192
        - **sense_angle** - X, Y angles.  Both are 0 if hub is lying flat with button up
193
        - **sense_tilt** - value from 0-9 if hub is tilted around any of its axis. Seems to be
194
          a rough mesaure of how much the hub is tilted away from lying flat.
195
          There is no update for just a translation along an axis
196
        - **sense_orientation** - returns one of the nine orientations below (0-9)
197
            - `InternalTiltSensor.orientation`.up = flat with button on top
198
            - `InternalTiltSensor.orientation`.right - standing up on side closest to button
199
            - `InternalTiltSensor.orientation`.left - standing up on side furthest from button
200
            - `InternalTiltSensor.orientation`.far_side - on long face facing away
201
            - `InternalTiltSensor.orientation`.near_side -  on long face facing you
202
            - `InternalTiltSensor.orientation`.down - upside down
203
        - **sense_impact** - 32-bit count of impacts to sensor
204
        - **sense_acceleration_3_axis** - 3 bytes of raw accelerometer data.
205
206
        Any combination of the above modes are allowed.
207
208
        Examples::
209
210
            # Basic tilt sensor
211
            @attach(InternalTiltSensor, name='tilt', capabilities=['sense_tilt'])
212
            # Or use the capability Enum
213
            @attach(InternalTiltSensor, name='tilt', capabilities=[InternalTiltSensor.sense_tilt])
214
215
            # Tilt and orientation sensor
216
            @attach(InternalTiltSensor, name='tilt', capabilities=['sense_tilt, sense_orientation'])
217
218
        The values returned by the sensor will always be available in the
219
        instance variable `self.value`.  For example, when the `sense_angle`
220
        and `sense_orientation` capabilities are enabled, the following values
221
        will be stored and updated::
222
223
            self.value = { InternalTiltSensor.capability.sense_angle:  [uint8, uint8],
224
                           InternalTiltSensor.capability.sense_orientation: 
225
                                            Enum(InternalTiltSensor.orientation)
226
                         }
227
    """
228
    _sensor_id = 0x0028
229
    capability = Enum("capability", 
230
                      [('sense_angle', 0),
231
                       ('sense_tilt', 1),
232
                       ('sense_orientation', 2),
233
                       ('sense_impact', 3),
234
                       ('sense_acceleration_3_axis', 4),
235
                       ])
236
237
    datasets = { capability.sense_angle: (2, 1),
238
                 capability.sense_tilt: (1, 1),
239
                 capability.sense_orientation: (1, 1),  
240
                 capability.sense_impact: (1, 4),
241
                 capability.sense_acceleration_3_axis: (3, 1),
242
                }
243
244
    allowed_combo = [ capability.sense_angle,
245
                      capability.sense_tilt,
246
                      capability.sense_orientation,
247
                      capability.sense_impact,
248
                      capability.sense_acceleration_3_axis,
249
                    ]
250
251
    orientation = Enum('orientation', 
252
                        {   'up': 0,
253
                            'right': 1, 
254
                            'left': 2, 
255
                            'far_side':3,
256
                            'near_side':4,
257
                            'down':5,
258
                        })
259
260
261
    async def update_value(self, msg_bytes):
262
        """If sense_orientation, then substitute the `IntenalTiltSensor.orientation`
263
           enumeration value into the self.value dict.  Otherwise, don't do anything
264
           special to the self.value dict.
265
        """
266
        await super().update_value(msg_bytes)
267
        so = self.capability.sense_orientation
268
        if so in self.value:
269
            self.value[so] = self.orientation(self.value[so])
270
271
272
class LED(Peripheral):
273
    """ Changes the LED color on the Hubs::
274
275
            @attach(LED, name='hub_led')
276
277
            self.hub_led.set_output(Color.red)
278
    """
279
    _sensor_id = 0x0017
280
281
    async def set_color(self, color: Color):
282
        """ Converts a Color enumeration to a color value"""
283
284
        # For now, only support preset colors
285
        assert isinstance(color, Color)
286
        col = color.value
287
        assert col < 11
288
        mode = 0
289
        await self.set_output(mode, col)
290
        #b = [0x00, 0x81, self.port, 0x11, 0x51, mode, col ]
291
        #await self.message_info(f'set color to {color}')
292
293
294
class Light(Peripheral):
295
    """
296
        Connects to the external light.
297
298
        Example::
299
300
             @attach(Light, name='light')
301
302
        And then within the run body, use::
303
304
            await self.light.set_brightness(brightness)
305
    """
306
    _sensor_id = 0x0008
307
308
    async def set_brightness(self, brightness: int):
309
        """Sets the brightness of the light.
310
311
        Args:
312
            brightness (int) : A value between -100 and 100 where 0 is off and
313
                -100 or 100 are both maximum brightness.
314
        """
315
        mode = 0
316
        brightness, = pack('b', int(brightness))
317
        await self.set_output(mode, brightness)
318
319
320
class TrainMotor(Motor):
321
    """
322
        Connects to the train motors.
323
324
        TrainMotor has no sensing capabilities and only supports a single output mode that
325
        sets the speed.
326
327
        Examples::
328
329
             @attach(TrainMotor, name='train')
330
331
        And then within the run body, use::
332
333
            await self.train.set_speed(speed)
334
335
        Attributes:
336
            speed (int) : Keep track of the current speed in order to ramp it
337
338
        See Also:
339
            :class:`InternalMotor`
340
    """
341
    _sensor_id = 0x0002
342
343
344
345
class RemoteButtons(Peripheral):
346
    """Represents one set of '+', '-', 'red' buttons on the PoweredHub Remote
347
348
       Each remote has two sets of buttons, on the left and right side.  Pick the one
349
       your want to attach to by using the port argument with either Port.L or Port.R.
350
351
       There are actually a few different modes that the hardware supports, but we are
352
       only going to use one of them called 'KEYSD' (see the notes in the documentation on the
353
       raw values reported by the hub).  This mode makes the remote send three values back
354
       in a list.  To access each button state, there are three helper methods provided 
355
       (see below)
356
357
       Examples::
358
359
            # Basic connection to the left buttons
360
            @attach(RemoteButtons, name='left_buttons', port=RemoteButtons.Port.L)
361
362
            # Getting values back in the handler
363
            async def left_buttons_change(self):
364
365
                is_plus_pressed = self.left_buttons.plus_pressed()
366
                is_minus_pressed = self.left_buttons.minus_pressed()
367
                is_red_pressed = self.left_buttons.red_pressed()
368
369
    """
370
371
    _sensor_id = 0x0037
372
    Port = Enum('Port', 'L R', start=0)
373
    Button = IntEnum('Button', 'PLUS RED MINUS', start=0)
374
    """The button index in the value list returned by the sensor"""
375
376
    capability = Enum('capability', {'sense_press':4},)
377
378
    datasets = { capability.sense_press: (3,1) }
379
    allowed_combo = []
380
381
    def __init__(self, name, port=None, capabilities=[]):
382
        """Maps the port names `L`, `R`"""
383
        if port:
384
            port = port.value
385
        super().__init__(name, port, capabilities)
386
387
    def plus_pressed(self):
388
        """Return whether `value` reflects that the PLUS button is pressed"""
389
        button_list = self.value[self.capability.sense_press]
390
        return button_list[self.Button.PLUS] == 1
391
    def minus_pressed(self):
392
        """Return whether `value` reflects that the MINUS button is pressed"""
393
        button_list = self.value[self.capability.sense_press]
394
        return button_list[self.Button.MINUS] == 1
395
    def red_pressed(self):
396
        """Return whether `value` reflects that the RED button is pressed"""
397
        button_list = self.value[self.capability.sense_press]
398
        return button_list[self.Button.RED] == 1
399
400
class Button(Peripheral):
401
    """ Register to be notified of button presses on the Hub (Boost or PoweredUp)
402
403
        This is actually a slight hack, since the Hub button is not a peripheral that is 
404
        attached like other sensors in the Lego protocol.  Instead, the buttons are accessed
405
        through Hub property messages.  We abstract away these special messages to make the
406
        button appear to be like any other peripheral sensor.
407
408
        Examples::
409
410
            @attach(Button, name='hub_btn')
411
412
        Notes:
413
            Since there is no attach I/O message from the hub to trigger the
414
            :func:`activate_updates` method, we instead insert a fake
415
            "attaach" message from this fake sensor on port 255 in the
416
            `BLEventQ.get_messages` method that is used to register for updates
417
            from a given sensor.
418
419
    """
420
    _sensor_id = 0x0005
421
    """Piggy back the hub button off the normal peripheral button id 0x0005.
422
       Might need to change this in the future"""
423
424
    capability = Enum('capability', {'sense_press':0})
425
426
    datasets = { capability.sense_press: (1,1)
427
               }
428
    allowed_combo = [capability.sense_press]
429
430
    def __init__(self, name, port=None, capabilities=[]):
431
        """Call super-class with port set to 255 """
432
        super().__init__(name, 255, capabilities)
433
434
    async def activate_updates(self):
435
        """Use a special Hub Properties button message updates activation message"""
436
        self.value = {}
437
        for cap in self.capabilities:
438
            self.value[cap] = [None]*self.datasets[cap][0]
439
440
        b = [0x00, 0x01, 0x02, 0x02]  # Button reports from "Hub Properties Message Type"
441
        await self.send_message(f'Activate button reports: port {self.port}', b) 
442
443
class DuploTrainMotor(Motor):
444
    """Train Motor on Duplo Trains
445
446
       Make sure that the train is sitting on the ground (the front wheels need to keep rotating) in 
447
       order to keep the train motor powered.  If you pick up the train, the motor will stop operating
448
       withina few seconds.
449
450
       Examples::
451
452
            @attach(DuploTrainMotor, name='motor')
453
454
       And then within the run body, use::
455
456
            await self.train.set_speed(speed)
457
458
       Attributes:
459
            speed (int): Keep track of the current speed in order to ramp it
460
461
       See Also:
462
            :class:`TrainMotor` for connecting to a PoweredUp train motor
463
    """
464
    _sensor_id = 0x0029
465
466
class DuploSpeedSensor(Peripheral):
467
    """Speedometer on Duplo train base that measures front wheel speed.
468
469
       This can measure the following values:
470
471
       - *sense_speed*: Returns the speed of the front wheels
472
       - *sense_count*: Keeps count of the number of revolutions the front wheels have spun
473
474
       Either or both can be enabled for measurement. 
475
476
       Examples::
477
478
            # Report speed changes
479
            @attach(DuploSpeedSensor, name='speed_sensor', capabilities=['sense_speed'])
480
481
            # Report all
482
            @attach(DuploSpeedSensor, name='speed_sensor', capabilities=['sense_speed', 'sense_count'])
483
484
       The values returned by the sensor will be in `self.value`.  For the first example, get the
485
       current speed by::
486
487
            speed = self.speed_sensor.value
488
        
489
       For the second example, the two values will be in a dict::
490
491
            speed = self.speed_sensor.value[DuploSpeedSensor.sense_speed]
492
            revs  = self.speed_sensor.value[DuploSpeedSensor.sense_count]
493
494
    """
495
    _sensor_id = 0x002C
496
    capability = Enum("capability", 
497
                      [('sense_speed', 0),
498
                       ('sense_count', 1),
499
                       ])
500
501
    datasets = { capability.sense_speed: (1, 2),
502
                 capability.sense_count: (1, 4),
503
                }
504
505
    allowed_combo = [ capability.sense_speed,
506
                      capability.sense_count,
507
                    ]
508
509
class DuploVisionSensor(Peripheral):
510
    """ Access the Duplo Vision/Distance Sensor
511
512
        - *sense_color*: Returns one of the 10 predefined colors
513
        - *sense_ctag*: Returns one of the 10 predefined tags
514
        - *sense_reflectivity*: Under distances of one inch, the inverse of the distance
515
        - *sense_rgb*: R, G, B values (3 sets of uint16)
516
517
        Any combination of sense_color, sense_ctag, sense_reflectivity, 
518
        and sense_rgb is supported.
519
520
        Examples::
521
522
            # Basic color sensor
523
            @attach(DuploVisionSensor, name='vision', capabilities=['sense_color'])
524
            # Or use the capability Enum
525
            @attach(DuploVisionSensor, name='vision', capabilities=[DuploVisionSensor.capability.sense_color])
526
527
            # Ctag and reflectivity sensor
528
            @attach(DuploVisionSensor, name='vision', capabilities=['sense_ctag', 'sense_reflectivity'])
529
530
            # Distance and rgb sensor with different thresholds to trigger updates
531
            @attach(DuploVisionSensor, name='vision', capabilities=[('sense_color', 1), ('sense_rgb', 5)])
532
533
        The values returned by the sensor will always be available in the instance variable
534
        `self.value`.  For example, when the `sense_color` and `sense_rgb` capabilities are 
535
        enabled, the following values will be stored and updated::
536
537
            self.value = { DuploVisionSensor.capability.sense_color:  uint8,
538
                           DuploVisionSensor.capability.sense_rgb: 
539
                                            [ uint16, uint16, uint16 ]
540
                         }
541
542
        Notes:
543
            The actual modes supported by the sensor are as follows:
544
545
            -  0 = color (0-10)
546
            -  1 = ctag (32-bit int)
547
            -  2 = Reflt   (inverse of distance when closer than 1")
548
            -  3 = RGB I
549
    """
550
    _sensor_id = 0x002B
551
    capability = Enum("capability", 
552
                      [('sense_color', 0),
553
                       ('sense_ctag', 1),
554
                       ('sense_reflectivity', 2),
555
                       ('sense_rgb', 3),
556
                       ])
557
558
    datasets = { capability.sense_color: (1, 1),
559
                 capability.sense_ctag: (1, 1),  # 4-bytes (32-bit)
560
                 capability.sense_reflectivity: (1, 1),
561
                 capability.sense_rgb: (3, 2)   # 3 16-bit values
562
                }
563
564
    allowed_combo = [ capability.sense_color,
565
                      capability.sense_ctag,
566
                      capability.sense_reflectivity,
567
                      capability.sense_rgb,
568
                    ]
569
570
class DuploSpeaker(Peripheral):
571
    """Plays one of five preset sounds through the Duplo built-in speaker
572
573
       See :class:`sounds` for the list.
574
575
       Examples::
576
577
            @attach(DuploSpeaker, name='speaker')
578
            ...
579
            await self.speaker.play_sound(DuploSpeaker.sounds.brake)
580
           
581
       Notes:
582
            Uses Mode 1 to play the presets
583
584
    """
585
    _sensor_id = 0x002A
586
    sounds = Enum('sounds', { 'brake': 3,
587
                              'station': 5,
588
                              'water': 7,
589
                              'horn': 9,
590
                              'steam': 10,
591
                              })
592
593
    async def activate_updates(self):
594
        """For some reason, even though the speaker is an output device
595
           we need to send a Port Input Format Setup command (0x41) to enable
596
           notifications.  Otherwise, none of the sound output commands will play.  This function
597
           is called automatically after this sensor is attached.
598
        """
599
        mode = 1
600
        b = [0x00, 0x41, self.port, mode, 0x01, 0x00, 0x00, 0x00, 0x01]
601
        await self.send_message('Activate DUPLO Speaker: port {self.port}', b)
602
603
    async def play_sound(self, sound):
604
        assert isinstance(sound, self.sounds), 'Can only play sounds that are enums (DuploSpeaker.sounds.brake, etc)'
605
        mode = 1
606
        self.message_info(f'Playing sound {sound.name}:{sound.value}')
607
        await self.set_output(mode, sound.value)
608
609
610
class VoltageSensor(Peripheral):
611
    """Voltage sensor
612
613
       Returns the raw mV value (0-3893) which probably needs to be scaled to 0-9600.
614
615
       It contains two capabilities, although they both appear to do the same thing:
616
       * sense_l
617
       * sense_s
618
619
       Examples::
620
621
            @attach(VoltageSensor, name='volts', capabilities=['sense_l'])
622
623
    """
624
    _sensor_id = 0x14
625
626
    capability = Enum("capability", {'sense_s': 0, 'sense_l': 1})
627
    datasets = {capability.sense_s: (1, 2),   # 2-bytes (16-bit)
628
                capability.sense_l: (1, 2), 
629
               }
630
    allowed_combo = [ ]
631
632
class CurrentSensor(Peripheral):
633
    """Voltage sensor
634
635
       Returns the raw mA value (0-4095) which probably needs to be scaled to 0-2444.
636
637
       It contains two capabilities, although they both appear to do the same thing:
638
       * sense_l
639
       * sense_s
640
641
       Examples::
642
643
            @attach(CurrentSensor, name='cur', capabilities=['sense_l'])
644
645
    """
646
    _sensor_id = 0x15
647
648
    capability = Enum("capability", {'sense_s': 0, 'sense_l': 1})
649
    datasets = {capability.sense_s: (1, 2),   # 2-bytes (16-bit)
650
                capability.sense_l: (1, 2), 
651
               }
652
    allowed_combo = [ ]
653
654
655