BrainClient


BrainClient

Event-based async client for Kramer Control Brains.

Related documentation resources:

Constructor

new BrainClient(opts)

Create a new BrainClient. Note this does not initate connection to the brain. Use BrainClient#connectToBrain to connect (await the promise, of course).

For more customization of the setup, you can use BrainClient#prepareConnection in order to bypass the setup in BrainClient#setupConnection and do your own setup.

Example

const bc1 = new BrainClient(); // default opts
const bc2 = new BrainClient({
	reconnectWaitTime: 500 // change reconnect wait time to 500ms
});
Parameters
opts:object

Options for configuring the client, all optional, documented below

opts.remoteAuthorization:string

Data structure from nebula-web-ui for Remote Control via cloud

opts.reconnectWaitTime:number

Time to wait to try to reconnect a socket on disconnect or error in milliseconds (default to 1000 milliseconds)

opts.requestTimeout:number

Timeout value for requests internally to the Brain. If the Brain takes longer than this parameter, the request will fail with a timeout. Defaults to 1000 milliseconds.

opts.disableAnalytics:boolean

Set to true to disable analytics collection via Google Analytics (defaults to false)

Properties
BrainClient.DEFAULT_BRAIN_PORT:number

Default port for Brain (8000)

Classes

ErrorClientNotInitalized
ErrorExpressModeDisabled
ErrorNotProvisioned
ReactHooks

Methods

(static) getBrainClient(ipAddress, opts)

Static method to retrieve/create a BrainClient instance. The ipAddress is used as the cache key - if no client exists for that IP, then a new one will be created and initalized. However, if an existing client is found, that client will be returned as-is.

The client is initalized using the BrainClient#connectToBrain method.

Note that this static method is intentionally designed to NOT be async - in otherwords, it is guaranteed to return a BrainClient instance right away. The BrainClient#connectToBrain method is NOT called right away (on a new instance) - it is delayed until the next clock tick. This is done so that callers can attach event listeners, for example, to cach communication failures on connection.

Example

const bc1 = BrainClient.getBrainClient('127.0.0.1:8000');
const bc2 = BrainClient.getBrainClient('127.0.0.1:8000');
console.log("Caching worked? ", (bc1 === bc2) ? true : false);

Auto mode

If you pass an object like { auto: true } as the ipAddress, BrainClient will check the window query string for brainIp=<whatever>, and if not found, will use the origin host/port as brain IP.

If you pass an object like { auto: true, default: "some.host:8000" }, BrainClient will still check the query string, but it will fallback to "some.host:8000" instead of the origin.

If you pass an object like { param: "someParam", default: "some.host:8000" }, BrainClient will check for someParam=<whatever> instead of "brainIp", and fallback to the value given in "default" if not found.

NOTE: Auto mode will not work server-side (e.g. from Node, etc) - it is designed specifically for use client-side. If you try to use { auto: true } or { param: ... } on Node, your program will likely fail with an exception.

In all cases, default is optional. If "auto" or "param" given and "default" not specified, it will fallback to the window origin (e.g. the host/port that the page using BrainClient was served from.) This "origin" mode is only really useful if you are taking advantage of the "bundle upload" mode in the Kramer UI of the SL brains to serve your custom javascript from the Brain's built-in webserver at the /bundle/ URL.

Parameters
ipAddress:string|object

IP and optional port to connect to. Example: 10.0.1.123:8000. Note: If you pass an object like { auto: true, default: 'something' }, BrainClient will check the window querystring for 'brainIp=', and if not found, will use the default param or origin host/port as brain IP if no default param given. You can also specify param: "someOtherParam" instead of auto: true to use a different query string param other than "brainIp".

ipAddress.auto:boolean (optional)

If true, BrainClient will check the window query string for brainIp=<whatever>, and if not found, will use the origin host/port as brain IP. Not supported server-side, only useful when using BrainClient in a browser.

ipAddress.param:string (optional)

BrainClient will use the param value to check the query string for an IP address (this will set .auto to true). Not supported server-side, only useful when using BrainClient in a browser.

ipAddress.default:string (optional)

If you use .auto or .param to turn on auto mode, and no IP is found in the query string, then BrainCient will fallback to .default. If .default is not given and .auto or .param is given, then BrainClient will instead fallback to the origin host/port serving the page. Not supported server-side, only useful when using BrainClient in a browser.

opts:object

Options to pass to the BrainClient constructor. See the constructor for all options honored. However, one option the constructor doesn't consume is the pin option, below.

opts.pin:string|function

PIN string or callback function to get PIN. Callback will only be executed if the Brain indicates a PIN is required. See BrainClient#setupConnection for more notes on the callback.

opts.auth:string

String containing a JWT token to authenticate with the brain instead of using a PIN code.

asObservable():Observable

Get an RxJS Observable object. This method is provided to simplify integration of BrainClient into Angluar applications.

This observable is created using a Subject object, allowing the same event from this client to be multicast to any observers attached.

This observable will contain every event emitted by this client. For example, using event listeners, you might do:

bc.on(BrainClient.EVENTS.CONNECTION_STATUS_CHANGED, ({status}) => console.log("Status:", status));
bc.on(BrainClient.PIN_REQUIRED, () => bc.submitPin(prompt("PIN?")));

Using the Observable returned from this method, you can instead do:

bc.asObservable().subscribe(({ event, ...data }) => {
	switch(event) {
		case BrainClient.EVENTS.CONNECTION_STATUS_CHANGED:
			console.log("Status:", data.status);
			break;
		case BrainClient.EVENTS.PIN_REQUIRED:
			bc.submitPin(prompt("PIN?"));
			break;
		default:
			break;
	}
})

The shape of the message emitted by the Observable from this method looks like:

const message = {
	event: "",
	...data
}

The event field above is always and only one of the values from BrainClient.EVENTS. The data spread operator above indicates that all other fields from the event (e.g. the args that would be passed to your event callback if you used the on method to attach callbacks) are spread into the message, so you receive a single flat-ish object.

That means that the message you would receive from the observable for the CONNECTION_STATUS_CHANGED event would look like:

const message = {
	event:  BrainClient.EVENTS.CONNECTION_STATUS_CHANGED,
	status: BrainClient.CONNECTION_ACTIVE // for example...
}

It is useful to note that you can listen for BrainClient.EVENTS.WS_MESSAGE to receive ALL events (WebSocket messages) from the brain, unfiltered and unprocessed by the BrainClient.

You can also listen for just BrainClient.EVENTS.BRAIN_EVENT to listen only for events from the brain that are not handled internally by logic inside the BrainClient.

Returns
Type
:Observable

RxJS Observable object - see RxJS Observable docs for API docs.

(async) brainId():string|null

Retrieve the ID of the connected brain. Promise returned will not resolve until brain is connected in some fashion.

Returns
Type
:string|null

Promise of a brainId or null if BrainClient#connectToBrain or BrainClient#prepareConnection not called yet.

(async) brainInfo():BrainInfo

Get the brain information (the "general" API route)

Throws
Type
:Error

Throws an error if there is a problem talking to the brain

Returns
Type
:BrainInfo

A BrainInfo object describing the currently connected brain

(async) connectToBrain(ipAddress, pin, auth)

Connect the client to the brain. await this function to ensure the connection is setup before calling other methods. See BrainClient#setupConnection for further notes on connection setup if this method is too opaque for your needs.

Example usage:

const bc = new BrainClient();
await bc.connectToBrain("127.0.0.1");
Parameters
ipAddress:string

IP address of Brain to connect to, with optional port, like "127.0.0.1:8000" - port defaults to 8000 if not specified

pin:string|function

PIN string or callback function to get PIN. Callback will only be executed if the Brain indicates a PIN is required. See BrainClient#setupConnection for more notes on the callback.

auth:string

Optional, JWT token to use to auth with brain instead of using the PIN

Throws
Type
:Error

May throw errors from BrainClient#setupConnection - see that method for Errors that could be thrown.

disconnect()

Disconnect the WebSocket, if connected, closing all communication with the brain.

getConnectionStatus():string

Get the current connection status of the Brain Client. For possible states, please see BrainClient.CONNECTION. When the connection status of the client changes, the event BrainClient.EVENTS.CONNECTION_STATUS_CHANGED will be emitted by the client as well.

Returns
Type
:string

One of the possible states enumerated in BrainClient.CONNECTION

(async) getDevice(deviceNameOrId):BrainDevice|null

Retrieve a BrainDevice based on name or ID of the device

Parameters
deviceNameOrId:sring

Name or ID of the device to retrieve

Returns
Type
:BrainDevice|null

Returns BrainDevice instance if deviceNameOrId found, or null if no matching device found.

(async) getDevices():object|null

Return a hash of deviceId => BrainDevice instances for this Brain's devices, allowing you to retrieve/watch states and execute commands.

Returns
Type
:object|null

Returns hash with keys being deviceIds and values being BrainDevice instances, or null if BrainClient#connectToBrain or BrainClient#prepareConnection not called yet.

getHandsetLayout(handsetId)

Get the layout for a specific handset from the brain and notify the Brain of the selected handset.

NOTE: Response to this query is returned as a separate event via the WebSocket

Parameters
handsetId:string

ID of the handset to requeset

(async) getSystemDevice():BrainDevice|null

Find and return the BrainDevice instance for this Brain's System Device, which will provide states and commands for things like time, weather, and custom states created in the KC Builder.

Returns
Type
:BrainDevice|null

BrainDevice instance for the System device on the Brain, or null if not found or null if BrainClient#connectToBrain or BrainClient#prepareConnection not called yet.

(async) isAuthorized():boolean

Returns a flag indicating if the client has cleanly authorized with the Brain in order to communicate with this space.

Returns
Type
:boolean

Promised true/false indicating if the client has completed authorization or null if BrainClient#connectToBrain or BrainClient#prepareConnection not called yet.

(async) isExpressModeEnabled():boolean

Returns a flag indicating if express mode has been enabled in the Manager for the space provisioned to this Brain.

NOTE: Without Express Mode enabled, the client will not be able to control this space via the WebSocket. However, some basic REST APIs will still work to retrieve information about the space.

Returns
Type
:boolean

Promised true/false indicating if express mode has been enabled or null if BrainClient#connectToBrain or BrainClient#prepareConnection not called yet.

(async) isLoginNeeded():boolean

Returns a flag indicating if authentication is required for this space.

Use BrainClient#submitPin to send PIN to the Brain, then await BrainClient#isAuthorized to be notified when BrainClient ready for use. You can also listen for BrainClient.EVENTS.AUTHORIZED instead of awaiting isAuthorized.

NOTE: You can also be notified of the need for a PIN by listening for the BrainClient.EVENTS.PIN_REQUIRED event. See BrainClient.EVENTS for discussion of that event.

Returns
Type
:boolean

Promised true/false indicating if the Brain requires a PIN or null if BrainClient#connectToBrain or BrainClient#prepareConnection not called yet.

(async) isProvisioned():boolean

Returns a flag indicating if any space has been provisioned to the brain. Without a space provisioned to the brain, there is nothing to control.

Returns
Type
:boolean

Promised true/false indicating if the Brain has been provisioned or null if BrainClient#connectToBrain or BrainClient#prepareConnection not called yet.

off(event, callback)

Remove an event attached with BrainClient#on. Inherited from the core node module EventEmitter - see that class for more complete on/off documentation. Included here for documentation purposes and to link to BrainClient.EVENTS for event names.

Parameters
event:string

Name of the event to disconnect from. See BrainClient.EVENTS for defined event names availale on BrainClient

callback:function

previously-attached callback

on(event, callback)

Attach listeners to events. Inherited from the core node module EventEmitter - see that class for more complete on/off documentation. Included here for documentation purposes and to link to BrainClient.EVENTS for event names.

Remove event listeners with BrainClient#off

Parameters
event:string

Name of the event to listen to. See BrainClient.EVENTS for defined event names availale on BrainClient

callback:function

Your callback to call when the event is triggered

(async) prepareConnection(ipAddress):Promise

Prepare the BrainClient to connect to a brain by setting up REST client and WebSocket connections.

You usually will not need to call this. Instead, we recommend awaiting BrainClient#connectToBrain instead. However, if you wish to reimplement BrainClient#setupConnection (which connectToBrain uses internally to bootstrap the client), then you should NOT call connectToBrain. Instead, create your client and call prepareConnection then implement whatever connection steps you want to use.

NOTE: You can/should await this method if you call it to ensure the Brain connection works. This method WILL throw an Error of undetermined type if there is a problem connecting with the Brain.

Example usage:

const bc = new BrainClient();
await bc.prepareConnection("10.0.1.138:8000");
if(await bc.isLoginNeeded()) {
	const pin = prompt("Please enter your PIN to connect to the Brain")
	bc.submitPin(pin);
}
await bc.isAuthorized();
const sys = bc.getSystemDevice();
// ...
Parameters
ipAddress:string

IP with optional port specified

Throws
Type
:Error

Error if trouble connecting to Brain specified

Returns
Type
:Promise

Promise that will resolve once the WebSocket is connected

queryExpressModeEnabled()

Query to see if express mode is enabled.

For more a user-friendly interface to this information, see BrainClient#isExpressModeEnabled.

NOTE: Response to this query is returned as a separate event via the WebSocket

queryHandsets()

Query handsets from the brain

NOTE: Response to this query is returned as a separate event via the WebSocket

queryProvisionedInfo()

Request the brain's provisioning status.

For more a user-friendly interface to this information, see BrainClient#isProvisioned.

NOTE: Response to this query is returned as a separate event via the WebSocket

queryStatus()

Request the brain's gateway status.

For more a user-friendly interface to similar information, see BrainClient#brainInfo and BrainInfo.

NOTE: Response to this query is returned as a separate event via the WebSocket

sendAction()

Send a UI action from the handset layout to the Brain. This function deals with prebuilt layouts from the Builder.

NOTE: To send actions/commands from specific devices, see BrainDevice.

sendData(data)

Send the string or object over the WebSocket to the brain as-is, no checking/changes.

Parameters
data:string|Object

String or JSON object to send to the Brain

sendRemoteAuthorization(auth)

Send a prebuilt authorization structure to the Brain

Parameters
auth:object

(async) setupConnection(pin):BrainInfo

This method promises to return only when the client is completely setup and free of any obvious error states, such is not being provisioned or not having express mode enabled.

It's important to note that consumers of BrainClient can implement all the checks/steps of this setup routine themselves - for example, to prompt for a PIN code interactively.

However, note that you CAN supply a function as the pin argument, which will be used as a callback if the brain requires a non-blank PIN. This function will be awaited. That means you could use still this method in a UI and simply provide a callback arg to pop up a dialog, for example.

A creative user of BrainClient could decide to not use setupConnection at all, and instead implement each of the steps that setupConnection does in their own code.

setupConnection does not use any private data, it just reuses accessors from BrainClient and packages them in the form of an easy-to-use and simple awaitable method. setupConnection will work for the majority of users that use BrainClient as-is.

However, if you find yourself needing more control over the init process of the connection, just call BrainClient#prepareConnection after constructing your BrainClient instance and then setup the connection however you would like.

Parameters
pin:string|function

Express mode PIN code, only needed if not left blank in KC Manager. NOTE: an async function can be supplied for the pin instead of a string, which will only be called if the pin is actually required for connection.

Throws
Type
:Error
Returns
Type
:BrainInfo

An object describing the current brain. See BrainInfo for fields.

submitAuthorization(authorization)

Send the Authorization token to the brain

Once you submit the token, you can await BrainClient#isAuthorized or listen for BrainClient.EVENTS.AUTHORIZED to be notified when the authorization succeeeds.

If the token submitted fails, the BrainClient.EVENTS.PIN_REQUIRED event will be emitted again, but the connection status will not change.

NOTE: Response returned as a separate event via the WebSocket

Parameters
authorization:string

submitPin(pin)

Send the PIN to the brain

Once you submit the pin, you can await BrainClient#isAuthorized or listen for BrainClient.EVENTS.AUTHORIZED to be notified when the authorization succeeeds.

If the pin submitted fails, the BrainClient.EVENTS.PIN_REQUIRED event will be emitted again, but the connection status will not change.

NOTE: Response returned as a separate event via the WebSocket

Parameters
pin:string

watchStates(device_id, states, unwatch)

Inform the Brain that this client wants to receive notifications of changes to a device's states via the WebSocket.

NOTE: This function is not designed for direct user access, although you can call if it desired, no ill side effects.

However, a more user-friendly way of watching for state changes would be to get the BrainDevice instance you want to work with and calling BrainDevice#on, for example:

device.on(BrainDevice.STATE_CHANGED, myCallback)

When you attach a listener to the STATE_CHANGED event, BrainDevice#on will automatically notify the Brain to send state changes using this method.

Parameters
device_id:string

Required, device ID of the device on the Brain to watch

states:Array

Optional array of states, currently unused in the latest Brain software versions

unwatch:boolean = false

[unwatch=false] Defaults to false. If true, client will tell the brain to unsubscribe this client from state changes for the given device.

Type Definitions

CONNECTION

The BrainClient.CONNECTION_* properties define the various connection states that the BrainClient can be in. To get the current connection state, see BrainClient#getConnectionStatus. You cannot directly set the Brain Client's connectionStatus.

Example usage:

const bc = new BrainClient();
const state = bc.getConnectionStatus();
Properties
BrainClient.CONNECTION_CONNECTING
  1. Client starts in this state and goes to this state when it's reconnecting
BrainClient.CONNECTION_FAILURE
  1. Client encountered a failure in communication, either on initial connection or lost connection to the Brain
BrainClient.CONNECTION_DISCONNECTED
  1. Client has become disconnected from the Brain, either via BrainClient#disconnect or due to network issues
BrainClient.CONNECTION_RECONNECTING
  1. Client has become disconnected and now is waiting to try to reconnect. The time the client waits to reconnect can be set by passing the reconnectWaitTime option to the BrainClient's constructor
BrainClient.CONNECTION_AUTHORIZING
  1. Client has connected to the Brain and is attempting to authorize the connection. NOTE: The client will attempt to authorize using an empty passcode first, and that may succeeed. If that succeeds, the client will go to BrainClient.CONNECTION_ACTIVE. However, if an empty passcode does not succeed, the client will transition to CONNECTION_UNAUTHORIZED and the client will emit the event BrainClient.EVENTS.PIN_REQUIRED. In that case, you will need to supply the passcode to the client to use using BrainClient#submitPin. Once it succeeds, the client will transition to CONNECTION_ACTIVE. If the pin submitted fails, the BrainClient.EVENTS.PIN_REQUIRED event will be emitted again, but the connection status will not change.
BrainClient.CONNECTION_UNAUTHORIZED
  1. If the client's default empty passcode attempt fails or if the PIN you submit via submitPin() fails, the client will transition to CONNECTION_UNAUTHORIZED until something else happens.
BrainClient.CONNECTION_ACTIVE
  1. Once the client is online, authorized, and ready to use, it will transition to this state.
BrainClient.CONNECTION_SYNCHRONIZING
  1. When you "Publish" a space via the KC Builder or make changes via the KC Manager, or the Brain synchronizes for some other reason, the client will enter this CONNECTION_SYNCHRONIZING state

EVENTS

The BrainClient.EVENTS object holds keys that function as the event names for various events emitted by the BrainClient.

It's important to note that BrainDevice also emits it's own events when states change. See: BrainDevice and STATE_CHANGED property.

Use these properties below in conjunction with BrainClient#on to attach listeners.

Example usage:

const bc = new BrainClient();
bc.on(BrainClient.EVENTS.STATUS_MESSAGE, data => console.log(data));
Properties
BrainClient.EVENTS.WS_CONNECTED:string

Emitted when the Brain's WebSocket connects

BrainClient.EVENTS.WS_CLOSED:string

Emitted when the Brain's WebSocket is closed

BrainClient.EVENTS.RECONNECTING:string

Emitted when reconnecting timer is started, indicating the client will soon attempt to automatically reconnect

BrainClient.EVENTS.PIN_REQUIRED:string

Emitted when the BrainClient is informed by the Brain that a PIN is required. The BrainClient will have already tried the default "empty" PIN ("") when this event is emitted. Use BrainClient#submitPin to submit the PIN to the Brain. You can then await BrainClient#isAuthorized (or listen for the AUTHORIZED event). An alternative to listening for the PIN_REQUIRED event is to await BrainClient#isLoginNeeded and then call BrainClient#submitPin if isLoginNeeded resolves to true.

BrainClient.EVENTS.EXPRESS_MODE:string

Emitted when the client receives the response from the Brain indicating if express mode is enabled or not. The payload provided an object with a single boolean key, enabled. A value of true for enabled indicates that express mode IS enabled, and false, of course, indicating that express mode is disabled on the provisioned space.

BrainClient.EVENTS.AUTHORIZED:string

Emitted when BrainClient is completely authorized and ready to be used.

BrainClient.EVENTS.CONNECTION_STATUS_CHANGED:string

Emitted when the Brain's connection status changed. The current connection state will be included in the event payload as an object with a single key status, like: { status: "" }. The current connection status can retrieved from the client via BrainClient#getConnectionStatus. See BrainClient.CONNECTION for documentation on the connection states possible - these are the values that will be used for the status field of the event payload mentioned previously.

BrainClient.EVENTS.HANDSET_MESSAGE:string

Emitted when any of the handset_* events are received from the Brain, for example, in response to BrainClient#queryHandsets or BrainClient#getHandsetLayout.

BrainClient.EVENTS.BRAIN_EVENT:string

Emitted when an event is received by the brain that is NOT handled internally by the BrainClient. This is ONLY emitted for events NOT covered elsewhere in this list.

BrainClient.EVENTS.WS_MESSAGE:string

Emitted when a new WebSocket message is received from the brain with the contents of the message as the payload. Note that this event is emitted for EVERY WebSocket message containing the raw message, making this a good generic event if you want to handle ALL interaction with the brain directly.

BrainClient.EVENTS.STATUS_MESSAGE:string

Emitted when a new status message is received from the Brain

BrainClient.EVENTS.COLOR_MESSAGE:string

Emitted when a Brain color message is received