roomba-iot – Rev 2

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.        //
///////////////////////////////////////////////////////////////////////////

const SENSOR_PACKET_SIZE = 80;

const PACKET_DECODE_MASKS = {
    BUMP_WHEELDROP: {
        BUMP_RIGHT: 0x01,
        BUMP_LEFT: 0x02,
        WHEELDROP_RIGHT: 0x04,
        WHEELDROP_LEFT: 0x08
    },
    WHEEL_OVERCURRENT: {
        SIDEBRUSH: 0x01,
        RESERVED: 0x02,
        MAINBRUSH: 0x04,
        RIGHT_WHEEL: 0x08,
        LEFT_WHEEL: 0x10
    },
    BUTTONS: {
        CLEAN: 0x01,
        SPOT: 0x02,
        DOCK: 0x03,
        MINUTE: 0x04,
        HOUR: 0x05,
        DAY: 0x06,
        SCHEDULE: 0x07,
        CLOCK: 0x08
    },
    LIGHT_BUMPER: {
        LIGHT_BUMPER_LEFT: 0x01,
        LIGHT_BUMPER_FRONT_LEFT: 0x02,
        LIGHT_BUMPER_CENTER_LEFT: 0x03,
        LIGHT_BUMPER_CENTER_RIGHT: 0x04,
        LIGHT_BUMPER_FRONT_RIGHT: 0x05,
        LIGHT_BUMPER_RIGHT: 0x06
    },
    CHARGING_SOURCES: {
        INTERNAL_CHARGER: 0x01,
        HOME_BASE: 0x02
    }
}

/*
 * Decode sensor data for packet group 100 (all sensors).
 */
const decode_all_sensors =  function(packetBuffer, callback) {
    if (packetBuffer.length != SENSOR_PACKET_SIZE) {
        callback(null, 0, 'Invalid packet size')
        return
    }

    var packet = {}
    var pos = 0
    var byte = 0x00
    var mask = {}

    // Bumps and Wheel Drops (Packet ID: 7, Data Bytes: 1, unsigned)
    byte = packetBuffer.readUInt8(pos++)
    mask = PACKET_DECODE_MASKS.BUMP_WHEELDROP

    packet.bump = {}
    packet.bump.right = byte & mask.BUMP_RIGHT
    packet.bump.left = byte & mask.BUMP_LEFT
    packet.wheeldrop = {}
    packet.wheeldrop.right = byte & mask.WHEELDROP_RIGHT
    packet.wheeldrop.left = byte & mask.WHEELDROP_LEFT

    // Wall (Packet ID: 8, Data Bytes: 1, unsigned)
    packet.wall = packetBuffer.readUInt8(pos++) == 1

    // Cliff Sensors (Packet ID: 9 - 12)
    packet.cliff = {}
    packet.cliff.left = packetBuffer.readUInt8(pos++) == 1
    packet.cliff.front_left = packetBuffer.readUInt8(pos++) == 1
    packet.cliff.front_right = packetBuffer.readUInt8(pos++) == 1
    packet.cliff.right = packetBuffer.readUInt8(pos++) == 1

    // Virtual Wall (Packet ID: 13, Data Bytes: 1, unsigned)
    packet.virtual_wall = packetBuffer.readUInt8(pos++) == 1

    // Wheel Overcurrents (Packet ID: 14, Data Bytes: 1, unsigned)
    byte = packetBuffer.readUInt8(pos++)
    mask = PACKET_DECODE_MASKS.WHEEL_OVERCURRENT
    packet.wheel_overcurrent = {}
    packet.wheel_overcurrent.side_brush = byte & mask.SIDEBRUSH
    packet.wheel_overcurrent.main_brush = byte & mask.MAINBRUSH
    packet.wheel_overcurrent.right_wheel = byte & mask.RIGHT_WHEEL
    packet.wheel_overcurrent.left_wheel = byte & mask.LEFT_WHEEL

    // Dirt Detect (Packet ID: 15, Data Bytes: 1)
    packet.dirt_level = packetBuffer.readInt8(pos++)

    // For Packet IDs: 0 1 or 6, 1 unused byte (Packet ID: 16, Data Bytes: 1)
    // Apparently also for 100
    ++pos

    // Infrared Character Omni (Packet ID: 17, Data Bytes: 1, unsigned)
    packet.remote = packetBuffer.readUInt8(pos++)

    // Buttons (Packet ID: 18, Data Bytes: 1, unsigned)
    // These toggle only when the button is held down!
    byte = packetBuffer.readUInt8(pos++)
    mask = PACKET_DECODE_MASKS.BUTTONS
    packet.buttons = {}
    packet.buttons.clean = byte & mask.CLEAN
    packet.buttons.spot = byte & mask.SPOT
    packet.buttons.dock = byte & mask.DOCK
    packet.buttons.minute = byte & mask.MINUTE
    packet.buttons.hour = byte & mask.HOUR
    packet.buttons.day = byte & mask.DAY
    packet.buttons.schedule = byte & mask.SCHEDULE
    packet.buttons.clock = byte & mask.CLOCK

    // Distance (Packet ID: 19, Data Bytes: 2, signed)
    packet.distance = packetBuffer.readInt16BE(pos)
    pos += 2

    // Angle (Packet ID: 20, Data Bytes: 2, signed)
    packet.angle = packetBuffer.readInt16BE(pos)
    pos += 2

    // Charging State (Packet ID: 21, Data Bytes: 1, unsigned)
    packet.battery = {}
    packet.battery.charging_state = packetBuffer.readUInt8(pos++)

    // Voltage (Packet ID: 22, Data Bytes: 2, unsigned)
    packet.battery.voltage = packetBuffer.readUInt16BE(pos)
    pos += 2

    // Current (Packet ID: 23, Data Bytes: 2, signed)
    packet.battery.current = packetBuffer.readInt16BE(pos)
    pos += 2

    // Temperature (Packet ID: 24, Data Bytes: 1, signed)
    packet.battery.temp = packetBuffer.readInt8(pos++)

    // Battery Charge (Packet ID: 25, Data Bytes: 2, unsigned)
    packet.battery.charge = packetBuffer.readUInt16BE(pos)
    pos += 2

    // Battery Capacity (Packet ID: 26, Data Bytes: 2, unsigned)
    packet.battery.capacity = packetBuffer.readUInt16BE(pos)
    pos += 2

    // Wall Signal (Packet ID: 27, Data Bytes: 2, unsigned)
    packet.wall_signal = packetBuffer.readUInt16BE(pos)
    pos += 2

    // cliff signals (Packet IDs: 28-31, Data Bytes: 2, unsigned)
    packet.cliff_signal = {}
    packet.cliff_signal.left = packetBuffer.readUInt16BE(pos)
    pos += 2
    packet.cliff_signal.front_left = packetBuffer.readUInt16BE(pos)
    pos += 2
    packet.cliff_signal.front_right = packetBuffer.readUInt16BE(pos)
    pos += 2
    packet.cliff_signal.right = packetBuffer.readUInt16BE(pos)
    pos += 2

    // Unused (Packet IDs: 32 - 33, Data Bytes: 3)
    pos += 3

    // Charging Sources Available (Packet ID: 34, Data Bytes: 1, unsigned)
    byte = packetBuffer.readUInt8(pos++)
    mask = PACKET_DECODE_MASKS.CHARGING_SOURCES
    packet.charging_source = {}
    packet.charging_source.internal_charger = byte & mask.INTERNAL_CHARGER
    packet.charging_source.home_base = byte & mask.HOME_BASE

    // OI Mode (Packet ID: 35, Data Bytes 1, unsigned)
    packet.oi_mode = packetBuffer.readUInt8(pos++)

    // Song Number (Packet ID: 36, Data Bytes: 1, unsigned)
    packet.song = {}
    packet.song.number = packetBuffer.readUInt8(pos++)

    // Song Playing (Packet ID: 37, Data Bytes: 1, unsigned)
    packet.song.playing = packetBuffer.readUInt8(pos++)

    // Number of Stream Packets (Packet ID: 38, Data Bytes: 1, unsigned)
    packet.stream_packets = packetBuffer.readUInt8(pos++)

    // Requested Velocity (Packet ID: 39, Data Bytes: 2, signed)
    packet.last_requested_velocity = {}
    packet.last_requested_velocity.general = packetBuffer.readInt16BE(pos)
    pos += 2

    // Requested Radius (Packet ID: 40, Data Bytes: 2, signed)
    packet.requested_radius = packetBuffer.readInt16BE(pos)
    pos += 2

    // Requested Right Velocity (Packet ID: 41, Data Bytes: 2, signed)
    packet.last_requested_velocity.right = packetBuffer.readInt16BE(pos)
    pos += 2

    // Requested Left Velocity (Packet ID: 42, Data Bytes: 2, signed)
    packet.last_requested_velocity.left = packetBuffer.readInt16BE(pos)
    pos += 2

    // Left Encoder Counts (Packet ID: 43, Data Bytes: 2, signed)
    packet.encoder = {}
    packet.encoder.left = packetBuffer.readInt16BE(pos)
    pos += 2

    // Right Encoder Counts (Packet ID: 44, Data Bytes: 2, signed)
    packet.encoder.right = packetBuffer.readInt16BE(pos)
    pos += 2

    // Light Bumper (Packet ID: 45, Data Bytes: 1, unsigned)
    byte = packetBuffer.readUInt8(pos++)
    mask = PACKET_DECODE_MASKS.LIGHT_BUMPER
    packet.light_bumper = {}
    packet.light_bumper.left = byte & mask.LIGHT_BUMPER_LEFT
    packet.light_bumper.front_left = byte & mask.LIGHT_BUMPER_FRONT_LEFT
    packet.light_bumper.center_left = byte & mask.LIGHT_BUMPER_CENTER_LEFT
    packet.light_bumper.center_right = byte & mask.LIGHT_BUMPER_CENTER_RIGHT
    packet.light_bumper.front_right = byte & mask.LIGHT_BUMPER_FRONT_RIGHT
    packet.light_bumper.right = byte & mask.LIGHT_BUMPER_RIGHT

    // Light Bump Left Signal (Packet ID: 46, Data Bytes: 2, unsigned)
    packet.light_bump_signal = {}
    packet.light_bump_signal.left = packetBuffer.readUInt16BE(pos)
    pos += 2

    // Light Bump Front Left Signal (Packet ID: 47, Data Bytes: 2, unsigned)
    packet.light_bump_signal.front_left = packetBuffer.readUInt16BE(pos)
    pos += 2

    // Light Bump Center Left Signal (Packet ID: 48, Data Bytes: 2, unsigned)
    packet.light_bump_signal.center_left = packetBuffer.readUInt16BE(pos)
    pos += 2

    // Light Bump Center Right Signal (Packet ID: 49, Data Bytes: 2, unsigned)
    packet.light_bump_signal.center_right = packetBuffer.readUInt16BE(pos)
    pos += 2

    // Light Bump Front Right Signal (Packet ID: 50, Data Bytes: 2, unsigned)
    packet.light_bump_signal.front_right = packetBuffer.readUInt16BE(pos)
    pos += 2

    // Light Bump Right Signal (Packet ID: 51, Data Bytes: 2, unsigned)
    packet.light_bump_signal.right = packetBuffer.readUInt16BE(pos)
    pos += 2

    // Infrared Character Left (Packet ID: 52, Data Bytes: 1, unsigned)
    packet.infrared = {}
    packet.infrared.character = {}
    packet.infrared.character.left = packetBuffer.readUInt8(pos++)

    // Infrared Character Left (Packet ID: 53, Data Bytes: 1, unsigned)
    packet.infrared.character.right = packetBuffer.readUInt8(pos++)

    // Left Motor Current (Packet IDs: 54 - 57, Data Bytes: 2, signed)
    packet.motor_current = {}
    packet.motor_current.left = packetBuffer.readInt16BE(pos)
    pos += 2
    packet.motor_current.right = packetBuffer.readInt16BE(pos)
    pos += 2
    packet.motor_current.main_brush = packetBuffer.readInt16BE(pos)
    pos += 2
    packet.motor_current.side_brush = packetBuffer.readInt16BE(pos)
    pos += 2

    // Stasis (Packet ID: 58, Data Bytes: 1)
    packet.stasis = packetBuffer.readUInt8(pos++)

    // Check whether the complete packet has been consumed.
    if (pos != SENSOR_PACKET_SIZE) {
        callback(null, pos, 'Insuficient data to assemble a complete packet')
        return
    }

    // Return the data
    callback(packet, pos, null)
}

module.exports = {
    PACKET_DECODE_MASKS: PACKET_DECODE_MASKS,
    SENSOR_PACKET_SIZE: SENSOR_PACKET_SIZE,
    decode_all_sensors: decode_all_sensors
}