Sensor Mappings and Architecture - delize/home-assistant-loggamera-integration GitHub Wiki
Sensor Mappings and Architecture
This guide explains how the Loggamera integration discovers, maps, and creates sensors in Home Assistant.
๐๏ธ Integration Architecture Overview
graph TB
A[Home Assistant] --> B[Loggamera Integration]
B --> C[Data Update Coordinator]
C --> D[Loggamera API Client]
D --> E[Multiple API Endpoints]
E --> F[PowerMeter Endpoint]
E --> G[RoomSensor Endpoint]
E --> H[WaterMeter Endpoint]
E --> I[RawData Endpoint]
E --> J[Organizations Endpoint]
C --> K[Sensor Discovery Engine]
K --> L[Standard Sensors]
K --> M[RawData Sensors]
K --> N[Organization Sensors]
L --> O[Home Assistant Entities]
M --> O
N --> O
๐ Sensor Discovery Process
1. Device Discovery Phase
# Device discovery from Organizations endpoint
organizations = api.get_organizations()
devices = api.get_devices(organization_id)
# For each device:
for device in devices:
device_id = device["Id"]
device_type = device["Class"] # PowerMeter, RoomSensor, etc.
2. Endpoint Testing Phase
# Primary endpoint cascade
def _get_primary_endpoint_for_device(device_id, device_type):
# 1. Try device-specific endpoint
if device_type == "PowerMeter":
endpoint = API_ENDPOINT_POWER_METER
elif device_type == "RoomSensor":
endpoint = API_ENDPOINT_ROOM_SENSOR
# ... etc
# 2. Fallback to generic endpoints
fallback_endpoints = [API_ENDPOINT_RAW_DATA, API_ENDPOINT_GENERIC_DEVICE]
3. Sensor Creation Phase
# Standard sensors from device endpoints
standard_sensors = create_standard_sensors(device_data)
# Detailed sensors from RawData endpoint
rawdata_sensors = create_rawdata_sensors(rawdata)
# Organization sensors from Organizations endpoint
org_sensors = create_organization_sensors(organizations)
๐ฏ Sensor Type Classification
Standard Sensors (Enabled by Default)
Source: Device-specific API endpoints (PowerMeter, RoomSensor, etc.)
Purpose: Essential device functionality
Naming: sensor.loggamera_DEVICE_ID_SENSOR_NAME
RawData Sensors (Disabled by Default)
Source: RawData API endpoint
Purpose: Detailed device telemetry
Naming: sensor.rawdata_DEVICE_ID_DEVICE_TYPE_SENSOR_NAME
Organization Sensors (Always Enabled)
Source: Organizations API endpoint
Purpose: Infrastructure monitoring
Naming: sensor.loggamera_organization_SENSOR_NAME
๐บ๏ธ Sensor Mapping System
Device Type to Sensor Mapping
PowerMeter Standard Mapping
POWERMETER_STANDARD_SENSORS = {
"ConsumedTotalInkWh": {
"name": "Total Energy Consumption",
"unit": "kWh",
"device_class": "energy",
"state_class": "total_increasing"
},
"PowerInkW": {
"name": "Current Power Consumption",
"unit": "kW",
"device_class": "power",
"state_class": "measurement"
}
}
RoomSensor Standard Mapping
ROOMSENSOR_STANDARD_SENSORS = {
"Temperature": {
"name": "Temperature",
"unit": "ยฐC",
"device_class": "temperature",
"state_class": "measurement"
},
"RelativeHumidity": {
"name": "Relative Humidity",
"unit": "%",
"device_class": "humidity",
"state_class": "measurement"
}
}
RawData ID Mapping System
HeatMeter RawData Mappings
HEATMETER_RAWDATA_MAPPINGS = {
"544310": {
"name": "Total Energy",
"unit": "kWh",
"device_class": "energy",
"clear_name": "Total Energy"
},
"544311": {
"name": "Total Volume",
"unit": "mยณ",
"device_class": "water",
"clear_name": "Total Volume"
},
"544312": {
"name": "Flow Rate",
"unit": "mยณ/h",
"device_class": "volume_flow_rate",
"clear_name": "Flow Rate"
}
# ... additional mappings
}
ChargingStation RawData Mappings
CHARGINGSTATION_RAWDATA_MAPPINGS = {
"544420": {
"name": "Total Consumption",
"unit": "kWh",
"device_class": "energy",
"clear_name": "Total Consumption"
},
"544426": { # API returns "Voltate" - corrected to "Voltage"
"name": "Voltage Phase 1",
"unit": "V",
"device_class": "voltage",
"clear_name": "Voltage (Phase 1)"
}
}
๐ง Intelligent Sensor Detection
Automatic Sensor Type Inference
def _infer_sensor_type(self, sensor_name: str, unit_type: str, clear_name: str) -> str:
"""Intelligently determine sensor type from available metadata."""
# Energy detection
if any(keyword in sensor_name.lower() for keyword in ["energy", "kwh", "consumption"]):
return "energy"
# Temperature detection
if any(keyword in sensor_name.lower() for keyword in ["temp", "temperature"]):
return "temperature"
# Unit-based detection
unit_lower = unit_type.lower()
if unit_lower in ["ยฐc", "celsius"]:
return "temperature"
elif unit_lower in ["%", "percent"]:
return "humidity" if "humidity" in sensor_name.lower() else "generic"
elif unit_lower in ["kwh", "wh"]:
return "energy"
elif unit_lower in ["kw", "w"]:
return "power"
# Fallback to generic
return "generic"
Device Class Assignment
def _get_device_class(self, sensor_type: str) -> Optional[str]:
"""Get Home Assistant device class for sensor type."""
DEVICE_CLASS_MAP = {
"energy": "energy",
"power": "power",
"temperature": "temperature",
"humidity": "humidity",
"water": "water",
"voltage": "voltage",
"current": "current",
"battery": "battery",
"signal_strength": "signal_strength"
}
return DEVICE_CLASS_MAP.get(sensor_type)
๐ง Sensor Creation Workflow
Standard Sensor Creation
def create_standard_sensors(device_data, device_id, device_name, device_type):
"""Create standard sensors from device endpoint data."""
sensors = []
if "Data" in device_data and "Values" in device_data["Data"]:
for value in device_data["Data"]["Values"]:
sensor = LoggameraSensor(
coordinator=coordinator,
device_id=device_id,
device_name=device_name,
device_type=device_type,
sensor_name=value.get("Name"),
value_data=value,
is_rawdata=False
)
sensors.append(sensor)
return sensors
RawData Sensor Creation
def create_rawdata_sensors(rawdata, device_id, device_name, device_type):
"""Create detailed sensors from RawData endpoint."""
sensors = []
if rawdata and "Data" in rawdata and "Values" in rawdata["Data"]:
for value in rawdata["Data"]["Values"]:
# Check for specific device type mappings
if device_type == "HeatMeter":
mapping = HEATMETER_RAWDATA_MAPPINGS.get(value.get("Name"))
elif device_type == "ChargingStation":
mapping = CHARGINGSTATION_RAWDATA_MAPPINGS.get(value.get("Name"))
sensor = LoggameraSensor(
coordinator=coordinator,
device_id=device_id,
device_name=device_name,
device_type=device_type,
sensor_name=value.get("Name"),
value_data=value,
is_rawdata=True,
mapping=mapping
)
sensors.append(sensor)
return sensors
๐ Organization Sensor Architecture
Organization Data Structure
# Organizations API response structure
{
"Data": {
"Organizations": [
{
"Id": 12345,
"Name": "Main Organization",
"ParentOrganizationId": null,
"ChildOrganizations": [...],
"UserCount": 5,
"MemberCount": 10
}
]
}
}
Organization Sensor Implementation
class LoggameraOrganizationSensor(CoordinatorEntity, SensorEntity):
"""Organization-level sensor for infrastructure monitoring."""
def __init__(self, coordinator, sensor_name, display_name):
super().__init__(coordinator)
self.sensor_name = sensor_name
self._attr_name = f"Loggamera {display_name}"
self._attr_unique_id = f"loggamera_organization_{sensor_name}"
@property
def native_value(self):
"""Calculate organization sensor value dynamically."""
if self.sensor_name == "device_count":
devices = self.coordinator.data.get("devices", [])
return len(devices)
elif self.sensor_name == "organization_count":
organizations = self.coordinator.data.get("organizations", [])
return len(organizations)
# ... additional calculations
๐ ๏ธ Error Handling and Fallbacks
Endpoint Fallback Mechanism
def _get_primary_endpoint_for_device(device_id, device_type):
"""Cascading endpoint fallback system."""
# 1. Try device-specific endpoint
primary_endpoint = get_primary_endpoint(device_type)
if primary_endpoint and not endpoint_is_cached_as_invalid(primary_endpoint):
try:
return make_request(primary_endpoint, {"DeviceId": device_id})
except LoggameraAPIError:
cache_endpoint_as_invalid(primary_endpoint)
# 2. Fallback to generic endpoints
for fallback_endpoint in [API_ENDPOINT_RAW_DATA, API_ENDPOINT_GENERIC_DEVICE]:
try:
return make_request(fallback_endpoint, {"DeviceId": device_id})
except LoggameraAPIError:
continue
# 3. All endpoints failed
raise LoggameraAPIError(f"All endpoints failed for device {device_id}")
Missing Sensor Handling
def handle_unknown_sensor(value_data, device_type):
"""Handle sensors not in predefined mappings."""
# Extract metadata
sensor_name = value_data.get("Name", "unknown")
clear_name = value_data.get("ClearTextName", sensor_name)
unit_type = value_data.get("UnitType", "")
value_type = value_data.get("ValueType", "")
# Intelligent type inference
sensor_type = _infer_sensor_type(sensor_name, unit_type, clear_name)
device_class = _get_device_class(sensor_type)
# Create dynamic mapping
return {
"name": clear_name or f"Sensor {sensor_name}",
"unit": unit_type,
"device_class": device_class,
"sensor_type": sensor_type
}
๐ Diagnostic Tools for Sensor Mapping
Sensor Mapping Validation
# Validate all sensor mappings
python tools/validate_sensor_mappings.py
# Output includes:
# - Mapped sensor coverage statistics
# - Unmapped sensor identification
# - Device class recommendations
# - Integration improvement suggestions
Organization Infrastructure Mapping
# Complete infrastructure analysis
python tools/organization_mapper.py YOUR_API_KEY --format json
# Generates:
# - Device inventory across all organizations
# - Sensor discovery results per device
# - Endpoint availability matrix
# - Sensor mapping coverage analysis
Comprehensive Sensor Discovery
# Test all endpoints against all devices
python tools/comprehensive_sensor_discovery.py
# Creates:
# - Complete sensor database
# - Endpoint coverage matrix
# - New sensor identification
๐ Sensor Performance and Optimization
Entity Creation Optimization
- Standard sensors: Created immediately and enabled by default
- RawData sensors: Created but disabled by default to prevent entity spam
- Organization sensors: Always enabled for infrastructure monitoring
Update Frequency Optimization
- PowerMeter: 20-minute default (API limitation ~30 minutes)
- RoomSensor: Real-time to hourly (device dependent)
- Organization: Same as device scan interval
- RawData: Only when manually enabled
Memory and Performance
- Endpoint caching: Avoid repeated failed requests
- Lazy loading: RawData sensors only created when accessed
- Efficient coordination: Single coordinator for all sensors per organization
๐งช Testing Sensor Mappings
Mapping Validation Process
- Run comprehensive discovery:
comprehensive_sensor_discovery.py
- Validate mappings:
validate_sensor_mappings.py
- Test coverage:
check_actual_coverage.py
- Organization mapping:
organization_mapper.py
Adding New Device Support
- Identify device type and available endpoints
- Map standard sensors from device endpoint
- Map RawData sensors if available
- Add device class mappings for proper HA integration
- Test with diagnostic tools
- Update documentation
๐ง Advanced Sensor Configuration
Custom Sensor Mappings
# Example: Adding new sensor type
CUSTOM_SENSOR_MAPPING = {
"new_sensor_id": {
"name": "Custom Sensor Name",
"unit": "custom_unit",
"device_class": "energy", # or appropriate class
"state_class": "measurement",
"clear_name": "User-friendly name"
}
}
Entity Registry Integration
- Unique IDs: Prevent duplicate sensors across restarts
- Entity customization: Allow user renaming and disabling
- Device grouping: Proper device association for organization
Future Extensibility
- Unknown sensor handling: Automatic integration of new sensor types
- Dynamic mapping updates: Support for API changes without code updates
- User-defined mappings: Allow custom sensor configurations
This architecture ensures robust sensor discovery, mapping, and integration while maintaining performance and extensibility for future Loggamera API changes.