hybridgroup / cylon

JavaScript framework for robotics, drones, and the Internet of Things (IoT)
https://cylonjs.com
Other
4.2k stars 361 forks source link

Adding devices on runtime. #357

Open usmantahirr opened 7 years ago

usmantahirr commented 7 years ago

I am using Cylon for my Smart Devices framework, I am using MQTT for this purpose. I've defined a driver for my AirConditionar let's say. I have an ESP8026 chip in each AC which may use the different driver, but let's stick to one for now and I am using MQTT for communication.

I've added MQTT device with a topic devices/events on which every device will send data. I am using this device inside ESP's custom driver.

Now I want to add more devices dynamically to this robot in my MQTT's onConnect event on broker side. How can I do that?

This is the way I am defining my driver

Cylon.robot({
  connections: {
    server: { adaptor: 'mqtt', host: 'mqtt://localhost:1883' }
  },

  devices: {
    deviceAdapter: { driver: 'mqtt', topic: 'device/event', adaptor: 'server' },
    ultron: { driver: 'orient-ultron', deviceId: '5CCF7F095FE9', topic: 'device/5CCF7F095FE9/command' }
  },

  work: function(my) {
    my.ultron.initDriver(null, () => {
      console.log('driver bootstraped');
    });

    my.deviceAdapter.on('message', function (data) {
      debug(data);
      my.ultron.setState(data);
      sendCommand();
    });

    my.ultron.on('state', function (data) {
      debug('inside state event', data.toString());
    });

    function sendCommand() {
      my.ultron.thermostatDrySetpoint(30);
    }
  }
}).start();

and this is the way I've defined my driver.

class Driver extends Cylon.Driver {
  constructor(opts, cb) {
    super(opts);

    this.setupCommands(Commands);
    this.deviceId = opts.deviceId || null;
    this.topic = opts.topic;
    this.commands = Commands;
    this.protobuf = {};

    this.state = {};

    this.initDriver(cb);

    debug(this.deviceId);
  }

  start(callback) {
    const events = ['state', 'measurement', 'alert', 'log', 'ack'];

    events.forEach(function(e) {
      this.defineDriverEvent(e);
    }.bind(this));

    if (this.robot.devices.deviceAdapter) {
      this.connection.robot.devices.deviceAdapter.on('message', function(data) {
        // TODO modify event based on type of data received
        debug('Got data inside driver throught adapter', data.toString());
        this.emit('state', data);
      }.bind(this));
    }

    callback()
  }
  halt(callback) { callback() }

  /**
   * Send standby command to OrientUltron
   * This capability is used to set standby mode of a device.
   * @param {String} value true | false
   */
  standby(value) {
    const action = this.protobuf.message.lookup('standby').name;
    this.sendToDevice(action, value || false);
  }

  /**
   * Send refresh command to OrientUltron
   * This capability is used to request all latest state values from a device.
   */
  refresh() {
    const action = this.protobuf.message.lookup('refresh').name;
    this.sendToDevice(action, true);
  }

  /**
   * This is the thermostat's setpoint value, when the thermostat mode is set to Auto. The value will be fixed on 24.
   */
  thermostatAutoSetpoint() {
    const action = this.protobuf.message.lookup('thermostatAutoSetpoint').name;
    this.sendToDevice(action, 24);
  }

  /**
   * This is the thermostat's setpoint value, when the thermostat mode is set to Cool. The default value will be 24. The values ranges from 16 to 32.
   * @param {Number} value  16-32
   */
  thermostatCoolingSetpoint(value) {
    const action = this.protobuf.message.lookup('thermostatCoolingSetpoint').name;
    this.sendToDevice(action, value || 24);
  }

  /**
   * This is the thermostat's setpoint value, when the thermostat mode is set to Dry. The default value will be 24. The values ranges from 16 to 32.
   * @param {Number} value  16-32
   */
  thermostatDrySetpoint(value) {
    const action = this.protobuf.message.lookup('thermostatDrySetpoint').name;
    this.sendToDevice(action, value);
  }

  /**
   * This is the thermostat's fan mode (speed). e.g. "4 = high", "3 = mid", "2 = low", "1 = auto". The default value will be "auto".
   * @param {Number} value  4 | 3 | 2 | 1
   */
  thermostatFanMode(value) {
    const action = this.protobuf.message.lookup('thermostatFanMode').name;
    this.sendToDevice(action, value);
  }

  /**
   * This is the thermostat's setpoint value, when the thermostat mode is set to Heat.
   * @param {Number} value
   */
  thermostatHeatingSetpoint(value) {
    const action = this.protobuf.message.lookup('thermostatHeatingSetpoint').name;
    this.sendToDevice(action, value);
  }

  /**
   * This is the thermostat's fan mode. e.g. "1 = auto", "2 = cool", "3 = dry", "4 = heat", "5 = fan"
   * @param {Number} value 1 | 2 | 3 | 4 | 5
   */
  thermostatMode(value) {
    const action = this.protobuf.message.lookup('thermostatMode').name;
    this.sendToDevice(action, value);
  }

  /**
   * Controls Vertical air flow direction of a device.
   * Send air flow change command to OrientUltron
   */
  verticalAirFlowDirection() {
    const action = this.protobuf.message.lookup('verticalAirFlowDirection').name;
    this.sendToDevice(action, true);
  }

  /**
   * Controls horizontal air flow direction of a device.
   * Send air flow change command to OrientUltron
   */
  horizontalAirFlowDirection() {
    const action = this.protobuf.message.lookup('horizontalAirFlowDirection').name;
    this.sendToDevice(action, true);
  }

  setState(state) {
    if (this.protobuf.message) {
      this.state = this.protobuf.decode(state);
      debug(this.state);
    } else {
      debug('state not updated');
    }
  }

  initDriver(filePath, cb) {
    if (!this.protobuf.message) {
      const path = filePath || './';
      this.protobuf = new ProtocolBuffer('format', path, 'ACDriver', 'ACActions', () => {
        debug('protobuf loaded');
        if (cb) cb(this);
      });
    } else {
      debug('protobuf already loaded');
    }
  }

  sendToDevice(key, value) {
    this.state[key] = value;
    this.connection.publish(this.topic, this.protobuf.encode(this.state));
  }
}

module.exports = Driver;
kendemu commented 7 years ago

Hello, I'm doing a dynamic driver configuration from electron UI & IPC by using require. I think you can re-implement this code using Socket.io or MQTT. But probably you need to setup a different HTTP / MQTT port from the cylon-api-mqtt / http module. :

electron IPC Code:

ipc.on('device', (event, arg) => {
    console.log("ipc called");
    const DeviceServer = new DeviceHttpServer(arg);
    DeviceServer.start();
});

Dynamic Server Code:

import http from "http";

class DeviceHttpServer{
    constructor(device){
        this.device = device;
        console.log("DeviceHttpServer object created");
    }

    start(){
        require( __dirname +"/" + this.device);
    }
}
export default DeviceHttpServer;

cylon module code called by require:

import Cylon from "cylon";

Cylon.api("http", {
    host: '127.0.0.1',
    port: '3001',
    ssl: false,
    auth: false
});

Cylon.robot({
    name: "hoge",
    connections: {
        bebop: { adaptor: 'hoge' }
    },

    devices: {
        drone: { driver : 'hoge' }
    },

    work: function(my){

    }
}).start();