async def authenticate(self, host, servers): """Authenticate with one or more roon servers.""" def stop_apis(apis): for api in apis: api.stop() token = None core_id = None secs = 0 if host is None: apis = [ RoonApi(ROON_APPINFO, None, server[0], server[1], blocking_init=False) for server in servers ] else: apis = [RoonApi(ROON_APPINFO, None, host, blocking_init=False)] while secs <= TIMEOUT: # Roon can discover multiple devices - not all of which are proper servers, so try and authenticate with them all. # The user will only enable one - so look for a valid token auth_api = [api for api in apis if api.token is not None] secs += AUTHENTICATE_TIMEOUT if auth_api: core_id = auth_api[0].core_id token = auth_api[0].token break await asyncio.sleep(AUTHENTICATE_TIMEOUT) await self._hass.async_add_executor_job(stop_apis, apis) return (token, core_id)
async def async_setup(self, tries=0): """Set up a roon server based on config parameters.""" opp = self.opp # Host will be None for configs using discovery host = self.config_entry.data[CONF_HOST] token = self.config_entry.data[CONF_API_KEY] # Default to None for compatibility with older configs core_id = self.config_entry.data.get(CONF_ROON_ID) _LOGGER.debug("async_setup: host=%s core_id=%s token=%s", host, core_id, token) self.roonapi = RoonApi(ROON_APPINFO, token, host, blocking_init=False, core_id=core_id) self.roonapi.register_state_callback(self.roonapi_state_callback, event_filter=["zones_changed"]) # Default to 'host' for compatibility with older configs without core_id self.roon_id = core_id if core_id is not None else host # initialize media_player platform opp.async_create_task( opp.config_entries.async_forward_entry_setup( self.config_entry, "media_player")) # Initialize Roon background polling asyncio.create_task(self.async_do_loop()) return True
async def authenticate(self, hass) -> bool: """Test if we can authenticate with the host.""" token = None secs = 0 roonapi = RoonApi(ROON_APPINFO, None, self._host, blocking_init=False) while secs < TIMEOUT: token = roonapi.token secs += AUTHENTICATE_TIMEOUT if token: break await asyncio.sleep(AUTHENTICATE_TIMEOUT) token = roonapi.token roonapi.stop() return token
async def async_setup(self, tries=0): """Set up a roon server based on host parameter.""" host = self.host hass = self.hass token = self.config_entry.data[CONF_API_KEY] _LOGGER.debug("async_setup: %s %s", token, host) self.roonapi = RoonApi(ROON_APPINFO, token, host, blocking_init=False) self.roonapi.register_state_callback( self.roonapi_state_callback, event_filter=["zones_changed"] ) # initialize media_player platform hass.async_create_task( hass.config_entries.async_forward_entry_setup( self.config_entry, "media_player" ) ) # Initialize Roon background polling asyncio.create_task(self.async_do_loop()) return True
from roonapi import RoonApi appinfo = { "extension_id": "python_roon_test", "display_name": "Python library for Roon", "display_version": "1.0.0", "publisher": "gregd", "email": "*****@*****.**", } # Can be None if you don't yet have a token if path.exists(tokenfile): token = open(tokenfile).read() else: token = "None" roonapi = RoonApi(appinfo, token, server) # get target zone output_id outputs = roonapi.outputs output_id = None for (k, v) in outputs.items(): if target_zone in v["display_name"]: output_id = k if output_id is None: print("No zone found matching", target_zone) exit() # Play tag (not yet working) tags = roonapi.list_media(output_id, ["Library", "Tags", tag])
} target_zone = "Mixing Speakers" try: core_id = open("my_core_id_file").read() token = open("my_token_file").read() except OSError: print("Please authorise first using discovery.py") exit() discover = RoonDiscovery(core_id) server = discover.first() discover.stop() roonapi = RoonApi(appinfo, token, server[0], server[1], True) # get target zone output_id zones = roonapi.zones output_id = [ output["zone_id"] for output in zones.values() if output["display_name"] == target_zone ][0] print("OUTPUT ID", output_id) # Examples of using play_media print("PLAY Something unplayable - should give error") items = roonapi.play_media(output_id, ["Qobuz", "My Qobuz", "Favorite Albums"]) print("PLAY Something playable - this should work")
appinfo = { "extension_id": "python_roon_test", "display_name": "Python library for Roon", "display_version": "1.0.0", "publisher": "gregd", "email": "*****@*****.**", } # Can be None if you don't yet have a token token = open("mytokenfile").read() # Take a look at examples/discovery if you want to use discovery. server = "192.168.3.60" roonapi = RoonApi(appinfo, token, server) def my_state_callback(event, changed_ids): """Call when something changes in roon.""" print("my_state_callback event:%s changed_ids: %s" % (event, changed_ids)) for zone_id in changed_ids: zone = roonapi.zones[zone_id] print("zone_id:%s zone_info: %s" % (zone_id, zone)) # receive state updates in your callback roonapi.register_state_callback(my_state_callback) time.sleep(60)
from roonapi import RoonApi appinfo = { "extension_id": "python_roon_test", "display_name": "Python library for Roon", "display_version": "1.0.0", "publisher": "gregd", "email": "*****@*****.**", } # Can be None if you don't yet have a token if path.exists(tokenfile): token = open(tokenfile).read() else: token = "None" roonapi = RoonApi(appinfo, token, server) # get target zone output_id outputs = roonapi.outputs output_id = None for (k, v) in outputs.items(): if target_zone in v["display_name"]: output_id = k if output_id is None: print("No zone found matching", target_zone) exit() # List matching artists artists = roonapi.list_media(output_id, ["Library", "Artists", searchterm])
token = None if os.path.isfile("roon_test_token.txt"): with open("roon_test_token.txt") as f: token = f.read() appinfo = { "extension_id": "python_roon_test", "display_name": "Python library for Roon", "display_version": "1.0.0", "publisher": "pavoni", "email": "*****@*****.**", } # with RoonApi(appinfo, token, host, blocking_init=True) as roonapi: with RoonApi(appinfo, token, None, blocking_init=True) as roonapi: # Test basic zone fetching zones = [zone["display_name"] for zone in roonapi.zones.values()] zones.sort() assert len(zones) == 6 assert zones == [ "Bedroom", "Hi Fi", "Kitchen", "Mixing Speakers", "Shower", "Study", ] # Test basic output fetching
class RoonServer: """Manages a single Roon Server.""" def __init__(self, opp, config_entry): """Initialize the system.""" self.config_entry = config_entry self.opp = opp self.roonapi = None self.roon_id = None self.all_player_ids = set() self.all_playlists = [] self.offline_devices = set() self._exit = False self._roon_name_by_id = {} self._id_by_roon_name = {} async def async_setup(self, tries=0): """Set up a roon server based on config parameters.""" opp = self.opp # Host will be None for configs using discovery host = self.config_entry.data[CONF_HOST] token = self.config_entry.data[CONF_API_KEY] # Default to None for compatibility with older configs core_id = self.config_entry.data.get(CONF_ROON_ID) _LOGGER.debug("async_setup: host=%s core_id=%s token=%s", host, core_id, token) self.roonapi = RoonApi(ROON_APPINFO, token, host, blocking_init=False, core_id=core_id) self.roonapi.register_state_callback(self.roonapi_state_callback, event_filter=["zones_changed"]) # Default to 'host' for compatibility with older configs without core_id self.roon_id = core_id if core_id is not None else host # initialize media_player platform opp.async_create_task( opp.config_entries.async_forward_entry_setup( self.config_entry, "media_player")) # Initialize Roon background polling asyncio.create_task(self.async_do_loop()) return True async def async_reset(self): """Reset this connection to default state. Will cancel any scheduled setup retry and will unload the config entry. """ self.stop_roon() return True @property def zones(self): """Return list of zones.""" return self.roonapi.zones def add_player_id(self, entity_id, roon_name): """Register a roon player.""" self._roon_name_by_id[entity_id] = roon_name self._id_by_roon_name[roon_name] = entity_id def roon_name(self, entity_id): """Get the name of the roon player from entity_id.""" return self._roon_name_by_id.get(entity_id) def entity_id(self, roon_name): """Get the id of the roon player from the roon name.""" return self._id_by_roon_name.get(roon_name) def stop_roon(self): """Stop background worker.""" self.roonapi.stop() self._exit = True def roonapi_state_callback(self, event, changed_zones): """Callbacks from the roon api websockets.""" self.opp.add_job(self.async_update_changed_players(changed_zones)) async def async_do_loop(self): """Background work loop.""" self._exit = False while not self._exit: await self.async_update_players() # await self.async_update_playlists() await asyncio.sleep(FULL_SYNC_INTERVAL) async def async_update_changed_players(self, changed_zones_ids): """Update the players which were reported as changed by the Roon API.""" for zone_id in changed_zones_ids: if zone_id not in self.roonapi.zones: # device was removed ? continue zone = self.roonapi.zones[zone_id] for device in zone["outputs"]: dev_name = device["display_name"] if dev_name == "Unnamed" or not dev_name: # ignore unnamed devices continue player_data = await self.async_create_player_data(zone, device) dev_id = player_data["dev_id"] player_data["is_available"] = True if dev_id in self.offline_devices: # player back online self.offline_devices.remove(dev_id) async_dispatcher_send(self.opp, "roon_media_player", player_data) self.all_player_ids.add(dev_id) async def async_update_players(self): """Periodic full scan of all devices.""" zone_ids = self.roonapi.zones.keys() await self.async_update_changed_players(zone_ids) # check for any removed devices all_devs = {} for zone in self.roonapi.zones.values(): for device in zone["outputs"]: player_data = await self.async_create_player_data(zone, device) dev_id = player_data["dev_id"] all_devs[dev_id] = player_data for dev_id in self.all_player_ids: if dev_id in all_devs: continue # player was removed! player_data = {"dev_id": dev_id} player_data["is_available"] = False async_dispatcher_send(self.opp, "roon_media_player", player_data) self.offline_devices.add(dev_id) async def async_create_player_data(self, zone, output): """Create player object dict by combining zone with output.""" new_dict = zone.copy() new_dict.update(output) new_dict.pop("outputs") new_dict["roon_id"] = self.roon_id new_dict["is_synced"] = len(zone["outputs"]) > 1 new_dict["zone_name"] = zone["display_name"] new_dict["display_name"] = output["display_name"] new_dict["last_changed"] = utcnow() # we don't use the zone_id or output_id for now as unique id as I've seen cases were it changes for some reason new_dict["dev_id"] = f"roon_{self.roon_id}_{output['display_name']}" return new_dict
from roonapi import RoonApi appinfo = { "extension_id": "python_roon_test", "display_name": "Python library for Roon", "display_version": "1.0.0", "publisher": "gregd", "email": "*****@*****.**", } # Can be None if you don't yet have a token token = open("mytokenfile").read() # Take a look at examples/discovery if you want to use discovery. server = "192.168.3.60" roonapi = RoonApi(appinfo, token, server) # get all zones (as dict) print(roonapi.zones) # get all outputs (as dict) print(roonapi.outputs) # save the token for next time with open("mytokenfile", "w") as f: f.write(roonapi.token)
from roonapi import RoonApi appinfo = { "extension_id": "python_roon_test", "display_name": "Python library for Roon", "display_version": "1.0.0", "publisher": "gregd", "email": "*****@*****.**", } # Can be None if you don't yet have a token if path.exists(tokenfile): token = open(tokenfile).read() else: token = "None" roonapi = RoonApi(appinfo, token, server) # get target zone output_id outputs = roonapi.outputs output_id = None for (k, v) in outputs.items(): if target_zone in v["display_name"]: zone_name = v["display_name"] output_id = k if zone_command == "verify": if output_id is None: print("") else: print(zone_name)
import time from roonapi import RoonApi, RoonDiscovery appinfo = { "extension_id": "python_roon_test", "display_name": "Python library for Roon", "display_version": "1.0.0", "publisher": "gregd", "email": "*****@*****.**", } discover = RoonDiscovery(None) servers = discover.all() apis = [ RoonApi(appinfo, None, server[0], server[1], False) for server in servers ] auth_api = [] while len(auth_api) == 0: print( "\nWaiting for authorization - in Roon, click \033[1mSettings -> Extensions -> Enable\033[0m\n", flush=True) time.sleep(15) auth_api = [api for api in apis if api.token is not None] api = auth_api[0] print("RoonCoreIP =", api.host)
from roonapi import RoonApi appinfo = { "extension_id": "python_roon_test", "display_name": "Python library for Roon", "display_version": "1.0.0", "publisher": "gregd", "email": "*****@*****.**", } # Can be None if you don't yet have a token if path.exists(tokenfile): token = open(tokenfile).read() else: token = "None" roonapi = RoonApi(appinfo, token, server) # get target zone output_id outputs = roonapi.outputs output_id = None for (k, v) in outputs.items(): if target_zone in v["display_name"]: output_id = k if output_id is None: print("No zone found matching", target_zone) exit() # Play album from Library found = roonapi.play_media(output_id, ["Library", "Albums", album])
callback_count = 0 events = [] def state_callback(event, changed_items): """Update details when the roon state changes.""" global callback_count, events callback_count += 1 events.append(event) LOGGER.info("%s: %s", event, changed_items) # initialize Roon api and register the callback for state changes host = "192.168.1.160" roonapi = RoonApi(appinfo, token, host) roonapi.register_state_callback(state_callback) zones = [ zone for zone in roonapi.zones.values() if zone["display_name"] == "Mixing Speakers" ] assert len(zones) == 1 test_zone = zones[0] test_output_id = test_zone["outputs"][0]["output_id"] assert callback_count == 0 assert events == []
appinfo = { "extension_id": "python_roon_test", "display_name": "Python library for Roon", "display_version": "1.0.0", "publisher": "gregd", "email": "*****@*****.**", } # Can be None if you don't yet have a token if path.exists(tokenfile): token = open(tokenfile).read() else: token = "None" roonapi = RoonApi(appinfo, token, server) # get target zone output_id outputs = roonapi.outputs output_id = None for (k, v) in outputs.items(): if target_zone in v["display_name"]: output_id = k if output_id is None: print("No zone found matching", target_zone) exit() # Play artist from Library found = roonapi.play_media(output_id, ["Library", "Artists", artist])
"extension_id": "python_roon_test", "display_name": "Python library for Roon", "display_version": "1.0.0", "publisher": "gregd", "email": "*****@*****.**", } # Can be None if you don't yet have a token token = open("mytokenfile").read() # token= None # Take a look at examples/discovery if you want to use discovery. server = "192.168.3.60" roonapi = RoonApi(appinfo, token, server) # get all zones (as dict) zones = roonapi.zones outputs = roonapi.outputs for (k, v) in outputs.items(): zone_id = v["zone_id"] output_id = k display_name = v["display_name"] is_group_main = roonapi.is_group_main(output_id) is_grouped = roonapi.is_grouped(output_id) grouped_zone_names = roonapi.grouped_zone_names(output_id) print( display_name, "grouped?",
def get_roon_api(): token = self.config_entry.data[CONF_API_KEY] (host, port) = get_roon_host() return RoonApi(ROON_APPINFO, token, host, port, blocking_init=True)
from roonapi import RoonApi appinfo = { "extension_id": "python_roon_test", "display_name": "Python library for Roon", "display_version": "1.0.0", "publisher": "gregd", "email": "*****@*****.**", } server = "192.168.3.60" target_zone = "Mixing Speakers" # Can be None if you don't yet have a token token = open("mytokenfile").read() roonapi = RoonApi(appinfo, token, server) # get target zone output_id zones = roonapi.zones output_id = [ output["zone_id"] for output in zones.values() if output["display_name"] == target_zone ][0] print("OUTPUT ID", output_id) # Examples of using play_media print("RADIO") items = roonapi.play_media(output_id, ["My Live Radio", "BBC Radio 4"]) print("SINGLE ARTIST") items = roonapi.play_media(output_id, ["Library", "Artists", "Neil Young"])
from roonapi import RoonApi, RoonDiscovery appinfo = { "extension_id": "python_roon_test", "display_name": "Python library for Roon", "display_version": "1.0.0", "publisher": "gregd", "email": "*****@*****.**", } # Can be None if you don't yet have a token try: core_id = open("my_core_id_file").read() token = open("my_token_file").read() except OSError: print("Please authorise first using discovery.py") exit() discover = RoonDiscovery(core_id) server = discover.first() discover.stop() roonapi = RoonApi(appinfo, token, server[0], server[1], True) # get all zones (as dict) print(roonapi.zones) # get all outputs (as dict) print(roonapi.outputs)
class RoonServer: """Manages a single Roon Server.""" def __init__(self, hass, config_entry): """Initialize the system.""" self.config_entry = config_entry self.hass = hass self.roonapi = None self.all_player_ids = set() self.all_playlists = [] self.offline_devices = set() self._exit = False self._roon_name_by_id = {} @property def host(self): """Return the host of this server.""" return self.config_entry.data[CONF_HOST] async def async_setup(self, tries=0): """Set up a roon server based on host parameter.""" host = self.host hass = self.hass token = self.config_entry.data[CONF_API_KEY] _LOGGER.debug("async_setup: %s %s", token, host) self.roonapi = RoonApi(ROON_APPINFO, token, host, blocking_init=False) self.roonapi.register_state_callback( self.roonapi_state_callback, event_filter=["zones_changed"] ) # initialize media_player platform hass.async_create_task( hass.config_entries.async_forward_entry_setup( self.config_entry, "media_player" ) ) # Initialize Roon background polling asyncio.create_task(self.async_do_loop()) return True async def async_reset(self): """Reset this connection to default state. Will cancel any scheduled setup retry and will unload the config entry. """ self.stop_roon() return True @property def zones(self): """Return list of zones.""" return self.roonapi.zones def add_player_id(self, entity_id, roon_name): """Register a roon player.""" self._roon_name_by_id[entity_id] = roon_name def roon_name(self, entity_id): """Get the name of the roon player from entity_id.""" return self._roon_name_by_id.get(entity_id) def stop_roon(self): """Stop background worker.""" self.roonapi.stop() self._exit = True def roonapi_state_callback(self, event, changed_zones): """Callbacks from the roon api websockets.""" self.hass.add_job(self.async_update_changed_players(changed_zones)) async def async_do_loop(self): """Background work loop.""" self._exit = False while not self._exit: await self.async_update_players() # await self.async_update_playlists() await asyncio.sleep(FULL_SYNC_INTERVAL) async def async_update_changed_players(self, changed_zones_ids): """Update the players which were reported as changed by the Roon API.""" for zone_id in changed_zones_ids: if zone_id not in self.roonapi.zones: # device was removed ? continue zone = self.roonapi.zones[zone_id] for device in zone["outputs"]: dev_name = device["display_name"] if dev_name == "Unnamed" or not dev_name: # ignore unnamed devices continue player_data = await self.async_create_player_data(zone, device) dev_id = player_data["dev_id"] player_data["is_available"] = True if dev_id in self.offline_devices: # player back online self.offline_devices.remove(dev_id) async_dispatcher_send(self.hass, "roon_media_player", player_data) self.all_player_ids.add(dev_id) async def async_update_players(self): """Periodic full scan of all devices.""" zone_ids = self.roonapi.zones.keys() await self.async_update_changed_players(zone_ids) # check for any removed devices all_devs = {} for zone in self.roonapi.zones.values(): for device in zone["outputs"]: player_data = await self.async_create_player_data(zone, device) dev_id = player_data["dev_id"] all_devs[dev_id] = player_data for dev_id in self.all_player_ids: if dev_id in all_devs: continue # player was removed! player_data = {"dev_id": dev_id} player_data["is_available"] = False async_dispatcher_send(self.hass, "roon_media_player", player_data) self.offline_devices.add(dev_id) async def async_update_playlists(self): """Store lists in memory with all playlists - could be used by a custom lovelace card.""" all_playlists = [] roon_playlists = self.roonapi.playlists() if roon_playlists and "items" in roon_playlists: all_playlists += [item["title"] for item in roon_playlists["items"]] roon_playlists = self.roonapi.internet_radio() if roon_playlists and "items" in roon_playlists: all_playlists += [item["title"] for item in roon_playlists["items"]] self.all_playlists = all_playlists async def async_create_player_data(self, zone, output): """Create player object dict by combining zone with output.""" new_dict = zone.copy() new_dict.update(output) new_dict.pop("outputs") new_dict["host"] = self.host new_dict["is_synced"] = len(zone["outputs"]) > 1 new_dict["zone_name"] = zone["display_name"] new_dict["display_name"] = output["display_name"] new_dict["last_changed"] = utcnow() # we don't use the zone_id or output_id for now as unique id as I've seen cases were it changes for some reason new_dict["dev_id"] = f"roon_{self.host}_{output['display_name']}" return new_dict
from roonapi import RoonApi appinfo = { "extension_id": "python_roon_test", "display_name": "Python library for Roon", "display_version": "1.0.0", "publisher": "gregd", "email": "*****@*****.**", } # Can be None if you don't yet have a token if path.exists(tokenfile): token = open(tokenfile).read() else: token = "None" roonapi = RoonApi(appinfo, token, server) # get target zone output_id outputs = roonapi.outputs output_id = None for (k, v) in outputs.items(): if target_zone in v["display_name"]: output_id = k if output_id is None: print("No zone found matching", target_zone) exit() # List matching playlists playlists = roonapi.list_media(output_id, ["Playlists", searchterm])
from roonapi import RoonApi appinfo = { "extension_id": "python_roon_test", "display_name": "Python library for Roon", "display_version": "1.0.0", "publisher": "gregd", "email": "*****@*****.**", } # Can be None if you don't yet have a token if path.exists(tokenfile): token = open(tokenfile).read() else: token = "None" roonapi = RoonApi(appinfo, token, server) # get target zone output_id outputs = roonapi.outputs output_id = None for (k, v) in outputs.items(): if target_zone in v["display_name"]: output_id = k if output_id is None: print("No zone found matching", target_zone) exit() # Play playlist found = roonapi.play_media(output_id, ["Playlists", playlist])
from roonapi import RoonApi appinfo = { "extension_id": "python_roon_test", "display_name": "Python library for Roon", "display_version": "1.0.0", "publisher": "gregd", "email": "*****@*****.**", } # Can be None if you don't yet have a token if path.exists(tokenfile): token = open(tokenfile).read() else: token = "None" roonapi = RoonApi(appinfo, token, server) # get target zone output_id outputs = roonapi.outputs output_id = None for (k, v) in outputs.items(): if target_zone in v["display_name"]: output_id = k if output_id is None: print("No zone found matching", target_zone) exit() # Play genre found = roonapi.play_media(output_id, ["Genres", genre])
from roonapi import RoonApi appinfo = { "extension_id": "python_roon_test", "display_name": "Python library for Roon", "display_version": "1.0.0", "publisher": "gregd", "email": "*****@*****.**", } # Can be None if you don't yet have a token if path.exists(tokenfile): token = open(tokenfile).read() else: token = "None" roonapi = RoonApi(appinfo, token, server) # get target zone output_id outputs = roonapi.outputs output_id = None for (k, v) in outputs.items(): if target_zone in v["display_name"]: output_id = k if output_id is None: print("No zone found matching", target_zone) exit() # Play Radio Paradise Main roonapi.play_media(output_id, ["My Live Radio", "Radio Paradise: Main mix"])
from roonapi import RoonApi appinfo = { "extension_id": "python_roon_test", "display_name": "Python library for Roon", "display_version": "1.0.0", "publisher": "gregd", "email": "*****@*****.**", } # Can be None if you don't yet have a token if path.exists(tokenfile): token = open(tokenfile).read() else: token = "None" roonapi = RoonApi(appinfo, token, server) # get target zone output_id outputs = roonapi.outputs output_id = None for (k, v) in outputs.items(): if target_zone in v["display_name"]: output_id = k if output_id is None: print("No zone found matching", target_zone) exit() # List matching genres genres = roonapi.list_media(output_id, ["Genres", searchterm])
from roonapi import RoonApi appinfo = { "extension_id": "python_roon_test", "display_name": "Python library for Roon", "display_version": "1.0.0", "publisher": "gregd", "email": "*****@*****.**", } # Can be None if you don't yet have a token if path.exists(tokenfile): token = open(tokenfile).read() else: token = "None" roonapi = RoonApi(appinfo, token, server) # get target zone output_id outputs = roonapi.outputs output_ids = [] output_names = [] zone_name = None # get the first zone in this grouping for (k, v) in outputs.items(): if v["display_name"] == group_zones[0]: zone_name = v["display_name"] output_ids.append(k) output_names.append(zone_name) if zone_name is None:
import time from roonapi import RoonApi, RoonDiscovery appinfo = { "extension_id": "python_roon_test", "display_name": "Python library for Roon", "display_version": "1.0.0", "publisher": "gregd", "email": "*****@*****.**", } discover = RoonDiscovery(None) servers = discover.all() apis = [RoonApi(appinfo, None, server[0], server[1], False) for server in servers] auth_api = [] while len(auth_api) == 0: print("Waiting for authorisation") time.sleep(1) auth_api = [api for api in apis if api.token is not None] api = auth_api[0] print("Got authorisation") print(api.host) print(api.core_name) print(api.core_id) # This is what we need to reconnect core_id = api.core_id