Sample Plugin
- prefix.node_test_plugin
- /scripts/
- /scripts/modules/
- /scripts/functions/
- /scripts/items/
- /configs/
- Download Sample Plugin
{
"id": "prefix.node_test_plugin",
"version": "1.1",
"meta": {
"name": {
"text": "Node test plugin"
},
"description": {
"text": "This plugin will create a fake switch with an item upon installation."
},
"author": {
"text": "Ezlo Cloud Device Integration team"
},
"type": "node",
"language": "lua",
"placement": {
"static": true,
"custom": true
}
},
"type": "gateway",
"dependencies": {
"firmware": "2.0",
"addons": [
{
"id": "lua",
"version": "1.0"
}
]
},
"permissions": [
"core",
"http",
"json",
"logging",
"storage",
"timer"
],
"executionPolicy": "restoreLastScriptState",
"startup": "scripts/startup",
"teardown": "scripts/teardown",
"gateway": {
"name": "Node test plugin",
"label": "Node test plugin",
"forceRemoveDeviceCommand": "HUB:prefix.node_test_plugin/scripts/delete_device",
"setItemValueCommand": "HUB:prefix.node_test_plugin/scripts/set_item_value",
"setItemValueResponsePolicy": "auto"
}
}
// 'config.json' is where you define configuration attributes, dependencies and metadata:
// Prefix.Name
// Version
// Type
// Metadata (User friendly name, description, author)
// Dependencies
// References
// Execution Policy
// Entry and Exit points
// 'config.json' must be in the top level directory. Its name cannot be changed.
// In this example of the file, 'config.json' calls 'startup.lua' which in-turn calls 'create_device.lua'...
// …'create_device.lua' contains script which lets you create a device which will appear in EZlogic.
{
"id": "prefix.node_test_plugin",
"version": "1.1",
// 'id' is The name of your plugin as it will be known in the file-system. This id should be unique among your plugins on a specific hub.
// You must create and activate a prefix for your plugin. Add the prefix before the name of the plugin in the 'id' field.
// Make sure all paths in all scripts also use the prefix.
// For example, if your prefix is 'acme' and your plugin is called 'my_plugin', then your 'id' field is "acme.my_plugin".
// Your .tar.gz file should be called 'acme.my_plugin.tar.gz'. An example path to your plugin in your scripts is "HUB:acme.my_plugin/scripts/set_item_value"
// The plugin install folder on the firmware and the tar.gz archive name should have the same name as this config.json “id” value.
// Your custom plugin is referenced in the API by the name you assign to it in this “id” line. For example, ‘prefix.node_test_plugin’ is the name of the plugin defined here:
// {
// "id": "prefix.node_test_plugin",
// "version": "1.1",
// This identifier is used in the path when calling scripts and other API components. For example,
// constants = require(HUB:prefix.node_test_plugin/scripts/definitions/constants”),
"meta": {
// 'meta' is an object which contains public-facing/general information about the plugin.
// This object is described in our API documents at https://developer.mios.com/api/hub/user-functionality/custom-scripts/hub-custom-nodes-list/
"name": {
"text": "Node test plugin"
},
"description": {
"text": "This plugin will create a fake switch with an item upon installation."
},
"author": {
"text": "Ezlo Cloud Device Integration team"
},
"type": "node",
"language": "lua",
"placement": {
"static": true,
"custom": true
// “name”/”description”/”author” - public-facing information about the plugin.
// “type” - this should always be “node”.
// “language” - must be set to “lua”.
// "placement": {
// “static”: true
// “custom”: true
// You must have "static": true and "custom": true as shown. The plugin will fail
// if these are omitted or are specified as a different type.
}
},
"type": "gateway",
// The type of plugin you are creating. You must use 'gateway' as the type here.
// 'gateway' plugins can create logical (virtual) devices, items (device capabilities) and settings (capability values).
// After creating a gateway plugin, you can select the devices created by the plugin as a (logical/virtual) device in a meshbot trigger.
// You can read more about the gateway type of plugin at https://developer.mios.com/docs/create-plugins/plugin-types/
// You can read more about devices, items and settings at https://developer.mios.com/docs/create-plugins/quick-start-guide/#concepts-and-terminology
"dependencies": {
// The 'dependencies' section lists the minimum firmware version and addon versions that are required for your plugin to run.
"firmware": "2.0",
"addons": [
{
"id": "lua",
"version": "1.0"
}
]
},
"permissions": [
// 'Permissions' is a list of the Lua modules that you want to use in the plugin. End-users will have to agree to let the plugin use these permissions when they install it.
// For example, a plugin which requires http requests will need to specify the HTTP module and its events as listed at https://developer.mios.com/api/scripting/modules/http/functionality/http-request/.
// Full list of Lua modules for which you can request permissions: https://developer.mios.com/api/scripting/lua/list-of-lua-modules/
"core",
"http",
"json",
"logging",
"storage",
"timer"
],
"executionPolicy": "restoreLastScriptState",
// 'executionPolicy' lets you specify that a plugin saves a global state and restores it when the plugin is re-started.
// Read more on execution policy options at https://developer.mios.com/api/hub/plugins/executionpolicy/
"startup": "scripts/startup",
// 'startup' refers to 'startup.lua'.
// This line lets you specify a path to a startup script that is called every time the firmware is started or rebooted. In our example, ‘startup.lua’ is loaded next after config.json and can call other scripts from within it.
// Users must first login to their account before we can begin adding devices to the plugin. This is because we need to know how many devices they already have on the plugin.
// Once access rights have been verified, the script will call the device creation script with the following command:
// loadfile(“HUB:prefix.node_test_plugin/scripts/functions/create_device””)()
// This loads ‘create_device.lua’ which contains a call to 'core.add.device' in the core module.
// See the annotated startup.lua file in this section for more info on startup.lua
// See the annotated create_device.lua in this section for more info on create_device.lua
"teardown": "scripts/teardown",
// This line calls 'teardown.lua' when the plugin is uninstalled. 'teardown.lua' is a script which contains cleanup logic to remove devices, temporary files, timers etc created by the plugin on the hub. This saves resources on the hub.
"gateway": {
// Configure the name, label and commands for the plugin. ‘Gateway’ is the entity created to orchestrate all the devices and items created by the plugin.
// All commands and fields you can add to the “gateway” section are listed at https://developer.mios.com/api/hub/plugins/api/gateway/.
// Some are required and others optional. You should include all ‘required’ commands and any ‘optional’ commands that are needed by your plugin.
"name": "Node test plugin",
// [Required] - "name:" is the internal id of the plugin. This name identifies the plugin in the list of hub plugins at https://developer.mios.com/api/hub/gateways-api/hub-gateways-list/
// This allows the system to call and reference the plugin. The gateways list mentioned above contains references to generic scripts about each plugin. These include gateway value set commands, dictionary value set commands, ready status, unreachable actions and so forth.
"label": "Node test plugin",
// [Required] - "label:" is the public-facing name of the plugin for end-users.
"forceRemoveDeviceCommand": "HUB:prefix.node_test_plugin/scripts/delete_device",
// [Required] - "forceRemoveDeviceCommand" calls a script to uninstall a device. Example:
// "forceRemoveDeviceCommand": "HUB:prefix.node_test_plugin/scripts/delete_device",
// In the example above, ‘delete_device’ calls ‘delete_device.lua’, which uses the following command to remove a device:
// hub.device.force_remove (https://developer.mios.com/api/hub/devices/api/hub-device-force_remove/)
"setItemValueCommand": "HUB:prefix.node_test_plugin/scripts/set_item_value",
// [Required] - "setItemValueCommand" calls a script which defines a device capability (aka ‘item’). For example, ‘Take a snapshot’ on a camera device. Example:
// "setItemValueCommand": "HUB:prefix.node_test_plugin/scripts/set_item_value",
// In the example above, ‘set_item_value' calls ‘set_item_value.lua’ which uses one of the following three commands to specify a device capability (item):
// 1) https://developer.mios.com/api/hub/items/api/hub-item-value-set-single-item/
// 2) https://developer.mios.com/api/hub/items/api/hub-item-value-set-multiply-items-with-one-value/
// 3) https://developer.mios.com/api/hub/items/api/hub-item-value-set-different-items-with-different-values/
"setItemValueResponsePolicy": "auto"
// [Optional] - "setItemValueResponsePolicy" specifies the response type for requests sent by hub.item.value.set (single or multiple version - see above). The response policy field applies to Linux firmware only. Example:
// "setItemValueResponsePolicy": "auto"
// Possible values:
// “auto” - The firmware sends the response immediately after receiving the hub.item.value.set request. This is the default setting.
// “custom” - The plugin is responsible for sending the response to the hub.item.value.set request. It must call core.send_response() to do this. You must specify an additional parameter, “operation id”, in the script you call in setSettingValueCommand. The firmware will send a timeout error if the plugin was unable to send a response within 2 minutes.
// ** The next command *is not included* in the "gateway" section of our example config,json file. We include it here as just another example of a command you can use:
// "setSettingValueCommand":
// [Optional] - "setSettingValueCommand" calls a script to modify the value of a device capability (aka ‘item’). Example:
// "setSettingValueCommand": "HUB:prefix.node_test/scripts/set_setting_value"
// In the example above, ‘set_setting_value’calls a script named ‘set_setting_value.lua’ which uses the following command to specify a capability value:
// hub.device.setting.value.set (https://developer.mios.com/api/hub/devices/settings/api/hub-device-setting-value-set/)
}
}
{
"configuration": {
"type": "static",
"script": "HUB:prefix.node_test_plugin/configs/account",
"placement": "device,plugin",
"inputs": [
{
"name": "Username",
"description": "Fake Account Username",
"required": true,
"field": "username",
"type": "string"
},
{
"name": "Password",
"description": "Fake Account Password",
"required": true,
"field": "password",
"type": "string"
"contents":[
{
"id": "ezlo_httprequests_apicall",
"ui": {
"name": "Do a webapi call via http(s)",
"description": "Do an api call via http or https to an ip",
"placement": "plugin"
},
"type": "setter-api",
"apply": [
{
"element": "item",
"when": {
"property": "name",
"operator": "=",
"value": "http_client_send"
},
"to_api": "hub.item.value.set"
}
],
"inputs":[
{
"name": "method",
"type": "string"
},
{
"name":"ip_address",
"type": "string"
},
{
"name": "port",
"type": "integer",
"minimum": 1,
"maximum": 65535
}
]
},
{
"id": "ezlo_httprequests_apicall",
"ui": {
"name": "Do a webapi call via http(s)",
"description": "Do an api call via http or https to a domain",
"placement": "plugin"
},
"type": "setter-api",
"apply": [
{
"element": "item",
"when": {
"property": "name",
"operator": "=",
"value": "http_client_send"
},
"to_api": "hub.item.value.set"
}
],
"inputs":[
{
"name": "method",
"type": "string"
},
{
"name":"domain",
"type": "string"
},
{
"name":"port",
"type": "integer",
"minimum": 1,
"maximum": 65535
}
]
}
}
// interface.jason defines a set of inputs that are displayed on the web UI.
// It handles the configuration of the plugin after installation. For example, if the plugin needs credentials to work, then this requirement is listed in interface.json.
{
"configuration": {
// The configuration section lists all elements required to configure the plugin. You can call other Lua config scripts that you have created from here. For example
// “script”: “HUB:prefix.node_test_plugin/configs/account”,
// The account part calls ‘account.lua’ and requests the inputs as shown below. accounts.lua collects the actual UN/PW from the account.
// In this case, the username and password are passed and used to populate the ‘local args’ part of account.lua.
// See the annotated 'account.lua' in this section for more info on account.lua.
"type": "static",
"script": "HUB:prefix.node_test_plugin/configs/account",
"placement": "device,plugin",
"inputs": [
{
"name": "Username",
"description": "Fake Account Username",
"required": true,
"field": "username",
"type": "string"
},
{
"name": "Password",
"description": "Fake Account Password",
"required": true,
"field": "password",
"type": "string"
"contents":[
{
"id": "ezlo_httprequests_apicall",
"ui": {
"name": "Do a webapi call via http(s)",
"description": "Do an api call via http or https to an ip",
"placement": "plugin"
},
"type": "setter-api",
"apply": [
{
"element": "item",
"when": {
"property": "name",
"operator": "=",
"value": "http_client_send"
},
"to_api": "hub.item.value.set"
}
],
"inputs":[
{
"name": "method",
"type": "string"
},
{
"name":"ip_address",
"type": "string"
},
{
"name":"port",
"type": "integer",
"minimum": 1,
"maximum": 65535
}
]
},
{
"id": "ezlo_httprequests_apicall",
"ui": {
"name": "Do a webapi call via http(s)",
"description": "Do an api call via http or https to a domain",
"placement": "plugin"
},
"type": "setter-api",
"apply": [
{
"element": "item",
"when": {
"property": "name",
"operator": "=",
"value": "http_client_send"
},
"to_api": "hub.item.value.set"
}
],
"inputs":[
{
"name": "method",
"type": "string"
},
{
"name":"domain",
"type": "string"
},
{
"name":"port",
"type": "integer",
"minimum": 1,
"maximum": 65535
}
]
}
}
}
]
}
}
local _logger = require("logging")
_logger.info("prefix.node_test_plugin starting up…")
loadfile("HUB:prefix.node_test_plugin/scripts/functions/create_device")()
local _constants = require("HUB:prefix.node_test_plugin/configs/constants")
_G.constants = _constants or {}
-- The 'logging' module handles log processing and supports different log severities, indents, etc.
-- Users can change the global log severity on our HUB and can modify log verbosity levels.
-- More verbose logs are helpful when debugging issues.
-- The Logger API, which lets you change hub log level, is at https://developer.mios.com/api/hub/system/logging/hub-log-local-set/
-- Lua logging module documentation is available at https://developer.mios.com/api/scripting/modules/logging/
local _logger = require("logging")
-- 'startup.lua' is a script which is called every time the firmware is started or rebooted.
-- Among other things, this example of 'startup.lua' calls ‘create_device.lua’ which contains script which
-- lets you create a device in the EZlogic interface.
-- '_logger.info' will save the logs on the HUB at: /var/log/firmware/ha-luad.log
-- Example: 2021-12-13 19:32:11.863117 INFO : prefix.node_test_plugin: prefix.node_test_plugin starting up…
-- Lua logging module documentation is available at https://developer.mios.com/api/scripting/modules/logging/
_logger.info("prefix.node_test_plugin starting up…")
-- Because the 'constants.lua' file returns a table, we can use 'require' to directly load
-- and return the result of the script (which is a table with constants):
local _constants = require("HUB:prefix.node_test_plugin/configs/constants")
-- Now we initialize a global object that holds constants which are used throughout the plugin execution.
-- We can use this approach because we have set the following configuration in the config.json file:
-- "executionPolicy": "restoreLastScriptState"
-- This enables us to use the same environment across each script execution. All global variables are saved and restored this way.
-- Be careful when using this approach as it can easily lead to memory leaks if not cleared properly after you are done with it!
_G.constants = _constants or {}
-- The 'loadfile' function runs a script located at a specific path and returns a
-- function that we call to execute the script.
-- NOTE - The path must begin with the identifier "HUB:"
-- Our lua interpreter is modified so that when calling 'loadfile' it will replace the "HUB:" identifier
-- with the real path to the plugin directory on the controller. For example:
-- Official plugins directory is: /opt/firmware/plugins/ (subject to change)
-- Custom plugins ("HUB:" plugins) directory is: /home/data/custom_plugins (also subject to change)
-- The following line loads create_device.lua. This script lets you add devices which will show up in the EZlogic web UI.
loadfile("HUB:prefix.node_test_plugin/scripts/functions/create_device")()
-- Notes and guidelines for the startup.lua script:
-- * Keep this file as simple and as clean as possible. It should be readable and tell you
-- clearly what this plugin needs to do when it starts.
-- * Only write "business logic" code here that shows what this plugin needs to do when started
-- * Make sure you initialize all your data at plugin startup. For example, starting timers,
-- initializing global state, doing initial checks, initializing devices, starting discovery, etc.
-- * 'startup.lua' is loaded every time the firmware starts (or the ha-luad process is rebooted to be exact).
-- This can happen when a dependency crashes and the dependency tree is restarted, or when the hub is power-cycled,
-- rebooted or manually restarted. The latter often happens as plugin developers usually reboot to reload the config.json file when it contains changes.
-- * Note - All plugins execute in the same (single) thread. A core aspect of our Lua API is that it is mostly asynchronous.
-- This lets plugins run quickly even if they all execute under a single thread. You'll notice that, for example,
-- when working with http requests or sockets, you have to subscribe to module events to continue processing the reponse.
-- * Documentation on config.json and interface.json structure is available at https://developer.mios.com/api/hub/plugins/
-- * Ezlo API concepts are as follows
--[[
- GATEWAY - The plugin object. It describes the plugin using the information from config.json
* You can check all gateways on a controller with the following websocket request
in our API tool at: https://apitool .ezlo.com/dashboard:
{"id": "_AUTO_74107","method": "hub.gateways.list","params": {}}
* The API tool lets you simulate the requests that are made by our mobile and web apps
so you can test and debug your plugins.
* The UI will be notified with the following broadcast every time you install a gateway:
https://developer.mios.com/api/hub/broadcasts/hub-gateway-added/
- [0..*] DEVICE - The virtual entity/container representing a physical device, service or component.
Basically anything you want.
* Each gateway can have zero or more devices
* Devices can have a parent-child relationship. For example, a thermostat device may contain
several sensors (motion,temperature, humidity etc) as child devices.
* We can represent this as a main device (the thermostat) and additional devices representing
each sensor which are mapped as children of the parent.
The advantage is that once you remove the parent, all child devices are also removed.
* The UI will be notified with the following broadcast every time
you modify/add/remove a device inside the plugin:
https://developer.mios.com/api/hub/broadcasts/hub-device-added/
* The Lua device object is documented here: https://developer.mios.com/api/scripting/modules/core/objects/device/
* APIs to work with devices from plugin code: https://developer.mios.com/api/scripting/modules/core/functionality/
* APIs to work with devices from the API tool: https://developer.mios.com/api/hub/devices/api/
- [0..*] ITEM - a device capability that we interact with when using a device.
For example, 'Start Recording' and 'Stop Recording' are capabilities (items) of a camera device.
* Each device can have zero or more items (capabilities)
* Items can participate in automations (scenes or meshbots).
* Items we have already defined are available at: https://developer.mios.com/api/hub/items/item-names/items/
The 'Enum' column links to allowed values/settings for the item.
* Use the defined items in your plugin if you want the item to be visible in the UI.
Plugin items not shown on this list will not be visible in the UI.
* Theoretically you can add any number of items to a device, but in the UI we only support rendering
specific items for specific types of devices. For now, there is no support in the UI to render
any item dynamically.
* The UI will be notified with the following broadcast each time
you modify/add/remove a device inside a plugin:
https://developer.mios.com/api/hub/broadcasts/hub-item-added/
* The item object is documented here:
https://developer.mios.com/api/scripting/modules/core/objects/item/
* API's to work with items from plugin code:
https://developer.mios.com/api/scripting/modules/core/functionality/
* API's to work with items from the API tool: https://developer.mios.com/api/hub/items/api/
- [0..*] DEVICE SETTING - Settings are configuration values for device capabilities/items.
With gateway plugins, you can create custom settings for your logical/virtual devices.
* Each device can have zero or more device settings.
* Device settings behave like items but are a little different.
* Device settings cannot participate in automations (scenes, meshbots).
* Device settings can change the behavior of a device, but can also be used to add
new settings that are solely plugin managed.
Example 1 - To let users change the polling frequency of each device’s status.
Example 2 - A setting to change the color saturation of a camera could be forwarded
to the physical device via an API call, just as you would do with an item implementation.
* You can view available settings for each of your devices in the ‘Devices’ section
of the EZlogic portal - https://ezlogic.mios.com ('Settings > 'Devices')
* With a few exceptions, our mobile apps do not currently show device settings in the UI.
* The UI will be notified with the following broadcast each time you modify/add/remove
a device setting inside a plugin:
https://developer.mios.com/api/hub/broadcasts/hub-device-setting-added/
* The device setting object is documented here:
https://developer.mios.com/api/scripting/modules/core/objects/device-setting/
* API's to work with device settings from plugin code:
https://developer.mios.com/api/scripting/modules/core/functionality/add_setting/
* API's to work with device settings from the API tool:
https://developer.mios.com/api/hub/devices/settings/api/
]]
-- -* We have additional API's not documented here because this is a "getting started" example.
-- Our documentation, available at https://developer.mios.com/api/, contains examples
-- and explanations for all APIs and modules we make available for use in plugins.
-- For example, under 'Scripting' > 'Modules', you can find the timer module:
-- https://developer.mios.com/api/scripting/modules/timer/timer-module-description/
local _core = require("core")
local _storage = require("storage")
local _logger = require("logging")
_logger.info("prefix.node_test_plugin teardown...")
_logger.info("Cleanup storage...")
_storage.delete_all()
_logger.info("Cleanup devices...")
_core.remove_gateway_devices(_core.get_gateway().id)
-- The 'core' module provides the ability to work with the Ezlo Lua API to manage devices, items, settings and more.
-- Lua core module documentation is available at: https://developer.mios.com/api/scripting/modules/core/functionality/
local _core = require("core")
-- The 'storage' module provides the ability to store data that is specific to a plugin.
-- Each plugin has its own private storage in a key-value table.
-- This storage keeps a record of persistent private data like credentials, device IP address, MAC address, etc.
-- Lua storage module documentation is available at https://developer.mios.com/api/scripting/modules/storage/
local _storage = require("storage")
-- The 'logging' module handles log processing and supports different log severities, indents, etc.
-- Users can change the global log severity on our HUB and can modify log verbosity levels.
-- More verbose logs are helpful when debugging issues.
-- The Logger API, which lets you change hub log level, is at https://developer.mios.com/api/hub/system/logging/hub-log-local-set/
-- Lua log module documentation is available at https://developer.mios.com/api/scripting/modules/logging/
local _logger = require("logging")
-- '_logger.info' will save the logs on the HUB at: /var/log/firmware/ha-luad.log
-- Example: 2021-12-13 19:32:11.863117 INFO : prefix.node_test_plugin: prefix.node_test_plugin teardown…
-- Lua logging module documentation is available at https://developer.mios.com/api/scripting/modules/logging/
-- It is best to log lifecycle events at 'info' severity level as this is the default severity for all controllers.
_logger.info("prefix.node_test_plugin teardown…")
-- Another good practice is to log a message before calling some functionality so that
-- you can trace the execution to the line that threw an exception/error
_logger.info("Cleanup storage…")
-- '_storage.delete_all()' clears all your stored data. It is always best to clear stored data
-- when teardown is called so you don't leave uncleaned resources behind.
_storage.delete_all()
_logger.info("Cleanup devices…")
-- '_core.remove_gateway_devices()' deletes all devices created by the plugin.
-- NOTE - a 'plugin' is called a 'gateway' in controller API terminology, but they are the same thing.
-- The firmware will automatically remove all devices when a plugin is uninstalled, but it is still
-- good practice to call this method anyway. For one, it makes testing your plugin easier.
_core.remove_gateway_devices(_core.get_gateway().id)
-- NOTES and guidelines for the teardown.lua script
-- * Always clean any resources used *exclusively* by the plugin (timers, pending requests, opened sockets, temporary files, etc)
-- * I also recommend you manually delete devices created by your plugin as older firmware versions don't do it.
local _core = require("core")
local _logger = require("logging")
local params = … or {}
if not params.deviceId then
_logger.warning("Missing device ID, can't delete device")
return
end
_logger.info("Delete device: " .. params.deviceId)
_core.remove_device(params.deviceId)
-- The 'core' module provides the ability to work with the Ezlo Lua API to manage devices, items, settings and more.
-- Lua core module documentation is available at: https://developer.mios.com/api/scripting/modules/core/functionality/
local _core = require("core")
-- The 'logging' module handles log processing and supports different log severities, indents, etc.
-- Users can change the global log severity on our HUB and can modify log verbosity levels.
-- More verbose logs are helpful when debugging issues.
-- The Logger API, which lets you change hub log level, is at https://developer.mios.com/api/hub/system/logging/hub-log-local-set/
-- Lua logging module documentation is available at https://developer.mios.com/api/scripting/modules/logging/
local _logger = require("logging")
-- Any parameter passed to the script can be obtained by using the vararg expression "..."
-- See https://www.lua.org/manual/5.4/manual.html#3.4.11
-- We store the parameter in the "args" variable for later use (while also making sure to initialize it if no parameters are passed).
-- Example payload passed to this script by the firmware when the user initiates a device removal:
--[[
{
deviceId: "61b1f9420000001241d7831c"
}
]]
local params = ... or {}
-- Now we make a sanity check to see if this script was called correctly and a device ID was provided:
if not params.deviceId then
-- '_logger.warning' will save the logs on the HUB at: /var/log/firmware/ha-luad.log
-- Example: 2021-12-13 19:32:11.863117 WARN : prefix.node_test_plugin: Missing device ID, can't delete device
-- Lua logging module documentation is available at https://developer.mios.com/api/scripting/modules/logging/
-- 'Warning' severity severity logs are bigger than 'Info' logs, so these are always written
-- in the ha-luad.log file.
_logger.warning("Missing device ID, can't delete device")
return
end
-- Next, log the device deletion at the INFO level for debugging purposes.
-- This is an important lifecycle event which must be logged always.
_logger.info("Delete device: " .. params.deviceId)
-- Now, we attempt to remove the device by its ID:
_core.remove_device(params.deviceId)
-- NOTES and guidelines for the delete_device.lua script
-- * This script is configured in the config.json file like this:
--[[
{
"gateway":
{
"forceRemoveDeviceCommand": "HUB:prefix.node_test_plugin/scripts/delete_device",
}
} ]]
-- * You can configure any script path as your device remover or use this as a template
-- Choosing not to configure this script means that users cannot remove
-- individual devices that the plugin creates EXCEPT when uninstalling the plugin. In the latter case,
-- *all* devices created by the plugin are removed.
-- * API Tool - https://apitool.ezlo.com/dashboard
-- This tool lets you simulate the requests made by our mobile and web apps so
-- you can test and debug your plugins.
-- You can use this tool to call your items, devices, device setting and even your individual
-- scripts without having to use the mobile or web apps.
-- Example request with the API tool to test your device removal script without using a mobile or web app
-- (you need to provide the correct device ID, of course):
--[[
{
"method": "hub.device.force_remove",
"id": "_AUTO_738951",
"params": {
"_id": "61a0f524123e3309fc77ae7b"
}
}
]]
local _core = require("core")
local _logger = require("logging")
local params = … or {}
local items = {}
if type(params.item_id) == "string" then
items = { [params.item_id] = { value = params.value } }
elseif type(params.item_ids) == "table" then
for _, item_id in ipairs(params.item_ids) do
items[item_id] = { value = params.value }
end
elseif type(params.items) == "table" then
items = params.items
end
for item_id, item_value in pairs(items) do
local item = _core.get_item (item_id )
local item_script = loadfile ("HUB:prefix.node_test_plugin/scripts/items/" .. item.name)
if item_script then
local success, errmsg = pcall (item_script, {
device_id = item.device_id,
item_id = item_id,
value = item_value.value,
source = params.source
})
if not success then
_logger.error("Failed to set item:" .. item.name .. (errmsg and (", error: " .. errmsg ) or ""))
end
else
_logger.error("Failed to load handler for item: " .. item.name)
end
end
-- The 'core' module provides the ability to work with the Ezlo Lua API to manage devices, items, settings and more.
-- Lua core module documentation is available at: https://developer.mios.com/api/scripting/modules/core/functionality/
local _core = require("core")
-- The 'logging' module handles log processing and supports different log severities, indents, etc.
-- Users can change the global log severity on our HUB and can modify log verbosity levels.
-- More verbose logs are helpful when debugging issues.
-- The Logger API, which lets you change hub log level, is at https://developer.mios.com/api/hub/system/logging/hub-log-local-set/
-- Lua log module documentation is available at https://developer.mios.com/api/scripting/modules/logging/
local _logger = require("logging")
-- Any parameter passed to the script can be obtained by using the vararg expression "…"
-- See https://www.lua.org/manual/5.4/manual.html#3.4.11
-- We store the parameter in the "args" variable for later use (while also making sure to initialize it if no parameters are passed):
local params = … or {}
-- There are 3 ways you can call this script:
-- 1. https://developer.mios.com/api/hub/items/api/hub-item-value-set-single-item/
-- 2. https://developer.mios.com/api/hub/items/api/hub-item-value-set-multiply-items-with-one-value/
-- 3. https://developer.mios.com/api/hub/items/api/hub-item-value-set-different-items-with-different-values/
-- The 'if' block shown below converts the first 2 syntaxes to the third so that we support all types of calls to this script.
-- Developers can chose to support only the first (set-single-item) which means the other 2 will return an error.
-- Currently the mobile and web apps only use the first approach (set-single-item), but this may change in the future.
-- Options #2 and #3 are meant to optimize commands for a batch of devices (e.g. 'turn off all lights'), or a
-- combination of different commands for multiple device types in a single web socket message.
local items = {}
if type(params.item_id) == "string" then
-- Single item and single value.
-- Create a map with a single item and its value.
--[[ Example, the structure of the params payload for option #1 is as follows:
{
"item_id": "619634c0123e33191314af83",
"value": true
} ]]
items = { [params.item_id] = { value = params.value } }
elseif type(params.item_ids) == "table" then
-- Multiple items and single value.
-- Create a map with multiple items where each item has the same value.
--[[ Example, the structure of the params payload for option #2 is as follows:
{
"item_ids": ["619634c0123e33191314af83", "619634c0123e33191314af84"]
"value": true
} ]]
for _, item_id in ipairs(params.item_ids) do
items[item_id] = { value = params.value }
end
elseif type(params.items) == "table" then
-- Multiple items with different values.
-- Just save it as is.
--[[ Example, the structure of the params payload for option #3 is as follows:
{
"items": {
"619634c0123e33191314af83": {
"value": {
"value": true
}
}
}
} ]]
items = params.items
end
-- At this point we have a map of items with (possibly diferent) values.
-- Loop each item in the "items" table and execute the script for it.
-- NOTE: In our case we have only 1 item (switch) but this pattern can
-- apply to any arbitrary number of items if you chose to keep this plugin structure
for item_id, item_value in pairs(items) do
-- We only have the item ID's so we need to get the full item info.
-- Item info details are at: https://developer.mios.com/api/scripting/modules/core/objects/item/
local item = _core.get_item(item_id)
-- Load the script that holds the item implementation for our device.
-- We chose to create scripts that encapsulate all the item logic inside them
-- and we named the scripts the same as the item name to be more intuitive.
-- If all items are placed in the same directory we can have a centralized handling
-- mechanism like this one which just ditributes the requests accordingly:
local item_script = loadfile("HUB:prefix.node_test_plugin/scripts/items/" .. item.name)
-- If the script is loaded (which means it exists and the item is implemented)
if item_script then
-- Execute the script in protected mode with 'pcall' to catch errors, passing many details
-- (id of device, item, value, etc) to the item scripts to aid in processing the request.
-- We use 'pcall' because we don't want errors in any item handler to stop execution of the other
-- item handlers (when there are multiple items that must be changed).
local success, errmsg = pcall(item_script, {
device_id = item.device_id,
item_id = item_id,
value = item_value.value,
source = params.source
})
if not success then
-- '_logger.error' will save the logs on the HUB at: /var/log/firmware/ha-luad.log
-- Example: 2021-12-13 19:32:11.863117 ERROR: prefix.node_test_plugin: Failed to set item switch
-- Lua logging module documentation is available at https://developer.mios.com/api/scripting/modules/logging/
-- 'Error' severity logs are bigger than 'Info' logs, so these are always written in the ha-luad.log file.
_logger.error("Failed to set item: " .. item.name .. (errmsg and (", error: " .. errmsg) or ""))
end
else
-- '_logger.error' will save the logs on the HUB at: /var/log/firmware/ha-luad.log
-- Example: 2021-12-13 19:32:11.863117 ERROR: prefix.node_test_plugin: Failed to load handler for item: switch
-- Lua logging module documentation: https://api.ezlo.com/scripting/logging/index.html
-- 'Error' severity logs are bigger than 'Info' logs, so these are always written in the ha-luad.log file.
_logger.error("Failed to load handler for item: " .. item.name)
end
end
-- NOTES and guidelines for the set_item_value.lua script
-- * This script is configured in the config.json file like this:
--[[
{
"gateway":
{
"setItemValueCommand": "HUB:prefix.node_test_plugin/scripts/set_item_value",
}
} ]]
-- * You can configure any script path as your item handler or use this as a template.
-- Choosing not to configure this script means that users will not be able to
-- set or modify any item the plugin creates.
-- * This script is called automatically when making any of the following requests from the web socket API tool:
-- 1. https://developer.mios.com/api/hub/items/api/hub-item-value-set-single-item/
-- 2. https://developer.mios.com/api/hub/items/api/hub-item-value-set-multiply-items-with-one-value/
-- 3. https://developer.mios.com/api/hub/items/api/hub-item-value-set-different-items-with-different-values/
-- * API Tool - https://apitool.ezlo.com/dashboard
-- This tool lets you simulate the requests made by our mobile and web apps so
-- you can test and debug your plugins.
-- You can use this tool to call your items, devices, device setting and even your individual
-- scripts without having to use the mobile or web apps.
-- Example API request to call any script from the API tool:
--[[
{
"method": "hub.extensions.plugin.run",
"id": "_ID_",
"params": {
"script": "HUB:prefix.node_test_plugin/configs/account",
"scriptParams": {
"username": "demo123",
"password": "123demo"
}
}
}
]]
-- This will perform authentication then start creating a device.
-- This could be an API call made from a DPW (device pairing wizard) as part of the plugin
-- config. to authenticate your plugin so it works properly.
local _M = {}
function _M.URL_Encode (url)
return url:gsub("%W", function (c)
return string.format("%%%02X", string.byte(c))
end)
end
function _M.RandomHexString (len)
local rs = ""
for _ = 1, len do
rs = rs .. string.format("%02x", math.random(32, 126))
end
return rs
end
return _M
-- This folder (scripts/modules) contains lua modules that provide generic functionality which
-- can be used anywhere in the plugin.
local _M = {}
-- This function can be used to encode a URL with a specific parameter:
function _M.URL_Encode (url)
return url:gsub("%W", function (c)
return string.format("%%%02X", string.byte(c))
end)
end
-- This function can be used to generate a random hex string:
function _M.RandomHexString (len)
local rs = ""
for _ = 1, len do
rs = rs .. string.format("%02x", math.random(32, 126))
end
return rs
end
-- By returning a table which contains the functions we want to expose we are simulating the
-- implementation of a class. Any local variable we declare in our plugin scripts is private
-- to this module only. This lets us simplistically mimic OOP programming.
return _M
-- NOTE - We don't use this module's functionality in this demo plugin, but we included it as an example.
local _core = require("core")
local _logger = require("logging")
local _storage = require("storage")
local credentials = _storage.get_table(_G.constants.STORAGE_ACCOUNT_KEY)
if not credentials then
_logger.warning("No account is configured… The user did not log in yet.")
end
local function CreateDevice ()
local my_gateway_id = (_core.get_gateway() or {}).id
if not my_gateway_id then
return nil
end
local count = 0
for _, device in pairs(_core.get_devices() or {}) do
if device.gateway_id == my_gateway_id then
count = count + 1
if not credentials or count >=2 then
return device.id
end
end
end
_logger.info("Create new fake device")
return _core.add_device {
type = "switch.inwall",
device_type_id = "switch.inwall.fake",
category = "switch",
subcategory = "interior_plugin",
battery_powered = false,
gateway_id = _core.get_gateway().id,
name = not credentials and "Fake Switch (install)" or "Fake Switch (login)",
info = {
manufacturer = "Ezlo",
model = "1.0"
}
}
end
local function CreateItem (device_id)
if not device_id then
_logger.error("Cannot create item. Missing device_id…")
return
end
for _, item in ipairs(_core.get_items_by_device_id(device_id) or {}) do
return
end
_logger.info("Create new fake 'switch' item")
_core.add_item({
device_id = device_id,
name = "switch",
value_type = "bool",
value = false,
has_getter = true,
has_setter = true
})
end
local device_id = CreateDevice()
CreateItem(device_id)
-- This script lets you call an API function to create a device on your plugin. You can also use it to specify device type, category, id, battery requirements etc.
– The script contains a call to core.add_device in the core api module. Devices created by this script will be available in the EZlogic interface after installing your plugin.
-- Note - ‘create_device.lua’ is just our name for the script in our example. You can name it however you please.
-- The 'core' module provides the ability to work with the Ezlo Lua API to manage devices, items, settings and more.
-- Lua core module documentation is available at: https://developer.mios.com/api/scripting/modules/core/functionality/
local _core = require("core")
-- The 'logging' module handles log processing and supports different log severities, indents, etc.
-- Users can change the global log severity on our HUB and can modify log verbosity levels.
-- More verbose logs are helpful when debugging issues.
-- The Logger API, which lets you change hub log level, is at https://developer.mios.com/api/hub/system/logging/hub-log-local-set/
-- Lua log module documentation is available at https://developer.mios.com/api/scripting/modules/logging/
local _logger = require("logging")
-- The 'storage' module provides the ability to store data that is specific to a plugin.
-- Each plugin has its own private storage in a key-value table.
-- This storage keeps a record of persistent private data like credentials, device IP address, MAC address, etc.
-- Lua storage module documentation is available at https://developer.mios.com/api/scripting/modules/storage/
local _storage = require("storage")
--------------------------------------------------------------------
-- Here we access the '_G.constants' table that we initialized in our startup.lua script.
-- We also retrieve the credentials saved in our plugin storage table by checking a 'known' key:
local credentials = _storage.get_table(_G.constants.STORAGE_ACCOUNT_KEY)
-- No credentials means that the user did not log in so we display a warning:
if not credentials then
_logger.warning("No accout is configured... The user did not log in yet.")
-- return
end
--------------------------------------------------------------------
-- Here we define a function that handles the logic for creating a device.
-- NOTE - the function is defined here but is actually called at the end of the script.
local function CreateDevice ()
-- First we try to get the gateway/plugin ID:
local my_gateway_id = (_core.get_gateway() or {}).id
-- If we cannot get the gateway/plugin ID then just log an error and return. There's nothing else we can do.
if not my_gateway_id then
-- This should never happen, but it's better to be on the safe side.
_logger.error("Failed to get current gateway ID, skip creating devices")
return nil
end
local count = 0
-- Next we enumerate all devices in our controller:
for _, device in pairs(_core.get_devices() or {}) do
-- ...and filter out devices that don't belong to our gateway/plugin:
if device.gateway_id == my_gateway_id then
-- We now count how many devices we have created:
count = count + 1
-- Our objective is to create a fake switch device if the user installed the plugin, and create
-- a second device after the user has logged in.
-- So we check how many devices we have and if the user is logged in:
if not credentials or count >=2 then
-- This means that if a device is found but the user isn't authenticated, we still return the
-- device.id to signal that we have a device and don't need another one.
-- But if the user is authenticated (we found his credentials), then we check the device count. If it is 1 then
-- we don't return which means a new device will be created below after the loop ends.
return device.id
end
end
end
_logger.info("Create new fake device")
-- '_core.add_device' is used to create devices on Ezlo controllers.
-- It returns the ID of any newly created device.
-- Device add documentation: https://developer.mios.com/api/scripting/modules/core/functionality/add_device/
-- Device Object documentation: https://developer.mios.com/api/scripting/modules/core/objects/device/
return _core.add_device {
type = "switch.inwall",
device_type_id = "switch.inwall.fake",
category = "switch",
subcategory = "interior_plugin",
battery_powered = false,
gateway_id = _core.get_gateway().id,
-- Here, we just slightly modify the device name to see the difference between them:
name = not credentials and "Fake Switch (install)" or "Fake Switch (login)",
info = {
manufacturer = "Ezlo",
model = "1.0"
}
}
end
-- Here we define a function that handles the logic to create an item.
-- NOTE - the function is defined here but is actually called at the end of the script.
local function CreateItem (device_id)
-- 'items' are associated with devices, so if we don't have a device_id then we cannot create the item:
if not device_id then
-- ...log an error:
_logger.error("Cannot create item. Missing device_id...")
-- and return control
return
end
-- '_core.get_items_by_device_id(device_id)' will only return the items of a specific device.
-- Documentation: https://developer.mios.com/api/scripting/modules/core/functionality/get_items_by_device_id/
for _, item in ipairs(_core.get_items_by_device_id(device_id) or {}) do
-- Here we check whether the device already has items. If so, the control will reach
-- this block and simply return. We are not interested in creating duplicate items
-- and this is a simple way of preventing it.
return
end
-- Log a message at 'info' severity level since this is an important lifecycle message:
_logger.info("Create new fake 'switch' item")
-- '_core.add_item' is used to create items on Ezlo controllers.
-- It returns the ID of any newly created item.
-- Item add documentation: https://developer.mios.com/api/scripting/modules/core/functionality/add_item/
-- Item object documentation: https://developer.mios.com/api/scripting/modules/core/objects/item/
_core.add_item({
device_id = device_id,
name = "switch",
value_type = "bool",
value = false,
has_getter = true,
has_setter = true
-- has_setter - Capabilities which can be set are exposed as ‘Actions’ in meshbots. This is defined by the has_setter field of the item object.
-- You can view a list of setters at https://developer.mios.com/api/scripting/modules/core/functionality/set_setting_value/
-- has_getter - Capabilities which can be read are exposed as ‘Triggers’ in meshbots. This is defined by the has_getter field of the item object.
-- You can view a list of getters at https://developer.mios.com/api/scripting/modules/core/functionality/getters/
})
end
--------------------------------------------------------------------
-- We've put the 'business logic' at the end of the script because the Lua language forces us to define functions before calling them.
-- Every time this script is called, we want to try and create a device and its corresponding item:
local device_id = CreateDevice()
-- Next, create the item for our new device. The cases where 'device_id'is nil are handled inside the 'CreateItem' function:
CreateItem(device_id)
local _core = require("core")
local args = … or {}
_core.update_item_value(args.item_id, args.value)
-- The 'core' module provides the ability to work with the Ezlo Lua API to manage devices, items, settings and more.
-- Lua core module documentation is available at: https://developer.mios.com/api/scripting/modules/core/functionality/
local _core = require("core")
-- Any parameter passed to the script can be obtained by using the vararg expression "..."
-- See https://www.lua.org/manual/5.4/manual.html#3.4.11
-- We store the parameter in the "args" variable for later use (while also making sure to initialize it if no parameters are passed):
local args = ... or {}
-- This is a demo plugin, so we directly update our existing item with the new value and we're done:
_core.update_item_value(args.item_id, args.value)
-- In a real-world case we would write logic in this script for the following situations:
-- 1. Item initialization
-- * Create an item if it has not already been created
-- * Initialize the item
-- 2. Item update
-- * This usually means sending a request to a real device then waiting for the response
-- * Based on the response we update the item value
-- * OR we set the device as offline
-- * OR mark an error by changing device status
-- * OR broadcast a custom error
-- 3. Item sync/remove
-- * In certain situations you may want to dynamically add/remove items
-- * For example - you have a device setting which controls the max. number of items on a device (e.g. the number
-- of buttons on a remote control). This call can remove some of them or add them back later based on your logic.
-- 4. Basically, in item (and device settings - not covered in this demo), scripts is where all the 'plugin magic' happens!
local _storage = require("storage")
local _logger = require("logging")
local STORAGE_ACCOUNT_KEY = _G.constants.STORAGE_ACCOUNT_KEY
local args = … or {}
local account_credentials = {}
if type(args.username) == "string" and args.username ~= "" then
account_credentials.username = args.username
end
if type(args.password) == "string" and args.password ~= "" then
account_credentials.password = args.password
end
if account_credentials.username == _G.constants.DEFAULT_USERNAME and account_credentials.password == _G.constants.DEFAULT_PASSWORD then
_logger.info("Logged in successfully…")
_storage.set_table(STORAGE_ACCOUNT_KEY, account_credentials)
loadfile("HUB:prefix.node_test_plugin/scripts/functions/create_device")()
else
_logger.warning("The provided credentials are invalid.")
end
-- The 'storage' module provides the ability to store data that is specific to a plugin.
-- Each plugin has its own private storage in a key-value table.
-- This storage keeps a record of persistent private data like credentials, device IP address, MAC address, etc.
-- Lua storage module documentation is available at https://developer.mios.com/api/scripting/modules/storage/
local _storage = require("storage")
-- The 'logging' module handles log processing and supports different log severities, indents, etc.
-- Users can change the global log severity on our HUB and can modify log verbosity levels.
-- More verbose logs are helpful when debugging issues.
-- The Logger API, which lets you change hub log level, is at https://developer.mios.com/api/hub/system/logging/hub-log-local-set/
-- Lua logging module documentation is available at https://developer.mios.com/api/scripting/modules/logging/
local _logger = require("logging")
-- We declare module level variables in UPPERCASE LETTERS to better distinguish their scope:
local STORAGE_ACCOUNT_KEY = _G.constants.STORAGE_ACCOUNT_KEY
-----------------------------------------------------------
-- Any parameter passed to the script can be obtained by using the vararg expression "…"
-- See https://www.lua.org/manual/5.4/manual.html#3.4.11
-- We store the parameter in the "args" variable for later use (while also making sure to initialize it if no parameters are passed):
local args = … or {}
-- Here, we declare a table that will hold the new credentials after we check them:
local account_credentials = {}
if type(args.username) == "string" and args.username ~= "" then
account_credentials.username = args.username
end
if type(args.password) == "string" and args.password ~= "" then
account_credentials.password = args.password
end
-- The following simulates a valid login if we have a new credential set:
if account_credentials.username == _G.constants.DEFAULT_USERNAME and account_credentials.password == _G.constants.DEFAULT_PASSWORD then
-- '_logger.info' will save the logs on the HUB at: /var/log/firmware/ha-luad.log
-- Example: 2021-12-13 19:32:11.863117 INFO : prefix.node_test_plugin: Logged in successfully…
-- Lua logging module documentation is available at https://developer.mios.com/api/scripting/modules/logging/
_logger.info("Logged in successfully…")
-- '_storage.set_table' will save the table in the plugin's own storage under the key specified by the
-- 'STORAGE_ACCOUNT_KEY' variable.
-- Storage module documentation is available at https://developer.mios.com/api/scripting/modules/storage/
_storage.set_table(STORAGE_ACCOUNT_KEY, account_credentials)
-- The 'loadfile' function runs a script located at a specific path and returns a
-- function that we call to execute the script.
-- NOTE - The path must begin with the identifier "HUB:"
-- Our lua interpreter is modified so that when calling 'loadfile' it will replace the "HUB:" identifier
-- with the real path to the plugin directory on the controller. For example:
-- Official plugins directory is: /opt/firmware/plugins/ (subject to change)
-- Custom plugins ("HUB:" plugins) directory is: /home/data/custom_plugins (also subject to change)
-- After we've successfully authenticated we begin the process of adding the second device:
loadfile("HUB:prefix.node_test_plugin/scripts/functions/create_device")()
else
-- Or else, log a warning and finish
-- '_logger.warning' will save the logs on the HUB at: /var/log/firmware/ha-luad.log
-- For example: 2021-12-13 19:32:11.863117 WARN : prefix.node_test_plugin: Missing device ID, can't delete device
-- Lua logging module documentation is available at https://developer.mios.com/api/scripting/modules/logging/
-- 'Warning' severity logs are bigger than 'Info' logs, so these are always written in the ha-luad.log file.
_logger.warning("The provided credentials are invalid.")
end
local _M = {}
_M.DEFAULT_USERNAME = "demo123"
_M.DEFAULT_PASSWORD = "123demo"
_M.STORAGE_ACCOUNT_KEY = "account"
return _M
-- This module is for saving some persistent data that we use in this demo plugin.
-- This facilitates ease of management. For example, 'constants.lua' is referenced and called in 'account.lua'.
local _M = {}
_M.DEFAULT_USERNAME = "demo123"
_M.DEFAULT_PASSWORD = "123demo"
_M.STORAGE_ACCOUNT_KEY = "account"
return _M