roomba-iot – Rev 4

Subversion Repositories:
Rev:
#!/usr/bin/env nodejs
///////////////////////////////////////////////////////////////////////////
//  Copyright (C) Wizardry and Steamworks 2018 - License: GNU GPLv3      //
//  Please see: http://www.gnu.org/licenses/gpl.html for legal details,  //
//  rights of fair usage, the disclaimer and warranty conditions.        //
///////////////////////////////////////////////////////////////////////////

// Global package definitions.
const Gpio = require('onoff').Gpio
const mqtt = require('mqtt')
const YAML = require('yamljs')
const winston = require('winston')
const SerialPort = require('serialport')

// Local package definitions.
const decoder = require('./lib/decoder.js')

// Load configuration file.
const config = YAML.load('config.yml')

// Set up logger.
const logger = winston.createLogger({
  transports: [
    new winston.transports.Console(),
    new winston.transports.File({ filename: config.log })
  ]
});

// Initiate connection to MQTT.
const mqttClient = mqtt.connect(config.mqtt.url)

// Set up serial port.
var port = new SerialPort(config.serial_device, {
  // Roomba 4xx and Dirt Dog: 57600
  // Roomba 5xx and 7xx: 115200
  baudRate: 115200,
  dataBits: 8,
  parity: 'none',
  stopBits: 1,
  flowControl: false
})

// Set up sensor packet assembler.
port.on('data', PublishSensorPacket);

// Set up GPIO wake pin.
const wakeGPIO = new Gpio(config.wake, 'out')
// Wake up Roomba.
RoombaWake(wakeGPIO, function () {
  // Switch to safe mode.
  RoombaSafe(function () {
    // Poll sensors periodically.
    setInterval(function () {
      port.write(new Buffer.from(COMMAND_SEQUENCES['query_all_sensors']))
    }, config.sensor_poll_interval)
  })
})

// Subscribe to configured topic when client connects and start polling sensors.
mqttClient.on('connect', function () {
  logger.info('Connected to MQTT server')
  mqttClient.subscribe(config.mqtt.topic, function (error) {
    if (error) {
      logger.error('Unable to subscribe to MQTT topic')
    }
    logger.info("Subscribed to MQTT topic " + config.mqtt.topic)
  })
})

// Execute command when message is received on configured topic.
mqttClient.on('message', function (topic, message) {
  if (message.length === 0)
    return

  message = message.toString()

  logger.debug('Received message: ' + message)

  // Skip commands that do not figure in the command sequence table.
  if (!(message in COMMAND_SEQUENCES))
    return

  logger.info('Executing Roomba command: ' + message)

  // Execute Roomba command in safe mode.
  RoombaSafe(function () {
    port.write(new Buffer.from(COMMAND_SEQUENCES[message]))
  })
})

mqttClient.on('reconnect', () => {
  logger.info('Reconnecting to MQTT server...')
})

/*
 * Wake Roomba device.
 * Sequence:
 *  1. Set DD to HIGH
 *  2. Wait 100ms
 *  3. Set DD to LOW
 *  4. Wait 500ms
 *  5. Set DD to HIGH
 */
function RoombaWake(gpio, callback) {
  gpio.write(1, err => {
    if (err) {
      logger.error('Could not wake Roomba!')
      return
    }

    setTimeout(function () {
      gpio.write(0, function (err) {
        if (err) {
          logger.error('Could not wake Roomba!')
          return
        }
        setTimeout(function () {
          gpio.write(1, function (err) {
            if (err) {
              logger.error('Could not wake Roomba!')
              return
            }
            callback()
          })
        }, 500)
      })
    }, 100)
  })
}

/*
 * Put Roomba in safe mode.
 */
function RoombaSafe(callback) {
  setTimeout(function () {
    port.write(new Buffer.from(COMMAND_SEQUENCES['start']))
    setTimeout(function () {
      port.write(new Buffer.from(COMMAND_SEQUENCES['safe']))
      setTimeout(callback, 500)
    }, 100)
  }, 100)
}

/*
 * Build the sensor buffer as the data is read from the serial port.
 */
var packetBuffer = null

function PublishSensorPacket(data) {
  if (packetBuffer) {
    data = Buffer.concat(
      [packetBuffer, data], packetBuffer.length + data.length)
  }

  if (data.length >= decoder.SENSOR_PACKET_SIZE) {
    var packet = data.slice(0, decoder.SENSOR_PACKET_SIZE)

    decoder.decode_all_sensors(packet, function (result, position, error) {
      if (error) {
        logger.error('Packet assembly failed with error:' + error)
        return
      }

      // Push the packet onto MQTT.
      mqttClient.publish(config.mqtt.topic, JSON.stringify(result))
    })

    data = data.slice(decoder.SENSOR_PACKET_SIZE)
  }

  if (data.length > 0) {
    packetBuffer = data
    return
  }

  packetBuffer = null
}

const COMMAND_SEQUENCES = {
  safe: [131],
  start: [128],
  spot: [134],
  clean: [135],
  dock: [143],
  query_all_sensors: [142, 100],
  stream_all_sensors: [148, 1, 100],
  stream_sensors_off: [150, 0],
  stream_sensors_on: [150, 1]
}

Generated by GNU Enscript 1.6.5.90.