Wake On Lan Plugin
Про плагін
Цей простий плагін надає можливість віддалено вмикати Ethernet пристрої за допомогою Wake On Lan пакету
примітка
Пристрій повинен бути в підмережі та, скоріш за все, повинен бути належним чином налаштований, щоб дозволити його вмикання таким чином, зверніться до документації вашої ОС/обладнання
Wake Up Lan плагін не потребує жодних спеціальних кроків встановлення чи налаштування, просто дотримуйтесь загальних інструкцій
Вихідний код
Вихідний код плагіна
local json = require("json")
local io = require("io")
local uv = require("uv")
local dgram = require("dgram")
local timer = require("timer")
local plugin = {}
local function print_variable(var, ident, depth)
if depth > 10 then
print(ident .. "...")
return
end
if type(var) == "table" then
print(ident .. "{")
for k, v in pairs(var) do
print(ident .. " " .. tostring(k) .. " = ")
print_variable(v, ident .. " ", depth + 1)
end
print(ident .. "}")
else
print(ident .. tostring(var))
end
end
-- Plugin definition
plugin.name = "Wake on LAN"
plugin.version = { 0, 0, 1 }
plugin.provided_interfaces = {
WakeOnLAN = {
actions = {
wakeUp = {
description = "Send WOL packet to computer",
arguments = {},
handler = function(self, args)
print("Wake up called")
return true
end
}
},
parameters = {
identity = {
type = "string",
value = "<Computer MAC address>"
},
version = {
type = "integer",
value = "1"
},
manufacturer = {
type = "string",
value = "ConnectHome"
},
network_interface = {
type = "string",
value = "eth0"
}
}
}
}
plugin.overridden_interfaces = {
SwitchBinary = {
actions = {
setStatus = {}
}
}
}
plugin.devices = {
Computer = {
type = "DevSwitch",
role = "Control",
icon = "ch-socket",
interfaces = { "WakeOnLAN", "SwitchBinary" },
parameters = {
identity = {
type = "string",
value = "11:22:33:44:55:66",
role = "edit",
locale = {
en = "MAC address",
ru = "MAC адрес",
ua = "MAC адреса"
}
},
version = {
type = "integer",
value = 1
},
manufacturer = {
type = "string",
value = "ConnectHome",
role = "show",
locale = {
en = "Manufacturer",
ru = "Производитель",
ua = "Виробник"
}
},
network_interface = {
type = "string",
value = "eth0",
role = "edit",
locale = {
en = "Network interface",
ru = "Сетевой интерфейс",
ua = "Мережевий інтерфейс"
}
},
Status = {
type = "boolean",
value = false,
},
pulsable = {
type = "boolean",
value = false,
}
}
}
}
-- Plugin initialization
function plugin:init(reason)
print("Wake on LAN plugin init")
end
-- Plugin registered
function plugin:register(uuid)
print("Wake on LAN plugin registered")
end
-- Plugin unregistered
function plugin:unregister(topic, payload)
print("Wake on LAN plugin unregistered")
end
local function gethostname(ip)
local address = {
ip = ip,
port = nil,
family = nil
}
local hostname = uv.getnameinfo(address, nil)
return hostname
end
-- Discover devices
function plugin:discover_devices(topic, payload)
print("Discover devices")
local discovery_result = {}
-- /sbin/ip -json neighbour
local f = io.popen("/sbin/ip -json neighbour")
local output = f:read("*a")
f:close()
local json_data = json.decode(output)
local discovered_devices = {} -- Table to keep track of discovered devices
for _, v in ipairs(json_data) do
if v["state"][1] ~= "FAILED" then
local mac = v["lladdr"]
local ip = v["dst"]
local hostname = gethostname(ip)
local state = v["state"][1]
print("Discovered device: " .. mac .. " " .. ip .. " " .. hostname .. " " .. state)
-- Check if device with the same MAC address already exists
if not discovered_devices[mac] then
local device = {
identity = mac,
info = ip,
type = "Computer",
icon = "ch-socket",
params = {
network_interface = v["dev"],
}
}
table.insert(discovery_result, device)
discovered_devices[mac] = true -- Mark device as discovered
end
end
end
self:reply(topic, payload.message_id, 200, true, {
devices = discovery_result
})
end
-- 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
function plugin:device_create(topic, payload)
print("Device create")
local message_id = payload.message_id
local name = payload.body.params.name
local dev_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 dev_type or not room_id then
self:reply(topic, message_id, 400, true, {
error = "Missing required parameters"
})
return
end
local new_device = plugin.devices and plugin.devices[dev_type]
if not new_device then
self:reply(topic, message_id, 400, true, {
error = "Unknown device type"
})
return
end
self:pub {
topic = "internal/plugins_hub/" .. self.uuid .. "/devices/create",
payload = json.stringify({
version = "2",
method = "POST",
message_id = self.mqtt:pending_message_add(new_device),
token = self.token,
body = {
plugin_type = dev_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
}
self:reply(topic, message_id, 200, true)
end
-- Device was created
function plugin:device_created(topic, payload)
print("Device created")
if not payload.body.success then
print("Device created error:", payload.status)
return
end
self:db_add_device(payload.body.device_id, payload.body.plugin_type)
self:db_add_default_params(payload.body.device_id, payload.body.interfaces, payload.body.params)
end
-- callback
function plugin:device_registered(device_id, name, params)
print("device_registered")
self:notify("Registered device " .. device_id)
end
-- callback
function plugin:device_removed(topic, payload)
print("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
-- Prepare and send WOL packet
local function send_wol_packet(mac, broadcast_address)
local mac_bytes = {}
local magic_packet_str = ""
-- Convert MAC address to byte array
local mac0, mac1, mac2, mac3, mac4, mac5 = string.match(mac, "(%x%x):(%x%x):(%x%x):(%x%x):(%x%x):(%x%x)")
mac_bytes = {
tonumber(mac0, 16),
tonumber(mac1, 16),
tonumber(mac2, 16),
tonumber(mac3, 16),
tonumber(mac4, 16),
tonumber(mac5, 16)
}
-- Add 6 bytes with 0xFF
for i = 1, 6 do
magic_packet_str = magic_packet_str .. string.char(0xFF)
end
-- Add 16 repetitions of MAC address
for i = 1, 16 do
for j = 1, 6 do
magic_packet_str = magic_packet_str .. string.char(mac_bytes[j])
end
end
-- Send packet
local socket = dgram:createSocket("udp4")
socket:bind(0, broadcast_address)
socket:setBroadcast(true)
-- Repeat packet sending 5 times
for i = 1, 5 do
socket:send(magic_packet_str, 9, broadcast_address, function(err)
if err then
print("Error sending WOL packet: " .. err)
end
if i == 5 then
socket:close()
end
end)
end
end
-- Convert IP address from string to binary form
local function inet_pton(ip_str)
local ip_bytes = {}
local ip_addr = 0
local ip0, ip1, ip2, ip3 = string.match(ip_str, "(%d+)%.(%d+)%.(%d+)%.(%d+)")
if ip0 and ip1 and ip2 and ip3 then
ip_bytes = {
tonumber(ip0),
tonumber(ip1),
tonumber(ip2),
tonumber(ip3)
}
else
return 0
end
for i = 1, 4 do
ip_addr = bit.bor(ip_addr, bit.lshift(ip_bytes[i], (4 - i) * 8))
end
return ip_addr
end
-- Convert IP address from binary form to string
local function inet_ntoa(ip_addr)
local ip_bytes = {}
local ip_str = ""
for i = 1, 4 do
ip_bytes[i] = bit.band(bit.rshift(ip_addr, (4 - i) * 8), 0xFF)
end
ip_str = ip_bytes[1] .. "." .. ip_bytes[2] .. "." .. ip_bytes[3] .. "." .. ip_bytes[4]
return ip_str
end
-- Get broadcast address for specified network interface
local function get_broadcast_address(interface)
local addresses = uv.interface_addresses()
local interface_address = addresses[interface]
if not interface_address then
return nil
end
for _, address in ipairs(interface_address) do
if address.family == "inet" then
local ip = inet_pton(address.ip)
local netmask = inet_pton(address.netmask)
local broadcast = inet_ntoa(bit.bor(ip, bit.bnot(netmask)));
print(" IP: " .. address.ip)
print(" Netmask: " .. address.netmask)
print("Broadcast: " .. broadcast)
return broadcast
end
end
return nil
end
function plugin:action(topic, payload)
print("Action")
local device_id = tonumber(self:get_topic_segment(topic, 5))
if payload.body.method == "setStatus" and payload.body.interface == "SwitchBinary" and payload.body.params then
local status = payload.body.params[1] or false
print("Set status: " .. tostring(status) .. " for device " .. tostring(device_id))
if status == "true" then
local mac_address = self:db_get_param(device_id, "identity")
local network_interface = self:db_get_param(device_id, "network_interface")
local broadcast_address = get_broadcast_address(network_interface)
if not broadcast_address then
print("Broadcast address not found")
return
end
-- Send WOL packet
send_wol_packet(mac_address, broadcast_address)
timer.setTimeout(107, function()
self:update_param_boolean(device_id, "Status", false)
end)
end
end
end
return plugin