Skip to content
Snippets Groups Projects
VizBlocks.hpp 8.39 KiB
Newer Older
emorgan's avatar
emorgan committed
#ifndef VIZBLOCKS_H
#define VIZBLOCKS_H
Dave Murray-Rust's avatar
Dave Murray-Rust committed
#include "Arduino.h"
#include "Behaviours.h"
#include "ServoBehaviours.h"
#include "LEDBehaviours.h"
Dave Murray-Rust's avatar
Dave Murray-Rust committed

#include <ESP8266WiFi.h>
#include "Adafruit_MQTT.h"
#include "Adafruit_MQTT_Client.h"

#define MQTT_topic  "new001"
Joe Revans's avatar
Joe Revans committed
#define NUM_BACKGROUND_BEHAVIOURS 5
Dave Murray-Rust's avatar
Dave Murray-Rust committed

emorgan's avatar
emorgan committed
class VizBlocks {
Dave Murray-Rust's avatar
Dave Murray-Rust committed
  char* _ssid;
  char* _wifi_pass;
  char* _id;
  char* _server;
  int _port;
  BehaviourTable _behaviours;
Dave Murray-Rust's avatar
Dave Murray-Rust committed
  Behaviour* _active = nullptr;
Joe Revans's avatar
Joe Revans committed
  Behaviour* _background[NUM_BACKGROUND_BEHAVIOURS];
Dave Murray-Rust's avatar
Dave Murray-Rust committed
  int _loop_time = 5;
  Adafruit_MQTT_Client* _mqtt;
  Adafruit_MQTT_Subscribe* _device_subscription;
  Adafruit_MQTT_Publish* _announce;
  Adafruit_MQTT_Publish* _my_announce;
  String _my_announce_channel;

  Adafruit_MQTT_Publish* _input;
  Adafruit_MQTT_Publish* _my_input;
  String _my_input_channel;

  WiFiClient* _client;
Dave Murray-Rust's avatar
Dave Murray-Rust committed

Joe Revans's avatar
Joe Revans committed
  String capabilitiesJSON[50];

Dave Murray-Rust's avatar
Dave Murray-Rust committed
public:
Joe Revans's avatar
Joe Revans committed
  VizBlocks(char* id, char* ssid="VizBlocksNet", char* wifi_pass="VizBlocksAP",
    char* server="172.20.10.8",int port=1883) : _id(id), _server(server), _port(port), _ssid(ssid), _wifi_pass(wifi_pass) {} ;
Dave Murray-Rust's avatar
Dave Murray-Rust committed

  void command_callback(char *data, uint16_t len) {
    Serial.println("Got command: ");
    Serial.println(data);
  }

Joe Revans's avatar
Joe Revans committed
  void set_wifi(boolean v) { _wifi = v; }
Dave Murray-Rust's avatar
Dave Murray-Rust committed

emorgan's avatar
emorgan committed
   * Set up the VizBlocks node - WiFi, MQTT
Dave Murray-Rust's avatar
Dave Murray-Rust committed
  void init() {
    Serial.setTimeout(100);
    Serial.println();
emorgan's avatar
emorgan committed
    Serial.println(F("VizBlocks Node starting up"));
    Serial.println("Initialising " + String(_id));
Dave Murray-Rust's avatar
Dave Murray-Rust committed

Dave Murray-Rust's avatar
Dave Murray-Rust committed

      WiFi.setSleepMode(WIFI_NONE_SLEEP);
Dave Murray-Rust's avatar
Dave Murray-Rust committed

      // Connect to WiFi access point.
      Serial.println();
      Serial.print("Connecting to ");
      Serial.println(_ssid);
Dave Murray-Rust's avatar
Dave Murray-Rust committed

      WiFi.begin(_ssid, _wifi_pass);
      while (WiFi.status() != WL_CONNECTED) {
       delay(500);
       Serial.print(".");
      }
      Serial.println();

      Serial.println("WiFi connected");
      Serial.println("IP address: "); Serial.println(WiFi.localIP());
      // Done Wifi

      // Setup MQTT
      _client = new WiFiClient();
      _mqtt = new Adafruit_MQTT_Client(_client, _server, _port, "" /* mqttt username */, "" /* mqtt pass*/);
      _device_subscription = new Adafruit_MQTT_Subscribe(_mqtt, _id);
      _announce = new Adafruit_MQTT_Publish(_mqtt, "announce");
      _my_announce_channel = String("announce/") + String(_id);
      _my_announce = new Adafruit_MQTT_Publish(_mqtt, _my_announce_channel.c_str());

      // Setup MQTT subscription for this device
      _mqtt->subscribe(_device_subscription);
      // This *would* setup a callback, but we're not doing this right now...
      //_device_subscription->setCallback(test_callback);

      MQTT_connect();
    }
Dave Murray-Rust's avatar
Dave Murray-Rust committed

Joe Revans's avatar
Joe Revans committed
    generateCapabilitiesJSON();
    announce_capabilities();
    Serial.println("Init finished");
  /*
   * Add a behaviour to the list of possible behaviours
   */
Dave Murray-Rust's avatar
Dave Murray-Rust committed
  void add(Behaviour *b) {
    _behaviours.add(b);
  }

  /*
   * This is the main loop. It should be called from within loop() - really
   * this function is the only thing you should need to call. It will manage
   * it's own delay, so you can call as often as possible.
   */
Joe Revans's avatar
Joe Revans committed
  void run() {
Dave Murray-Rust's avatar
Dave Murray-Rust committed
    int loop_start_time = millis();
    serial_command();
Dave Murray-Rust's avatar
Dave Murray-Rust committed
    if( _active ) {
      _active -> update();
Joe Revans's avatar
Joe Revans committed
      if( ! _active->is_running() ) { _active = nullptr; }
    }

    for (int i = 0; i < NUM_BACKGROUND_BEHAVIOURS; i++) {
      if( _background[i] ) {
        _background[i] -> update();
        if( !_background[i] -> is_running() ) { _background[i] = nullptr; }
      }
Dave Murray-Rust's avatar
Dave Murray-Rust committed
    }
Joe Revans's avatar
Joe Revans committed

Dave Murray-Rust's avatar
Dave Murray-Rust committed
    int loop_time_taken = millis()-loop_start_time;
    if( loop_time_taken < _loop_time ) {
      delay( _loop_time - loop_time_taken );
    }
  }

  /*
   * Read a command from the serial input and process it
   */
Dave Murray-Rust's avatar
Dave Murray-Rust committed
  void serial_command() {
    if( Serial.available() ) {
      String cmd = Serial.readStringUntil('\n');
Joe Revans's avatar
Joe Revans committed
      cmd.replace("\r", "");
Dave Murray-Rust's avatar
Dave Murray-Rust committed
      Serial.println(process(cmd));
    }
  }

  /*
   * Read a command from the serial input and process it. It only waits for
   * 50ms to allow other behaviours to continue.
   */
Dave Murray-Rust's avatar
Dave Murray-Rust committed
  void mqtt_command() {
      MQTT_connect(); //ensure connection
      Adafruit_MQTT_Subscribe *subscription;
      while ((subscription = _mqtt->readSubscription(50))) {
        if (subscription == _device_subscription) {
          Serial.print(F("Got: "));
          Serial.println((char *)_device_subscription->lastread);
          Serial.println(process((char *)_device_subscription->lastread));
        }
      }
  }

  /*
   * Process a command. This means:
   * - split the command name from the arguments
Joe Revans's avatar
Joe Revans committed
   * - call process_command with the separated command and argument string
Dave Murray-Rust's avatar
Dave Murray-Rust committed
  String process(String input) {

Joe Revans's avatar
Joe Revans committed
    if (!(input.indexOf("<") >= 0 || input.indexOf(">") >= 0 || input.indexOf("-") >= 0)) {
      int index = input.indexOf(" ");
      String command = "";
      String args = "";

      if( index ) {
        command = input.substring(0,index);
        args = input.substring(index+1);
      } else {
        command = input;
      }
      return process_command(command, args);
  String input_event(String input) {
    return process(input);
  /*
   * Process a command and its arguments. This means:
Joe Revans's avatar
Joe Revans committed
   * - look for a Behaviour with the right name
   * - if found, then call that behaviour with the arguments (which are still a single string)
   */
Dave Murray-Rust's avatar
Dave Murray-Rust committed
  String process_command(String command, String args) {
    Serial.println("Processing <"+command+"> <"+args+">");
    Behaviour* b = _behaviours.get(command);
    if(b) {
Joe Revans's avatar
Joe Revans committed
      // Stop whatever behaviour is running in the active slot.
Dave Murray-Rust's avatar
Dave Murray-Rust committed
      if( _active ) { _active->stop(); }
      Serial.println( "Found behaviour: <"+command+">" );
Joe Revans's avatar
Joe Revans committed

      if ( b->is_background() ) {
        // If this behaviour is already running in the background, refresh it and move on.
        for (int i = 0; i < NUM_BACKGROUND_BEHAVIOURS; i++) {
          if ( _background[i] ) {
            if ( b->name() == _background[i]->name() ) {
              return( b->start(args) );
            }
          }
        }
        // Else, if there is space for another background behaviour, start it running.
        for (int i = 0; i < NUM_BACKGROUND_BEHAVIOURS; i++) {
          if ( !_background[i] ) {
            _background[i] = b;
            return( b->start(args) );
          }
        }
        return "Couldn't run command: " + command + ". Background behaviours full.";
      }

Dave Murray-Rust's avatar
Dave Murray-Rust committed
      _active = b;
      return( b->start(args) );
    } else {
      return "Couldn't process command: " + command;
    }
  }

  /*
  * Function to connect and reconnect as necessary to the MQTT server.
  */
Dave Murray-Rust's avatar
Dave Murray-Rust committed
  // Should be called in the loop function and it will take care if connecting.
  void MQTT_connect() {
    int8_t ret;

    // Stop if already connected.
    if (_mqtt->connected()) {
      return;
    }

    Serial.print("Connecting to MQTT... ");

    uint8_t retries = 3;
    while ((ret = _mqtt->connect()) != 0) { // connect will return 0 for connected
      Serial.println(_mqtt->connectErrorString(ret));
      Serial.println("Retrying MQTT connection in 5 seconds...");
      _mqtt->disconnect();
      delay(5000);  // wait 5 seconds
      retries--;
      if (retries == 0) {
        // basically die and wait for WDT to reset me
        while (1);
      }
Dave Murray-Rust's avatar
Dave Murray-Rust committed
    }
    Serial.println("MQTT Connected!");
  }

Joe Revans's avatar
Joe Revans committed
  void generateCapabilitiesJSON() {
Joe Revans's avatar
Joe Revans committed
    String head = "{\"id\":\"" + String(_id) + "\",\"Behaviour\":{";
Joe Revans's avatar
Joe Revans committed
    for (int i = 0; i < _behaviours.get_num_behaviours(); i++) {
      Behaviour* b = _behaviours.get_by_num(i);
      String args = b->args();
Joe Revans's avatar
Joe Revans committed
      String body = "\"name\":\"" + b->name() + "\",\"args\":\"" + args + "\"}}";
Joe Revans's avatar
Joe Revans committed
      String str = head + body;
      capabilitiesJSON[i] = str;
    }
  }

Joe Revans's avatar
Joe Revans committed
  void announce(String doc) {
    if( _wifi ) { _my_announce->publish(doc.c_str()); }
    Serial.print("-->");
    Serial.println(doc);
  }

  void announce_capabilities() {
Joe Revans's avatar
Joe Revans committed
    String doc;
    if( _wifi ) {
      if( ! _announce->publish(_id) ) { Serial.println("Couldn't make announcement"); }
    }
    for( int i = 0; i < _behaviours.get_num_behaviours(); i++ ) {
Joe Revans's avatar
Joe Revans committed
      doc = capabilitiesJSON[i];
Joe Revans's avatar
Joe Revans committed
      announce(doc);
  void setID(char* id) {
    _id = id;
  }
Joe Revans's avatar
Joe Revans committed
  char* getId() {
    return _id;
Joe Revans's avatar
Joe Revans committed
  }
Joe Revans's avatar
Joe Revans committed
};
Dave Murray-Rust's avatar
Dave Murray-Rust committed

Joe Revans's avatar
Joe Revans committed
 * These behaviours depend on VizBlocks class so they must be included after
 * it has been defined.
 */
Joe Revans's avatar
Joe Revans committed
#include "CommsBehaviours.h"
#include "ButtonBehaviours.h"
#include "PotentiometerBehaviours.h"
#include "RotaryEncoderBehaviours.h"
Dave Murray-Rust's avatar
Dave Murray-Rust committed

#endif