Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Endpoint /devices obselète #564

Open
AbigailAsselin-Hilo opened this issue Feb 3, 2025 · 1 comment
Open

Endpoint /devices obselète #564

AbigailAsselin-Hilo opened this issue Feb 3, 2025 · 1 comment

Comments

@AbigailAsselin-Hilo
Copy link

Helloo

Context

La route /api/Locations/{LocationId}/Devices est obselete. Toute l'information des appareils passe par le DeviceHub, avec un SubscribeToLocation

Information initiales de la liste d'appareil d'une location

DeviceListInitialValuesReceived

Un array des appareils de la location.
L'objet devrait avoir le même format que celui dans le endpoint qui est obselete

[
  {
    "id": 0,
    "assetId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "hiloId": "string",
    "identifier": "string",
    "gatewayId": 0,
    "gatewayExternalId": "string",
    "gatewayAssetId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "name": "string",
    "type": "Hub",
    "groupId": 0,
    "category": "Heating",
    "icon": "string",
    "loadConnected": 0,
    "modelNumber": "string",
    "locationId": 0,
    "parameters": "string",
    "externalGroup": "string",
    "provider": 1,
    "providerData": "string",
    "isFavorite": true,
    "eTag": "string",
    "supportedAttributesList": [
      "None"
    ],
    "settableAttributesList": [
      "None"
    ],
    "supportedParametersList": [
      "None"
    ]
  }
]

Pour les mises à jour de la liste des appareils (Ajout et supression)

DeviceAdded

Les informations du device qui à été ajouté (même objet que la liste ci-haut)

  {
    "id": 0,
    "assetId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "hiloId": "string",
    "identifier": "string",
    "gatewayId": 0,
    "gatewayExternalId": "string",
    "gatewayAssetId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "name": "string",
    "type": "Hub",
    "groupId": 0,
    "category": "Heating",
    "icon": "string",
    "loadConnected": 0,
    "modelNumber": "string",
    "locationId": 0,
    "parameters": "string",
    "externalGroup": "string",
    "provider": 1,
    "providerData": "string",
    "isFavorite": true,
    "eTag": "string",
    "supportedAttributesList": [
      "None"
    ],
    "settableAttributesList": [
      "None"
    ],
    "supportedParametersList": [
      "None"
    ]
  }

DeviceDeleted

Les ids (deviceId et HiloId) du device qui a été supprimé.

{
  "id": 0,
  "hiloId": "string"
}

Information initiale et mise à jour des valeurs des appareils

DevicesValuesReceived ET GatewayValuesReceived

Les valeurs des appareils ET du gateway, ils sont séparés dans 2 messages mais le format est le même

[
  {
    "deviceId": 0,
    "hiloId": "string",
    "locationId": 0,
    "locationHiloId": "string",
    "timeStampUTC": "2025-02-03T19:06:34.202Z",
    "attribute": "None",
    "value": "string",
    "valueType": "OnOff",
    "operationId": "string"
  }
]

Donc :
Lors d'un SubscribeToLocation, on devrait recevoir 3 messages avec les informations nécessaires pour afficher les états/valeurs des appareils:

  • DeviceListInitialValuesReceived
  • DevicesValuesReceived
  • GatewayValuesReceived

Ensuite, le DeviceHub met à jour les informations reçus avec ces messages:

  • DeviceAdded
  • DeviceDeleted
  • DevicesValuesReceived
  • GatewayValuesReceived

DeviceListInitialValuesReceived, DeviceAdded ET DeviceDeleted servent à bartir la liste d'appareils d'une location
DevicesValuesReceived ET GatewayValuesReceived font juste mettre à jour les états/valeurs des ces appareils.

@ic-dev21
Copy link
Collaborator

ic-dev21 commented Feb 3, 2025

Salut, pour info supplémentaire, si quelqu'un avait envie d'y travailler, il y a déjà un callback pour une portion du /devicehub qui est gérée:

@callback
async def on_websocket_event(self, event: WebsocketEvent) -> None:
"""Define a callback for receiving a websocket event."""
async_dispatcher_send(self._hass, DISPATCHER_TOPIC_WEBSOCKET_EVENT, event)
if event.event_type == "COMPLETE":
cb = self.invocations.get(event.invocation)
if cb:
async_call_later(self._hass, 3, cb(event.invocation))
elif event.target == "Heartbeat":
self.validate_heartbeat(event)
elif event.target == "DevicesValuesReceived":
# When receiving attribute values for unknown devices, assume
# we have refresh the device list.
new_devices = any(
self.devices.find_device(item["deviceId"]) is None
for item in event.arguments[0]
)
if new_devices:
LOG.warning(
"Device list appears to be desynchronized, forcing a refresh thru the API..."
)
await self.devices.update()
updated_devices = self.devices.parse_values_received(event.arguments[0])
# NOTE(dvd): If we don't do this, we need to wait until the coordinator
# runs (scan_interval) to have updated data in the dashboard.
for device in updated_devices:
async_dispatcher_send(
self._hass, SIGNAL_UPDATE_ENTITY.format(device.id)
)
elif event.target == "DeviceListInitialValuesReceived":
# This websocket event only happens after calling SubscribeToLocation.
# This triggers an update without throwing an exception
new_devices = await self.devices.update_devicelist_from_signalr(
event.arguments[0]
)
elif event.target == "DeviceListUpdatedValuesReceived":
# This message only contains display information, such as the Device's name (as set in the app), it's groupid, icon, etc.
# Updating the device name causes issues in the integration, it detects it as a new device and creates a new entity.
# Ignore this call, for now... (update_devicelist_from_signalr does work, but causes the issue above)
# await self.devices.update_devicelist_from_signalr(event.arguments[0])
LOG.debug(
"Received 'DeviceListUpdatedValuesReceived' message, not implemented yet."
)
elif event.target == "DevicesListChanged":
# This message only contains the location_id and is used to inform us that devices have been removed from the location.
# Device deletion is not implemented yet, so we just log the message for now.
LOG.debug("Received 'DevicesListChanged' message, not implemented yet.")
elif event.target == "DeviceAdded":
# Same structure as DeviceList* but only one device instead of a list
devices = []
devices.append(event.arguments[0])
new_devices = await self.devices.update_devicelist_from_signalr(devices)
elif event.target == "DeviceDeleted":
# Device deletion is not implemented yet, so we just log the message for now.
LOG.debug("Received 'DeviceDeleted' message, not implemented yet.")
elif event.target == "GatewayValuesReceived":
# Gateway deviceId hardcoded to 1 as it is not returned by Gateways/Info.
# First time we encounter a GatewayValueReceived event, update device with proper deviceid.
gateway = self.devices.find_device(1)
if gateway:
gateway.id = event.arguments[0][0]["deviceId"]
LOG.debug(f"Updated Gateway's deviceId from default 1 to {gateway.id}")
updated_devices = self.devices.parse_values_received(event.arguments[0])
# NOTE(dvd): If we don't do this, we need to wait until the coordinator
# runs (scan_interval) to have updated data in the dashboard.
for device in updated_devices:
async_dispatcher_send(
self._hass, SIGNAL_UPDATE_ENTITY.format(device.id)
)
else:
LOG.warning(f"Unhandled websocket event: {event}")

Dans la version avec challenge sensor via signalR/websocket j'ai séparé le /devicehub du /challengehub, c'est un work in progress donc prière de pas trop rire de moi:

def register_websocket_listener(self, listener):
"""Register a listener for websocket events."""
LOG.debug(f"Registering websocket listener: {listener.__class__.__name__}")
self._websocket_listeners.append(listener)
async def _handle_websocket_message(self, event):
"""Process websocket messages and notify listeners."""
LOG.debug(f"Received websocket message type: {event}")
target = event.target
LOG.debug(f"handle_websocket_message_target {target}")
msg_data = event
LOG.debug(f"handle_websocket_message_ msg_data {msg_data}")
if target == "ChallengeListInitialValuesReceived":
msg_type = "challenge_list_initial"
elif target == "ChallengeAdded":
msg_type = "challenge_added"
elif target == "ChallengeDetailsUpdated":
msg_type = "challenge_details_update"
elif target == "ChallengeConsumptionUpdatedValuesReceived":
msg_type = "challenge_details_update"
elif target == "ChallengeDetailsUpdatedValuesReceived":
msg_type = "challenge_details_update"
elif target == "ChallengeDetailsInitialValuesReceived":
msg_type = "challenge_details_update"
elif target == "ChallengeListUpdatedValuesReceived":
msg_type = "challenge_details_update"
# ic-dev21 Notify listeners
for listener in self._websocket_listeners:
handler_name = f"handle_{msg_type}"
if hasattr(listener, handler_name):
handler = getattr(listener, handler_name)
try:
# ic-dev21 Extract the arguments from the WebsocketEvent object
if isinstance(msg_data, WebsocketEvent):
arguments = msg_data.arguments
if arguments: # ic-dev21 check if there are arguments
await handler(arguments[0])
else:
LOG.warning(f"Received empty arguments for {msg_type}")
else:
await handler(msg_data)
except Exception as e:
LOG.error(f"Error in websocket handler {handler_name}: {e}")
async def _handle_challenge_events(self, event: WebsocketEvent) -> None:
"""Handle all challenge-related websocket events."""
if event.target == "ChallengeDetailsInitialValuesRecei0ved":
challenge = event.arguments[0]
LOG.debug(f"ChallengeDetailsInitialValuesReceived, challenge = {challenge}")
self.challenge_id = challenge.get("id")
elif event.target == "ChallengeDetailsUpdatedValuesReceived":
LOG.debug("ChallengeDetailsUpdatedValuesReceived")
elif event.target == "ChallengeListUpdatedValuesReceived":
LOG.debug("ChallengeListUpdatedValuesReceived")
self.challenge_phase = event.arguments[0][0]["currentPhase"]
elif event.target == "ChallengeAdded":
LOG.debug("ChallengeAdded")
challenge = event.arguments[0]
self.challenge_id = challenge.get("id")
await self.subscribe_to_challenge(1, self.challenge_id)
elif event.target == "ChallengeListInitialValuesReceived":
LOG.debug("ChallengeListInitialValuesReceived")
challenges = event.arguments[0]
for challenge in challenges:
challenge_id = challenge.get("id")
self.challenge_phase = challenge.get("currentPhase")
self.challenge_id = challenge.get("id")
await self.subscribe_to_challenge(1, challenge_id)
async def _handle_device_events(self, event: WebsocketEvent) -> None:
"""Handle all device-related websocket events."""
if event.target == "DevicesValuesReceived":
new_devices = any(
self.devices.find_device(item["deviceId"]) is None
for item in event.arguments[0]
)
if new_devices:
LOG.warning(
"Device list appears to be desynchronized, forcing a refresh thru the API..."
)
await self.devices.update()
updated_devices = self.devices.parse_values_received(event.arguments[0])
for device in updated_devices:
async_dispatcher_send(
self._hass, SIGNAL_UPDATE_ENTITY.format(device.id)
)
elif event.target == "DeviceListInitialValuesReceived":
await self.devices.update_devicelist_from_signalr(event.arguments[0])
elif event.target == "DeviceListUpdatedValuesReceived":
LOG.debug(
"Received 'DeviceListUpdatedValuesReceived' message, not implemented yet."
)
elif event.target == "DevicesListChanged":
LOG.debug("Received 'DevicesListChanged' message, not implemented yet.")
elif event.target == "DeviceAdded":
devices = [event.arguments[0]]
await self.devices.update_devicelist_from_signalr(devices)
elif event.target == "DeviceDeleted":
LOG.debug("Received 'DeviceDeleted' message, not implemented yet.")
elif event.target == "GatewayValuesReceived":
gateway = self.devices.find_device(1)
if gateway:
gateway.id = event.arguments[0][0]["deviceId"]
LOG.debug(f"Updated Gateway's deviceId from default 1 to {gateway.id}")
updated_devices = self.devices.parse_values_received(event.arguments[0])
for device in updated_devices:
async_dispatcher_send(
self._hass, SIGNAL_UPDATE_ENTITY.format(device.id)
)
@callback
async def on_websocket_event(self, event: WebsocketEvent) -> None:
"""Define a callback for receiving a websocket event."""
async_dispatcher_send(self._hass, DISPATCHER_TOPIC_WEBSOCKET_EVENT, event)
if event.event_type == "COMPLETE":
cb = self.invocations.get(event.invocation)
if cb:
async_call_later(self._hass, 3, cb(event.invocation))
elif event.target == "Heartbeat":
self.validate_heartbeat(event)
elif "Challenge" in event.target:
await self._handle_challenge_events(event)
await self._handle_websocket_message(event)
elif "Device" in event.target or event.target == "GatewayValuesReceived":
await self._handle_device_events(event)
else:
LOG.warning(f"Unhandled websocket event: {event}")

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants