Hello,
I’m encountering an issue with an integration where it only displays the correct states upon initialization but fails to refresh them subsequently. In the coordinator.py
file, an update interval of 10 seconds is defined.
To debug, I inserted _LOGGER.error
statements. Upon reviewing the logs below, it appears that _async_update_data
is called once at the start but never again, leading to the persistence of the initial states. Additionally, based on the log, it seems that _handle_coordinator_update
is never invoked.
How can I address this issue and ensure that the integration fetches and displays new states correctly?
Thank you in advance for your assistance.
The integration is at: GitHub - alejandro5x/jtechdigital-ha: Home Assistant integration of J-Tech Digital HDMI Matrix.
coordinator.py
import asyncio
import logging
from datetime import timedelta
from typing import Any
from aiohttp import CookieJar
from pyjtechdigital import JtechClient, JtechAuthError, JtechConnectionError
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.aiohttp_client import async_create_clientsession
from .const import DOMAIN, ATTR_MANUFACTURER, ERROR_CONNECT_FAILED, ERROR_FETCH_DATA_FAILED, ERROR_AUTH_FAILED, ERROR_UNKNOWN
from .structures import JtechOutputInfo, JtechSourceInfo
_LOGGER = logging.getLogger(__name__)
class JtechCoordinator(DataUpdateCoordinator):
"""Class to manage fetching and storing J-Tech Digital HDMI Matrix data."""
def __init__(self, hass, host, username, password):
"""Initialize the data coordinator."""
self.hass = hass
self.host = host
self.username = username
self.password = password
self._client = None
self.connected = False
self.outputs = []
self.sources = []
super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=timedelta(seconds=10))
async def _async_update_data(self):
"""Fetch the latest data from the J-Tech Digital HDMI Matrix."""
# Implementing the update logic from both versions here
await self._client_ensure()
try:
# TODO: dont rely on all requests, some can fail, its ok for the whole update
statuses = await self._fetch_status()
_LOGGER.debug("fetch_statuses", statuses)
_LOGGER.error("fetch_statuses", statuses)
#edid_status = statuses["edid"]
self.outputs = self._handle_output_update(statuses)
self.sources = self._handle_source_update(statuses)
# Return the combined_data with only the required information
combined_data = { }
status = statuses["status"]
if status:
combined_data["power"] = status.power
combined_data["model"] = status.model
combined_data["version"] = status.version
network_status = statuses["network"]
if network_status:
combined_data["hostname"] = network_status.hostname
combined_data["ipaddress"] = network_status.ipaddress
combined_data["subnet"] = network_status.subnet
combined_data["gateway"] = network_status.gateway
combined_data["macaddress"] = network_status.macaddress
combined_data["dhcp"] = network_status.dhcp
combined_data["telnetport"] = network_status.telnetport
combined_data["tcpport"] = network_status.tcpport
system_status = statuses["system"]
if system_status:
combined_data["baudrate_index"] = system_status.baudrate_index
combined_data["beep"] = system_status.beep
combined_data["lock"] = system_status.lock
combined_data["mode"] = system_status.mode
web_details = statuses["web_details"]
if web_details:
combined_data["title"] = web_details.title
return combined_data
except JtechAuthError as err:
self.connected = False
raise UpdateFailed(ERROR_AUTH_FAILED, err)
except Exception as err:
self.connected = False
raise UpdateFailed(ERROR_FETCH_DATA_FAILED, err)
def _handle_source_update(self, statuses):
_LOGGER.error("_handle_source_update")
output_status = statuses["output"]
source_status = statuses["source"]
cec_status = statuses["cec"]
# Split the source data
if source_status and source_status.source_names:
sources_data = []
for idx, source_name in enumerate(source_status.source_names):
active = source_status.active_sources[idx]
edid_index = source_status.edid_indexes[idx]
cec_selected = idx in cec_status.selected_cec_sources
outputs = []
for output_idx, source in enumerate(output_status.selected_sources):
if source == idx:
outputs.append(output_idx + 1)
sources_data.append(JtechSourceInfo(outputs, source_name, active, edid_index, cec_selected))
return sources_data
def _handle_output_update(self, statuses):
_LOGGER.error("_handle_output_update")
output_status = statuses["output"]
cec_status = statuses["cec"]
# Split the output data
if output_status and output_status.output_names:
outputs_data = []
for idx, output_name in enumerate(output_status.output_names):
source_idx = output_status.selected_sources[idx]
cat_name = output_status.output_cat_names[idx]
connected = output_status.connected_outputs[idx]
cat_connected = output_status.connected_cat_outputs[idx]
enabled = output_status.enabled_outputs[idx]
cat_enabled = output_status.enabled_cat_outputs[idx]
scaler = output_status.selected_output_scalers[idx]
cec_selected = idx in cec_status.selected_cec_outputs
outputs_data.append(JtechOutputInfo(source_idx, output_name, cat_name, connected, cat_connected, enabled, cat_enabled, scaler, cec_selected))
return outputs_data
async def _client_connect(self):
if not self.connected:
try:
await self._client.connect(self.username, self.password)
self.connected = True
except JtechAuthError as err:
self.connected = False
raise ConfigEntryAuthFailed(ERROR_AUTH_FAILED, err) from err
except JtechConnectionError as err:
self.connected = False
raise UpdateFailed(ERROR_CONNECT_FAILED, err) from err
except Exception as err:
self.connected = False
raise UpdateFailed(ERROR_UNKNOWN, err) from err
async def _client_ensure(self):
if not self._client:
session = async_create_clientsession(self.hass, cookie_jar=CookieJar(unsafe=True, quote_cookie=False))
self._client = JtechClient(self.host, session)
await self._client_connect()
async def _fetch_status(self):
_LOGGER.error("_fetch_status")
tasks = [
self._client.get_status(),
self._client.get_source_status(),
self._client.get_output_status(),
self._client.get_cec_status(),
self._client.get_network(),
self._client.get_system_status(),
self._client.get_web_details(),
]
responses = await asyncio.gather(*tasks, return_exceptions=True) # continue even if there is an exception
return dict(zip(
[
"status",
"source",
"output",
"cec",
"network",
"system",
"web_details",
],
[response if not isinstance(response, Exception) else None for response in responses] # filter out exceptions
))
async def async_enable_output(self, output: int) -> bool:
"""Enable the output with the specified index."""
return await self._client.set_output_stream(output, True)
async def async_enable_cat_output(self, output: int) -> bool:
"""Enable the cat output with the specified index."""
return await self._client.set_output_cat_stream(output, True)
async def async_disable_output(self, output: int) -> bool:
"""Disable the output with the specified index."""
return await self._client.set_output_stream(output, False)
async def async_disable_cat_output(self, output: int) -> bool:
"""Disable the cat output with the specified index."""
return await self._client.set_output_cat_stream(output, False)
async def async_select_source(self, output: int, source: int) -> bool:
_LOGGER.error("async_select_source")
"""Select the input source for the specified output."""
return await self._client.set_video_source(output, source)
async def async_send_cec_output(self, output: int, command: int) -> bool:
"""Send a CEC command to the specified output."""
return await self._client.send_cec_output(output, command)
async def async_send_cec_source(self, source: int, command: int) -> bool:
"""Send a CEC command to the specified source."""
return await self._client.send_cec_source(source, command)
media_player.py
"""Media player support for J-Tech Digital HDMI Matrix integration."""
from __future__ import annotations
import asyncio
import logging
from homeassistant.components.media_player import (
MediaPlayerDeviceClass,
MediaPlayerEntity,
SUPPORT_PAUSE,
SUPPORT_PLAY,
SUPPORT_STOP,
SUPPORT_PREVIOUS_TRACK,
SUPPORT_NEXT_TRACK,
SUPPORT_TURN_ON,
SUPPORT_TURN_OFF,
SUPPORT_SELECT_SOURCE,
SUPPORT_PLAY_MEDIA,
SUPPORT_VOLUME_STEP,
SUPPORT_VOLUME_MUTE,
)
from homeassistant.components.homekit.const import (
EVENT_HOMEKIT_TV_REMOTE_KEY_PRESSED,
KEY_ARROW_DOWN,
KEY_ARROW_LEFT,
KEY_ARROW_RIGHT,
KEY_ARROW_UP,
KEY_BACK,
KEY_EXIT,
KEY_FAST_FORWARD,
KEY_INFORMATION,
KEY_NEXT_TRACK,
KEY_PREVIOUS_TRACK,
KEY_REWIND,
KEY_SELECT,
KEY_PLAY_PAUSE,
ATTR_KEY_NAME,
)
from homeassistant.const import STATE_OFF, STATE_PLAYING, STATE_ON, STATE_UNAVAILABLE
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.config_entries import ConfigEntry
from .const import (
DOMAIN,
ATTR_MANUFACTURER,
CONF_HDMI_STREAM_TOGGLE,
CONF_CAT_STREAM_TOGGLE,
CONF_CEC_VOLUME_CONTROL,
CONF_CEC_SOURCE_TOGGLE,
CONF_CEC_OUTPUT_TOGGLE,
CONF_CEC_DELAY_POWER,
CONF_CEC_DELAY_SOURCE,
)
from .coordinator import JtechCoordinator
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up J-Tech Digital HDMI Matrix Media Player from a config_entry."""
# Get the coordinator for this entry
coordinator = hass.data[DOMAIN][config_entry.entry_id]
assert config_entry.unique_id is not None
# Ensure we have the latest data from the coordinator
await coordinator.async_config_entry_first_refresh()
# Create media player entities for each output in the HDMI matrix
entities = [
JtechMediaPlayer(config_entry, coordinator, output_idx + 1)
for output_idx, output_info in enumerate(coordinator.outputs)
]
# Add the media player entities to Home Assistant
async_add_entities(entities, update_before_add=True)
class JtechMediaPlayer(MediaPlayerEntity):
"""Representation of a J-Tech Digital HDMI Matrix Output as a media player."""
_attr_assumed_state = True
_attr_has_entity_name = True
_attr_name = None
_attr_icon = "mdi:video-input-hdmi"
def _get_output_info(self):
"""Get the output information for the current output_index."""
return self._coordinator.outputs[self._output_index - 1]
def _get_source_info(self):
"""Get the source information for the given output_info."""
_LOGGER.error("_get_source_info")
output_info = self._get_output_info()
if output_info:
return self._coordinator.sources[output_info.source - 1]
return None
def _get_hdmi_stream_toggle(self):
"""Get the hdmi_stream_toggle option."""
return self._config_entry.options.get(CONF_HDMI_STREAM_TOGGLE, False)
def _get_cat_stream_toggle(self):
"""Get the cat_stream_toggle option."""
return self._config_entry.options.get(CONF_CAT_STREAM_TOGGLE, False)
def _get_cec_delay_power(self):
"""Get the cec_delay_power option."""
return self._config_entry.options.get(CONF_CEC_DELAY_POWER, 0)
def _get_cec_delay_source(self):
"""Get the cec_delay_source option."""
return self._config_entry.options.get(CONF_CEC_DELAY_SOURCE, 0)
def _get_cec_source_toggle(self):
"""Get the cec_source_toggle option."""
return self._config_entry.options.get(CONF_CEC_SOURCE_TOGGLE, False)
def _get_cec_output_toggle(self):
"""Get the cec_output_toggle option."""
return self._config_entry.options.get(CONF_CEC_OUTPUT_TOGGLE, False)
def _get_cec_volume_control(self):
"""Get the volume_control option."""
return self._config_entry.options.get(CONF_CEC_VOLUME_CONTROL, "none")
def _get_output_state(self, output_info):
hdmi_stream_toggle = self._get_hdmi_stream_toggle()
cat_stream_toggle = self._get_cat_stream_toggle()
if cat_stream_toggle and hdmi_stream_toggle:
if (output_info.cat_connected and output_info.cat_enabled and output_info.connected and output_info.enabled):
return True
else:
if cat_stream_toggle:
if (output_info.cat_connected and output_info.cat_enabled):
return True
if hdmi_stream_toggle:
if (output_info.connected and output_info.enabled):
return True
return True
return False
def __init__(self, config_entry: ConfigEntry, coordinator: JtechCoordinator, output_index: int):
"""Initialize the media player."""
self._config_entry = config_entry
self._coordinator = coordinator
self._output_index = output_index
@property
def supported_features(self):
"""Flag media player features that are supported."""
_supported_features = (
SUPPORT_SELECT_SOURCE
| SUPPORT_PLAY_MEDIA
| SUPPORT_PAUSE
| SUPPORT_PLAY
| SUPPORT_STOP
| SUPPORT_PREVIOUS_TRACK
| SUPPORT_NEXT_TRACK
)
# Add volume controls if available
cec_volume_control = self._get_cec_volume_control()
if cec_volume_control and cec_volume_control != "none":
_supported_features |= SUPPORT_VOLUME_STEP
_supported_features |= SUPPORT_VOLUME_MUTE
# Add turn on/off controls if HDMI and CAT switches are not available
hdmi_stream_toggle = self._get_hdmi_stream_toggle()
cat_stream_toggle = self._get_cat_stream_toggle()
if hdmi_stream_toggle or cat_stream_toggle:
_supported_features |= SUPPORT_TURN_ON
_supported_features |= SUPPORT_TURN_OFF
return _supported_features
@property
def unique_id(self):
"""Return a unique ID for the media player."""
return f"{self._config_entry.unique_id}_output_{self._output_index}"
@property
def device_info(self) -> DeviceInfo:
"""Return the device info."""
output_info = self._get_output_info()
return DeviceInfo(
identifiers={ (DOMAIN, self.unique_id) },
name=output_info.name if output_info else f"Output {self._output_index}",
manufacturer=ATTR_MANUFACTURER,
model=self._coordinator.data["model"],
sw_version=self._coordinator.data["version"],
via_device=(DOMAIN, self._config_entry.unique_id),
configuration_url=f"http://{self._coordinator.data['hostname']}" if self._coordinator.data["hostname"] else None
)
@property
def device_class(self):
"""Return the device class."""
return MediaPlayerDeviceClass.TV
# TODO: handle power of coordinator
@property
def state(self):
"""Return the state of the media player."""
output_info = self._get_output_info()
if output_info:
if self._get_output_state(output_info):
source_info = self._get_source_info()
if source_info and source_info.active:
return STATE_PLAYING
return STATE_ON
return STATE_OFF
# Output information not available, assume the state is unavailable
return STATE_UNAVAILABLE
@property
def source_list(self):
"""Return the list of available input sources."""
return [source.name for source in self._coordinator.sources]
@property
def source(self):
"""Return the current input source."""
output_info = self._get_output_info()
if output_info:
source_info = self._get_source_info()
if source_info:
return source_info.name
return None
async def async_select_source(self, source):
"""Select input source."""
for source_idx, source_info in enumerate(self._coordinator.sources):
if source_info.name == source:
await self._coordinator.async_select_source(self._output_index, source_idx + 1)
break
async def async_turn_on(self):
"""Enable the output and send necessary CEC commands to turn on the connected devices."""
hdmi_stream_toggle = self._get_hdmi_stream_toggle()
cat_stream_toggle = self._get_cat_stream_toggle()
if hdmi_stream_toggle:
await self._coordinator.async_enable_output(self._output_index)
if cat_stream_toggle:
await self._coordinator.async_enable_cat_output(self._output_index)
cec_source_toggle = self._get_cec_source_toggle()
cec_output_toggle = self._get_cec_output_toggle()
if (cec_source_toggle or cec_output_toggle) and (hdmi_stream_toggle or cat_stream_toggle):
delay_power = self._get_cec_delay_power()
if delay_power and delay_power > 0:
await asyncio.sleep(delay_power)
if cec_output_toggle:
await self._async_send_cec_source(1) # turn on selected source
if cec_output_toggle:
await self._coordinator.async_send_cec_output(self._output_index, 0) # turn on hdmi monitor
if cec_output_toggle:
delay_source = self._get_cec_delay_source()
if delay_source and delay_source > 0:
await asyncio.sleep(delay_source)
await self._coordinator.async_send_cec_output(self._output_index, 5)
async def async_turn_off(self):
"""Disable the output."""
cec_source_toggle = self._get_cec_source_toggle()
cec_output_toggle = self._get_cec_output_toggle()
if cec_output_toggle:
await self._coordinator.async_send_cec_output(self._output_index, 1)
if cec_source_toggle:
await self._async_send_cec_source(2) # turn off selected source
if cec_source_toggle or cec_output_toggle:
delay_power = self._get_cec_delay_power()
if delay_power and delay_power > 0:
await asyncio.sleep(self._get_cec_delay_power())
hdmi_stream_toggle = self._get_hdmi_stream_toggle()
cat_stream_toggle = self._get_cat_stream_toggle()
if hdmi_stream_toggle:
await self._coordinator.async_disable_output(self._output_index)
if cat_stream_toggle:
await self._coordinator.async_disable_cat_output(self._output_index)
async def async_volume_up(self):
"""Send CEC command to increase volume."""
await self._async_volume_send_cec(4, 19)
async def async_volume_down(self):
"""Send CEC command to decrease volume."""
await self._async_volume_send_cec(3, 18)
async def async_volume_mute(self):
"""Send CEC command to mute volume."""
await self._async_volume_send_cec(2, 17)
async def _async_volume_send_cec(self, output_command, source_command):
cec_volume_control = self._get_cec_volume_control()
if cec_volume_control == "output":
await self._coordinator.async_send_cec_output(self._output_index, output_command)
elif cec_volume_control == "source":
await self._async_send_cec_source(source_command)
async def _async_send_cec_source(self, source_command):
output_info = self._get_output_info()
if output_info:
await self._coordinator.async_send_cec_source(output_info.source, source_command)
async def async_media_previous_track(self):
"""Send CEC command for previous track."""
await self._async_send_cec_source(10)
async def async_media_next_track(self):
"""Send CEC command for next track."""
await self._async_send_cec_source(12)
async def async_media_play(self):
"""Send CEC command to play."""
await self._async_send_cec_source(11)
async def async_media_stop(self):
"""Send CEC command to stop."""
await self._async_send_cec_source(16)
async def async_media_pause(self):
"""Send CEC command to pause."""
await self._async_send_cec_source(14)
async def _handle_tv_remote_key_press(self, event):
"""Handle HomeKit TV remote key presses."""
remote_id = event.data.get("unique_id")
key_name = event.data.get(ATTR_KEY_NAME)
_LOGGER.debug("homekit_tv_remote_key_pressed", event, event.data)
if remote_id == self.unique_id:
key_name_mapping = {
KEY_ARROW_UP: 3,
KEY_ARROW_LEFT: 4,
KEY_ARROW_RIGHT: 6,
KEY_ARROW_DOWN: 8,
KEY_SELECT: 5,
KEY_INFORMATION: 7,
KEY_BACK: 9,
KEY_REWIND: 13,
KEY_FAST_FORWARD: 15,
}
cec_command = key_name_mapping.get(key_name)
if cec_command:
# Send the CEC command
self._async_send_cec_source(cec_command)
async def async_added_to_hass(self):
"""Subscribe to updates and handle HomeKit TV remote key presses."""
_LOGGER.error("async_added_to_hass")
self.hass.bus.async_listen(EVENT_HOMEKIT_TV_REMOTE_KEY_PRESSED, self._handle_tv_remote_key_press)
@callback
async def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator."""
# Trigger an entity state update to reflect the latest data from the coordinator
#_LOGGER.debug("handle_coordinator_update_data", self._coordinator.data)
_LOGGER.error("handle_coordinator_update_data")
self.async_write_ha_state()
system-log
2024-05-05 00:56:20.264 ERROR (MainThread) [custom_components.jtechdigital.coordinator] _fetch_status
2024-05-05 00:56:20.677 ERROR (MainThread) [custom_components.jtechdigital.coordinator] fetch_statuses
2024-05-05 00:56:20.677 ERROR (MainThread) [custom_components.jtechdigital.coordinator] _handle_output_update
2024-05-05 00:56:20.677 ERROR (MainThread) [custom_components.jtechdigital.coordinator] _handle_source_update
2024-05-05 00:56:20.698 ERROR (MainThread) [custom_components.jtechdigital.coordinator] _fetch_status
2024-05-05 00:56:21.110 ERROR (MainThread) [custom_components.jtechdigital.coordinator] fetch_statuses
2024-05-05 00:56:21.110 ERROR (MainThread) [custom_components.jtechdigital.coordinator] _handle_output_update
2024-05-05 00:56:21.110 ERROR (MainThread) [custom_components.jtechdigital.coordinator] _handle_source_update
2024-05-05 00:56:21.111 ERROR (MainThread) [custom_components.jtechdigital.media_player] async_added_to_hass
2024-05-05 00:56:21.111 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:21.111 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:21.111 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:21.111 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:21.111 ERROR (MainThread) [custom_components.jtechdigital.media_player] async_added_to_hass
2024-05-05 00:56:21.111 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:21.111 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:21.111 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:21.111 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:21.112 ERROR (MainThread) [custom_components.jtechdigital.media_player] async_added_to_hass
2024-05-05 00:56:21.112 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:21.112 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:21.112 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:21.112 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:21.112 ERROR (MainThread) [custom_components.jtechdigital.media_player] async_added_to_hass
2024-05-05 00:56:21.112 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:21.112 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:21.112 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:21.112 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:31.114 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:31.114 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:31.114 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:31.114 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:31.114 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:31.114 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:31.114 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:31.114 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:31.114 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:31.114 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:31.114 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:31.114 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:31.114 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:31.114 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:31.114 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:31.115 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:41.115 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:41.115 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:41.115 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:41.115 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:41.115 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:41.115 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:41.115 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:41.115 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:41.116 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:41.116 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:41.116 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:41.116 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:41.116 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:41.116 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:41.116 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:41.116 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:51.116 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:51.116 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:51.116 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:51.116 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:51.117 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:51.117 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:51.117 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:51.117 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info
2024-05-05 00:56:51.117 ERROR (MainThread) [custom_components.jtechdigital.media_player] _get_source_info