inital commit
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
"""Sensor platform for MySmartBike BLE integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
PERCENTAGE,
|
||||
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
|
||||
UnitOfEnergy,
|
||||
UnitOfLength,
|
||||
UnitOfSpeed,
|
||||
UnitOfTemperature,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity import EntityCategory
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import DOMAIN, MANUFACTURER, MODEL, CONF_DEVICE_NAME
|
||||
from .coordinator import MySmartBikeCoordinator
|
||||
|
||||
|
||||
def safe_get(data: dict[str, Any] | None, *keys: str) -> Any:
|
||||
"""Safely get nested dictionary values."""
|
||||
if data is None:
|
||||
return None
|
||||
|
||||
result = data
|
||||
for key in keys:
|
||||
if result is None or not isinstance(result, dict):
|
||||
return None
|
||||
result = result.get(key)
|
||||
return result
|
||||
|
||||
|
||||
@dataclass
|
||||
class MySmartBikeSensorEntityDescription(SensorEntityDescription):
|
||||
"""Describes MySmartBike sensor entity."""
|
||||
|
||||
value_fn: callable[[dict[str, Any]], Any] | None = None
|
||||
|
||||
|
||||
SENSORS: tuple[MySmartBikeSensorEntityDescription, ...] = (
|
||||
# Battery Primary Sensors
|
||||
MySmartBikeSensorEntityDescription(
|
||||
key="battery_primary_soc",
|
||||
name="Battery SoC",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda data: safe_get(data, "battery_primary", "soc"),
|
||||
),
|
||||
MySmartBikeSensorEntityDescription(
|
||||
key="battery_primary_temperature",
|
||||
name="Battery Temperature",
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda data: safe_get(data, "battery_primary", "temperature"),
|
||||
),
|
||||
MySmartBikeSensorEntityDescription(
|
||||
key="battery_primary_remaining_wh",
|
||||
name="Battery Remaining Energy",
|
||||
native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
|
||||
device_class=SensorDeviceClass.ENERGY_STORAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda data: safe_get(data, "battery_primary", "remaining_wh"),
|
||||
),
|
||||
# Motor Sensors
|
||||
MySmartBikeSensorEntityDescription(
|
||||
key="motor_assist_level",
|
||||
name="Assist Level",
|
||||
icon="mdi:speedometer",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
value_fn=lambda data: safe_get(data, "motor", "assist_level"),
|
||||
),
|
||||
MySmartBikeSensorEntityDescription(
|
||||
key="motor_temperature",
|
||||
name="Motor Temperature",
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda data: safe_get(data, "motor", "temperature_celsius"),
|
||||
),
|
||||
MySmartBikeSensorEntityDescription(
|
||||
key="motor_speed",
|
||||
name="Speed",
|
||||
native_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
|
||||
device_class=SensorDeviceClass.SPEED,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
icon="mdi:speedometer",
|
||||
value_fn=lambda data: safe_get(data, "motor", "speed_kmh"),
|
||||
),
|
||||
# EBM Sensors
|
||||
MySmartBikeSensorEntityDescription(
|
||||
key="odometer",
|
||||
name="Odometer",
|
||||
native_unit_of_measurement=UnitOfLength.KILOMETERS,
|
||||
device_class=SensorDeviceClass.DISTANCE,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
icon="mdi:counter",
|
||||
value_fn=lambda data: safe_get(data, "ebm", "odometry"),
|
||||
),
|
||||
MySmartBikeSensorEntityDescription(
|
||||
key="range",
|
||||
name="Range",
|
||||
native_unit_of_measurement=UnitOfLength.KILOMETERS,
|
||||
device_class=SensorDeviceClass.DISTANCE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
icon="mdi:map-marker-distance",
|
||||
value_fn=lambda data: safe_get(data, "ebm", "autonomy"),
|
||||
),
|
||||
MySmartBikeSensorEntityDescription(
|
||||
key="light",
|
||||
name="Light",
|
||||
icon="mdi:lightbulb",
|
||||
value_fn=lambda data: "On" if safe_get(data, "ebm", "is_light_on") else ("Off" if safe_get(data, "ebm") else None),
|
||||
),
|
||||
MySmartBikeSensorEntityDescription(
|
||||
key="ebm_status",
|
||||
name="EBM Status",
|
||||
icon="mdi:information",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
value_fn=lambda data: safe_get(data, "ebm", "status"),
|
||||
),
|
||||
# Connection Diagnostic Sensors
|
||||
MySmartBikeSensorEntityDescription(
|
||||
key="rssi",
|
||||
name="Signal Strength",
|
||||
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
|
||||
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
icon="mdi:wifi",
|
||||
value_fn=lambda data: safe_get(data, "rssi"),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up MySmartBike BLE sensors."""
|
||||
coordinator: MySmartBikeCoordinator = entry.runtime_data
|
||||
|
||||
async_add_entities(
|
||||
MySmartBikeSensor(coordinator, entry, description)
|
||||
for description in SENSORS
|
||||
)
|
||||
|
||||
|
||||
class MySmartBikeSensor(CoordinatorEntity[MySmartBikeCoordinator], SensorEntity):
|
||||
"""Representation of a MySmartBike sensor."""
|
||||
|
||||
entity_description: MySmartBikeSensorEntityDescription
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: MySmartBikeCoordinator,
|
||||
entry: ConfigEntry,
|
||||
description: MySmartBikeSensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the sensor."""
|
||||
super().__init__(coordinator)
|
||||
self.entity_description = description
|
||||
|
||||
device_name = entry.data[CONF_DEVICE_NAME]
|
||||
self._attr_unique_id = f"{entry.entry_id}_{description.key}"
|
||||
# Build device info with optional serial number (VIN)
|
||||
self._attr_device_info = {
|
||||
"identifiers": {(DOMAIN, entry.entry_id)},
|
||||
"name": device_name,
|
||||
"manufacturer": MANUFACTURER,
|
||||
"model": MODEL,
|
||||
}
|
||||
# Add serial number if available
|
||||
if coordinator.vin:
|
||||
self._attr_device_info["serial_number"] = coordinator.vin
|
||||
# Add protocol version as software version
|
||||
if coordinator.protocol_version:
|
||||
self._attr_device_info["sw_version"] = coordinator.protocol_version
|
||||
|
||||
@property
|
||||
def native_value(self) -> Any:
|
||||
"""Return the state of the sensor."""
|
||||
if self.entity_description.value_fn:
|
||||
return self.entity_description.value_fn(self.coordinator.data)
|
||||
return None
|
||||
Reference in New Issue
Block a user