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

  1. Run comprehensive discovery: comprehensive_sensor_discovery.py
  2. Validate mappings: validate_sensor_mappings.py
  3. Test coverage: check_actual_coverage.py
  4. Organization mapping: organization_mapper.py

Adding New Device Support

  1. Identify device type and available endpoints
  2. Map standard sensors from device endpoint
  3. Map RawData sensors if available
  4. Add device class mappings for proper HA integration
  5. Test with diagnostic tools
  6. 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.