websocket.lua - kirayatail/computercraft GitHub Wiki

Websocket

Websocket is a small framework for enabling real-time communication between your application(s) and a web server. It can be used for P2P procedure calling (like RedNet without cables, modems or range limitations), building web-based control panels or whatever you like, depending on how the server is designed and what other applications register to it. Your application can use Websocket as a replacement or complement to a local UI or key listener.

Core concept

The main idea is that a computer runs a program with an indefinite running loop, and regularly announces data about the current state and available methods, and listens for commands corresponding to those methods. The server (not part of this program) acts like an exchange and passes along messages to/from other nodes. The application gets a set of interface functions which allows it to register Methods and perform connection-related actions. Once Websocket receives a command message for a valid Method, the function it links to inside the application will be called - with appropriate parameters. If you just want to send information out from your application - there's a convenience function for that.

Features at a glance

  • Configuration file stores server address, first use triggers a dialog for that
  • You can emit Methods or Info individually, Websocket retains the latest of each and sends both
  • Methods contain both actions and state for each action, your function may act on function trigger, or state change. If action state is changed internally, this can be emitted.
  • Only three function calls needed for minimal use

API

Generally, the API functions are accessed via require: ws = require('websocket'). This will start the Websocket framework which loads configuration from file (if available) or prompts user for details. The configuration file is named /var/websocket.conf. The following functions are available on the ws object:

connect

  • Parameters: applicationType: string
  • Return: void

Attempts a websocket connection and performs a handshake, registering applicationType as the type of the computer. Changing applicationType requires making a new connection. This function only triggers a connection attempt, whether the attempt succeeded or failed can be determined by polling ws.isConnected()

disconnect

  • Parameters: void
  • Return: void

Disconnects from the server, sets ID from handshake to nil

group

  • Parameters: string
  • Return: void

Sets the group property locally and sends the data to the server. Groups can be used to associate clients for batch processing, for example: Multi block battery systems can have multiple computers for monitoring, and those are ideally grouped as a big unit.

hidden

  • Parameters: boolean
  • Return: void

Sets the hidden property locally and sends the data to the server. The server may take this property into account for hiding clients that are not supposed to show up on controller dashboards, for example: Clients that are grouped and processed together may be hidden to avoid duplication. Server may choose to forward hidden clients - or not.

id

  • Parameters: void
  • Return: ID: integer | nil

ID is provided by the server on successful handshake, is an integer when connection is established, nil otherwise. ID is reset to nil on ws.disconnect() or if internal socket is reset by server

isConnected

  • Parameters: void
  • Return: boolean

True if internal socket object exists and ID is received, false otherwise

info

  • Parameters: infoList: Table<infoObject>
  • Return: void
  • infoObject: { key: string, value: any, type: string }

This method sends structured data to the server as a list. Data elements may be unrelated as they are accompanied by the individual key, which should label the data. The type parameter should generally relate to the actual type of the value (string, number, boolean...), but that is supposed to be dictated by the server's API. infoList will be converted to JSON before being sent, so it makes sense to relate it to a Javascript type.

Using this function will also send the list of Methods which is retained since the last time ws.methods() was called. Websocket will retain the infoList, overwriting any previously set data, for the corresponding functionality when ws.methods() is called.

methods

  • Parameters: methodList: Table<methodObject>
  • Return: void
  • methodObject: { key: string, type: string, fn: function(value?: V): V | nil, name?: string, value?: V, options?: Table<string>, min?: number, max?: number }

This method announces what methods are available on your application, and binds a key to a callback function fn. The method is for the time being allowed a single parameter value, just like most form elements (buttons, input fields, sliders, etc.) in HTML holds one current value each. Just like the info method, the type parameter is mainly for the benefit of the receiver, except for the case when type is 'void'. In that specific case fn is called without parameter, and is not expected to return any value.

fn is where you bind behavior in your application to the Method. Since it has the closure of your application but is called from Websocket, it behaves much like an event listener. For non-void types of methods, fn usually sets a value (some kind of state) in your application. fn is expected to return the new value of that state once the action is done, and at that point the updated state is automatically sent just as if ws.methods() was called again. If type is void or fn returns nil, no updated value will be sent. The common case (returning the value) is useful for most stateful actions, where there may be some delay until the action is done and the action only changes its own state. The alternative case (no return) is useful when one action changes the state of other actions, and you may update your application state to reflect that and perform a new ws.methods() to update the server instead of returning a single value.

key should be a unique and indexable string among the other keys in the methodList. The optional parameter name allows for a presentation label that may be different for the key. Whether name is used or prioritized before key is dependent on the server, but key is mandatory and is what is listened for when receiving command messages.

options is treated as a list of possible values when value is treated like an enumerable. Typical usage is when a method should be presented like a drop-down list or a set of radio buttons. In those cases, value can be expected to be an element of the options list. Warning Make sure that type is set to an appropriate value. All this depends on the server API.

min and max are similarly guiding parameters and optional. They typically relate to number types like a number input or a slider.

runtime

  • Parameters: void
  • Return: void

This is a special function which allows Websocket to run with its own event listener behind the scenes while your application is running. This function will not return as it's an infinite loop. You place it in the application like:

parallel.waitForAny(keyListener, programLoop, ws.runtime)

This indefinite loop is waiting for OS events and only concerns itself with websocket events.

Examples

Here are a few simple examples of how to use the API

Door opener

Program registers one single method that doesn't change apart from the value, which changes by the toggle. The 'toggle' type is server dependent, but should behave like a latching boolean, possibly rendered like a checkbox form element.

As the method fn returns the same value it received, the state change is acknowledged and sent to the server.

The final statement ws.runtime() will invoke an infinite loop that continuously listens for websocket events - the program will not return.

local ws = require('websocket')

ws.methods({
  {
    type = 'toggle',
    key = 'Open',
    value = false,
    fn = function(openDoor)
      redstone.setOutput('right', openDoor)
      return openDoor
    end
  }
})

ws.connect('Door')
ws.runtime()

User controlled websocket lifecycle

This program demonstrates how the user can control connecting/disconnecting the websocket features and even halt the program while using the websocket module. Pressing 'C' will toggle the websocket connection, pressing 'Q' will halt the program.

local ws = require('websocket')

function keyboard()
  local running = true
  while running do
    local evt, key = os.pullEvent('key')
    if key == keys.c then
      if ws.isConnected() then
        ws.disconnect()
      else
        ws.connect('computer')
      end
    end
    if key == keys.q then
      running = false
    end
  end
end

parallel.waitForAny(keyboard, ws.runtime)

Changelog

9, 10, 11, 12

Bugfixes

8

Support empty lists of 'info' and 'methods'

7

Bugfix

6

Add application properties 'hidden' and 'group'. Add support for automatically reconnecting.

4, 5

Bugfixes related to conf file

3

Move configuration file to var/websocket.conf, Add migration support for older versions - conf file will be moved automatically.

2

Methods have optional extra parameters "min" and "max", typically used in range type actions

1

Initial version

⚠️ **GitHub.com Fallback** ⚠️