homebridge-mqttthing icon indicating copy to clipboard operation
homebridge-mqttthing copied to clipboard

Tampered and Low Battery Status not showing in the Home app

Open jimmyfisher opened this issue 3 years ago • 8 comments

I have 4 sensors in my house that are RF. I'm using an antenna plugged into USB on a RPi4 using a program to convert the signal to MQTT. That gets broadcast with Mosquitto out of the Pi to send the message to MQTT-Thing.

The Sensors345 program is set up so when the status is ok, it returns the message 'OK'. If the battery is low for that topic, it reads 'LOW'. For tampered, it reads 'TAMPER'.

This is what it looks like in the advanced configuration right now (#-ed out numbers for security)

        "type": "contactSensor",
        "name": "Front Door Sensor",
        "url": "mqtt://hidden:1883",
        "username": "hidden",
        "password": "hidden",
        "topics": {
            "getContactSensorState": "security/sensors345/sensor/#####/loop1",
            "getStatusTampered": {
                "topic": "security/sensors345/sensor/#####/tamper",
                "apply": "if ( message == 'TAMPER' ) return 1; else return 0;"
            },
            "getStatusLowBattery": {
                "topic": "security/sensors345/sensor/#####/battery",
                "apply": "if ( message == 'LOW' ) return 1; else return 0;"
            }
        },
        "integerValue": false,
        "onValue": "OPEN",
        "offValue": "CLOSED",
        "accessory": "mqttthing",
        "history": true`

I tried to test this 'if' statement by switching 'TAMPER' and 'LOW' to 'OK', since that's what the MQTT message is showing. In any case, when I open a door, the message is coming through loud and clear. The icon in the Home app shows the door as being open, but two things:

  1. The 'Status Tampered' shows 'No', even when the message is 'OK' and I'm running the test - meaning I can't get it to say 'Yes' even debugging
  2. There's no indication at all in the Home app about low battery (the battery is fine - just debugging for the future)

Am I missing something or is this just a limitation between MQTT-Thing and the Home app?

Thanks in advance!

jimmyfisher avatar Jul 15 '21 03:07 jimmyfisher

I think the problem might be that all Boolean values need to match your configured onValue and offValue. You might be better using integer values (1 and 0) and putting a corresponding if into the contact sensor state too.

arachnetech avatar Jul 15 '21 08:07 arachnetech

It's definitely something wrong with my 'if' statement. I tried to swap the getContactSensorState with the same type of test and it stopped changing in the Home app.

My input MQTT message is a simple string. What should the 'apply' format be to test the value of the MQTT input string?

Thanks!

jimmyfisher avatar Jul 15 '21 14:07 jimmyfisher

Quick update - I think I was able to get this sorted out by doing the following:

        "type": "contactSensor",
        "name": "Front Door Sensor",
        "url": "mqtt://hidden:1883",
        "username": "hidden",
        "password": "hidden",
        "topics": {
            "getContactSensorState": {
                "topic": "security/sensors345/sensor/#####/loop1",
                "apply": "return message == 'OPEN' ? 1 : 0;"
            },
            "getStatusTampered": {
                "topic": "security/sensors345/sensor/#####/tamper",
                "apply": "return message == 'OK' ? 1 : 0;"
            },
            "getStatusLowBattery": {
                "topic": "security/sensors345/sensor/#####/battery",
                "apply": "return message == 'OK' ? 1 : 0;"
            }
        },
        "integerValue": "true",
        "accessory": "mqttthing",
        "history": "true"

The changes were:

  1. setting "integerValue" to "true" (had left on false, but even then, I didn't include the "" around the word 😬 )
  2. Looked up the ? functionality in JS (I'm not super well-versed) and realized I could return 1 or 0 by just testing if 'message' was exactly equal to a specific thing by writing it out like the above

However, while it's now reflected in the Home app (I set the Low Battery and Tamper test values so 'OK' triggered the 1 command, prompting the notice of either 'yes, tampered' or 'yes, low battery' to be sent to the Home app), I'm seeing this in the logs:

image

Now, I'm fairly certain that the integer is being returned, but not sure where [true] is getting generated since I'm only passing the integer in my configuration. I'm guessing there's something in the MQTT-Thing script that's converting the integer into either 'true' or 'false' to pass to the Home app, but I still want to make sure that I resolve this error within the HOOBS logs

Thanks again!

jimmyfisher avatar Jul 15 '21 16:07 jimmyfisher

I have the same issue... with integerValue set to true, I get a validation error but the low battery state seems to be set in homekit. I am unable to clear the low battery state though without a homebridge restart.

image

config:

        {
            "accessory": "mqttthing",
            "type": "leakSensor",
            "name": "Kitchen water leak",
            "url": "mqtt://192.168.20.15:1883",
            "topics": {
                "getLeakDetected": {
                    "topic": "zigbee2mqtt/waterleak",
                    "apply": "return JSON.parse(message).water_leak ? 1 : 0;"
                },
                "getStatusLowBattery": {
                    "topic": "zigbee2mqtt/waterleak",
                    "apply": "return JSON.parse(message).battery_low"
                },
                "getBatteryLevel": {
                    "topic": "zigbee2mqtt/waterleak",
                    "apply": "return JSON.parse(message).battery;"
                }
            },
            "integerValue": true,
            "logMqtt": true
        }

Interestingly, I see the apply() method to be run 4 times, while it is only defined 3 times... If I remove the apply from getStatusLowBattery, I - correctly - only get 2 apply() log entries.

andrasg avatar Dec 27 '21 19:12 andrasg

Played with it a little more... looks like the Home app only polls the low battery state once in a while instead of it being event driven. If I switch away to an other app on my iPhone and immediately back, the right low battery status shows up right away. This update seems to be pushed to other devices as well. As soon as one device gets the status right, all of them get it right.

andrasg avatar Dec 27 '21 19:12 andrasg

@andrasg : I have the same issues here. I can only get it to work when I set integerValue to true. Despite it is mentioned differently in the documentation I get the same warning too:

My config:

"getStatusLowBattery": {
    "topic": "openWB/lp/1/%Soc",
    "apply": "if (JSON.parse(message) < 20) return 1; else return 0;"
},

leads to:

[3/5/2022, 4:29:28 PM] [EQA] Ignoring invalid value [true] for Status Low Battery - not an integer
[3/5/2022, 4:29:28 PM] [EQA] apply() function decoded message to [true]

It's not a problem for now as HomeKit works as expected :smile: . The way of clearing the low battery event is a little bit annoying but the more Apple devices one uses the higher is the chance to see the correct state.

smhex avatar Mar 05 '22 15:03 smhex

same here with getStatusFault

{
            "accessory": "mqttthing",
            "type": "contactSensor",
            "name": "Porte Soggiorno - Cucina - Camera Armadi",
            "url": "mqtt://127.0.0.1:1883",
            "username": "XXXX",
            "password": "XXXX",
            "caption": "Zona 38: Porte Soggiorno - Cucina - Camera Armadi",
            "topics": {
                "getContactSensorState": {
                    "topic": "ialarm/sensors/zone_38",
                    "apply": "if (JSON.parse(message).status > 1) return 1; else return 0;"
                },
                "getStatusActive": {
                    "topic": "ialarm/sensors/zone_38",
                    "apply": "return JSON.parse(message).ok;"
                },
                "getStatusFault": {
                    "topic": "ialarm/sensors/zone_38",
                    "apply": "return JSON.parse(message).fault;"
                },
                "getStatusLowBattery": {
                    "topic": "ialarm/sensors/zone_38",
                    "apply": "return JSON.parse(message).lowbat;"
                }
            },
            "integerValue": true
        }

log console homebridge:

[3/6/2022, 10:06:39 AM] [Porte Soggiorno - Cucina - Camera Armadi] Ignoring invalid value [true] for Status Fault - not an integer
[3/6/2022, 10:06:56 AM] [Porte Soggiorno - Cucina - Camera Armadi] Ignoring invalid value [false] for Status Fault - not an integer

mrMiimo avatar Mar 06 '22 11:03 mrMiimo

I did a quick look at the plugin's source code. The log message is thrown in function IsValid()

function isValid( charac, value ) {

    // if validation is disabled, accept anything
    if( config.validate === false ) {
        return true;
    }

    const format = charac.props.format;
    if( format === 'int' || format === "uint8" || format == "uint16" || format == "uint32" ) {
        if( ! Number.isInteger( value ) ) {
            log( `Ignoring invalid value [${value}] for ${charac.displayName} - not an integer` );
            return false;
        }
        if( isSet( charac.props.minValue ) && value < charac.props.minValue ) {
            log( `Ignoring invalid value [${value}] for ${charac.displayName} - below minimum (${charac.props.minValue})` );
            return false;
        }
        if( isSet( charac.props.maxValue ) && value > charac.props.maxValue ) {
            log( `Ignoring invalid value [${value}] for ${charac.displayName} - above maximum (${charac.props.maxValue})` );
            return false;
        }
    } else if( format === 'float' ) {
        if( typeof value !== 'number' || isNaN( value ) ) {
            log( `Ignoring invalid value [${value}] for ${charac.displayName} - not a number` );
            return false;
        }
        if( isSet( charac.props.minValue ) && value < charac.props.minValue ) {
            log( `Ignoring invalid value [${value}] for ${charac.displayName} - below minimum (${charac.props.minValue})` );
            return false;
        }
        if( isSet( charac.props.maxValue ) && value > charac.props.maxValue ) {
            log( `Ignoring invalid value [${value}] for ${charac.displayName} - above maximum (${charac.props.maxValue})` );
            return false;
        }
    } else if( format === 'bool' ) {
        if( value !== true && value !== false ) {
            log( `Ignoring invalid value [${value}] for ${charac.displayName} - not a Boolean` );
            return false;
        }
    } else if( format === 'string' ) {
        if( typeof value !== 'string' ) {
            log( `Ignoring invalid value [${value}] for ${charac.displayName} - not a string` );
            return false;
        }
    } else {
        log( `Unable to validate ${charac.displayName}, format [${charac.props.format}] - ${JSON.stringify(charac)}` );
    }
    return true;
}

The StatusLowBattery characteristic is of type Boolean. It is also created as such in the code. However, the format variable contains "uint8". That's why the message is logged. I have not found the location in the code where it is modified...

smhex avatar Mar 08 '22 20:03 smhex