Example #1
0
async def agent_request_input(bus: BaseMessageBus, impl: ConnmanAgent, service,
                              fields):
    introspection = await bus.introspect("net.connman", service)
    proxy = bus.get_proxy_object("net.connman", service, introspection)
    iface = proxy.get_interface("net.connman.Service")
    props = await iface.call_get_properties()
    svc = model.create_service_from_props(service, props)

    plain_fields = unpack_variants(fields, "a{sv}")
    try:
        res = await impl.request_input(svc, plain_fields)
    except Canceled as e:
        raise DBusError("net.connman.Agent.Error.Canceled", str(e))
    varres = {k: Variant("s", v) for k, v in res.items() if v is not None}
    return varres
Example #2
0
 async def send(
     self,
     element_path: str,
     destination: int,
     key_index: int,
     data: bytes,
     force_segmented: bool = False,
 ) -> None:
     await self._interface.call_send(
         element_path,
         destination,
         key_index,
         dict(ForceSegmented=Variant("b", force_segmented)),
         data,
         flags=MessageFlag.NO_REPLY_EXPECTED,
     )
Example #3
0
    async def request_input(
            self,
            service: "o",
            fields: "a{sv}",  # type: ignore
    ) -> "a{sv}":  # type: ignore
        """This method gets called when trying to connect to
        a service and some extra input is required. For
        example a passphrase or the name of a hidden network.

        The return value should be a dictionary where the
        keys are the field names and the values are the
        actual fields. Alternatively an error indicating that
        the request got canceled can be returned.
        OperationAborted will be return on a successful
        cancel request.

        Most common return field names are "Name" and of
        course "Passphrase".

        The dictionary arguments contains field names with
        their input parameters.

        In case of WISPr credentials requests and if the user
        prefers to login through the browser by himself, agent
        will have to return a LaunchBrowser error (see below).

        Possible Errors: net.connman.Agent.Error.Canceled
                 net.connman.Agent.Error.LaunchBrowser
        """

        # Fetch the service properties
        introspection = await self._bus.introspect("net.connman", service)
        proxy = self._bus.get_proxy_object("net.connman", service,
                                           introspection)
        iface = proxy.get_interface("net.connman.Service")
        props = await iface.call_get_properties()
        svc = model.create_service_from_props(service, props)

        plain_fields = unpack_variants(fields, "a{sv}")
        print("REQUEST INPUT", svc, plain_fields)
        try:
            res = await self._impl.request_input(svc, plain_fields)
        except Canceled as e:
            raise DBusError("net.connman.Agent.Error.Canceled", str(e))
        varres = {k: Variant("s", v) for k, v in res.items()}
        return varres
Example #4
0
    async def test_services_changed(self) -> None:
        # GIVEN
        mgr = ConnmanManagerImpl(self.bus)
        svc1_iface = NetConnmanServiceStub()
        self.bus.stub_register_interface("/svc1", "net.connman.Service",
                                         svc1_iface)
        await mgr.setup()

        self.net_connman_manager.stub_update_services(
            [(
                "/svc1",
                self.sample_service_props,
            )],
            [],
        )
        await asyncio.sleep(0)

        # WHEN
        svcs = mgr.list_services()

        # THEN
        assert len(svcs) == 1
        svc1 = svcs[0]
        assert svc1.path == "/svc1"
        assert svc1.name == "Skynet"
        assert svc1.state == ConnmanServiceState.IDLE

        # Manager should be subscribed to service updates now
        signals = svc1_iface.stub_get_signals()
        assert signals.keys() == {"property_changed"}

        # WHEN
        self.net_connman_manager.stub_update_services(
            [("/svc1", {
                "State": Variant("s", "online")
            })],
            [],
        )

        # THEN
        svcs = mgr.list_services()
        assert len(svcs) == 1
        svc1 = svcs[0]
        assert svc1.path == "/svc1"
        assert svc1.name == "Skynet"
        assert svc1.state == ConnmanServiceState.ONLINE
Example #5
0
    async def test_scan_all(self, ovshell: testing.OpenVarioShellStub) -> None:
        # GIVEN
        bus = ovshell.os.stub_connect_bus()
        net_connman_manager = NetConnmanManagerStub()

        net_connman_manager.stub_set_technologies([
            (
                "/eth",
                {
                    "Name": Variant("s", "Ethernet"),
                    "Type": Variant("s", "ethernet"),
                    "Connected": Variant("b", False),
                    "Powered": Variant("b", False),
                },
            ),
            (
                "/wifi",
                {
                    "Name": Variant("s", "Wifi"),
                    "Type": Variant("s", "wifi"),
                    "Connected": Variant("b", False),
                    "Powered": Variant("b", False),
                },
            ),
        ])

        net_connman_tech = NetConnmanTechnologyStub()

        bus.stub_register_interface("/", "net.connman.Manager",
                                    net_connman_manager)
        bus.stub_register_interface("/wifi", "net.connman.Technology",
                                    net_connman_tech)
        mgr = ConnmanManagerImpl(await ovshell.os.get_system_bus())
        await mgr.setup()

        # WHEN
        assert len(mgr.technologies) > 0
        scanned = await mgr.scan_all()

        # THEN
        assert scanned == 1  # only wifi is scanned
        assert net_connman_tech.scan_called == 1
Example #6
0
    async def test_get_state(self) -> None:
        # GIVEN
        mgr = ConnmanManagerImpl(self.bus)
        await mgr.setup()
        await asyncio.sleep(0)

        # WHEN
        state = mgr.get_state()

        # THEN
        assert state == ConnmanState.UNKNOWN

        # WHEN
        self.net_connman_manager.stub_set_properties(
            {"State": Variant("s", "online")})
        state = mgr.get_state()

        # THEN
        assert state == ConnmanState.ONLINE
async def test_property_changed_signal(interface_class):
    bus1 = await MessageBus().connect()
    bus2 = await MessageBus().connect()

    await bus2.call(
        Message(destination='org.freedesktop.DBus',
                path='/org/freedesktop/DBus',
                interface='org.freedesktop.DBus',
                member='AddMatch',
                signature='s',
                body=[f'sender={bus1.unique_name}']))

    interface = interface_class('test.interface')
    export_path = '/test/path'
    bus1.export(export_path, interface)

    async def wait_for_message():
        # TODO timeout
        future = asyncio.get_event_loop().create_future()

        def message_handler(signal):
            if signal.interface == 'org.freedesktop.DBus.Properties':
                bus2.remove_message_handler(message_handler)
                future.set_result(signal)

        bus2.add_message_handler(message_handler)
        return await future

    bus2.send(
        Message(destination=bus1.unique_name,
                interface=interface.name,
                path=export_path,
                member='do_emit_properties_changed'))

    signal = await wait_for_message()
    assert signal.interface == 'org.freedesktop.DBus.Properties'
    assert signal.member == 'PropertiesChanged'
    assert signal.signature == 'sa{sv}as'
    assert signal.body == [
        interface.name, {
            'string_prop': Variant('s', 'asdf')
        }, ['container_prop']
    ]
Example #8
0
async def test_emoji(bus_address):
    [mpris] = await setup_mpris('emoji-format-test', bus_address=bus_address)
    mpris.metadata = {'mpris:length': Variant('x', 100000)}
    await mpris.ping()
    playerctl = PlayerctlCli(bus_address)

    status_emoji_cmd = 'metadata --format \'{{emoji(status)}}\''

    mpris.playback_status = 'Playing'
    cmd = await playerctl.run(status_emoji_cmd)
    assert cmd.stdout == '▶️', cmd.stderr

    mpris.playback_status = 'Paused'
    cmd = await playerctl.run(status_emoji_cmd)
    assert cmd.stdout == '⏸️', cmd.stderr

    mpris.playback_status = 'Stopped'
    cmd = await playerctl.run(status_emoji_cmd)
    assert cmd.stdout == '⏹️', cmd.stderr

    volume_emoji_cmd = 'metadata --format \'{{emoji(volume)}}\''
    mpris.volume = 0.0
    cmd = await playerctl.run(volume_emoji_cmd)
    assert cmd.stdout == '🔈', cmd.stderr

    mpris.volume = 0.5
    cmd = await playerctl.run(volume_emoji_cmd)
    assert cmd.stdout == '🔉', cmd.stderr

    mpris.volume = 1.0
    cmd = await playerctl.run(volume_emoji_cmd)
    assert cmd.stdout == '🔊', cmd.stderr

    cmd = await playerctl.run('metadata --format \'{{emoji("hi")}}\'')
    assert cmd.returncode == 1, cmd.stderr

    cmd = await playerctl.run('metadata --format \'{{emoji(status, volume)}}\''
                              )
    assert cmd.returncode == 1, cmd.stderr

    await mpris.disconnect()
Example #9
0
    async def test_tech_power(self) -> None:
        # GIVEN
        self.net_connman_manager.stub_set_technologies([
            ("/eth", self.sample_tech_props)
        ])
        net_connman_tech = NetConnmanTechnologyStub()
        self.bus.stub_register_interface("/eth", "net.connman.Technology",
                                         net_connman_tech)
        mgr = ConnmanManagerImpl(self.bus)
        await mgr.setup()
        await asyncio.sleep(0)

        # WHEN
        techs = mgr.technologies
        assert len(techs) == 1
        tech_eth = techs[0]
        await mgr.power(tech_eth, on=True)

        # THEN
        assert net_connman_tech.props_updated == [("Powered",
                                                   Variant("b", True))]
Example #10
0
    def setup(self, ovshell: testing.OpenVarioShellStub) -> None:
        self.bus = ovshell.os.stub_connect_bus()
        self.net_connman_manager = NetConnmanManagerStub()
        self.bus.stub_register_interface("/", "net.connman.Manager",
                                         self.net_connman_manager)
        self.agent = ConnmanAgentStub()
        self.agentiface = ConnmanAgentInterface(self.agent, self.bus)

        self.svc_iface = NetConnmanServiceStub()
        self.bus.stub_register_interface("/svc1", "net.connman.Service",
                                         self.svc_iface)

        self.sample_service_props = {
            "AutoConnect": Variant("b", False),
            "Favorite": Variant("b", False),
            "Name": Variant("s", "Skynet"),
            "Security": Variant("s", "wpa"),
            "Strength": Variant("i", 78),
            "Type": Variant("s", "wifi"),
            "State": Variant("s", "idle"),
        }
Example #11
0
    async def test_services_changed_incomplete_data_ignore(self) -> None:
        # GIVEN
        mgr = ConnmanManagerImpl(self.bus)
        svc1_iface = NetConnmanServiceStub()
        self.bus.stub_register_interface("/svc1", "net.connman.Service",
                                         svc1_iface)
        await mgr.setup()

        incomplete_props = {
            "Type": Variant("s", "Skynet"),
        }
        # WHEN
        self.net_connman_manager.stub_update_services(
            [(
                "/svc1",
                incomplete_props,
            )],
            [],
        )
        await asyncio.sleep(0)
        svcs = mgr.list_services()

        # THEN
        assert len(svcs) == 0
Example #12
0
 async def unprovisioned_scan(self, **kwargs) -> None:
     options = dict(Seconds=Variant("q", kwargs.get("seconds", 0)))
     await self._interface.call_unprovisioned_scan(options)
Example #13
0
def test_unpack_variants_explicit() -> None:
    inp = {"one": Variant("i", 1)}
    assert unpack_variants(inp, "a{sv}") == {"one": 1}
Example #14
0
async def test_format(bus_address):
    title = 'A Title'
    artist = 'An Artist'
    album = 'An Album'
    player_name = 'format-test'
    player_instance = f'{player_name}.instance123'

    [mpris] = await setup_mpris(player_instance, bus_address=bus_address)
    mpris.metadata = {
        'xesam:title': Variant('s', title),
        'xesam:artist': Variant('as', [artist]),
        'xesam:escapeme': Variant('s', '<hi>'),
        'xesam:album': Variant('s', album),
        'mpris:length': Variant('x', 100000)
    }
    mpris.volume = 2.0

    playerctl = PlayerctlCli(bus_address)

    test = MetadataTest(playerctl)

    test.add('{{artist}} - {{title}}', f'{artist} - {title}')
    test.add("{{markup_escape(xesam:escapeme)}}", "&lt;hi&gt;")
    test.add("{{lc(artist)}}", artist.lower())
    test.add("{{uc(title)}}", title.upper())
    test.add("{{uc(lc(title))}}", title.upper())
    test.add('{{uc("Hi")}}', "HI")
    test.add("{{mpris:length}}", "100000")
    test.add(
        '@{{ uc( "hi" ) }} - {{uc( lc( "HO"  ) ) }} . {{lc( uc(  title ) )   }}@',
        f'@HI - HO . {title.lower()}@')
    test.add("{{default(xesam:missing, artist)}}", artist)
    test.add("{{default(title, artist)}}", title)
    test.add('{{default("", "ok")}}', 'ok')
    test.add('{{default("ok", "not")}}', 'ok')
    test.add(' {{lc(album)}} ', album.lower())
    test.add('{{playerName}} - {{playerInstance}}',
             f'{player_name} - {player_instance}')

    await test.run()

    # numbers
    math = [
        '10',
        '-10 + 20',
        '10 + 10',
        '10 * 10',
        '10 / 10',
        '10 + 10 * 10 + 10',
        '10 + 10 * -10 + 10',
        '10 + 10 * -10 + -10',
        '-10 * 10 + 10',
        '-10 * -10 * -1 + -10',
        '-10 * 10 + -10 * -10 + 20 / 10 * -20 + -10',
        '8+-+--++-4',
        '2 - 10 * 1 + 1',
        '2 / -2 + 2 * 2 * -2 - 2 - 2 * -2',
        '2 * (2 + 2)',
        '10 * (10 + 12) - 4',
        '-(10)',
        '-(10 + 12 * -2)',
        '14 - (10 * 2 + 5) * -6',
        '(14 - 2 * 3) * (14 * -2 - 6) + -(4 - 2) * 5',
    ]

    # variables
    math += [
        'volume',
        'volume + 10',
        '-volume',
        '-volume * -1',
        '-volume + volume',
        'volume * volume',
        'volume * -volume',
        'volume + volume * -volume * volume + -volume',
        'volume / -volume + volume * volume * -volume - volume - volume * -volume',
        '-(volume + 3) * 5 * (volume + 2)',
    ]

    # functions
    math += [
        'default(5+5, None)',
        '-default(5 + 5, None)',
        '(-default(5 - 5, None) + 2) * 8',
        '2 + (5 * 4 + 3 * -default(5, default(6 * (3 + 4 * (6 + 2)) / 2, None)) + -56)',
    ]

    def default_shim(arg1, arg2):
        if arg1 is None:
            return arg2
        return arg1

    async def math_test(math):
        cmd = await playerctl.run("metadata --format '{{" + math + "}}'")
        assert cmd.returncode == 0, cmd.stderr
        assert float(cmd.stdout) == eval(math, {
            'volume': mpris.volume,
            'default': default_shim
        }), math

    await asyncio.gather(*[math_test(m) for m in math])

    await mpris.disconnect()
Example #15
0
    def setup(self, ovshell: testing.OpenVarioShellStub) -> None:
        self.ovshell = ovshell
        self.bus = ovshell.os.stub_connect_bus()
        self.net_connman_manager = NetConnmanManagerStub()
        self.bus.stub_register_interface("/", "net.connman.Manager",
                                         self.net_connman_manager)

        self.sample_service_props = {
            "AutoConnect": Variant("b", False),
            "Favorite": Variant("b", False),
            "Name": Variant("s", "Skynet"),
            "Security": Variant("s", "wpa"),
            "Strength": Variant("i", 78),
            "Type": Variant("s", "wifi"),
            "State": Variant("s", "idle"),
        }

        self.sample_tech_props = {
            "Name": Variant("s", "Ethernet"),
            "Type": Variant("s", "ethernet"),
            "Connected": Variant("b", False),
            "Powered": Variant("b", True),
        }
Example #16
0
async def test_format(bus_address):
    [mpris] = await setup_mpris('format-test', bus_address=bus_address)
    TITLE = 'A Title'
    ARTIST = 'An Artist'
    ALBUM = 'An Album'
    mpris.metadata = {
        'xesam:title': Variant('s', TITLE),
        'xesam:artist': Variant('as', [ARTIST]),
        'xesam:escapeme': Variant('s', '<hi>'),
        'xesam:album': Variant('s', ALBUM),
        'mpris:length': Variant('x', 100000)
    }
    await mpris.ping()

    playerctl = PlayerctlCli(bus_address)

    cmd = await playerctl.run('metadata --format "{{artist}} - {{title}}"')
    assert cmd.stdout == f'{ARTIST} - {TITLE}', cmd.stderr

    cmd = await playerctl.run(
        'metadata --format "{{markup_escape(xesam:escapeme)}}"')
    assert cmd.stdout == '&lt;hi&gt;', cmd.stderr

    cmd = await playerctl.run('metadata --format "{{lc(artist)}}"')
    assert cmd.stdout == ARTIST.lower(), cmd.stderr

    cmd = await playerctl.run('metadata --format "{{uc(title)}}"')
    assert cmd.stdout == TITLE.upper(), cmd.stderr

    cmd = await playerctl.run('metadata --format "{{uc(lc(title))}}"')
    assert cmd.stdout == TITLE.upper(), cmd.stderr

    cmd = await playerctl.run('metadata --format \'{{uc("Hi")}}\'')
    assert cmd.stdout == "HI", cmd.stderr

    cmd = await playerctl.run('metadata --format "{{mpris:length}}"')
    assert cmd.stdout == "100000", cmd.stderr

    cmd = await playerctl.run(
        'metadata --format \'@{{ uc( "hi" ) }} - {{uc( lc( "HO"  ) ) }} . {{lc( uc(  title ) )   }}@\''
    )
    assert cmd.stdout == f'@HI - HO . {TITLE.lower()}@', cmd.stderr

    cmd = await playerctl.run(
        'metadata --format \'{{default(xesam:missing, artist)}}\'')
    assert cmd.stdout == ARTIST, cmd.stderr

    cmd = await playerctl.run(
        'metadata --format \'{{default(title, artist)}}\'')
    assert cmd.stdout == TITLE, cmd.stderr

    cmd = await playerctl.run('metadata --format \'{{default("", "ok")}}\'')
    assert cmd.stdout == 'ok', cmd.stderr

    cmd = await playerctl.run('metadata --format \'{{default("ok", "not")}}\'')
    assert cmd.stdout == 'ok', cmd.stderr

    status_emoji_cmd = 'metadata --format \'{{emoji(status)}}\''

    mpris.playback_status = 'Playing'
    cmd = await playerctl.run(status_emoji_cmd)
    assert cmd.stdout == '▶️', cmd.stderr

    mpris.playback_status = 'Paused'
    cmd = await playerctl.run(status_emoji_cmd)
    assert cmd.stdout == '⏸️', cmd.stderr

    mpris.playback_status = 'Stopped'
    cmd = await playerctl.run(status_emoji_cmd)
    assert cmd.stdout == '⏹️', cmd.stderr

    volume_emoji_cmd = 'metadata --format \'{{emoji(volume)}}\''
    mpris.volume = 0.0
    cmd = await playerctl.run(volume_emoji_cmd)
    assert cmd.stdout == '🔈', cmd.stderr

    mpris.volume = 0.5
    cmd = await playerctl.run(volume_emoji_cmd)
    assert cmd.stdout == '🔉', cmd.stderr

    mpris.volume = 1.0
    cmd = await playerctl.run(volume_emoji_cmd)
    assert cmd.stdout == '🔊', cmd.stderr

    cmd = await playerctl.run('metadata --format \'{{emoji("hi")}}\'')
    assert cmd.returncode == 1, cmd.stderr

    cmd = await playerctl.run('metadata --format \'{{emoji(status, volume)}}\''
                              )
    assert cmd.returncode == 1, cmd.stderr

    cmd = await playerctl.run('metadata --format " {{lc(album)}} "')
    assert cmd.stdout == ALBUM.lower()

    mpris.disconnect()
Example #17
0
async def connect(pi):
    from dbus_next import Message, MessageType, BusType, Variant
    from dbus_next.aio import MessageBus
    from dbus_next.errors import DBusError

    pi["characteristics"] = None

    bus = await MessageBus(bus_type=BusType.SYSTEM,
                           bus_address=pi["dbus_address"]).connect()

    om_introspection = await bus.introspect(BLUEZ_SERVICE_NAME, '/')
    om = bus.get_proxy_object(BLUEZ_SERVICE_NAME, '/',
                              om_introspection).get_interface(DBUS_OM_IFACE)

    om_objects = await om.call_get_managed_objects()
    for path, interfaces in om_objects.items():
        if BLUEZ_ADAPTER_IFACE in interfaces.keys():
            _LOGGER.debug("Discovered bluetooth adapter %s" % (path))
            adapter_introspection = await bus.introspect(
                BLUEZ_SERVICE_NAME, path)
            adapter = bus.get_proxy_object(
                BLUEZ_SERVICE_NAME, path,
                adapter_introspection).get_interface(BLUEZ_ADAPTER_IFACE)
            break

    if not adapter:
        _LOGGER.error("No bluetooth adapter localized")
        return

    for path, interfaces in om_objects.items():
        if BLUEZ_DEVICE_IFACE in interfaces.keys():
            device_introspection = await bus.introspect(
                BLUEZ_SERVICE_NAME, path)
            dev = bus.get_proxy_object(
                BLUEZ_SERVICE_NAME, path,
                device_introspection).get_interface(BLUEZ_DEVICE_IFACE)
            connected = await dev.get_connected()
            if connected:
                _LOGGER.debug("Disconnecting %s" % (path))
                await dev.call_disconnect()
            await adapter.call_remove_device(path)

    plejds = []

    @callback
    def on_interfaces_added(path, interfaces):
        if BLUEZ_DEVICE_IFACE in interfaces:
            if PLEJD_SVC_UUID in interfaces[BLUEZ_DEVICE_IFACE]['UUIDs'].value:
                plejds.append({'path': path})

    om.on_interfaces_added(on_interfaces_added)

    scan_filter = {
        "UUIDs": Variant('as', [PLEJD_SVC_UUID]),
        "Transport": Variant('s', "le"),
    }
    await adapter.call_set_discovery_filter(scan_filter)
    await adapter.call_start_discovery()
    await asyncio.sleep(pi["discovery_timeout"])

    for plejd in plejds:
        device_introspection = await bus.introspect(BLUEZ_SERVICE_NAME,
                                                    plejd['path'])
        dev = bus.get_proxy_object(
            BLUEZ_SERVICE_NAME, plejd['path'],
            device_introspection).get_interface(BLUEZ_DEVICE_IFACE)
        plejd['RSSI'] = await dev.get_rssi()
        plejd['obj'] = dev
        _LOGGER.debug("Discovered plejd %s with RSSI %d" %
                      (plejd['path'], plejd['RSSI']))

    if len(plejds) == 0:
        _LOGGER.warning("No plejd devices found")
        return

    plejds.sort(key=lambda a: a['RSSI'], reverse=True)
    for plejd in plejds:
        try:
            _LOGGER.debug("Connecting to %s" % (plejd["path"]))
            await plejd['obj'].call_connect()
            break
        except DBusError as e:
            _LOGGER.warning("Error connecting to plejd: %s" % (str(e)))

    await asyncio.sleep(pi["discovery_timeout"])

    objects = await om.call_get_managed_objects()
    chrcs = []

    for path, interfaces in objects.items():
        if GATT_CHRC_IFACE not in interfaces.keys():
            continue
        chrcs.append(path)

    async def process_plejd_service(service_path, chrc_paths, bus):
        service_introspection = await bus.introspect(BLUEZ_SERVICE_NAME,
                                                     service_path)
        service = bus.get_proxy_object(
            BLUEZ_SERVICE_NAME, service_path,
            service_introspection).get_interface(GATT_SERVICE_IFACE)
        uuid = await service.get_uuid()
        if uuid != PLEJD_SVC_UUID:
            return None

        dev = await service.get_device()
        x = re.search('dev_([0-9A-F_]+)$', dev)
        addr = binascii.a2b_hex(x.group(1).replace("_", ""))[::-1]

        chars = {}

        # Process the characteristics.
        for chrc_path in chrc_paths:
            chrc_introspection = await bus.introspect(BLUEZ_SERVICE_NAME,
                                                      chrc_path)
            chrc_obj = bus.get_proxy_object(BLUEZ_SERVICE_NAME, chrc_path,
                                            chrc_introspection)
            chrc = chrc_obj.get_interface(GATT_CHRC_IFACE)
            chrc_prop = chrc_obj.get_interface(DBUS_PROP_IFACE)

            uuid = await chrc.get_uuid()

            if uuid == PLEJD_DATA_UUID:
                chars["data"] = chrc
            elif uuid == PLEJD_LAST_DATA_UUID:
                chars["last_data"] = chrc
                chars["last_data_prop"] = chrc_prop
            elif uuid == PLEJD_AUTH_UUID:
                chars["auth"] = chrc
            elif uuid == PLEJD_PING_UUID:
                chars["ping"] = chrc

        return (addr, chars)

    plejd_service = None
    for path, interfaces in objects.items():
        if GATT_SERVICE_IFACE not in interfaces.keys():
            continue

        chrc_paths = [d for d in chrcs if d.startswith(path + "/")]

        plejd_service = await process_plejd_service(path, chrc_paths, bus)
        if plejd_service:
            break

    if not plejd_service:
        _LOGGER.warning("Failed connecting to plejd service")
        return

    if await plejd_auth(pi["key"], plejd_service[1]["auth"]) == False:
        return

    pi["address"] = plejd_service[0]
    pi["characteristics"] = plejd_service[1]

    @callback
    def handle_notification_cb(iface, changed_props, invalidated_props):
        if iface != GATT_CHRC_IFACE:
            return
        if not len(changed_props):
            return
        value = changed_props.get('Value', None)
        if not value:
            return

        dec = plejd_enc_dec(pi["key"], pi["address"], value.value)
        # check if this is a device we care about
        if dec[0] in PLEJD_DEVICES:
            device = PLEJD_DEVICES[dec[0]]
        elif dec[0] == 0x01 and dec[3:5] == b'\x00\x1b':
            time = struct.unpack_from('<I', dec, 5)[0]
            _LOGGER.debug("Plejd network reports time as '%s'",
                          datetime.fromtimestamp(time))
            return
        else:
            _LOGGER.debug("No match for device '%02x' (%s)" %
                          (dec[0], binascii.b2a_hex(dec)))
            return
        dim = None
        state = None
        if dec[3:5] == b'\x00\xc8' or dec[3:5] == b'\x00\x98':
            # 00c8 and 0098 both mean state+dim
            state = dec[5]
            dim = int.from_bytes(dec[6:8], 'little')
        elif dec[3:5] == b'\x00\x97':
            # 0097 is state only
            state = dec[5]
        else:
            _LOGGER.debug("No match for command '%s' (%s)" %
                          (binascii.b2a_hex(dec[3:5]), binascii.b2a_hex(dec)))
            return
        if (state == 0):
            state = False
        else:
            state = True

        device.update_state(state, dim)

    await adapter.call_stop_discovery()

    pi["characteristics"]["last_data_prop"].on_properties_changed(
        handle_notification_cb)
    await pi["characteristics"]["last_data"].call_start_notify()

    return
async def test_methods(interface_class):
    bus1 = await MessageBus().connect()
    bus2 = await MessageBus().connect()

    interface = interface_class('test.interface')
    export_path = '/test/path'

    async def call(member, signature='', body=[], flags=MessageFlag.NONE):
        return await bus2.call(
            Message(destination=bus1.unique_name,
                    path=export_path,
                    interface=interface.name,
                    member=member,
                    signature=signature,
                    body=body,
                    flags=flags))

    bus1.export(export_path, interface)

    body = ['hello world']
    reply = await call('echo', 's', body)

    assert reply.message_type == MessageType.METHOD_RETURN, reply.body[0]
    assert reply.signature == 's'
    assert reply.body == body

    body = ['hello', 'world']
    reply = await call('echo_multiple', 'ss', body)
    assert reply.message_type == MessageType.METHOD_RETURN, reply.body[0]
    assert reply.signature == 'ss'
    assert reply.body == body

    body = [['hello', 'world'],
            Variant('v', Variant('(ss)', ['hello', 'world'])), {
                'foo': Variant('t', 100)
            }, ['one', ['two', [Variant('s', 'three')]]]]
    signature = 'asva{sv}(s(s(v)))'
    SignatureTree(signature).verify(body)
    reply = await call('echo_containers', signature, body)
    assert reply.message_type == MessageType.METHOD_RETURN, reply.body[0]
    assert reply.signature == signature
    assert reply.body == body

    reply = await call('ping')
    assert reply.message_type == MessageType.METHOD_RETURN, reply.body[0]
    assert reply.signature == ''
    assert reply.body == []

    reply = await call('throws_unexpected_error')
    assert reply.message_type == MessageType.ERROR, reply.body[0]
    assert reply.error_name == ErrorType.SERVICE_ERROR.value, reply.body[0]

    reply = await call('throws_dbus_error')
    assert reply.message_type == MessageType.ERROR, reply.body[0]
    assert reply.error_name == 'test.error', reply.body[0]
    assert reply.body == ['an error ocurred']

    reply = await call('ping', flags=MessageFlag.NO_REPLY_EXPECTED)
    assert reply is None

    reply = await call('throws_unexpected_error', flags=MessageFlag.NO_REPLY_EXPECTED)
    assert reply is None

    reply = await call('throws_dbus_error', flags=MessageFlag.NO_REPLY_EXPECTED)
    assert reply is None
 def GetVariantDict(self) -> 'a{sv}':
     return {
         'foo': Variant('s', 'bar'),
         'bat': Variant('x', -55),
         'a_list': Variant('as', ['hello', 'world'])
     }
Example #20
0
class DBusDesktopNotifier(DesktopNotifierBase):
    """DBus notification backend for Linux

    This implements the org.freedesktop.Notifications standard. The DBUS connection is
    created in a thread with a running asyncio loop to handle clicked notifications.
    """

    _to_native_urgency = {
        NotificationLevel.Low: Variant("y", 0),
        NotificationLevel.Normal: Variant("y", 1),
        NotificationLevel.Critical: Variant("y", 2),
    }

    def __init__(self, app_name: str, app_id: str) -> None:
        super().__init__(app_name, app_id)
        self._loop = asyncio.get_event_loop()
        self.interface: Optional[ProxyInterface] = None
        self._force_run_in_loop(self._init_dbus())

    def _force_run_in_loop(self, coro: Coroutine) -> None:

        if self._loop.is_running():
            asyncio.run_coroutine_threadsafe(coro, self._loop)
        else:
            self._loop.run_until_complete(coro)

    async def _init_dbus(self) -> None:

        try:
            self.bus = await MessageBus().connect()
            introspection = await self.bus.introspect(
                "org.freedesktop.Notifications",
                "/org/freedesktop/Notifications")
            self.proxy_object = self.bus.get_proxy_object(
                "org.freedesktop.Notifications",
                "/org/freedesktop/Notifications",
                introspection,
            )
            self.interface = self.proxy_object.get_interface(
                "org.freedesktop.Notifications")
            self.interface.on_action_invoked(self._on_action)
        except Exception:
            self.interface = None
            logger.warning("Could not connect to DBUS interface",
                           exc_info=True)

    def send(self, notification: Notification) -> None:
        """
        Sends a notification.

        :param notification: Notification to send.
        """
        self._force_run_in_loop(self._send(notification))

    async def _send(self, notification: Notification) -> None:

        # Do nothing if we couldn't connect.
        if not self.interface:
            return

        # Get an internal ID for the notifications. This will recycle an old ID if we
        # are above the max number of notifications.
        internal_nid = self._next_nid()

        # Get the old notification to replace, if any.
        notification_to_replace = self.current_notifications.get(internal_nid)

        if notification_to_replace:
            replaces_nid = notification_to_replace.identifier
        else:
            replaces_nid = 0

        # Create list of actions with default and user-supplied.
        actions = ["default", "default"]

        for button_name in notification.buttons.keys():
            actions += [button_name, button_name]

        try:
            # Post the new notification and record the platform ID assigned to it.
            platform_nid = await self.interface.call_notify(
                self.app_name,  # app_name
                replaces_nid,  # replaces_id
                notification.icon or "",  # app_icon
                notification.title,  # summary
                notification.message,  # body
                actions,  # actions
                {"urgency": self._to_native_urgency[notification.urgency]
                 },  # hints
                -1,  # expire_timeout (-1 = default)
            )
        except Exception:
            # This may fail for several reasons: there may not be a systemd service
            # file for 'org.freedesktop.Notifications' or the system configuration
            # may have changed after DesktopNotifierFreedesktopDBus was initialized.
            logger.warning("Notification failed", exc_info=True)
        else:
            # Store the notification for future replacement and to keep track of
            # user-supplied callbacks.
            notification.identifier = platform_nid
            self.current_notifications[internal_nid] = notification

    def _on_action(self, nid, action_key) -> None:

        # Get the notification instance from the platform ID.
        nid = int(nid)
        action_key = str(action_key)
        notification = next(
            iter(n for n in self.current_notifications.values()
                 if n.identifier == nid),
            None,
        )

        # Execute any callbacks for button clicks.
        if notification:
            if action_key == "default" and notification.action:
                notification.action()
            else:
                callback = notification.buttons.get(action_key)

                if callback:
                    callback()
async def test_property_methods(interface_class):
    bus1 = await MessageBus().connect()
    bus2 = await MessageBus().connect()

    interface = interface_class('test.interface')
    export_path = '/test/path'
    bus1.export(export_path, interface)

    async def call_properties(member, signature, body):
        return await bus2.call(
            Message(destination=bus1.unique_name,
                    path=export_path,
                    interface='org.freedesktop.DBus.Properties',
                    member=member,
                    signature=signature,
                    body=body))

    result = await call_properties('GetAll', 's', [interface.name])

    assert result.message_type == MessageType.METHOD_RETURN, result.body[0]
    assert result.signature == 'a{sv}'
    assert result.body == [{
        'string_prop': Variant('s', interface._string_prop),
        'readonly_prop': Variant('t', interface._readonly_prop),
        'container_prop': Variant('a(ss)', interface._container_prop),
        'renamed_prop': Variant('s', interface._renamed_prop)
    }]

    result = await call_properties('Get', 'ss', [interface.name, 'string_prop'])
    assert result.message_type == MessageType.METHOD_RETURN, result.body[0]
    assert result.signature == 'v'
    assert result.body == [Variant('s', 'hi')]

    result = await call_properties(
        'Set', 'ssv',
        [interface.name, 'string_prop', Variant('s', 'ho')])
    assert result.message_type == MessageType.METHOD_RETURN, result.body[0]
    assert interface._string_prop == 'ho'
    if interface_class is AsyncInterface:
        assert 'ho', await interface.string_prop()
    else:
        assert 'ho', interface.string_prop

    result = await call_properties(
        'Set', 'ssv',
        [interface.name, 'readonly_prop', Variant('t', 100)])
    assert result.message_type == MessageType.ERROR, result.body[0]
    assert result.error_name == ErrorType.PROPERTY_READ_ONLY.value, result.body[0]

    result = await call_properties(
        'Set', 'ssv',
        [interface.name, 'disabled_prop', Variant('s', 'asdf')])
    assert result.message_type == MessageType.ERROR, result.body[0]
    assert result.error_name == ErrorType.UNKNOWN_PROPERTY.value

    result = await call_properties(
        'Set', 'ssv',
        [interface.name, 'not_a_prop', Variant('s', 'asdf')])
    assert result.message_type == MessageType.ERROR, result.body[0]
    assert result.error_name == ErrorType.UNKNOWN_PROPERTY.value

    # wrong type
    result = await call_properties('Set', 'ssv', [interface.name, 'string_prop', Variant('t', 100)])
    assert result.message_type == MessageType.ERROR
    assert result.error_name == ErrorType.INVALID_SIGNATURE.value

    # enable the erroring properties so we can test them
    for prop in ServiceInterface._get_properties(interface):
        if prop.name in ['throws_error', 'returns_wrong_type']:
            prop.disabled = False

    result = await call_properties('Get', 'ss', [interface.name, 'returns_wrong_type'])
    assert result.message_type == MessageType.ERROR, result.body[0]
    assert result.error_name == ErrorType.SERVICE_ERROR.value

    result = await call_properties(
        'Set', 'ssv',
        [interface.name, 'throws_error', Variant('s', 'ho')])
    assert result.message_type == MessageType.ERROR, result.body[0]
    assert result.error_name == 'test.error'
    assert result.body == ['told you so']

    result = await call_properties('Get', 'ss', [interface.name, 'throws_error'])
    assert result.message_type == MessageType.ERROR, result.body[0]
    assert result.error_name == 'test.error'
    assert result.body == ['told you so']

    result = await call_properties('GetAll', 's', [interface.name])
    assert result.message_type == MessageType.ERROR, result.body[0]
    assert result.error_name == 'test.error'
    assert result.body == ['told you so']
Example #22
0
 async def power(self, tech: ConnmanTechnology, on: bool) -> None:
     proxy = ConnmanTechnologyProxy(tech, self._bus)
     await proxy.set_property("Powered", Variant("b", on))
Example #23
0
class DBusDesktopNotifier(DesktopNotifierBase):
    """DBus notification backend for Linux

    This implements the org.freedesktop.Notifications standard. The DBUS connection is
    created in a thread with a running asyncio loop to handle clicked notifications.

    :param app_name: The name of the app. If it matches the application name in an
        existing desktop entry, the icon from that entry will be used by default.
    :param app_icon: The default icon to use for notifications. Will take precedence
        over any icon from the desktop file. Should be a URI or a name in a
        freedesktop.org-compliant icon theme.
    :param notification_limit: Maximum number of notifications to keep in the system's
        notification center.
    """

    _to_native_urgency = {
        Urgency.Low: Variant("y", 0),
        Urgency.Normal: Variant("y", 1),
        Urgency.Critical: Variant("y", 2),
    }

    def __init__(
        self,
        app_name: str = "Python",
        app_icon: Optional[str] = None,
        notification_limit: Optional[int] = None,
    ) -> None:
        super().__init__(app_name, app_icon, notification_limit)
        self.interface: Optional[ProxyInterface] = None

    async def request_authorisation(self) -> bool:
        """
        Request authorisation to send notifications.

        :returns: Whether authorisation has been granted.
        """
        return True

    async def has_authorisation(self) -> bool:
        """
        Whether we have authorisation to send notifications.
        """
        return True

    async def _init_dbus(self) -> ProxyInterface:

        self.bus = await MessageBus().connect()
        introspection = await self.bus.introspect(
            "org.freedesktop.Notifications", "/org/freedesktop/Notifications")
        self.proxy_object = self.bus.get_proxy_object(
            "org.freedesktop.Notifications",
            "/org/freedesktop/Notifications",
            introspection,
        )
        self.interface = self.proxy_object.get_interface(
            "org.freedesktop.Notifications")

        # Some older interfaces may not support notification actions.

        if hasattr(self.interface, "on_notification_closed"):
            self.interface.on_notification_closed(
                self._on_closed)  # type: ignore

        if hasattr(self.interface, "on_action_invoked"):
            self.interface.on_action_invoked(self._on_action)  # type: ignore

        return self.interface

    async def _send(
        self,
        notification: Notification,
        notification_to_replace: Optional[Notification],
    ) -> int:
        """
        Asynchronously sends a notification via the Dbus interface.

        :param notification: Notification to send.
        :param notification_to_replace: Notification to replace, if any.
        """
        if not self.interface:
            self.interface = await self._init_dbus()

        if notification_to_replace:
            replaces_nid = notification_to_replace.identifier
        else:
            replaces_nid = 0

        # Create list of actions with default and user-supplied.
        actions = ["default", "default"]

        for n, button in enumerate(notification.buttons):
            actions += [str(n), button.title]

        hints = {"urgency": self._to_native_urgency[notification.urgency]}

        # sound
        if notification.sound:
            hints["sound-name"] = Variant("s", "message-new-instant")

        # attachment
        if notification.attachment:
            hints["image-path"] = Variant("s", notification.attachment)

        # Post the new notification and record the platform ID assigned to it.
        platform_nid = await self.interface.call_notify(  # type: ignore
            self.app_name,  # app_name
            replaces_nid,  # replaces_id
            notification.icon or "",  # app_icon
            notification.title,  # summary
            notification.message,  # body
            actions,  # actions
            hints,  # hints
            -1,  # expire_timeout (-1 = default)
        )

        return platform_nid

    async def _clear(self, notification: Notification) -> None:
        """
        Asynchronously removes a notification from the notification center
        """

        if not self.interface:
            return

        await self.interface.call_close_notification(notification.identifier
                                                     )  # type: ignore

    async def _clear_all(self) -> None:
        """
        Asynchronously clears all notifications from notification center
        """

        if not self.interface:
            return

        for notification in self.current_notifications:
            await self.interface.call_close_notification(
                notification.identifier)  # type: ignore

    # Note that _on_action and _on_closed might be called for the same notification
    # with some notification servers. This is not a problem because the _on_action
    # call will come first, in which case we are no longer interested in calling the
    # on_dismissed callback.

    def _on_action(self, nid: int, action_key: str) -> None:
        """
        Called when the user performs a notification action. This will invoke the
        handler callback.

        :param nid: The platform's notification ID as an integer.
        :param action_key: A string identifying the action to take. We choose those keys
            ourselves when scheduling the notification.
        """

        # Get the notification instance from the platform ID.
        notification = self._notification_for_nid.get(nid)

        # Execute any callbacks for button clicks.
        if notification:
            self._clear_notification_from_cache(notification)

            button_number: Optional[int]

            try:
                button_number = int(action_key)
            except ValueError:
                button_number = None

            if action_key == "default" and notification.on_clicked:
                notification.on_clicked()

            elif button_number is not None:

                button = notification.buttons[button_number]

                if button.on_pressed:
                    button.on_pressed()

    def _on_closed(self, nid: int, reason: int) -> None:
        """
        Called when the user closes a notification. This will invoke the registered
        callback.

        :param nid: The platform's notification ID as an integer.
        :param reason: An integer describing the reason why the notification was closed.
        """

        # Get the notification instance from the platform ID.
        notification = self._notification_for_nid.get(nid)

        # Execute callback for user dismissal.
        if notification:
            self._clear_notification_from_cache(notification)

            if reason == NOTIFICATION_CLOSED_DISMISSED and notification.on_dismissed:
                notification.on_dismissed()
Example #24
0
def test_unpack_variants() -> None:
    assert unpack_variants("plain") == "plain"

    inp = Variant("i", 5)
    assert unpack_variants(inp) == 5

    inp = Variant("a{sv}", {"one": Variant("i", 1)})
    assert unpack_variants(inp) == {"one": 1}

    inp = Variant("(sv)", ["foo", Variant("u", 5)])
    assert unpack_variants(inp) == ["foo", 5]

    inp = Variant("(asv)", [["foo"], Variant("u", 5)])
    assert unpack_variants(inp) == [["foo"], 5]

    inp = Variant("(avv)", [[Variant("s", "foo")], Variant("u", 5)])
    assert unpack_variants(inp) == [["foo"], 5]

    inp = Variant("aav", [[Variant("s", "foo"), Variant("u", 5)]])
    assert unpack_variants(inp) == [["foo", 5]]
Example #25
0
async def test_media_player_block():
    with patch("i3pyblocks.blocks.dbus.dbus_aio", autospec=True,
               spec_set=True):
        instance = dbus.MediaPlayerBlock()
        await instance.setup()

        changed_properties = {
            "Metadata":
            Variant(
                "a{sv}",
                {
                    "mpris:trackid":
                    Variant("s", "spotify:track:5hbg2YisSRgoGG85pl0g1F"),
                    "mpris:length":
                    Variant("t", 227040000),
                    "mpris:artUrl":
                    Variant(
                        "s",
                        "https://open.spotify.com/image/"
                        "ab67616d00001e02d583a42a4c3fc63b61f1eda9",
                    ),
                    "xesam:album":
                    Variant("s", "センチメートル"),
                    "xesam:albumArtist":
                    Variant("as", ["the peggies"]),
                    "xesam:artist":
                    Variant("as", ["the peggies"]),
                    "xesam:autoRating":
                    Variant("d", 0.67),
                    "xesam:discNumber":
                    Variant("i", 1),
                    "xesam:title":
                    Variant("s", "センチメートル"),
                    "xesam:trackNumber":
                    Variant("i", 1),
                    "xesam:url":
                    Variant(
                        "s",
                        "https://open.spotify.com/track/5hbg2YisSRgoGG85pl0g1F"
                    ),
                },
            ),
            "PlaybackStatus":
            Variant("s", "Playing"),
        }

        instance.update_callback(
            "org.mpris.MediaPlayer2.Player",
            changed_properties,
            [],
        )
        assert instance.result()["full_text"] == "the peggies - 1. センチメートル"
Example #26
0
class DBusDesktopNotifier(DesktopNotifierBase):
    """DBus notification backend for Linux. This implements the
    org.freedesktop.Notifications standard. The DBUS connection is created in a thread
    with a running asyncio loop to handle clicked notifications."""

    _to_native_urgency = {
        NotificationLevel.Low: Variant("y", 0),
        NotificationLevel.Normal: Variant("y", 1),
        NotificationLevel.Critical: Variant("y", 2),
    }

    def __init__(self, app_name: str, app_id: str) -> None:
        super().__init__(app_name, app_id)
        self._loop = asyncio.get_event_loop()
        self._force_run_in_loop(self._init_dbus())

    def _force_run_in_loop(self, coro: Coroutine) -> None:

        if self._loop.is_running():
            asyncio.run_coroutine_threadsafe(coro, self._loop)
        else:
            self._loop.run_until_complete(coro)

    async def _init_dbus(self) -> None:

        try:
            self.bus = await MessageBus().connect()
            introspection = await self.bus.introspect(
                "org.freedesktop.Notifications",
                "/org/freedesktop/Notifications")
            self.proxy_object = self.bus.get_proxy_object(
                "org.freedesktop.Notifications",
                "/org/freedesktop/Notifications",
                introspection,
            )
            self.interface = self.proxy_object.get_interface(
                "org.freedesktop.Notifications")
            self.interface.on_action_invoked(self._on_action)
        except Exception:
            self.interface = None
            logger.warning("Could not connect to DBUS interface",
                           exc_info=True)

    def send(self, notification: Notification) -> None:
        self._force_run_in_loop(self._send(notification))

    async def _send(self, notification: Notification) -> None:

        if not self.interface:
            return

        internal_nid = self._next_nid()
        notification_to_replace = self.current_notifications.get(internal_nid)

        if notification_to_replace:
            replaces_nid = notification_to_replace.identifier
        else:
            replaces_nid = 0

        actions = ["default", "default"]

        for button_name in notification.buttons.keys():
            actions += [button_name, button_name]

        try:
            platform_nid = await self.interface.call_notify(
                self.app_name,  # app_name
                replaces_nid,  # replaces_id
                notification.icon or "",  # app_icon
                notification.title,  # summary
                notification.message,  # body
                actions,  # actions
                {"urgency": self._to_native_urgency[notification.urgency]
                 },  # hints
                -1,  # expire_timeout (-1 = default)
            )
        except Exception:
            # This may fail for several reasons: there may not be a systemd service
            # file for 'org.freedesktop.Notifications' or the system configuration
            # may have changed after DesktopNotifierFreedesktopDBus was initialized.
            logger.warning("Notification failed", exc_info=True)
        else:
            notification.identifier = platform_nid
            self.current_notifications[internal_nid] = notification

    def _on_action(self, nid, action_key) -> None:

        nid = int(nid)
        action_key = str(action_key)
        notification = next(
            iter(n for n in self.current_notifications.values()
                 if n.identifier == nid),
            None,
        )

        if notification:
            if action_key == "default" and notification.action:
                notification.action()
            else:
                callback = notification.buttons.get(action_key)

                if callback:
                    callback()