class MicrophoneService(AdafruitService): # pylint: disable=too-few-public-methods """Digital microphone data.""" uuid = AdafruitService.adafruit_service_uuid(0xB00) sound_samples = Characteristic( uuid=AdafruitService.adafruit_service_uuid(0xB01), properties=(Characteristic.READ | Characteristic.NOTIFY), write_perm=Attribute.NO_ACCESS, max_length=512, ) """ Array of 16-bit sound samples, varying based on period. If num_channel == 2, the samples alternate left and right channels. """ number_of_channels = Uint8Characteristic( uuid=AdafruitService.adafruit_service_uuid(0xB02), properties=Characteristic.READ, write_perm=Attribute.NO_ACCESS, ) """1 for mono, 2 for stereo (left and right)""" measurement_period = AdafruitService.measurement_period_charac() """Initially 1000ms."""
class GestureService(AdafruitService): # pylint: disable=too-few-public-methods """Gesture sensor.""" UP = 1 # pylint: disable=invalid-name """swipe up""" DOWN = 2 """swipe down""" LEFT = 3 """swipe left""" RIGHT = 4 """swipe right""" uuid = AdafruitService.adafruit_service_uuid(0xF00) gesture = Uint8Characteristic( uuid=AdafruitService.adafruit_service_uuid(0xF01), properties=(Characteristic.READ | Characteristic.NOTIFY), read_perm=Attribute.OPEN, write_perm=Attribute.NO_ACCESS, ) """ 0: no gesture 1: swipe up (`UP`) 2: swipe down (`DOWN`) 3: swipe left (`LEFT`) 4: swipe right (`RIGHT`) """ measurement_period = AdafruitService.measurement_period_charac(0) """Initially 0: send notification only on changes. -1 means stop reading."""
class AddressablePixelService(AdafruitService): """Control of NeoPixels, DotStars, etc.""" uuid = AdafruitService.adafruit_service_uuid(0x900) pixel_pin = Uint8Characteristic( uuid=AdafruitService.adafruit_service_uuid(0x901), properties=(Characteristic.READ | Characteristic.WRITE), ) """Send data out on this pin.""" pixel_pin_type = Uint8Characteristic( uuid=AdafruitService.adafruit_service_uuid(0x902), properties=(Characteristic.READ | Characteristic.WRITE), ) pixel_buffer_size = Uint16Characteristic( uuid=AdafruitService.adafruit_service_uuid(0x904), properties=(Characteristic.READ | Characteristic.WRITE), initial_value=_PixelPacket.MAX_LENGTH, ) """ 0 = WS2812 (NeoPixel), 800kHz 1 = SPI (APA102: DotStar) """ _pixel_packet = _PixelPacket() """Pixel-setting data.""" def __init__(self, service=None): self._pixel_packet_buf = bytearray(_PixelPacket.MAX_LENGTH) super().__init__(service=service) @property def values(self): """Return a tuple (start, write_now, data) corresponding to the different parts of ``_pixel_packet``. """ buf = self._pixel_packet_buf num_read = self._pixel_packet.readinto(buf) # pylint: disable=no-member if num_read == 0: # No new values available return None return PixelValues( struct.unpack_from("<H", buf)[0], bool(buf[2] & 0x1), buf[3:num_read], )
class MyLightClient(Service): uuid = VendorUUID("6BFD8F3F-A704-4111-8DCE-F571BA26B40B") _control = Characteristic( uuid=VendorUUID("6BFD8F3E-A704-4111-8DCE-F571BA26B40B"), max_length=7) _light_level = Uint8Characteristic( uuid=VendorUUID("6BFD8F3D-A704-4111-8DCE-F571BA26B40B"), initial_value=100, properties=(Characteristic.READ | Characteristic.WRITE | Characteristic.WRITE_NO_RESPONSE)) print("my light init")
class LEDService(Service): uuid = VendorUUID(LED_SERVICE_UUID) redChar = Uint8Characteristic(uuid=VendorUUID(RED_LED_UUID), properties=Characteristic.WRITE_NO_RESPONSE) greenChar = Uint8Characteristic(uuid=VendorUUID(GREEN_LED_UUID), properties=Characteristic.WRITE_NO_RESPONSE) phase = 0 def phaseChange(self): self.phase = self.phase + 1 if self.phase > 3: self.phase = 0 if 0 == self.phase: self.charsWrite(1, 0) elif 1 == self.phase: self.charsWrite(1, 1) elif 2 == self.phase: self.charsWrite(0, 1) elif 3 == self.phase: self.charsWrite(0, 0) def charsWrite(self, pRed, pGreen): self.redCharWrite(pRed) self.greenCharWrite(pGreen) def redCharWrite(self, pValue): self.redChar = pValue def greenCharWrite(self, pValue): self.greenChar = pValue
class CoreService(PhysBrykService): """Core, 'on-board' charactistics for the physbryk.""" #the bryk 0000 uuid = PhysBrykService.physbryk_service_uuid(0x0000) # Default period set by MEASUREMENT_PERIOD. measurement_period = Int32Characteristic( uuid=PhysBrykService.physbryk_service_uuid(0x0001), properties=(Characteristic.READ | Characteristic.WRITE), initial_value=MEASUREMENT_PERIOD, ) battery = Uint16Characteristic( uuid=PhysBrykService.physbryk_service_uuid(0x0002), properties=(Characteristic.READ | Characteristic.NOTIFY), write_perm=Attribute.NO_ACCESS, ) # motion sensor 0100 motion_enabled = Uint8Characteristic( uuid=PhysBrykService.physbryk_service_uuid(0x0100), initial_value=1, ) # Tuple (x, y, z) float acceleration values, in m/s/s acceleration = StructCharacteristic( "<fff", uuid=PhysBrykService.physbryk_service_uuid(0x101), properties=(Characteristic.READ | Characteristic.NOTIFY), write_perm=Attribute.NO_ACCESS, ) # Tuple (x, y, z) float gyroscope values, in rad/s gyro = StructCharacteristic( "<fff", uuid=PhysBrykService.physbryk_service_uuid(0x102), properties=(Characteristic.READ | Characteristic.NOTIFY), write_perm=Attribute.NO_ACCESS, ) # magnetometer 0200 magnetic_enabled = Uint8Characteristic( uuid=PhysBrykService.physbryk_service_uuid(0x200), initial_value=1, ) # Tuple (x, y, z) float magnetometer values, in micro-Teslas (uT) magnetic = StructCharacteristic( "<fff", uuid=PhysBrykService.physbryk_service_uuid(0x201), properties=(Characteristic.READ | Characteristic.NOTIFY), write_perm=Attribute.NO_ACCESS, ) # light 0300 light_enabled = Uint8Characteristic( uuid=PhysBrykService.physbryk_service_uuid(0x300), initial_value=1, ) # Tuple (r, g, b, c) red/green/blue/clear color values, each in range 0-65535 (16 bits) spectrum = StructCharacteristic( "<ffff", uuid=PhysBrykService.physbryk_service_uuid(0x301), properties=(Characteristic.READ | Characteristic.NOTIFY), write_perm=Attribute.NO_ACCESS, initial_value=(0, 0, 0, 0)) # A higher number indicates a closer distance to the sensor. The value is unit-less. proximity = Uint16Characteristic( # "<H", uuid=PhysBrykService.physbryk_service_uuid(0x302), properties=(Characteristic.READ | Characteristic.NOTIFY), write_perm=Attribute.NO_ACCESS, ) # sound sound_enabled = Uint8Characteristic( uuid=PhysBrykService.physbryk_service_uuid(0x400), initial_value=1, ) loudness = Uint16Characteristic( # "<H", uuid=PhysBrykService.physbryk_service_uuid(0x402), properties=(Characteristic.READ | Characteristic.NOTIFY), write_perm=Attribute.NO_ACCESS, )
class SubkeypadService(UARTService): uuid = VendorUUID('39A00001-C17A-7AE2-EA02-A50E24DCCA9E') left_state = Uint8Characteristic( uuid=VendorUUID('39A00003-C1AB-74A2-EFA2-E50EA4DCCA9E')) right_state = Uint8Characteristic( uuid=VendorUUID('39A00004-C1AB-74A2-EFA2-E50EA4DCCA9E'))
class HIDService(Service): """ Provide devices for HID over BLE. :param str hid_descriptor: USB HID descriptor that describes the structure of the reports. Known as the report map in BLE HID. Example:: from adafruit_ble.hid_server import HIDServer hid = HIDServer() """ uuid = StandardUUID(0x1812) boot_keyboard_in = Characteristic( uuid=StandardUUID(0x2A22), properties=(Characteristic.READ | Characteristic.NOTIFY), read_perm=Attribute.ENCRYPT_NO_MITM, write_perm=Attribute.NO_ACCESS, max_length=8, fixed_length=True, ) boot_keyboard_out = Characteristic( uuid=StandardUUID(0x2A32), properties=(Characteristic.READ | Characteristic.WRITE | Characteristic.WRITE_NO_RESPONSE), read_perm=Attribute.ENCRYPT_NO_MITM, write_perm=Attribute.ENCRYPT_NO_MITM, max_length=1, fixed_length=True, ) protocol_mode = Uint8Characteristic( uuid=StandardUUID(0x2A4E), properties=(Characteristic.READ | Characteristic.WRITE_NO_RESPONSE), read_perm=Attribute.OPEN, write_perm=Attribute.OPEN, initial_value=1, max_value=1, ) """Protocol mode: boot (0) or report (1)""" # bcdHID (version), bCountryCode (0 not localized), Flags: RemoteWake, NormallyConnectable # bcd1.1, country = 0, flag = normal connect # TODO: Make this a struct. hid_information = Characteristic( uuid=StandardUUID(0x2A4A), properties=Characteristic.READ, read_perm=Attribute.ENCRYPT_NO_MITM, write_perm=Attribute.NO_ACCESS, initial_value=b"\x01\x01\x00\x02", ) """Hid information including version, country code and flags.""" report_map = Characteristic( uuid=StandardUUID(0x2A4B), properties=Characteristic.READ, read_perm=Attribute.ENCRYPT_NO_MITM, write_perm=Attribute.NO_ACCESS, fixed_length=True, ) """This is the USB HID descriptor (not to be confused with a BLE Descriptor). It describes which report characteristic are what.""" suspended = Uint8Characteristic( uuid=StandardUUID(0x2A4C), properties=Characteristic.WRITE_NO_RESPONSE, read_perm=Attribute.NO_ACCESS, write_perm=Attribute.ENCRYPT_NO_MITM, max_value=1, ) """Controls whether the device should be suspended (0) or not (1).""" def __init__(self, hid_descriptor=DEFAULT_HID_DESCRIPTOR, service=None): super().__init__(report_map=hid_descriptor) if service: # TODO: Add support for connecting to a remote hid server. pass self._init_devices() def _init_devices(self): # pylint: disable=too-many-branches,too-many-statements,too-many-locals self.devices = [] hid_descriptor = self.report_map global_table = [None] * 10 local_table = [None] * 3 collections = [] top_level_collections = [] i = 0 while i < len(hid_descriptor): b = hid_descriptor[i] tag = (b & 0xF0) >> 4 _type = (b & 0b1100) >> 2 size = b & 0b11 size = 4 if size == 3 else size i += 1 data = hid_descriptor[i:i + size] if _type == _ITEM_TYPE_GLOBAL: global_table[tag] = data elif _type == _ITEM_TYPE_MAIN: if tag == _MAIN_ITEM_TAG_START_COLLECTION: collections.append({ "type": data, "locals": list(local_table), "globals": list(global_table), "mains": [], }) elif tag == _MAIN_ITEM_TAG_END_COLLECTION: collection = collections.pop() # This is a top level collection if the collections list is now empty. if not collections: top_level_collections.append(collection) else: collections[-1]["mains"].append(collection) elif tag == _MAIN_ITEM_TAG_INPUT: collections[-1]["mains"].append({ "tag": "input", "locals": list(local_table), "globals": list(global_table), }) elif tag == _MAIN_ITEM_TAG_OUTPUT: collections[-1]["mains"].append({ "tag": "output", "locals": list(local_table), "globals": list(global_table), }) else: raise RuntimeError( "Unsupported main item in HID descriptor") local_table = [None] * 3 else: local_table[tag] = data i += size def get_report_info(collection, reports): """ Gets info about hid reports """ for main in collection["mains"]: if "type" in main: get_report_info(main, reports) else: report_size, report_id, report_count = [ x[0] for x in main["globals"][7:10] ] if report_id not in reports: reports[report_id] = { "input_size": 0, "output_size": 0 } if main["tag"] == "input": reports[report_id][ "input_size"] += report_size * report_count elif main["tag"] == "output": reports[report_id][ "output_size"] += report_size * report_count for collection in top_level_collections: if collection["type"][0] != 1: raise NotImplementedError( "Only Application top level collections supported.") usage_page = collection["globals"][0][0] usage = collection["locals"][0][0] reports = {} get_report_info(collection, reports) if len(reports) > 1: raise NotImplementedError( "Only one report id per Application collection supported") report_id, report = list(reports.items())[0] output_size = report["output_size"] if output_size > 0: self.devices.append( ReportOut(self, report_id, usage_page, usage, max_length=output_size // 8)) input_size = reports[report_id]["input_size"] if input_size > 0: self.devices.append( ReportIn(self, report_id, usage_page, usage, max_length=input_size // 8))
class CyclingSpeedAndCadenceService(Service): """Service for reading from a Cycling Speed and Cadence sensor.""" # 0x180D is the standard HRM 16-bit, on top of standard base UUID uuid = StandardUUID(0x1816) # Mandatory. csc_measurement = _CSCMeasurement() csc_feature = Uint8Characteristic(uuid=StandardUUID(0x2A5C), properties=Characteristic.READ) sensor_location = Uint8Characteristic(uuid=StandardUUID(0x2A5D), properties=Characteristic.READ) sc_control_point = Characteristic(uuid=StandardUUID(0x2A39), properties=Characteristic.WRITE) _SENSOR_LOCATIONS = ( "Other", "Top of shoe", "In shoe", "Hip", "Front Wheel", "Left Crank", "Right Crank", "Left Pedal", "Right Pedal", "Front Hub", "Rear Dropout", "Chainstay", "Rear Wheel", "Rear Hub", "Chest", "Spider", "Chain Ring", ) def __init__(self, service=None): super().__init__(service=service) # Defer creating buffer until we're definitely connected. self._measurement_buf = None @property def measurement_values(self): """All the measurement values, returned as a CSCMeasurementValues namedtuple. Return ``None`` if no packet has been read yet. """ # uint8: flags # bit 0 = 1: Wheel Revolution Data is present # bit 1 = 1: Crank Revolution Data is present # # The next two fields are present only if bit 0 above is 1: # uint32: Cumulative Wheel Revolutions # uint16: Last Wheel Event Time, in 1024ths of a second # # The next two fields are present only if bit 10 above is 1: # uint16: Cumulative Crank Revolutions # uint16: Last Crank Event Time, in 1024ths of a second # if self._measurement_buf is None: self._measurement_buf = bytearray( self.csc_measurement.incoming_packet_length # pylint: disable=no-member ) buf = self._measurement_buf packet_length = self.csc_measurement.readinto(buf) # pylint: disable=no-member if packet_length == 0: return None flags = buf[0] next_byte = 1 if flags & 0x1: wheel_revs = struct.unpack_from("<L", buf, next_byte)[0] wheel_time = struct.unpack_from("<H", buf, next_byte + 4)[0] next_byte += 6 else: wheel_revs = wheel_time = None if flags & 0x2: # Note that wheel revs is uint32 and and crank revs is uint16. crank_revs = struct.unpack_from("<H", buf, next_byte)[0] crank_time = struct.unpack_from("<H", buf, next_byte + 2)[0] else: crank_revs = crank_time = None return CSCMeasurementValues(wheel_revs, wheel_time, crank_revs, crank_time) @property def location(self): """The location of the sensor on the cycle, as a string. Possible values are: "Other", "Top of shoe", "In shoe", "Hip", "Front Wheel", "Left Crank", "Right Crank", "Left Pedal", "Right Pedal", "Front Hub", "Rear Dropout", "Chainstay", "Rear Wheel", "Rear Hub", "Chest", "Spider", "Chain Ring") "Other", "Chest", "Wrist", "Finger", "Hand", "Ear Lobe", "Foot", and "InvalidLocation" (if value returned does not match the specification). """ try: return self._SENSOR_LOCATIONS[self.sensor_location] except IndexError: return "InvalidLocation"
class HeartRateService(Service): """Service for reading from a Heart Rate sensor.""" # 0x180D is the standard HRM 16-bit, on top of standard base UUID uuid = StandardUUID(0x180D) # uint8: flags # bit 0 = 0: Heart Rate Value is uint8 # bit 0 = 1: Heart Rate Value is uint16 # bits 2:1 = 0 or 1: Sensor Contact Feature not supported # bits 2:1 = 2: Sensor Contact Feature supported, contact is not detected # bits 2:1 = 3: Sensor Contact Feature supported, contacted is detected # bit 3 = 0: Energy Expended field is not present # bit 3 = 1: Energy Expended field is present. Units: kilo Joules # bit 4 = 0: RR-Interval values are not present # bit 4 = 1: One or more RR-Interval values are present # # next uint8 or uint16: Heart Rate Value # next uint16: Energy Expended, if present # next uint16 (multiple): RR-Interval values, resolution of 1/1024 second # in order of oldest to newest # # Mandatory for Heart Rate Service heart_rate_measurement = _HeartRateMeasurement() # Optional for Heart Rate Service. body_sensor_location = Uint8Characteristic(uuid=StandardUUID(0x2A38), properties=Characteristic.READ) # Mandatory only if Energy Expended features is supported. heart_rate_control_point = Uint8Characteristic( uuid=StandardUUID(0x2A39), properties=Characteristic.WRITE) _BODY_LOCATIONS = ("Other", "Chest", "Wrist", "Finger", "Hand", "Ear Lobe", "Foot") def __init__(self, service=None): super().__init__(service=service) # Defer creating buffer until needed. self._measurement_buf = None @property def measurement_values(self): """All the measurement values, returned as a HeartRateMeasurementValues namedtuple. Return ``None`` if no packet has been read yet. """ if self._measurement_buf is None: self._measurement_buf = bytearray( self.heart_rate_measurement.packet_size # pylint: disable=no-member ) buf = self._measurement_buf packet_length = self.heart_rate_measurement.readinto( # pylint: disable=no-member buf) if packet_length == 0: return None flags = buf[0] next_byte = 1 if flags & 0x1: bpm = struct.unpack_from("<H", buf, next_byte)[0] next_byte += 2 else: bpm = struct.unpack_from("<B", buf, next_byte)[0] next_byte += 1 if flags & 0x4: # True or False if Sensor Contact Feature is supported. contact = bool(flags & 0x2) else: # None (meaning we don't know) if Sensor Contact Feature is not supported. contact = None if flags & 0x8: energy_expended = struct.unpack_from("<H", buf, next_byte)[0] next_byte += 2 else: energy_expended = None rr_values = [] if flags & 0x10: for offset in range(next_byte, packet_length, 2): rr_val = struct.unpack_from("<H", buf, offset)[0] rr_values.append(rr_val) return HeartRateMeasurementValues(bpm, contact, energy_expended, rr_values) @property def location(self): """The location of the sensor on the human body, as a string. Note that the specification describes a limited number of locations. But the sensor manufacturer may specify using a non-standard location. For instance, some armbands are meant to be worn just below the inner elbow, but that is not a prescribed location. So the sensor will report something else, such as "Wrist". Possible values are: "Other", "Chest", "Wrist", "Finger", "Hand", "Ear Lobe", "Foot", and "InvalidLocation" (if value returned does not match the specification). """ try: return self._BODY_LOCATIONS[self.body_sensor_location] except IndexError: return "InvalidLocation"