class BlueZGattService(DBusObject): """ org.bluez.GattService1 interface implementation """ interface_name: str = defs.GATT_SERVICE_INTERFACE iface: DBusInterface = DBusInterface( interface_name, Property("UUID", "s"), Property("Primary", "b"), ) dbusInterfaces: List[DBusInterface] = [iface] uuid: DBusProperty = DBusProperty("UUID") primary: DBusProperty = DBusProperty("Primary") def __init__( self, uuid: str, primary: bool, index: int, app: 'BlueZGattApplication', # noqa: F821 ): """ Initialize the DBusObject Parameters ---------- uuid : str A string representation of the unique identifier primary : bool Whether the service is the primary service for the application it belongs to index : int The index of the service amongst the other service of the application app : BlueZApp A BlueZApp object that owns this service """ self.path: str = app.base_path + "/service" + str(index) self.bus: client = app.bus self.destination: str = app.destination self.uuid: str = uuid self.primary: bool = primary self.loop: asyncio.AbstractEventLoop = app.loop self.app: 'BlueZGattApplication' = app # noqa: F821 self.characteristics: List[BlueZGattCharacteristic] = [] super(BlueZGattService, self).__init__(self.path)
def iface(self): if hasattr(self, "_iface"): iface = self._iface else: iface_methods = [] for method_name, (args_xform, returns_xform, fn) in self.methods.items(): iface_methods.append( Method( method_name, arguments=args_xform.signature(), returns=returns_xform.signature(), )) iface_properties = [] for prop_name, (xform, _, kwargs) in self.properties.items(): iface_properties.append( Property(prop_name, xform.signature(), **kwargs)) iface_signals = [] for signal_name, xform in self.signals.items(): iface_signals.append(Signal(signal_name, xform.signature())) iface = DBusInterface( f"{self.service.namespace}.{self.iface_name}", *(iface_methods + iface_properties + iface_signals), ) self._iface = iface return iface
class BlueZLEAdvertisement(DBusObject): """ org.bluez.LEAdvertisement1 interface implementation """ interface_name: str = "org.bluez.LEAdvertisement1" iface: DBusInterface = DBusInterface( interface_name, Method("Release"), Property("Type", "s"), Property("ServiceUUIDs", "as"), Property("ManufacturerData", "a{qay}"), Property("SolicitUUIDs", "as"), Property("ServiceData", "a{sv}"), Property("IncludeTxPower", "b")) dbusInterfaces: List[DBusInterface] = [iface] ad_type: DBusProperty = DBusProperty("Type") service_uuids: DBusProperty = DBusProperty("ServiceUUIDs") # manufacturer_data = DBusProperty("ManufacturerData") # solicit_uuids = DBusProperty("SolicitUUIDs") # service_data = DBusProperty("ServiceData") include_tx_power: DBusProperty = DBusProperty("IncludeTxPower") def __init__( self, advertising_type: Type, index: int, app: "BlueZGattApplication" # noqa: F821 ): """ New Low Energy Advertisement Parameters ---------- advertising_type : Type The type of advertisement index : int, The index of the advertisement app : BlueZGattApplication The Application that is responsible for this advertisement """ self.ad_type: str = advertising_type.value self.path = app.base_path + "/advertisement" + str(index) self.service_uuids: List[str] = [] self.manufacturer_data: Dict = {} self.solicit_uuids = [''] self.service_data = {'': 0} self.include_tx_power: bool = False self.data = None super(BlueZLEAdvertisement, self).__init__(self.path) @dbusMethod(interface_name, "Release") def Release(self): # noqa: N802 print("%s: Released!" % self.path)
class BlueZGattService(DBusObject): """ org.bluez.GattService1 interface implementation """ interface_name: str = defs.GATT_SERVICE_INTERFACE iface: DBusInterface = DBusInterface( interface_name, Property("UUID", "s"), Property("Primary", "b"), ) dbusInterfaces: List[DBusInterface] = [iface] uuid: DBusProperty = DBusProperty("UUID") primary: DBusProperty = DBusProperty("Primary") def __init__( self, uuid: str, primary: bool, index: int, app: "BlueZGattApplication", # noqa: F821 ): """ Initialize the DBusObject Parameters ---------- uuid : str A string representation of the unique identifier primary : bool Whether the service is the primary service for the application it belongs to index : int The index of the service amongst the other service of the application app : BlueZApp A BlueZApp object that owns this service """ hex_index: str = hex(index)[2:].rjust(4, "0") self.path: str = app.base_path + "/service" + hex_index self.bus: client = app.bus self.destination: str = app.destination self.uuid: str = uuid self.primary: bool = primary self.loop: asyncio.AbstractEventLoop = app.loop self.app: "BlueZGattApplication" = app # noqa: F821 self.characteristics: List[BlueZGattCharacteristic] = [] super(BlueZGattService, self).__init__(self.path) async def add_characteristic( self, uuid: str, flags: List[Flags], value: Any ) -> BlueZGattCharacteristic: """ Adds a BlueZGattCharacteristic to the service. Parameters ---------- uuid : str The string representation of the UUID for the characteristic flags : List[Flags], A list of flags to apply to the characteristic value : Any The characteristic's value """ index: int = len(self.characteristics) + 1 characteristic: BlueZGattCharacteristic = BlueZGattCharacteristic( uuid, flags, index, self ) characteristic.value = value self.characteristics.append(characteristic) await self.app._register_object(characteristic) return characteristic async def get_obj(self) -> Dict: """ Obtain the underlying dictionary within the BlueZ API that describes the service Returns ------- Dict The dictionary that describes the service """ dbus_obj: RemoteDBusObject = await self.app.bus.getRemoteObject( self.app.destination, self.path ).asFuture(self.app.loop) dict_obj: Dict = await dbus_obj.callRemote( "GetAll", defs.GATT_SERVICE_INTERFACE, interface=defs.PROPERTIES_INTERFACE, ).asFuture(self.app.loop) return dict_obj
class Rfm69DBusService(objects.DBusObject): class NotImplementedError(Exception): dbusErrorName = "org.agile-rfm69.NotImplemented" class IOError(Exception): dbusErrorName = "org.agile-rfm69.IOError" class ValueError(Exception): dbusErrorName = "org.agile-rfm69.ValueError" class TypeError(Exception): dbusErrorName = "org.agile-rfm69.TypeError" iface = DBusInterface("iot.agile.Protocol", Method("Connect"), Method("Connected", returns="b"), Method("Disconnect"), Method("Setup", arguments="a{sv}"), Method("Send", arguments="a{sv}"), Method("Receive", returns="a{sv}"), Method("Subscribe", arguments="a{sv}"), Method("StartDiscovery"), Method("StopDiscovery"), Property("Devices", "av", writeable=False), Property("Name", "s", writeable=False), Property("Driver", "s", writeable=False), Property("Data", "a{sv}", writeable=False), Property("Status", "n", writeable=False) ) _devices = DBusProperty("Devices") _name = DBusProperty("Name") _driver = DBusProperty("Driver") _lastRecord = DBusProperty("Data") _status = DBusProperty("Status") dbusInterfaces = [iface] def __init__(self, objectPath): super(Rfm69DBusService, self).__init__(objectPath) self._lastRecord = {"STATUS": "TIMEOUT"} self._status = 0 self._driver = "No driver" self._name = PROTOCOL_NAME self._devices = ["None"] self._logger = logging.getLogger() self._full_path = PROTOCOL_PATH self._connected = False self._setup = { "MODEM_CONFIG_TABLE": MODEM_CONFIG_TABLE, "MODEM_CONFIG": MODEM_CONFIG, "key": MODEM_KEY, "channel": CHANNEL } def _setModemConfig(self): # Set RFM69 registers as per config settings = MODEM_CONFIG_TABLE[self._setup["MODEM_CONFIG"]] addresses = [0x02, 0x03, 0x04, 0x05, 0x06, 0x19, 0x1a, 0x37] for value, address in zip(settings, addresses): self._rfm69.spi_write(address, value) def _setModemKey(self): self._logger.debug("enabling ecryption") self._rfm69.set_encryption(self._setup["key"]) def _getConnected(self): return self._connected def _setConnected(self, status): if status: self._connected = True else: self._connected = False def dbus_Connect(self): self._logger.debug( "%s@Connect: Connect INIT", self._full_path) if self._getConnected(): self._logger.debug( "%s@Connect: Module is already connected", self._full_path) raise self.IOError("Module is already connected.") self._logger.debug( "%s@Connect: MODE=%s", self._full_path, self._setup["MODEM_CONFIG"]) self._rfm69 = rfm69.RFM69(25, 24, 0, rfm69.RFM69Configuration(), True) self._rfm69.set_channel(self._setup["channel"]) self._rfm69.set_address(1) self._logger.debug("Class initialized") self._logger.debug("Calibrating RSSI") # self._rfm69.calibrate_rssi_threshold() self._logger.debug("Checking temperature") self._logger.debug(self._rfm69.read_temperature()) # Make sure settings are correct to talk to other radios self._setModemConfig() self._setModemKey() self._logger.debug("reading all registers") for result in self._rfm69.read_registers(): self._logger.debug(result) # Won't get here if something went wrong reading temps etc. self._setConnected(True) self._logger.debug("%s@Connect: Connect OK", self._full_path) def dbus_Connected(self): return self._connected def dbus_Disconnect(self): self._logger.debug( "%s@Disconnect: Disconnect INIT", self._full_path) self._setConnected(False) self._rfm69.disconnect() self._logger.debug("%s@Disconnect: Disconnect OK", self._full_path) def dbus_Setup(self, args): self._logger.debug("%s@Setup: Setup INIT", self._full_path) self._setup.clear() self._setup = {} modemConfigTable = args.pop("MODEM_CONFIG_TABLE", MODEM_CONFIG_TABLE) self._setup["MODEM_CONFIG_TABLE"] = modemConfigTable modemConfig = args.pop("MODEM_CONFIG", MODEM_CONFIG) self._setup["MODEM_CONFIG"] = modemConfig modemKey = args.pop("key", MODEM_KEY) self._setup["key"] = modemKey channel = args.pop("channel", CHANNEL) self._setup["channel"] = channel self._logger.debug( "%s@Setup: Parameters=%s", self._full_path, self._setup) self._logger.debug( "%s@Setup: Setup OK", self._full_path) def dbus_Send(self, args): self._logger.debug( "%s@Send: Send INIT", self._full_path) if not self._getConnected(): self._logger.debug( "%s@Send: Module is not connected", self._full_path) raise self.IOError("Module is not connected.") sendData = args.pop("DATA", "") if not sendData: self._logger.debug( "%s@Send/Rfm69: No data provided", self._full_path) raise self.ValueError("You must provide the data.") if not type(sendData) is list: self._logger.debug( "%s@Send/Rfm69: Data in wrong format", self._full_path) raise self.TypeError("You must provide the data as a list of values.") # Turn it back into bytes again, since D-Bus turns it into a list sendData = struct.pack("B"*len(sendData), *sendData) self._rfm69.send_packet(sendData) def dbus_Receive(self): self._logger.debug("%s@Receive: Receive INIT", self._full_path) if not self._getConnected(): self._logger.debug( "%s@Receive: Module is not connected", self._full_path) raise self.IOError("Module is not connected.") response = self._rfm69.wait_for_packet(timeout=60) if response: (data, rssi) = response self._logger.debug("%s@Receive: receiveDone()", self._full_path) self._lastRecord = {"DATA": data, "RSSI": rssi, "STATUS": "OK"} else: self._lastRecord = {"STATUS": "TIMEOUT"} return self._lastRecord def dbus_Subscribe(self, args): raise self.NotImplementedError("Function not supported.") def dbus_StartDiscovery(self): pass def dbus_StopDiscovery(self): pass
class BlueZGattCharacteristic(DBusObject): """ org.bluez.GattCharacteristic1 interface implementation """ interface_name: str = defs.GATT_CHARACTERISTIC_INTERFACE iface: DBusInterface = DBusInterface( interface_name, Method("ReadValue", arguments="a{sv}", returns="ay"), Method("WriteValue", arguments="aya{sv}"), Method("StartNotify"), Method("StopNotify"), Property("UUID", "s"), Property("Service", "o"), Property("Value", "ay"), Property("Notifying", "b"), Property("Flags", "as"), ) dbusInterfaces: List[DBusInterface] = [iface] uuid: DBusProperty = DBusProperty("UUID") service: DBusProperty = DBusProperty("Service") flags: DBusProperty = DBusProperty("Flags") value: DBusProperty = DBusProperty("Value") notifying: DBusProperty = DBusProperty("Notifying") def __init__( self, uuid: str, flags: List[Flags], index: int, service: "BlueZGattService", # noqa: F821 ): """ Create a BlueZ Gatt Characteristic Parameters ---------- uuid : str The unique identifier for the characteristic flags : List[Flags] A list of strings that represent the properties of the characteristic index : int The index number for this characteristic in the service service : BlueZService The Gatt Service that owns this characteristic """ self.path: str = service.path + "/char" + f"{index:04d}" self.uuid: str = uuid self.flags: List[str] = [x.value for x in flags] self.service: str = service.path # noqa: F821 self._service: "BlueZGattService" = service # noqa: F821 self.value: bytes = b"" self.notifying: bool = False self.descriptors: List["BlueZGattDescriptor"] = [] # noqa: F821 super(BlueZGattCharacteristic, self).__init__(self.path) @dbusMethod(interface_name, "ReadValue") def ReadValue(self, options: Dict) -> bytearray: # noqa: N802 """ Read the value of the characteristic. This is to be fully implemented at the application level Parameters ---------- options : Dict A list of options Returns ------- bytearray The bytearray that is the value of the characteristic """ f = self._service.app.Read if f is None: raise NotImplementedError() return f(self) @dbusMethod(interface_name, "WriteValue") def WriteValue(self, value: bytearray, options: Dict): # noqa: N802 """ Write a value to the characteristic This is to be fully implemented at the application level Parameters ---------- value : bytearray The value to set options : Dict Some options for you to select from """ f = self._service.app.Write if f is None: raise NotImplementedError() f(self, value) @dbusMethod(interface_name, "StartNotify") def StartNotify(self): # noqa: N802 """ Begin a subscription to the characteristic """ f = self._service.app.StartNotify if f is None: raise NotImplementedError() f(None) self._service.app.subscribed_characteristics.append(self.uuid) @dbusMethod(interface_name, "StopNotify") def StopNotify(self): # noqa: N802 """ Stop a subscription to the characteristic """ f = self._service.app.StopNotify if f is None: raise NotImplementedError() f(None) self._service.app.subscribed_characteristics.remove(self.uuid) async def get_obj(self) -> Dict: """ Obtain the underlying dictionary within the BlueZ API that describes the characteristic Returns ------- Dict The dictionary that describes the characteristic """ dbus_obj: RemoteDBusObject = await self._service.app.bus.getRemoteObject( self._service.app.destination, self.path).asFuture(self._service.app.loop) dict_obj: Dict = await dbus_obj.callRemote( "GetAll", defs.GATT_CHARACTERISTIC_INTERFACE, interface=defs.PROPERTIES_INTERFACE, ).asFuture(self._service.app.loop) return dict_obj