Ukraine Raid Alarm
About
Add device to your controller that represents air raid alarm in selected regions.
tip
To use this plugin you'll need to get an API token from UkraineAlarm
Installation
After following the installation instructions, you need to set API_TOKEN
variable to your API key.
For example if your key is 12345678:123456789012345678
, you should replace the line
local API_TOKEN = ""
with the line:
local API_TOKEN = "12345678:123456789012345678"
Source code
Plugin source code
local os = require 'os'
local JSON = require 'json'
local http = require('http')
local https = require('https')
local timer = require('timer')
local plugin = {}
local API_VERSION = 2
local API_TOKEN = ""
plugin.name = "Повітряна тривога"
plugin.version = {1, 2, 0}
local ukrainealarm = {}
ukrainealarm.host = "https://api.ukrainealarm.com"
ukrainealarm.token = API_TOKEN
ukrainealarm.version = 3
ukrainealarm.api_url = ukrainealarm.host .. "/api/v" .. ukrainealarm.version
ukrainealarm.api_regions = ukrainealarm.api_url .. "/regions"
ukrainealarm.api_alerts = ukrainealarm.api_url .. "/alerts"
-- poll timer for every device
local timers = {}
ukrainealarm.get_regions = function(cb)
print("ukrainealarm.get_regions")
local options = http.parseUrl(ukrainealarm.api_regions)
options.headers = {{"Authorization", API_TOKEN}}
options.method = 'GET'
local req = https.get(options, function(res)
local regions = ""
res:on('data', function(chunk)
regions = regions .. chunk
end)
res:on("end", function()
if cb and type(cb) == "function" then
cb(regions)
end
end)
end)
end
ukrainealarm.get_alerts = function(region, cb)
print("ukrainealarm.get_alerts")
local options = http.parseUrl(ukrainealarm.api_alerts .. "/" .. region)
options.headers = {{"Authorization", API_TOKEN}}
options.method = 'GET'
local req = https.get(options, function(res)
local data = ""
res:on('data', function(chunk)
data = data .. chunk
end)
res:on("end", function()
if cb and type(cb) == "function" then
cb(true, data)
end
end)
end)
req:on("error", function(err)
print("ukrainealarm.get_alerts error", err)
if cb and type(cb) == "function" then
cb(false, err)
end
end)
req:setTimeout(25000)
end
-- new interfaces provided by the plugin
plugin.provided_interfaces = {
AirRaidAlert = {
actions = {
pollDevice = {
description = "Refresh all device data",
arguments = {},
handler = function(path)
print("sample plugin pollDevice", path)
end
}
},
parameters = {
identity = {
type = "string",
value = "0",
role = "edit",
locale = {
en = "Region code",
ru = "Код региона",
ua = "Код регіону"
}
},
version = {
type = "integer",
value = "1"
},
manufacturer = {
type = "string",
value = "ConnectHome",
role = "show",
locale = {
en = "Manufacturer",
ru = "Поставщик",
ua = "Постачальник"
}
},
polling_time = {
type = "integer",
value = 10,
role = "edit",
locale = {
en = "Polling time, s",
ru = "Время опроса, с",
ua = "Час опитування, с"
}
}
}
}
}
-- controller present interfaces overridden by the plugin
plugin.overridden_interfaces = {}
-- complete device information profiles
plugin.devices = {
AirRaidAlertSensor = {
type = "DevBinarySensor",
role = "BinaryGenericSensor",
icon = "ch-security",
interfaces = {"AirRaidAlert", "SensorBinary"},
parameters = {
polling_time = 10,
Armed = false,
Tripped = false
}
}
}
-- helper function
local function get_params_name_value(device, identity)
local params = {}
for k, v in pairs(device.parameters) do
params[k] = v.value
end
if identity then
params.identity = identity
end
return params
end
----------------- API HANDLERS --------------------------------
-- create device API handler
function plugin:device_create(topic, payload)
-- todo hide inside api?
print("plugin device_create")
local device_id = tonumber(self:get_topic_segment(topic, 5)) -- should be 1
local message_id = payload.message_id
local name = payload.body.params.name
local type = payload.body.params.type or ""
local identity = payload.body.params.identity
local room_id = payload.body.params.room_id or 0
local params = payload.body.params.params or {}
if not message_id or not name or not identity or not type or not room_id then
self:reply(topic, message_id, 400, false, {
message = "missing requred parameters"
})
return
end
local new_device = plugin.devices and plugin.devices[type]
if not new_device then
print("device_create: device type not found", type)
self:reply(topic, message_id, 404, false, {
message = "device type not found"
})
return
end
-- calling a controller to create the device
self:pub{
topic = "internal/plugins_hub/" .. self.uuid .. "/devices/create",
payload = JSON.stringify({
version = tostring(API_VERSION),
method = "POST",
message_id = self.mqtt:pending_message_add(new_device),
token = self.token,
body = {
plugin_type = type,
name = name,
type = new_device.type,
role = new_device.role,
alive = true,
hidden = false,
room_id = room_id,
interfaces = new_device.interfaces,
icon = new_device.icon,
params = get_params_name_value(new_device, identity)
}
}),
qos = 2
}
-- reply to the caller
self:reply(topic, message_id, 200, true)
end
-- discover devices API handler
function plugin:discover_devices(topic, payload)
print("plugin discover_devices")
-- Query all regions
ukrainealarm.get_regions(function(regions)
local json = JSON.parse(regions)
local short_regions = {}
-- Loop through each state
if json then
for i, state in ipairs(json.states) do
table.insert(short_regions, {
identity = state.regionId,
info = state.regionName,
type = "AirRaidAlertSensor",
icon = "ch-siren_on"
})
end
end
self:reply(topic, payload.message_id, 200, true, {
devices = short_regions
})
end)
end
-- convert UTC to local timestamp
local function convert_datetime(utc)
if not utc then
return nil
end
local pattern = "(%d+)-(%d+)-(%d+)T(%d+):(%d+):(%d+)Z"
local year, month, day, hour, minute, second = utc:match(pattern)
local timestamp = os.time({
year = year,
month = month,
day = day,
hour = hour,
min = minute,
sec = second
})
local timezone = os.date('%z') -- "+0300"
local signum, hours, minutes = timezone:match '([+-])(%d%d)(%d%d)'
local dt = (tonumber(signum .. hours) * 3600 + tonumber(signum .. minutes) * 60)
return timestamp + dt
end
-- poll device
function plugin:poll_device(device_id)
-- get identity param
local region = self:db_get_param(device_id, "identity")
print("plugin poll_device", device_id, region)
if not region then
print("plugin poll_device: identity not found, terminating")
return
end
ukrainealarm.get_alerts(region, function(result, alerts)
p("alerts/" .. region, result, alerts)
if result then
local json = JSON.parse(alerts)
if json then
local active = json and json[1] and json[1].activeAlerts or nil
local tripped = active ~= nil and #active > 0
self:update_param_boolean(device_id, "Tripped", tripped)
if tripped then
local alert = active[1]
local timestamp = convert_datetime(alert.lastUpdate)
self:update_param_integer(device_id, "lasttrip", timestamp)
end
end
end
local time = self:db_get_param(device_id, "polling_time")
if type(time) ~= "number" then
time = 10
end
-- restart timer
if timers[device_id] then
timer.clearTimer(timers[device_id])
end
timers[device_id] = timer.setTimeout(time * 1000, function()
self:poll_device(device_id)
end)
end)
end
-- callback
function plugin:init(reason)
print("AirRaidAlert init")
end
-- callback
function plugin:device_created(topic, payload)
print("plugin device_created")
p(payload)
if payload.body.success ~= true then
print("plugin device_created error:", payload.status)
return
end
-- add device
self:db_add_device(payload.body.device_id, payload.body.plugin_type)
-- add params
self:db_add_default_params(payload.body.device_id, payload.body.interfaces, payload.body.params)
-- start poller
self:poll_device(payload.body.device_id)
end
-- callback
function plugin:device_registered(device_id, name, params)
print("device_registered")
self:notify("Registered device " .. device_id)
self:poll_device(device_id)
end
-- callback
function plugin:device_removed(topic, payload)
print("plugin device_removed")
p(payload)
if payload.body.success ~= true then
print("plugin device_removed error:", payload.status)
return
end
local device_id = payload.body.device_id
-- removing items from db
self:db_delete_params(device_id)
self:db_remove_device(device_id)
end
return plugin