Skip to content

mqtt

cleanup_mqtt_values_field

cleanup_mqtt_values_field(
    values: Dict[str, Any]
) -> Dict[str, Any]

Cleanup the values from invalid keys

Drop the possible invalid keys from the values and normalize the keys.

Parameters:

Returns:

Source code in src/grottext/ha/mqtt.py
70
71
72
73
74
75
76
77
78
79
80
81
def cleanup_mqtt_values_field(values: Dict[str, Any]) -> Dict[str, Any]:
    """Cleanup the values from invalid keys

    Drop the possible invalid keys from the values and normalize the keys.

    Parameters:
        values: Original dict

    Returns:
        The cleaned-up values
    """
    return {k.strip(): v for k, v in values.items() if is_valid_mqtt_topic(k)}

is_valid_mqtt_topic

is_valid_mqtt_topic(key_name: str) -> bool

Check if the key is a valid mqtt topic

Look If the possible topic is valid, based on the MQTT spec and reserved keywords

Parameters:

  • key_name (str) –

    The value of the key (e.g. "ACDischarWatt")

Returns:

  • bool

    True if the key is a valid mqtt topic, False otherwise

Source code in src/grottext/ha/mqtt.py
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
def is_valid_mqtt_topic(key_name: str) -> bool:
    """Check if the key is a valid mqtt topic

    Look If the possible topic is valid, based on the MQTT spec and reserved keywords

    Parameters:
        key_name: The value of the key (e.g. "ACDischarWatt")

    Returns:
         True if the key is a valid mqtt topic, False otherwise
    """
    key_name = key_name.strip()
    # Character used to bind wildcard topics
    if key_name.startswith("#"):
        return False
    # Character used to bind single level topics
    if key_name.startswith("+"):
        return False
    # should not start or end with /
    if key_name.startswith("/"):
        return False
    if key_name.endswith("/"):
        return False
    # system topics
    if key_name.startswith("$"):
        return False
    return True

make_payload

make_payload(
    conf: FakeConf,
    device: str,
    key: str,
    name: Optional[str] = None,
) -> Dict[str, str]

Generate a MQTT payload for a sensor

Use default values to create a sensor payload, then update with custom attributes if they exist. E.g., unit_of_measurement/total increasing/etc.

Parameters:

  • conf (FakeConf) –

    The configuration object, used to extract default divider

  • device (str) –

    Use the device name as part of the sensor name + device

  • key (str) –

    The key of the sensor sent by grott

  • name (Optional[str], default: None ) –

    The name of the sensor, if you want something different

Returns:

  • Dict[str, str]

    A dictionary with the MQTT configuration payload

Source code in src/grottext/ha/mqtt.py
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
def make_payload(conf: FakeConf, device: str, key: str, name: Optional[str] = None) -> Dict[str, str]:
    """Generate a MQTT payload for a sensor

    Use default values to create a sensor payload, then update with custom
    attributes if they exist.
    E.g., unit_of_measurement/total increasing/etc.

    Parameters:
        conf: The configuration object, used to extract default divider
        device: Use the device name as part of the sensor name + device
        key: The key of the sensor sent by grott
        name: The name of the sensor, if you want something different

    Returns:
        A dictionary with the MQTT configuration payload
    """

    if not device:
        msg = "device is required"
        raise AttributeError(msg)
    if not key:
        msg = "key is required"
        raise AttributeError(msg)
    if not conf.layout:
        msg = "grott config class error"
        raise AttributeError(msg)
    if conf.layout not in conf.recorddict:
        msg = "grott config class error, missing record layout"
        raise AttributeError(msg)

    if name is None:
        name = key

    sensor = mapping.get(key, None)

    value_template = None
    if sensor and sensor.value_template:
        value_template = sensor.value_template
    else:
        # Reuse the existing divide value if available and not existing
        # and apply it to the HA config
        layout = conf.recorddict[conf.layout]
        if key in layout:
            # From grottdata:207, default type is num, also process numx
            register_type = layout[key].get("type", "num")
            # The register is a numerical type
            if register_type in ("num", "numx"):
                # default divide is 1, if not found
                divider = layout[key].get("divide", "1")
                value_template = f"{{{{ value_json.{key} | float / {divider} }}}}"
            elif register_type == "log":
                # register is already a float, no need to divide
                value_template = f"{{{{ value_json.{key} | float }}}}"
            elif register_type == "logpos":
                # register is already a float, no need to divide
                value_template = f"{{{{ [value_json.{key} | float, 0.0] | max }}}}"
            elif register_type == "logneg":
                # register is already a float, no need to divide
                value_template = f"{{{{ [value_json.{key} | float, 0.0] | min }}}}"

    if value_template is None:
        value_template = f"{{{{ value_json.{key} }}}}"

    # Default configuration payload
    payload = MQTTConfigPayload(
        name="{name}",
        unique_id=f"grott_{device}_{key}",  # Generate a unique device ID
        state_topic=f"homeassistant/grott/{device}/state",
        device=Device(
            identifiers=[device],  # Group under a device
            name=device,
            manufacturer="GrowWatt",
        ),
        value_template=value_template,
        expire_after=MQTT_EXPIRE_AFTER,
    )

    if sensor is not None:
        # Update the payload with the sensor configuration
        payload.name = sensor.name
        payload.icon = sensor.icon
        payload.state_class = sensor.state_class
        payload.device_class = sensor.device_class
        payload.icon = sensor.icon
        payload.entity_category = sensor.entity_category
        payload.unit_of_measurement = sensor.unit_of_measurement

    # Generate the name of the key, with all the param available
    payload.name = payload.name.format(device=device, name=name, key=key)
    # HA automatically group the sensors if the device name is prepended

    return to_dict(payload)