💾Lidar Obstacle Avoidance Tutorial

MODi LIDAR Avoidance Tutorial

Learn how to command the motors using the board provided in your Scout kit, along with the LIDAR sensor on the MODi.

This guide requires no prior programming experience and users should focus more on the concepts being introduced versus getting lost in the minutia of the scripting language.

Overview

In this guide you will learn how to manipulate and control the Rover based on the following basic concepts:

  1. How to generate PWM commands for the Scout.

  2. Understand how the different PWM settings affect motor drive and direction.

  3. Visualize PWM concepts with an appreciation for generated outputs.

  4. Interpret the sensor command provided by the Scout sensor.

To access the program, simply follow this link, https://mantiscode.azurewebsites.net/webble/sctobstacle.html

The user have access to an internet connected device that runs a modern web browser that supports WebBluetooth. Most modern web browsers support this, with the exception of Safari on MacOS, and Firefox. Google Chrome or Edge are recommended for best results. Mobile browsers are not typically not supported.

This GUI code demo contains two files:

sctobstacle.html

o This must be in the same folder as the Java script file

o Contains metadata about the extension and how to configure all the Gui items in the browser.

sctobstacle.js

o This module contains the underlying BLE communications.

o We will be iteratively modifying the functions specific to the Joystick buttons above.

Connect SCT Board

Click on the Bluetooth icon and make sure your device is broadcasting. Once broadcasting, click on the pair button to connect to the board.

this example assumes that your MODI sensor is connected to your Scout board.

Once you have a successful connection, the interface will report that a connection was successful and start to show distance values once you press the modi on button.

sctobstacle.html Breakdown

Let's break down the contents of mthjoystick.html file. This code tells your browser how and where to place everything that you see on the screen demonstrated above. You can copy this code directly into a text editor and make sure to name it with the same convention above.

<!doctype html>
<html>

    <head>
        <title>Scout Control Panel</title>

        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
        <link rel="stylesheet" href="https://code.getmdl.io/1.1.3/material.indigo-pink.min.css">
        <script defer src="https://code.getmdl.io/1.1.3/material.min.js"></script>
        <style>

            body {
              background: #fff;
            }
            
            .mip-layout {
              display: flex;
              align-items: center;
              justify-content: center;
              background: #fff;
            }
            
            .mip-card {
              width: 400px;
            }
            
            .mip-pad {
              display: flex;
              align-items: center;
              flex-direction: column;
            }
            
            .mip-pad button {
              margin: 1em 0.5em;
            }
            
            
            #logTextArea {
              width: 950%;
              height: 150px;
              overflow: auto;
            }
            
                    </style>
    </head>

    <body>

        <div class="mip-layout mdl-layout mdl-js-layout">

            <div class="mdl-card mdl-shadow--2dp mip-card">
                <div class="mdl-card__title">
                    <h3 class="mdl-card__title-text">Mantis Scout</h3>
                </div>
                <div class="mdl-card__supporting-text">
                    Please connect and control
						
    
                    </button>
                </div>
                <div class="mdl-card__actions mdl-card--border mip-pad">

                    <button onclick="moveForward()" class="mdl-button mdl-js-button mdl-button--fab mdl-button--raised mdl-js-ripple-effect">
                        <i class="material-icons">arrow_upward</i>
                    </button>

                    <div>
                        <button onclick="turnLeft()" class="mdl-button mdl-js-button mdl-button--fab mdl-button--raised mdl-js-ripple-effect">
                            <i class="material-icons">arrow_back</i>
                        </button>

                        <button onclick="connect()" class="mdl-button mdl-js-button mdl-button--fab mdl-button--colored">
                            <i class="material-icons">bluetooth_searching</i>
                        </button>

                        <button onclick="turnRight()" class="mdl-button mdl-js-button mdl-button--fab mdl-button--raised mdl-js-ripple-effect">
                            <i class="material-icons">arrow_forward</i>
                        </button>
                    </div>

                    <button onclick="moveBackward()" class="mdl-button mdl-js-button mdl-button--fab mdl-button--raised mdl-js-ripple-effect">
                        <i class="material-icons">arrow_downward</i>
                    </button>
				

                </div>
                <div class="mdl-card__actions mdl-card--border">
                    <button onclick="stop()">
                        Stop
                    </button>
                    <button onclick="ModiState('on')">
                        Modi On
                    </button>
                    <button onclick="ModiState('off')">
                        Modi Off
                    </button>
                    <button onclick="EnableMonitoring(1)">
                        Start Monitoring 
                    </button>
                    <button onclick="EnableMonitoring(0)">
                        Stop Monitoring 
                    </button>
                    
                </div>
                <textarea id="logTextArea" readonly></textarea>
            </div>
           
        </div>
        

        <script src="sctobstacle.js"></script>
    </body>

</html>

For starters you can see where the buttons are called out here starting on line 1, five buttons in total.

                  <button onclick="stop()">
                        Stop
                    </button>
                    <BR><BR>
                    <button onclick="onStartButtonClick()">
                        Connect MODI
                    </button>
                    <button onclick="onStopButtonClick()">
                        Stop MODI
                    </button>

                    <BR><BR>
                    <button onclick="EnableMonitoring(1)">
                        Start Monitoring 
                    </button>
                    <button onclick="EnableMonitoring(0)">
                        Stop Monitoring 
                    </button>

sctobstacle.js Breakdown

The man-in-the-loop experiment calls for manipulating the drive strength of these buttons and observing the results as you maneuver around the obstacle course that you made. It is important when you learn to code that you appreciate certain aspects of the code while focusing your efforts on the specific regions of concern. All other sections in the sctobstacle.js java script file maintain very low level details of the BLE layer that is beyond the scope of this exercise. Before proceeding further go ahead and copy this code into a text editor in its entirety and save it with the exact naming convention above.

'use strict';

var mDev=null;
var Distance=null;

var isMonitoring=0;
var isConnected=false;
var interval=null;
const originalLog = console.log;

const logTextArea = document.getElementById("logTextArea");

function connect() {
    console.log('Requesting Scout Robot Device...');
    if (!navigator.bluetooth) {
        alert('Web Bluetooth is disabled! Please go to enable it.');
        return;
    }

    
    //let dev = devName.toUpperCase();//this.getDevBluetoothName(devName); //BT Advertising Name String

   // console.log("Filtering on BLE device name: " + dev + " Service: " + mDev.PrimaryService);
    navigator.bluetooth.requestDevice({
        filters: [{ namePrefix: "SCT" }],
        optionalServices: ['0000ffd0-0000-1000-8000-00805f9b34fb']
    })
        .then(device => {
          //  console.log("device:");
           // console.log(device);
            let deviceprefix=device.name.split("_")[0];
            mDev = new MantisDevice(deviceprefix.toLowerCase());
          //  mDev.device.addEventListener('gattserverdisconnected', this.onDisconnected);
            // Connect to the GATT server and get the services
            device.gatt.connect().then(server => {
                // Get the Service
                console.log("Connected!");
               // console.log("Primary Service Requested: " + mDev.PrimaryService);
                // mDev.primaryServiceObj = server.getPrimaryService(mDev0.PrimaryService);
                server.getPrimaryService(mDev.PrimaryService)
                    .then(service => {
                        mDev.primaryServiceObj = service;
                     //   console.log("got service");
                        //Set the disconnection callback
                        if ((mDev.NotificationChar != null) && (notificationCallback != null)) {
                            service.getCharacteristic(mDev.NotificationChar)
                                .then(notifyChar => {
                              //      console.log("got notify characteristic");
                                    notifyChar.addEventListener('characteristicvaluechanged',notificationCallback);
                                    mDev.notificationCharObj = notifyChar;
                                    mDev.notificationCharObj.startNotifications()
                                    .catch(error => {
                                        console.error("Error turning on notifications: " + error.message)
                                    })
                                })
                        }
                        if (mDev.InitializationChar != null) {
                            service.getCharacteristic(mDev.InitializationChar)
                                .then(initChar => {
                                 //   console.log("got initialize characteristic");
                                    mDev.initializationCharObj = initChar;
                                })
                        }
                        if (mDev.MailboxChar != null) {
                            service.getCharacteristic(mDev.MailboxChar)
                                .then(mailboxChar => {
                               //     console.log("got mailbox characteristic");
                                    mDev.mailboxCharObj = mailboxChar;
                                })
                        }

                        if (mDev.CommandChar != null) {
                            service.getCharacteristic(mDev.CommandChar)
                                .then(commandChar => {
                                    isConnected=true;
                                  //  console.log("got command characteristic");
                                    mDev.commandCharObj = commandChar;
                                })
                        }
                       

                    })
                    .catch(error => {
                        console.error("Failed to get services: " + error.message);
                    });
            })
                .catch(error => {
                    console.error("Error requesting device: " + error.message)
                })
        })

      //  console.log(mDev);
 
}


function writeBytesUUID(characteristic, bytes) {
   // console.log("writeBytesUUID:");
  //  console.log(characteristic);
   // console.log(bytes);
    characteristic.writeValue(bytes)
        .catch(error => {
            console.error("writeBytesUUID Error writing characteristic: " + error.message);
        })
}




  function MantisDevice (devName1) {
  
   
   // console.log("DeviceName: " + devName1.split("_")[0]);
    switch (devName1) {
      case "sct":
        this.PrimaryService =     '0000ffd0-0000-1000-8000-00805f9b34fb';
        this.NotificationChar =   '0000ffd4-0000-1000-8000-00805f9b34fb';
        this.MailboxChar = '0000ffd1-0000-1000-8000-00805f9b34fb';
        this.CommandChar = '0000ffd2-0000-1000-8000-00805f9b34fb';
      break;
      //default:
   //       console.log("Dev Init: Unknown device, doing nothing: " + devName1);
  }
  
  
    this.primaryServiceObj =     null;
    this.notificationCharObj =   null;
    this.initializationCharObj = null;  
    this.mailboxCharObj = null;  
    this.commandCharObj = null; 
  
  
   // console.log(this);
  }

 
  function notificationCallback(event) {
    let value = event.target.value;
    let a = [];
    // Convert raw data bytes to hex values just for the sake of showing something.
    // In the "real" world, you'd use data.getUint8, data.getUint16 or even
    // TextDecoder to process raw data bytes.
    for (let i = 0; i < value.byteLength; i++) {
      a.push('0x' + ('00' + value.getUint8(i).toString(16)).slice(-2));	
    }
    if (a[0] == "0x02") {//distance packet

       Distance = toUInt16(bitConverter(a, 3, 4));
       console.log("Distance: " + Distance);
    }
  
  // console.log('data: ' + a);
  }

  var bitConverter = function bitConverter (arr, start, end) {
    var populate = function populate () {
        var data = [];
        for (var i = start; i <= end; i++) {
            (function (val) {
                data.push(arr[val]);
            })(i);
        }
        return data;
    };
    var combine = function combine (data, index) {
        if (index >= 0) {
            var tmp = data[index] << (index << 3);
            return tmp | combine(data, --index);
        } else {
            return 0;
        }
    };
    if (start >= 0 && start < arr.length && end > 0 && end < arr.length) {
        return combine(populate(), arr.length - 1);
    } else {
        throw Error('Invalid parameters to bitConverter: start: ' + start + ' end: ' + end + ' arr.length: ' + arr.length);
    }
};
/**
 * A function to treat a 16 bit integer as two's complement signed
 *
 * @param val A 16 bit integer
 * @returns {int} The parameter converted to its two's complement signed value
 */
var toSignedInt16 = function toSignedInt16 (val) {
    return (new Int16Array([val]))[0];
};

var toUInt16=function toUInt16(val) {
    return (new Uint16Array([val]))[0];
};

function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}



// Override console.log to append logs to the text area
console.log = function() {
  // Convert all arguments to a single string
  const message = Array.from(arguments).map(arg => String(arg)).join(" ");

  // Append the log message to the text area
  logTextArea.value += message + "\n";
  logTextArea.scrollTop = logTextArea.scrollHeight;
  // Call the original console.log
  originalLog.apply(console, arguments);
};


 function EnableMonitoring(state) { 

    isMonitoring=state;
    if (isMonitoring){
        checkDistanceValue();
    }
    else{
        clearInterval(interval);
        setTimeout(function () {
            stop();
        }, 500);//give time for other commands to clear before stopping.
       
    }
   

    console.log("monitor state: "+ state)
 }

 function checkDistanceValue() {
    var theshold=60;
    interval = setInterval(() => {
    // Perform your distance value check here
    //console.log("Check Distance: " + Distance );
    
    if (isMonitoring==1 && isConnected==true){//only do this if monitoring was started
      if (Distance < theshold){ 
          setTimeout(function () {  //slight Delay to prevent spamming the robot       
              stop();
          }, 200)      
      }
      else{
          setTimeout(function () {  //slight Delay to prevent spamming the robot       
              moveForward();
          }, 200)    
      }
  }
    
 
  }, 350); // Interval in milliseconds
}

 function ModiState(modistate) { 
  
    let byte2='';
    if (modistate == 'on') {
        byte2 = 0x01;
    }
    else {
        byte2 = 0x00;
    }

   // console.log("byte2 " + byte2);
    let payload2 = new Uint8Array(2);
    payload2[0] = 0x00;
    payload2[1] = byte2;
    writeBytesUUID(mDev.mailboxCharObj, payload2);
    
    let payload3 = new Uint8Array(2);
    payload3[0] = 0x0D;
    payload3[1] = 0x00;
    
     setTimeout(function () {
         self.writeBytesUUID(mDev.commandCharObj, payload3);
     //    console.log("Executed CMD after 300 MS delay");
     }, 300);//timeout
 
     console.log("Modi State:" + modistate)
 
 }

 function movecmd(payload_cmd, payload_data) {
    writeBytesUUID(mDev.mailboxCharObj, payload_data);

    setTimeout(function () {
        self.writeBytesUUID(mDev.commandCharObj, payload_cmd);
    //    console.log("Executed CMD after 300 MS delay");
    }, 300);//timeout
 
}


function moveForward() {
  
    //both PWM's set to same direction.
    let MTR1_PWM=100;
    let MTR2_PWM=100;
    


    let  payload_data  = new Uint8Array(2);
    payload_data[0] = MTR1_PWM;
    payload_data[1] = MTR2_PWM;
    
    let  payload_cmd  = new Uint8Array(2);
    payload_cmd [0] = 0x05;//controls both motors
    payload_cmd [1] = 0x00;
    
    movecmd(payload_cmd,payload_data);
    console.log("Move Forward")
 
 }

 
function moveBackward() {
  
    //both PWM's set to same direction.
    let MTR1_PWM=-100;
    let MTR2_PWM=-100;
    


    let  payload_data  = new Uint8Array(2);
    payload_data[0] = MTR1_PWM;
    payload_data[1] = MTR2_PWM;
    
    let  payload_cmd  = new Uint8Array(2);
    payload_cmd [0] = 0x05;//controls both motors
    payload_cmd [1] = 0x00;
    
    movecmd(payload_cmd,payload_data);
    console.log("Move Forward")
 
 }

 function turnLeft() {
  
    //both PWM's set to same direction.
    let MTR1_PWM=100;
    let MTR2_PWM=-100;
  

    let  payload_data  = new Uint8Array(2);
    payload_data[0] = MTR1_PWM;
    payload_data[1] = MTR2_PWM;
    
    let  payload_cmd  = new Uint8Array(2);
    payload_cmd [0] = 0x05;//controls both motors
    payload_cmd [1] = 0x00;
    
    movecmd(payload_cmd,payload_data);
    console.log("Turn Left")
 
 }

 function turnRight() {
  
    //both PWM's set to same direction.
    let MTR1_PWM=-100;
    let MTR2_PWM=100;
    
    
    let  payload_data  = new Uint8Array(2);
    payload_data[0] = MTR1_PWM;
    payload_data[1] = MTR2_PWM;
    
    let  payload_cmd  = new Uint8Array(2);
    payload_cmd [0] = 0x05;//controls both motors
    payload_cmd [1] = 0x00;
    
    movecmd(payload_cmd,payload_data);
    console.log("Turn Right")
 
 }

function stop() {
  //both PWM's set to same direction.
  let MTR1_PWM=0;
  let MTR2_PWM=0;
  

  let  payload_data  = new Uint8Array(2);
  payload_data[0] = MTR1_PWM;
  payload_data[1] = MTR2_PWM;
  
  let  payload_cmd  = new Uint8Array(2);
  payload_cmd [0] = 0x05;//controls both motors
  payload_cmd [1] = 0x00;
  
  movecmd(payload_cmd,payload_data);

  console.log("Stop")

}

man-in-the-loop exercise

This example builds upon the previus Man in the Loop Example.

💾Man-In-The-Loop Obstacle Avoidance Tutorial

In the ongoing exercise, there's a monitoring function named EnableMonitoring. This function sets motor commands according to the distance value acquired from the MODI sensor. activation or deactivation of this function is accomplished by clicking either the Start or Stop Monitoring buttons.

function EnableMonitoring(state) { 

    isMonitoring=state;
    if (isMonitoring){
        checkDistanceValue();
    }
    else{
        clearInterval(interval);
        setTimeout(function () {
            stop();
        }, 500);//give time for other commands to clear before stopping.
       
    }
    console.log("monitor state: "+ state)
 }

When Monitoring, the code enables a callback that executes the checkDistanceValue() funtion once every 350ms. When in that function, the code will issue the a stop command if the distance value is below a threshold set on line 2. Otherwise, the code will issue the moveForward() command.

 function checkDistanceValue() {
    var theshold=60;
    interval = setInterval(() => {
    // Perform your distance value check here
    //console.log("Check Distance: " + Distance );
    
    if (isMonitoring==1 && isConnected==true){//only do this if monitoring was started
      if (Distance < theshold){ 
          setTimeout(function () {  //slight Delay to prevent spamming the robot       
              stop();
          }, 200)      
      }
      else{
          setTimeout(function () {  //slight Delay to prevent spamming the robot       
              moveForward();
          }, 200)    
      }
  }
    
 
  }, 350); // Interval in milliseconds
}

In this experiment, adjust the threshold value and observe the performance of your robot. Additionally, modify the moveForward() command to achieve the desired PWM values for your Scout. Operate the robot in a space with obstacles, and iteratively fine-tune your settings until it can successfully maneuver without collisions.

Last updated

Was this helpful?