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!