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 ( d19b58...5ac45b )
by Virantha
01:51 queued 10s
created

bricknil.sensor.Light.set_brightness()   A

Complexity

Conditions 1

Size

Total Lines 10
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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