iot-manager Usage Reference

The following contains information on the IoTManager module’s classes such as their inputs and member methods, how to connect to a the IoTManager using clients, and how to use the IoTManager class to interface with connected IoT Clients.

iot-manager Classes Documentation

Manager Class Documentation

This section details how the Manager class is written and what parameters and member methods it has.

class iot_manager.Manager(**params, **kwargs)

Parameters

  • valid_types (list) -
    A list of strings that are the type of a type of device which a client can identify as, below is an example of such a list that could be passed.
    types = ["light", "lock", "thermostat"]
    
  • ssl_context (Optional[ssl_context])[None] -
    A TLS/SSL wrapper context for securing socket communications over unsecured networks.
  • host (Optional[string])[“127.0.0.1”] -
    The host IP the server is running on. For example “0.0.0.0” on windows and “127.0.0.1” on Linux.
  • connection_port (Optional[int])[8595] -
    The port that clients will use to connect to the Manager with.
  • max_workers (Optional[int])[16] -
    The max number of ThreadPoolExecutor workers that the Manager can put to use concurrently.
  • heartbeat_rate (Optional[int])[60] -
    The frequency, in seconds, at which the Manager will check the heartbeat of all connected clients.
  • backlogged_connections (Optional[int])[10] -
    The number of backlogged connections the listening socket can have before it refuses new clients.
  • logging_id (Optional[string])[“[Manager]”] -
    Changes the ID of the logger used by the Manager in the logging messages.
  • logging_level (Optional[logging.LEVEL])[logging.INFO] -
    Changes the level of logging output generated by the Manager.

Class Members

  • host(string)
    The IP of the host connection, always ‘0.0.0.0’.
  • port(int)
    The port clients will use to connect to the Manager. Changing this value will not update the connection port so it should be treated as read-only.
  • The logger object used by the Manager to do logging.
  • heartbeat_rate(int)
    The frequency, in seconds, at which the Manager will check the heartbeat of all connected clients.

Class Methods

  • send_all(data[bytes])
    Sends the given data to all clients connected to the Manager.
  • send_type(device_type[string], data[bytes])
    Sends the given data to all clients connected to the Manager of a specified device_type.
  • send(unique_id[string], data[bytes])
    Sends the given data to a client connected to the Manager with a matching UUID.
  • send_all_string(string[string])
    Sends the given string to all clients connected to the Manager.
  • send_type_string(device_type[string], string[string])
    Sends the given string to all clients connected to the Manager of a specified device_type.
  • send(unique_id[string], string[string])
    Sends the given string to a client connected to the Manager with a matching UUID.
  • get_client_data()
    Returns a list of dicts which contain each connected client’s information. each dict is formatted like the following example
    {
        "uuid": (uuid of the client connection)[str],
        "type": (client's type)[str],
        "data": (client's data)[dict]
    }
    

Client Class Documentation

This section details what members and member methods the Client class has.

class iot_manager.Client(**params, **kwargs)

Parameters

  • connection(socket)
    An active socket which is connected to a device, used to communicate with the device.
  • address(tuple)
    The address info of the client as a (IP, PORT) pair.

Class Members

  • connection_lock(RLock)
    A RLock object used to prevent multiple threads from accessing the Client’s underlying socket connection at the same time. Useful for executing a send and recv pair without the Manager’s listener accessing the socket while awaiting a response. Example:
# acquire the lock
with client.connection_lock:
   # request some data
   client.send_string("get_data")

   # get some data (10 second timeout)
   response = client.recv_string(10)
# release the lock
  • data(Data)
    A object that stores the client’s data retrieved from the get_data method.

Class Methods

  • end(raise_exception[boolean](False))
    Calling this method will end the client’s socket connection with the server after sending the client the “endn” message. Finally raising a ConnectionEnd error when finished if raise_exception is set to true.
  • send(bytes)
    Similar to the socket.sendall method, however rather than raising exceptions it will return None instead of the # of bytes sent.
  • recv(timeout[int](15) )
    Similar to the socket.recv method, however rather than raising exceptions it will return None instead of the data received. Timeout is the delay before a socket timeout is triggered in seconds (The data received from this method is a bytes object)
  • send_string(string[string])
    Uses the send method to send a given string to the client.
  • recv_string(timeout[int](15) )
    Uses the recv method to recv a string from a client.
  • get_data(timeout[int](15) )
    Initiates a handshake procedure with the client, asking for the clients information such as UUID, type, and data. This handshake is imitated by the Manager when the client first connects and does not need to be used after, but can if needed if edit’s to the Client’s data are made while a ongoing connection exists. Can raise a ConnectionEnd and InvalidInfo exception if the client fails to answer or provides invalidly formatted data.
  • heartbeat(timeout[int](15) )
    A method used by the Manager’s heartbeat checking protocol to see if clients are still connected and responsive. It is a handshake procedure similar to the get_data method which has no real use outside of the Manager but is provided for convenience. Can raise a ConnectionEnd exception if the client fails to complete the handshake correctly.
  • return_data()
    An alternative to using Data’s get method.

Class Errors

  • ConnectionEnd(Exception)
    An exception raised whenever the end method is called by the client. This error can be raised by the Client get_data and heartbeat methods, as well as by the user explicitly calling the end method.
  • InvalidInfo(Exception)
    An exception raised by the Client’s get_data method whenever the client provides data in an invalid format which cannot be read by the server.

Data Class Documentation

This section details what members and member methods the Data class has.

class iot_manager.Data(**params, **kwargs)

Parameters

  • client_uuid(string)
    The UUID of the Client used for identifying a given device or sub process on a device. Devices can have many Client connections open with the server at once as long as each has its own UUID. Must be 32 bytes long or the server will reject the Client, and if another Client is already connected to the server with the same UUID the old Client’s connection will be terminated and replaced by the new Client.
  • client_type(string)
    The type of device the Client is. Must match one of the types provided when the Manager was created or the Client will be rejected from connecting to the Manager.
  • client_data(dict)
    Custom data send by the Client which is converted to a dict when received by the Manager.

Class Members

Class Methods

  • get()
    Returns the Data object as a dict so that it can be passed to other applications such as a web server.

Manager Events

This section explains how to make make use of events provided by the Manager, this including how to define your own events as well as examples showing you how they can work.

Event Explanation Events are a way of handling connections and messages from clients to the server. For example on_connect handlers will trigger when a client connects so the user can do additional connection management when the device connects to the manager. Another example is the on_message handler which allows the user to handle messages from a client to the server.

Global Events
Below is a list of global events that the Manager provides, these will trigger when any client meets the condition.
  • on_connect(client[Client])
    This event will trigger and run the code it contains when a new client connects to the Manager. It is provided
    the Client object of the client which has just connected.
  • on_message(client[Client], message[bytearray])
    This event will trigger and run the code it contains when a client sends a message to the Manager. It is provided
    the Client object of the client who sent the message as well as the message in bytearray form.
Type Specific Events
Below is an explanation on how to define device specific event handlers. Type Specific Events trigger under the same
conditions as Global Events, but only when the type of device specified is the one triggering the event.
For example the type specific event on_message_test will trigger only when a client with type “test” sends
a message to the server.
  • on_connect_[type](client[Client])
    This event will trigger and run the code it contains when a new client connects to the Manager. It is provided
    the Client object of the client which has just connected. Triggers only when client of matching type is the
    event triggered.
  • on_message_[type](client[Client], message[bytearray])
    This event will trigger and run the code it contains when a client sends a message to the Manager. It is provided
    the Client object of the client who sent the message as well as the message in bytearray form. Triggers only
    when client of matching type is the event triggered.

Manager Connection Protocol

This section details how to write a client that is able to connect to the IoTManager properly. As well giving as a basic client written in python which can connect to a IoT Manager.

The following steps occur when a client connects to the IoTManager an

  1. The Manager sends the client the getinfo command as the leading byte 0B00000001 or x01.

  2. The client must respond to the manager with a string containing the following information separated by a “##” delimiter.

    • UUID - A 36 char UUID string used to identify a client. Must be unique as clients that have conflicting UUIDs will cause one another to disconnect.

    • Type - The type of the device the client is, must match a value from the valid_types list entered when the Manager was created, if it does not match one of those values the client will be rejected.

    • Device Data - Extra data that can be supplied by the device, will be formatted into a dictionary when being read by the Manager. Below is an example of how this data should be formatted when sent from the client:

      data = '{ "key_1": "data_1", "key_2": "data_2" }'
      

      The example above would be converted to the following dictionary when read by the IoT Manager:

      data = {
         "key_1": "data_1",
         "key_2": "data_2"
      }
      

    This above information must be formatted into a single line with each piece of information separated by a :. The following string is an example of a valid response to the getinfo command.

    response = '1862cfe1-5dab-4d8e-a946-50d6728f830f##light##{ "key_1": "data_1", "key_2": "data_2" }'
    
  3. The client will now be accepted by the Manager but an additional step must be taken to ensure a continued connection.

  4. The Manager will send out a heartbeat request signified by the command heart as the byte 0B00000010 or x02), the client must respond to this command by sending back the matching beat command (as a string). If the client fails to respond to the manager within 15 seconds the client’s connection to the manager will be terminated.

iot-manager Examples

Example Client Code in Python

The following code will connect successfully to a running Manager, it does nothing but connect and print out every command received from the server.

import socket
import uuid

# the IP of the machine running the IoTManager
# ("localhost" if both the server can client are running on the same machine)
host = "localhost"

# the port the IoTManager is using to listen for devices
port = 8595

# device info (used for handshaking with the server, make sure the type is valid)
info = {
    "uuid": uuid.uuid1(),
    "type": "test",
    "data": '{ "name": "Test Client" }'
}

# formatted info so it can be sent to the IoTManager
formatted_info = (info["uuid"] + "##" + info["type"] + "##" + info["data"])

# the socket object that will be used to connect and communicate with the server
connection_socket = socket.socket()

# connect the to the IoTManager
connection_socket.connect((host, port))
print("Connected to IoTManager! Starting main loop...")

# main client loop
while True:
    # wait to receive a command from the server
    command = connection_socket.recv(4096).decode()

    # some debug output so we can see what command was received
    if command != "":
        print("Command Received: " + command)

    # respond to the 'getinfo' command with this clients info
    if command == "\x01":
        print("Sending Device Data: '" + formatted_info + "'")
        connection_socket.sendall(formatted_info.encode())
    # respond to the 'heart' command with the 'beat' response
    elif command == "\x02":
        connection_socket.sendall("beat".encode())
    # respond to the hello client command
    elif command == "hello client":
        connection_socket.sendall("hello server".encode())

    # go back to waiting for another command from the server

Manager Example Code

The following is an example showing how to instantiate the Manager as well as send commands using both events and the send function. The example uses a flask web server as the interface with the Manager and shows how the Manager can be used to create a basic IoT Web App. Below is both the flask server and required html file.

from flask import Flask, render_template, request
import logging
import iotmanager

# init a basic logging config
logging.basicConfig()

# list of valid device types
types = ["test"]

# create the manager (faster heartbeat rate and debug level verbose for demonstration purposes)
device_manager = iotmanager.Manager(types, heartbeat_rate=30, logging_level=logging.DEBUG)

# flask app
app = Flask("iot-manager example")

# web page to test device communication
@app.route('/', methods=['GET', 'POST'])
def hello():
    # get the client data so we can serve the page
    client_data = device_manager.get_client_data()

    # command form submission handling
    if request.method == 'POST' and "command" in request.form:
        # send the command
        device_manager.send_string(request.form["uuid"], request.form["command"])

    # command_all form submission handling
    if request.method == 'POST' and "command_all" in request.form:
        # send the command to all devices
        device_manager.send_all_string(request.form["command_all"])

    # html template rendering
    return render_template("index.html", data=client_data)

# handler for messaging test client on connection
@device_manager.event
def on_connect_test(client):
    client.send_string("hello client")


# handler for client sending messages to the server
@device_manager.event
def on_message_test(client, message):
    print("Message Received from Client: ")
    print(message)


# run the flaks app
if __name__ == '__main__':
    app.run()
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>IoT Light Manager</title>
    </head>
    <body>
        <h1>Connected Devices:</h1>
        <hr color="black">
        <ul>
            {% for client in data %}
                <li><b>{{ client.data.name }}</b>
                    <ul>
                        <li>
                            <form method="post">
                                <input type="text" name="command" value="" />
                                <input type="hidden" name="uuid" value="{{ client.uuid }}" />
                                <input type="submit" value="Send Command" />
                            </form>
                        </li>
                    </ul>
                </li>
            {% endfor %}
        </ul>
        <hr color="black">
        <h3>Communicate to Every Client</h3>
        <ul>
            <li>
                <form method="post">
                    <input type="text" name="command_all" value="" />
                    <input type="submit" value="Send Custom Command to All Devices" />
                </form>
            </li>
        </ul>
    </body>
</html>