Engine comes packaged with some handy helper functions that make interfacing with the wide variety of common IoT protocols easier.
Functions and Constants
These can be included into your driver class
include ::Orchestrator::Constants
Helper
Type
Description
On, Down, Open
true
Constants that can make code more readable
Off, Up, Close, Short
false
Constants that can make code more readable
in_range(input_number, max, min = 0)
returns a number in the range
if input exceeds the limits, the limit is returned
is_affirmative? value
returns true if value is affirmative
values such as: true 'yes' :On
is_negatory? value
returns true if value is negative
values such as: false 'no' :Inactive
The Constants include module also contains Configuration Helpers
include ::Orchestrator::Transcoder
Helper
Type
Description
hex_to_byte(data)
returns binary string
accepts any string containing hex characters and supports common formatting such as "0xDEADBEEF", "De:ad:Be:ef" etc
byte_to_hex(data)
returns an ASCII string
accepts binary strings or arrays of bytes
str_to_array(data)
returns an array of bytes
accepts strings
array_to_str(data)
returns a binary string
accepts array of bytes
Protocols
WebSockets
require 'protocols/websocket'
class WebsocketClient
include ::Orchestrator::Constants
include ::Orchestrator::Transcoder
generic_name :Websocket
descriptive_name 'Websocket example'
tcp_port 80
wait_response false
def connected
new_websocket_client
end
def disconnected
# clear the keepalive ping
schedule.clear
end
# Send a text message
def some_request
@ws.text "hello"
# or json format etc
@ws.text({
some: "message",
count: 234
}.to_json)
end
# send a binary message
def binary_send
@ws.binary("binstring".bytes)
# or
@ws.binary hex_to_byte("0xdeadbeef")
end
protected
def new_websocket_client
# NOTE:: you must use wss:// when using port 443 (TLS connection)
@ws = Protocols::Websocket.new(self, "ws://#{remote_address}/path/to/ws/endpoint")
# @ws.add_extension # https://github.com/faye/websocket-extensions-ruby
# @ws.set_header(name, value) # Sets a custom header to be sent as part of the handshake
@ws.start
end
def received(data, resolve, command)
@ws.parse(data)
:success
end
# ====================
# Websocket callbacks:
# ====================
# websocket ready
def on_open
logger.debug { "Websocket connected" }
schedule.every('30s') do
@ws.ping('keepalive')
end
end
def on_message(raw_string)
logger.debug { "received: #{raw_string}" }
# Process request here
# request = JSON.parse(raw_string)
# ...
end
def on_ping(payload)
logger.debug { "received ping: #{payload}" }
# optional
end
def on_pong(payload)
logger.debug { "received pong: #{payload}" }
# optional
end
# connection is closing
def on_close(event)
logger.debug { "closing... #{event.code} #{event.reason}" }
end
# connection is closing
def on_error(error)
logger.debug { "ERROR! #{error.message}" }
end
# ====================
end
Telnet
require 'protocols/telnet'
class TelnetClient
def on_load
new_telnet_client
# Telnet client returns only relevant data for buffering
config before_buffering: proc { |data|
@telnet.buffer data
}
end
def disconnected
# Ensures the buffer is cleared
new_telnet_client
end
def some_request
# Telnet deals with end of line characters
# (may have been negotiated on initial connection)
send @telnet.prepare('some request')
end
protected
def new_telnet_client
# Telnet client needs access to IO stream
@telnet = Protocols::Telnet.new do |data|
send data
end
end
end
KNX
BACnet
OAuth
require 'protocols/oauth'
class HttpClient
# =====================================
# Hook into HTTP request via middleware
# All requests will be sent with OAuth
# =====================================
def on_update
connected
end
# This is called directly after on_load.
# Middleware is not available until connected
def connected
@oauth = Protocols::OAuth.new({
key: setting(:consumer_key),
secret: setting(:consumer_secret),
site: remote_address
})
update_middleware
end
protected
def update_middleware
# middleware is service helper function
mid = middleware
mid.clear
mid << @oauth
end
end
SNMP
require 'protocols/snmp'
class SnmpClient
include ::Orchestrator::Constants
udp_port 161
def on_unload
@client.close
end
# This is called directly after on_load.
# Middleware is not available until connected
def connected
proxy = Protocols::Snmp.new(self)
@client = NETSNMP::Client.new({
proxy: proxy, version: "2c",
community: "public"
})
end
def query_something
self[:status] = @client.get(oid: '1.3.6.1.2.1.1.1.0')
end
def set_something(val)
@client.set('1.3.6.1.2.1.1.3.0', value: val)
self[:something] = val
end
protected
def received(data, resolve, command)
# return the data which resolves the request promise.
# the proxy uses fibers to provide this to the NETSNMP client
data
end
end
SOAP Services
Probably the easiest way to use these services at the moment via a Logic module. There are a number of supported ruby gems:
Savon usage:
# Ensure we are not blocking the IO reactor loop
require 'httpi/adapter/libuv'
require 'savon'
HTTPI.adapter = :libuv
# Make requests as per the savon documentation
client = Savon.client(wsdl: 'https://aca.im/service.wsdl')
logger.debug { "Available operations: #{client.operations}" }
Handsoap usage:
# Ensure we are not blocking the IO reactor loop
require 'handsoap/http/drivers/libuv_driver'
Handsoap.http_driver = :libuv
# Make requests as per the handsoap documentation
Wake on LAN
Wake on LAN is available to drivers of all types
# Supports any string with the correct number of hex digits
# as well as common formats (these are some examples)
mac_address_string = '0x62f81d4b6f00'
mac_address_string = '62:f8:1d:4b:6f:00'
mac_address_string = '62-f8-1d-4b-6f-00'
# Defaults to broadcast address `'255.255.255.255'`
wake_device(mac_address_string)
# You can define a VLan gateway for a directed broadcast (most common in enterprise)
wake_device(mac_address_string, '192.168.3.1')
ICMP (ping)
Uses the operating systems ping utility to perform a connectivity check.
# perform the ping
ping = ::UV::Ping.new(remote_address)
ping.ping # true / false to indicate success / failure
# check out the ping results
ping.pingable # true / false to indicate success / failure
ping.ip # IP pinged (remote_address can be a domain name)
ping.exception # any error messages
ping.warning # any warning messages