BobaosKit - accessories

About

Inspired by Apple HomeKit I was searching for android solution but couldn’t find appropriate. To be able to work in LAN without cloud I started own project.

So, bobaoskit is a simple implementation of accessory system without any complication. So, there is no such things like pairing, authorization, crypto, binary protocol implemented. It works with simple Websocket protocol to implement network communications. Only five websocket requests are implemented and four broadcasted events.

Accessories only have id, name, type, control, status fields.

Following example shows how switch accessory can be implemented. It has only state both control and status field that reflects switch relay state.

const Bobaos = require("bobaos.sub");
const BobaosKit = require("bobaoskit.accessory");

// init bobaos and bobaoskit sdk
const bobaos = Bobaos();
const sdk = BobaosKit.Sdk();

const switchDatapointControl = 3;
const switchDatapointStatus = 7;

const switch1 = BobaosKit.Accessory({
  id: "switch1",
  name: "Simple Switch",
  type: "switch",
  control: ["state"],
  status: ["state"],
  sdk: sdk
});

switch1.on("ready", async _ => {
  let value = (await bobaos.getValue(switchDatapointStatus)).value;
  return switch1.updateStatusValue({field: "state", value: value});
});

const processOneControlAccValue = payload => {
  let {field, value} = payload;
  // send datapoint value to KNX bus
  return bobaos.setValue({id: switchDatapointControl, value: value});
};

// on incoming event from bobaoskit.worker
// e.g. control request sent from mobile app
switch1.on("control accessory value", async (payload, cb) => {
  if (Array.isArray(payload)) {
    await Promise.all(payload.map(processOneControlAccValue));
    return;
  }

  await processOneControlAccValue(payload);
});

const processOneDatapointValue = payload => {
  let {id, value} = payload;
  if (id === switchDatapointStatus) {
    return switch1.updateStatusValue({field: "state", value: value});
  }
};

// register listener for incoming value from KNX bus
// after switch command state of relay was changed
bobaos.on("datapoint value", payload => {
  if (Array.isArray(payload)) {
    return payload.forEach(processOneDatapointValue);
  }

  processOneDatapointValue(payload);
});

// on exit, unregister accessory
const inTheEnd = async _ => {
  console.log("removing accessory");
  await switch1.unregisterAccessory();
  process.exit();
};
process.on('SIGINT', inTheEnd);
process.on('SIGTERM', inTheEnd);

How it works

Accessory backend service

First of all, there should be running instance of bobaoskit.worker on pc.

bobaoskit.worker script uses redis and bee-queue job manager to process incoming requests. There is possible requests as clear/add/remove accessory, get accessory info, get/update status value, control accessory value. Events are broadcasted over Redis Pub/Sub channel defined in config.json.

bobaoskit.worker listens port defined in config.json for WebSocket connections. Websocket API exposes request methods like get accessory info, get status/control accessory value

bobaoskit.worker uses dnssd module to advertise itself in local network. Advertised service name is defined in config.json.

Accessory SDK

bobaoskit.accessory nodejs module is used to register and manage accessories. So, use it to create accessory instance, receive control commands, control your devices and update accessory state.

Take a look at following code

const BobaosKit = require("bobaoskit.accessory");

// params:
//   redis: "redis_url"/client object
//   job_channel: "bobakit_job"
//   broadcast_channel: "bobaoskit_bcast"
// by default redis is undefined
// channels are from comments above,
// same as default in config.json
const sdk = BobaosKit.Sdk();


// sdk param is not required but 
// if you want to add multiple accessories
// it is better to create one sdk instance 
// and use it for all accessories
const accessory = BobaosKit.Accessory({
  id: "accessory",
  name: "Simple Switch",
  type: "switch",
  control: ["state"],
  status: ["state"],
  sdk: sdk
});

Now, sdk has following methods that is used by accessory instance:

  • ping()
  • getGeneralInfo()
  • clearAccessories()
  • addAccessory(payload)
  • removeAccessory(payload)
  • getAccessoryInfo(payload)
  • getStatusValue(payload)
  • updateStatusValue(payload)
  • controlAccessoryValue(payload)

Also, sdk emits broadcasted event with (method, payload) as a params.

BobaosKit.Accessory(..) creates object that represents accessory. It uses sdk to communicate via bee-queue with bobaoskit worker and creates own bee-queue instance to accept incoming requests. Exposed methods:

  • getAccessoryInfo()
  • getStatusValue(payload)
  • updateStatusValue(payload)
  • unregisterAccessory()

Accessory instance emits event control accessory value event with (method, payload) params when request to control accessory value is received. So, listen for this event and process it.

Client

Client application makes a dnssd discovery and after resolving host creates WebSocket connection to given port. Then get accessory info with null payload is sent and response contains list of all accessories.

To control accessory field/fields client sends control accessory value request with payload {id: accId, control: {field: field1, value: value}/[{field: .. value: ..}]}/[{id: ...}, ...].

Installation

Currently, no npm package is published.

So, clone git repositories:

git clone https://github.com/bobaoskit/bobaoskit.worker
git clone https://github.com/bobaoskit/bobaoskit.accessory

Install dependencies for worker and run

cd bobaoskit.worker
npm install
./bin/bobaos-kit.js

Install dependencies for bobaoskit.accessory

cd bobaoskit.accessory
npm install

Now, take a look at bobaoskit.accessory/examples/ folder.

You may be able to run radio player accessory scripted in mpvsw.js. Make sure you have mpv and latest version of youtube-dl installed.

Install dependencies for mpv client

npm install mympvspawn
npm install mympvclient

Now, start it with node

node ./examples/mpvsw.js

Mobile application

For mobile application flutter is used. It uses mdns plugin for dns service discovery, currently this plugin is not published, so there is git dependency in pubspec.yaml.

Install flutter(I use v1.0.0), dart sdk(2.1.0), all sdk(Android/XCode) to compile app.

Clone repository

git clone https://github.com/shabunin/bobaflu

Install packages

flutter packages get

To run, debug and build use your favourite editor/ide, I prefer IntelliJ IDEA.

I run it only on Android device, so, it will be great if somebody takes responsibility to compile it for iOS.

Note: Android emulator won’t discover any service due to isolated emulator network. Still, you are able to make direct connection.