MQTT Interface - ParadoxAlarmInterface/pai GitHub Wiki
PAI requires an external MQTT broker to be configured in pai.conf
At a minimum, you will need to configure the following MQTT settings (set MQTT_HOST to something applicable to your setup).
MQTT_ENABLE: True # Enable MQTT Interface
MQTT_HOST: 192.168.1.2 # Hostname or address for your MQTT server
MQTT_PORT: 1883 # TCP Port
If using TLS, in addition to changing the MQTT_PORT, you will also need to expose the broker's CA certificate file to PAI's mqtt client
MQTT_TLS_CERT_PATH: /etc/pai/certs/your_ca_crt_file.crt
If using a self-signed certificate for your MQTT broker, the certificate file will be something like ca.crt
If using proper SSL encryption with a service like Let’s Encrypt you may have to set the certificate to the operating system's own .crt certificates file (the operating system of the container in which PAI is running). In the instance of Ubuntu this would be certificate: /etc/ssl/certs/ca-certificates.crt
The MQTT Interface allows accessing all relevant states and setting some. The list of states will increase as the knowledge of the alarm internal memory and its states is improved. Note the initial caps of the syntax "True" for MQTT_Enable. Setting this to "true" will not work.
All interactions are made through a MQTT_BASE_TOPIC
, which defaults to paradox
. States are exposed by name with a boolean payload (True or False) and are mainly update by the alarm status messages, updated every KEEP_ALIVE_INTERFACE
seconds.
After every command you can add a space plus a user identifier (e.g. arm JOHN
). This will help logging commands from individual users.
Internal State
If the MQTT_PUBLISH_STATUS
is set to True
, the MQTT Interface will expose internal information. This can be used to trigger notifications or log actions with the indication of the user.
paradox/interface/status
: Exposes internal status of the MQTT Interface. Every command sent, as well as authentication attempts will be logged to this topic.
Panel States
paradox/states/partitions/name/property
: Exposes Partition properties wherename
identifies the Partition name (e.g. Interior) andproperty
identifies the property (e.g.arm
).paradox/states/zones/name/property
: Exposes Partition properties wherename
identifies the Zone name (e.g. Kitchen) andproperty
identifies the property (e.g.open
).paradox/states/outputs/name/property
: Exposes Partition properties wherename
identifies the PGM name (e.g. Gate) andproperty
identifies the property. (EVO does not have this yet)
The following states are supported:
- buses :
supervision_trouble, tamper, trouble
- keypads:
ac_loss_trouble, battery_failure_trouble, signal_strength, supervision_failure_trouble, trouble
- outputs:
signal_strength, supervision_trouble, tamper, trouble
- partitions:
alarm, alarms_in_memory, arm, arm_with_remote, audible_alarm, auto_arming_engaged, bell_activated, bell_delay_finished, entry_delay,entry_delay_finished, exit_delay, exit_delay_finished, force_arm, in_remote_delay, intellizone_delay, intellizone_delay_finished, paramedic_alarm, pulse_fire_alarm, ready_status, recent_closing_delay, silent_alarm, sleep_arm, stay_arm, stayd_mode_active, strobe_alarm, transmission_delay_finished, wait_window, zone_bypassed
- repeaters:
ac_loss_trouble, battery_failure_trouble, signal_strength, supervision_trouble, trouble
- system:
battery, dc, rf_noise_floor, vdc
- zones:
alarm, bypassed, entry_delay, exit_delay, fire, fire_delay, intellizone_delay, in_tx_delay, no_delay, open, rf_low_battery_trouble, rf_supervision_trouble, shutdown, signal_strength, tamper, trouble, was_bypassed, was_in_alarm
Events
paradox/states/raw
: Exposes raw event information, composed by a minor and major codes, as well as descriptive text. The payload is a JSON object with the following structure:
{
'major': (major_code, 'major_text'),
'type': 'event_type',
'minor': (minor_code, 'minor_text')
}
major_code
andmajor_text
represent the major code and corresponding text description as provided by the alarm. This will mostly identify the event.minor_code
andminor_text
represent the minor code and corresponding text description as provided by the alarm. This will mostly identify a detail of the event, such as the zone number/name, the user name, partition name, and so on.type
identifies the event category, and can take the following values:Partition
,Bell
,NonReportable
,User
,Remote
,Special
,Trouble
,Software
,Output
,Wireless
,Bus Module
,Zone
,System
.
Control
The MQTT Interface allows setting some properties for individual objects by specifying the correct name. In alternative, the all
keyword can be used to apply the same setting to all objects. This is useful to activate all PGMs or to Arm/Disarm all partitions.
name
in the topic needs to be replaced by a name with spaces replaced with underscores.
paradox/control/partitions/name
allow setting some partition properties wherename
identifies the partition. If thename
isall
, all partitions will be updated. The payload can have the valuesarm
,arm_force
,arm_stay
,arm_sleep
,arm_stay_stayd
,arm_sleep_stay
ordisarm
anddisarm_all
.paradox/control/zones/name
allow setting some zone properties wherename
identifies the zone. If thename
isall
, all zones will be updated. The payload can have the valuesbypass
andclear_bypass
.paradox/control/outputs/name
allow setting some PGM properties wherename
identifies the PGM. If thename
isall
, all PGMs will be updated. The payload can have the valuespulse
(Not on EVO),on
,on_override
,off
oroff_override
,release
(Only on EVO).
Check command execution result in logs.
name
sanitising function to put in the topic:
python -c 'name="my partition name"; import re; print(re.sub(r"\W", "_", name).strip("_"))'
Notifications
You can ask PAI to send a notification to other text interfaces (Pushover, Pushbullet, Signal, GSM)
paradox/notifications/info
info
is message level. For Pushover it will convert to priority. Supported are:NOTSET
,DEBUG
,INFO
,WARN
,ERROR
,CRITICAL
.
Message content should be put into MQTT payload
Code Toggle
Sometimes it is useful to toggle the ARM state through a remote device, such as a NFC reader. Therefore, Partitions arm state can be toggled by issuing a special command with the format code_toggle-code_number
(e.g., code_toggle-123456755). The code_toggle-
keyword is constant, while the code_number
is provided by the card (e.g., Card ID). If the code is present in the MQTT_TOGGLE_CODES
, the partition state will be toggled.
The MQTT_TOGGLE_CODES
should be composed by a dictionary where the key contains the code, and the value contains a description. This allows for easily sending notifications in the form: "Alarm unlocked by USERNAME".
This was throughly tested with ESPEasy running on a ESP8266 board (e.g. NodeMCU or Wemos D1 Mini) connected to a PN532 NFC reader. Besides the typical ESPEasy configuration, the only "code" required is a simple rule. The following example will publish any ID to the correct topic and will flash a LED for 2 seconds.
on reader#tag do
Publish paradox/control/partitions/all,code_toggle-[reader#tag]
Pulse,2,0,2000
endon
reader#tag
identifies the ESPEasy PN532 device name (reader
) and the property holding the RFID ID (tag
).
Authenticated mode
MQTT allows any client with write capabilities over the control topics to change the panel state without any information regarding its identity. Therefore PAI allows to restrict the actions so that only authenticated users can change the panel state. This authentication is overlayed on top of the MQTT authentication and independent of the MQTT authentication. It doesn't matter what authentication method you have to connect to the MQTT broker, in this mode PAI WILL require commands to be further authenticated. Even if the broker has no authentication and uses a plain connection, commands to the broker will be restricted to an authentication process.
Authenticated mode can be enabled by setting MQTT_CHALLENGE_SECRET
and MQTT_CHALLENGE_ROUNDS
If the MQTT_CHALLENGE_SECRET
is a String
a single challenge will be set for all users. In this mode it is not possible to specify the user after the command. As an example you can set it as:
MQTT_CHALLENGE_SECRET: "supersecret"
If it is a Dict
, multiple challenges will be set. You can use this to define individual authentication codes for multiple users. In this case you can add the username after the command. As an example you can set it as:
MQTT_CHALLENGE_SECRET: {'USER_A': 'supersecret', 'USER_B': 'topsecret'}
Enabling the MQTT_CHALLENGE_SECRET
setting will make all further commands to require a unique authentication code. This will make the MQTT interface incompatible with off the shelf systems such as HomeAssistant.
All commands will work has described previously, but the command should be concatenated with CODE
(e.g. char arm CODE
) or USER CODE
(e.g. arm USER_A CODE
). Without these last parts, the command will be rejected.
The MQTT_CHALLENGE_ROUNDS
defines the number of iterations of the to calculate the response CODE
. A larger number will increase the difficulty of breaking the challenge, but it will make the process slower. The default value is 10000.
The CODE
is generated as:
h = hashlib.new("SHA1")
challenge = the current unique challenge
secret = global or user code from MQTT_CHALLENGE_SECRET
rounds = MQTT_CHALLENGE_ROUNDS
text = challenge || secret
i = rounds
while i > 0:
h.update(text)
i -= 1
CODE = h.hexdigest()
The CODE
submitted is compared directly with the CODE
generated internally.
The challenge
is published periodically to a topic configurable by MQTT_BASE_TOPIC}/MQTT_STATES_TOPIC/MQTT_CHALLENGE_TOPIC
(which by default is at paradox/states/challenge
and consists of a random string. Every time an authenticated command is issued, a new challenge is generated and published. It will not be possible to repeat a command with the same CODE
.