Utilities and Helpers
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
1
include ::Orchestrator::Constants
Copied!
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
1
include ::Orchestrator::Transcoder
Copied!
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

Enables WebSocket communication using a standard TCP socket driver.
1
require 'protocols/websocket'
2
​
3
class WebsocketClient
4
include ::Orchestrator::Constants
5
include ::Orchestrator::Transcoder
6
​
7
generic_name :Websocket
8
descriptive_name 'Websocket example'
9
​
10
tcp_port 80
11
wait_response false
12
​
13
def connected
14
new_websocket_client
15
end
16
​
17
def disconnected
18
# clear the keepalive ping
19
schedule.clear
20
end
21
​
22
# Send a text message
23
def some_request
24
@ws.text "hello"
25
​
26
# or json format etc
27
​
28
@ws.text({
29
some: "message",
30
count: 234
31
}.to_json)
32
end
33
​
34
# send a binary message
35
def binary_send
36
@ws.binary("binstring".bytes)
37
​
38
# or
39
​
40
@ws.binary hex_to_byte("0xdeadbeef")
41
end
42
​
43
protected
44
​
45
def new_websocket_client
46
# NOTE:: you must use wss:// when using port 443 (TLS connection)
47
@ws = Protocols::Websocket.new(self, "ws://#{remote_address}/path/to/ws/endpoint")
48
# @ws.add_extension # https://github.com/faye/websocket-extensions-ruby
49
# @ws.set_header(name, value) # Sets a custom header to be sent as part of the handshake
50
@ws.start
51
end
52
​
53
def received(data, resolve, command)
54
@ws.parse(data)
55
:success
56
end
57
​
58
# ====================
59
# Websocket callbacks:
60
# ====================
61
​
62
# websocket ready
63
def on_open
64
logger.debug { "Websocket connected" }
65
schedule.every('30s') do
66
@ws.ping('keepalive')
67
end
68
end
69
​
70
def on_message(raw_string)
71
logger.debug { "received: #{raw_string}" }
72
​
73
# Process request here
74
# request = JSON.parse(raw_string)
75
# ...
76
end
77
​
78
def on_ping(payload)
79
logger.debug { "received ping: #{payload}" }
80
# optional
81
end
82
​
83
def on_pong(payload)
84
logger.debug { "received pong: #{payload}" }
85
# optional
86
end
87
​
88
# connection is closing
89
def on_close(event)
90
logger.debug { "closing... #{event.code} #{event.reason}" }
91
end
92
​
93
# connection is closing
94
def on_error(error)
95
logger.debug { "ERROR! #{error.message}" }
96
end
97
​
98
# ====================
99
end
Copied!

Telnet

Implements the telnet standard so that it is easy to communicate with devices that implement control codes or require negotiation.
1
require 'protocols/telnet'
2
​
3
class TelnetClient
4
def on_load
5
new_telnet_client
6
​
7
# Telnet client returns only relevant data for buffering
8
config before_buffering: proc { |data|
9
@telnet.buffer data
10
}
11
end
12
​
13
def disconnected
14
# Ensures the buffer is cleared
15
new_telnet_client
16
end
17
​
18
​
19
def some_request
20
# Telnet deals with end of line characters
21
# (may have been negotiated on initial connection)
22
send @telnet.prepare('some request')
23
end
24
​
25
protected
26
​
27
def new_telnet_client
28
# Telnet client needs access to IO stream
29
@telnet = Protocols::Telnet.new do |data|
30
send data
31
end
32
end
33
end
Copied!

KNX

Constructs KNX standard datagrams that make it easy to communicate with devices on KNX networks.
For more information see: https://github.com/acaprojects/ruby-knx​

BACnet

Constructs BACnet datagrams that make it easy to communicate with devices on BACnet networks.
For more information see: https://github.com/acaprojects/ruby-bacnet​

OAuth

For secure delegated access to services that implement it see wikipedia for details​
1
require 'protocols/oauth'
2
​
3
class HttpClient
4
# =====================================
5
# Hook into HTTP request via middleware
6
# All requests will be sent with OAuth
7
# =====================================
8
def on_update
9
connected
10
end
11
​
12
# This is called directly after on_load.
13
# Middleware is not available until connected
14
def connected
15
@oauth = Protocols::OAuth.new({
16
key: setting(:consumer_key),
17
secret: setting(:consumer_secret),
18
site: remote_address
19
})
20
update_middleware
21
end
22
​
23
protected
24
​
25
def update_middleware
26
# middleware is service helper function
27
mid = middleware
28
mid.clear
29
mid << @oauth
30
end
31
end
Copied!

SNMP

Provides an evented IO proxy for ruby-netsnmp​
1
require 'protocols/snmp'
2
​
3
class SnmpClient
4
include ::Orchestrator::Constants
5
udp_port 161
6
​
7
def on_unload
8
@client.close
9
end
10
​
11
# This is called directly after on_load.
12
# Middleware is not available until connected
13
def connected
14
proxy = Protocols::Snmp.new(self)
15
@client = NETSNMP::Client.new({
16
proxy: proxy, version: "2c",
17
community: "public"
18
})
19
end
20
​
21
def query_something
22
self[:status] = @client.get(oid: '1.3.6.1.2.1.1.1.0')
23
end
24
​
25
def set_something(val)
26
@client.set('1.3.6.1.2.1.1.3.0', value: val)
27
self[:something] = val
28
end
29
​
30
protected
31
​
32
def received(data, resolve, command)
33
# return the data which resolves the request promise.
34
# the proxy uses fibers to provide this to the NETSNMP client
35
data
36
end
37
end
Copied!

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:

1
# Ensure we are not blocking the IO reactor loop
2
require 'httpi/adapter/libuv'
3
require 'savon'
4
HTTPI.adapter = :libuv
5
​
6
# Make requests as per the savon documentation
7
client = Savon.client(wsdl: 'https://aca.im/service.wsdl')
8
logger.debug { "Available operations: #{client.operations}" }
Copied!

Handsoap usage:

1
# Ensure we are not blocking the IO reactor loop
2
require 'handsoap/http/drivers/libuv_driver'
3
Handsoap.http_driver = :libuv
4
​
5
# Make requests as per the handsoap documentation
Copied!

Wake on LAN

Wake on LAN is available to drivers of all types
1
# Supports any string with the correct number of hex digits
2
# as well as common formats (these are some examples)
3
mac_address_string = '0x62f81d4b6f00'
4
mac_address_string = '62:f8:1d:4b:6f:00'
5
mac_address_string = '62-f8-1d-4b-6f-00'
6
​
7
# Defaults to broadcast address `'255.255.255.255'`
8
wake_device(mac_address_string)
9
​
10
# You can define a VLan gateway for a directed broadcast (most common in enterprise)
11
wake_device(mac_address_string, '192.168.3.1')
Copied!

ICMP (ping)

Uses the operating systems ping utility to perform a connectivity check.
1
# perform the ping
2
ping = ::UV::Ping.new(remote_address)
3
ping.ping # true / false to indicate success / failure
4
​
5
# check out the ping results
6
ping.pingable # true / false to indicate success / failure
7
ping.ip # IP pinged (remote_address can be a domain name)
8
ping.exception # any error messages
9
ping.warning # any warning messages
Copied!

CRC Checks

  • gem install crc
Usage
1
require 'crc'
2
crc = CRC::CRC16_CCITT.new
3
crc.update "\x12\x34\x56\x78\x90"
4
crc.digest # => "ca"
Copied!