Johnny-Five Robotics and Hardware Programming Framework

repository·main·Indexed 12 days ago

https://github.com/rwaldron/johnny-five

An open-source JavaScript robotics and IoT programming framework for Node.js using the Firmata protocol. It provides consistent APIs to control hardware including Arduino (all models), Raspberry Pi, Beagle Bone, Intel Galileo & Edison, and more. Version 2.1.0 supports a wide range of components such as LEDs, servos, motors, and accelerometers (ADXL335, ADXL345, LIS3DH, MMA8452, MPU6050) through robust, composable APIs.

Tokens
134.1K
Snippets
384
Records
458
Agent score
95%

What's inside Johnny-Five

  1. What is Johnny-Five?

    main
    Johnny-Five is an open-source, Firmata Protocol-based IoT and robotics programming framework for JavaScript. It allows you to control various hardware platforms using Node.js. The framework focuses on providing robust, composable APIs that behave consistently across different hardware, making it easy to integrate with application libraries like Express.js or Socket.io, Bluetooth controllers, and other IoT frameworks.
  2. Browse Johnny-Five component examples by category

    main

    Examples are organized by component type to help you find specific implementation patterns:

    • Board: Initialization, cleanup, sampling intervals, and port specification.
    • LED: Blinking, fading, pulsing, RGB control, and matrix/digit displays.
    • Servo: Continuous, multi-turn, and animation examples.
    • Motor: DC motors, H-Bridges, and driver-specific implementations (e.g., PCA9685, L298).
    • Sensors: Accelerometers, Gyros, Proximity (HC-SR04, LIDAR), Motion, and Environmental sensors (Temperature, Humidity, Barometer).
    • Input: Buttons, Switches, Keypads, Joysticks, and IR/Motion sensors.
    • Display: LCD (I2C and standard) and LED Matrices.
    • Specialized Kits: Examples for Lego EVShield, Intel Edison + Grove, and TinkerKit.
  3. Hardware Support and IO Plugins

    main

    Johnny-Five is a Firmata Protocol based framework.

    • Arduino: Supports all Arduino models natively.
    • Non-Arduino Platforms: For platforms like Raspberry Pi, Beagle Bone, Intel Galileo, etc., use IO Plugins. These plugins allow Johnny-Five to communicate with non-Arduino hardware using the appropriate protocols.
  4. Supported Hardware and IO Plugins

    main

    Johnny-Five supports a wide range of hardware including Arduino (all models), Electric Imp, Beagle Bone, Intel Galileo & Edison, Linino One, Pinoccio, pcDuino3, Raspberry Pi, Particle/Spark Core & Photon, Tessel 2, and TI Launchpad.

    For non-Arduino based projects, you can use IO Plugins. These plugins allow Johnny-Five code to communicate with non-Arduino hardware by translating the commands into the specific language the platform understands.

  5. Implement a Navigator for BOE Bot using continuous servos

    main

    The Navigator class is a custom abstraction designed for the BOE Bot (or similar differential drive robots) using johnny-five continuous servos. It handles the inverse relationship between left and right servos (since they are mounted oppositely) and provides high-level movement commands.

    Initialization

    To create a Navigator instance, pass an options object specifying the pins for the right and left servos.

    Note: BOE Bot continuous servos are calibrated to stop at 90°. You can specify a custom center value in the options.

    Movement API

    • forward(speed): Moves the bot forward. speed is a value from 0 (stopped) to 5 (fastest).
    • reverse(speed): Moves the bot in reverse. speed is a value from 0 to 5.
    • stop(): Stops all movement.
    • left(): Performs a turn to the left.
    • right(): Performs a turn to the right.
    • pivot(which, time): Performs a pivot turn. which accepts strings like forward-right, forward-left, reverse-right, or reverse-left. time is the duration of the pivot in milliseconds.
    • move(right, left): Low-level method to set specific servo positions directly.
    var five = require("johnny-five");
    
    // Assuming a Board is ready...
    var bot = new Navigator({
      right: 10,
      left: 11,
      center: 90
    });
    
    bot.forward(3);
    bot.pivot("forward-left", 500);
    bot.stop();
  6. How Navigator handles cooperative servo motion

    main

    The Navigator is designed for robots where servos are mounted in opposition (e.g., on opposite sides of a chassis). Because of this mounting, a command to move "forward" requires the left and right servos to receive opposing values.

    The Navigator manages this mapping internally. When you call forward(), it calculates the necessary opposing values for the left and right servos based on the center value provided during initialization. This allows the developer to think in terms of robot direction (forward, reverse, left, right) rather than individual motor polarity.

  7. Configure GPS using Hardware Serial

    main

    When using the GPS class with an Arduino or similar microcontroller, you must upload the StandardFirmataPlus firmware to your board.

    To use a hardware serial port instead of a software serial port, pass the specific hardware serial ID to the port option in the GPS constructor. You can access these IDs via board.io.SERIAL_PORT_IDs.

    const { Board, GPS } = require("johnny-five");
    const board = new Board();
    
    board.on("ready", () => {
      // Explicitly setting HW_SERIAL1 for the port
      const gps = new GPS({
        port: board.io.SERIAL_PORT_IDs.HW_SERIAL1
      });
    
      // Listen for position changes
      gps.on("change", position => {
        const { latitude, longitude } = position;
        console.log("GPS Position:");
        console.log("  latitude   : ", latitude);
        console.log("  longitude  : ", longitude);
        console.log("--------------------------------------");
      });
    });
  8. Implement a Line Follower Robot

    main

    This guide demonstrates how to build a line-following robot using a reflectance array (like the Pololu QTR-8A) and continuous servos for wheels.

    Core Logic

    1. Reflectance Array: Use five.IR.Reflect.Collection to read sensor data. The sensors detect light/dark transitions to determine the line's position.
    2. Calibration: Use eyes.calibrateUntil() to capture ambient light and dark values. This data can be persisted to a file (e.g., .calibration) using eyes.calibration and reloaded via eyes.loadCalibration() to avoid repeated manual calibration.
    3. Driving Rules: Map the sensor's line value (emitted via the line event) to specific motor speeds and directions.
    4. Continuous Servos: Control the wheels using five.Servo.Continuous. Use methods like .stop(), .cw(speed), or .ccw(speed) to adjust movement.

    Implementation Example

    var five = require("johnny-five");
    var board = new five.Board();
    
    board.on("ready", function() {
      var eyes = new five.IR.Reflect.Collection({
        emitter: 13,
        pins: ["A0", "A1", "A2", "A3", "A4", "A5"],
        freq: 20
      });
    
      var wheels = {
        left: new five.Servo.Continuous(10),
        right: new five.Servo.Continuous(9)
      };
    
      // Calibration logic
      eyes.calibrateUntil(function() {
        // Return true when calibration is finished
        return false; 
      });
    
      // Driving logic
      eyes.on("line", function(err, line) {
        // 'line' value is used to determine movement rules
        if (line < 1000) {
          wheels.left.cw(0.01);
          wheels.right.ccw(0.07);
        }
        // ... additional rules
      });
    });
    // This is an example of a line following robot.  It uses a
    // Pololu QTR-8A reflectance array to read a line on my
    // counter drawn with electrical tape.  You can see the
    // bot in action here: https://www.youtube.com/watch?v=i6n4CwqQer0
    
    var fs = require("fs");
    var five = require("johnny-five");
    var board = new five.Board();
    
    // Setup Standard input.  We use this to let the bot know that we"ve finished
    // calibrating
    var stdin = process.stdin;
    stdin.setRawMode(true);
    stdin.resume();
    
    var calibrationFile = ".calibration";
    
    // VERY simple driving rules.  It uses a mapping from the line value that comes
    // from the Reflectance Array to the left and right wheel of the bot.n// This can be made much better, it is a good start.
    var drivingRules = {
      0: {
        left: {
          dir: "cw",
          speed: 0.01
        },
        right: {
          dir: "ccw",
          speed: 0.07
        }
      },
    
      1000: {
        left: {
          dir: "cw",
          speed: 0.02
        },
        right: {
          dir: "ccw",
          speed: 0.05
        }
      },
    
      2000: {
        left: {
          dir: "cw",
          speed: 0.04
        },
        right: {
          dir: "ccw",
          speed: 0.05
        }
      },
    
      2500: {
        left: {
          dir: "cw",
          speed: 0.05
        },
        right: {
          dir: "ccw",
          speed: 0.05
        }
      },
    
      3000: {
        left: {
          dir: "cw",
          speed: 0.05
        },
        right: {
          dir: "ccw",
          speed: 0.04
        }
      },
    
      4000: {
        left: {
          dir: "cw",
          speed: 0.05
        },
        right: {
          dir: "ccw",
          speed: 0.02
        }
      },
    
      5001: {
        left: {
          dir: "cw",
          speed: 0.07
        },
        right: {
          dir: "ccw",
          speed: 0.01
        }
      }
    };
    
    board.on("ready", function() {
    
      // Create an instance of the reflectance array.
      var eyes = new five.IR.Reflect.Collection({
        emitter: 13,
        pins: ["A0", "A1", "A2", "A3", "A4", "A5"],
        freq: 20
      });
    
      // These are the continuous servos that control the wheels
      var wheels = {
        left: new five.Servo.Continuous(10),
        right: new five.Servo.Continuous(9)
      };
    
      // Make the eyes and wheels available in the REPL UI
      this.repl.inject({
        eyes: eyes,
        wheels: wheels
      });
    
      // When the bot starts up, enable the IR emitters and tell the wheels
      // to stop.  Calibrate the device.  When complete, drive.
      function init() {
        eyes.enable();
        wheels.left.stop();
        wheels.right.stop();
    
        calibrate(drive);
      }
    
      // Calibrate the bot.  If the calibration has been persisted, use it.
      // If not, calibrate the bot until the user presses a key.  Move the sensor
      // over light and dark regions several times.  Persist the calibration data
      // to a file so it doesn"t need to do it again next time.
      function calibrate(whenComplete) {
        var savedCalibration, calibrating = true;
    
        if (fs.existsSync(calibrationFile)) {
          eyes.loadCalibration(JSON.parse(fs.readFileSync(calibrationFile)));
          whenComplete();
          return;
        }
    
        console.log("Calibrating.  Press a key...");
    
        eyes.calibrateUntil(function() {
          return !calibrating;
        });
    
        stdin.once("keypress", function() {
          calibrating = false;
          console.log("Done:", eyes.calibration);
          fs.writeFile(calibrationFile, JSON.stringify(eyes.calibration));
          whenComplete();
        });
      }
    
      // Drive the bot.  Every time a line value event comes in, figure out which
      // rule to follow from the rules mapping.  Tell the left and right wheels
      // which direction and how fast to spin.
      function drive() {
        eyes.on("line", function(err, line) {
          var rule;
          var threshold = Object.keys(drivingRules).find(function(r) {
            return line <= parseInt(r);
          });
    
          if (!threshold) {
            console.log("Could not find threshold for " + line);
          }
    
          rule = drivingRules[threshold];
    
          wheels.left[rule.left.dir](rule.left.speed);
          wheels.right[rule.right.dir](rule.right.speed);
        });
      }
    
      // Start the bot
      init();
    
    });
  9. Install Johnny-Five and pcduino-io for pcDuino

    main

    To use the pcduino-io library, you must first have Node.js (version 0.10.x or better) and npm installed on your pcDuino. Once the environment is set up, install the required packages using npm:

    npm install johnny-five pcduino-io
  10. Initialize a Board and wait for the 'ready' event

    main

    To use Johnny-Five, you must first instantiate a Board object. Because communication with the hardware takes time, you cannot access pins or components immediately after instantiation. You must listen for the ready event emitted by the board instance. Once the ready event fires, the hardware is connected and the pins are accessible for component initialization.

    const { Board, Led } = require("johnny-five");
    const board = new Board();
    
    // The board's pins will not be accessible until
    // the board has reported that it is ready
    board.on("ready", () => {
      console.log("Ready!");
    
      const led = new Led(13);
      led.blink(500);
    });