Sunday, April 3, 2016

Web Connected Mouse Robot - Part 3 - the backend

(Looking for the previous parts?  Here's part 1 of the series,  And here's part 2 )

The latest version for the code can always be found at: https://github.com/nirsoffer/robot-backend - I'd personally just copy & paste this into the particle.io IDE, because it doesn't seem to have very good integration with github (if at all).

Let's break it down, bit by bit - first the set up code:

 int leftPin = D5;   
 int middlePin = D6;  
 int rightPin = D7;  
 int pos = 0;  
 int dutyCycleDelay = 10;  
 void setup() {  
  pinMode(leftPin, OUTPUT);  
  pinMode(middlePin, OUTPUT);  
  pinMode(rightPin, OUTPUT);  
  Spark.function("setpos", setPosition);  
  Spark.function("getpos", getPosition);  
  Spark.function("right", rightDummy);  
  Spark.function("left", leftDummy);  
 }  

We are defining the left, middle and right pin to correspond where we've soldered the sensor pins before, D5, D6 and D7.

pos is a variable that controls the turning of the robot - 0 corresponds to all left, 100 to all right, 50 to middle. This is done to work more easily with the android seekbar, but of course, feel free to make it whatever makes sense to you :)

We set up pos to start at 0, which would make the robot turn around itself in a consistent loop. Which in my case is better than go forward in my limited kitchen space.  You can set it up to 50 if you want it to start with going forward.

At either rate - keep in mind that it takes about 20 seconds for the Particle core to get a grip on reality and start executing code, so there will be some unexpected results until it's up and running.

The next bit of code sets the IO mode of the pins and registers the internal functions with some web enabled functions.

On to the functions:
 // Next we have the loop function, the other essential part of a microcontroller program.  
 // This routine gets repeated over and over, as quickly as possible and as many times as possible, after the setup function is called.  
 // Note: Code that blocks for too long (like more than 5 seconds), can make weird things happen (like dropping the network connection). The built-in delay function shown below safely interleaves required background activity, so arbitrarily long delays can safely be done if you need them.  
 int resetPins() {  
   digitalWrite(leftPin, LOW);  
   digitalWrite(middlePin, LOW);  
   digitalWrite(rightPin, LOW);  
   return 0;  
 }  
 void pinBlink(int pin) {  
   resetPins();  
   digitalWrite(pin, HIGH);  
 }  
 void right() {   
   pinBlink(leftPin);  
 }  
 int rightDummy(String dummy) {  
   right();  
   return 1;  
 }  
 int leftDummy(String dummy) {  
   left();  
   return 1;  
 }  
 void left() {  
   pinBlink(rightPin);  
 }  
 void forward() {  
   digitalWrite(rightPin, LOW);  
   digitalWrite(leftPin, LOW);  
   digitalWrite(middlePin, HIGH);  
 }  
 int setPosition(String posValue) {  
   pos = posValue.toInt();  
   return pos;  
 }  
 int getPosition(String dummy) {  
   return pos;  
 }  


resetPins() moves all pins to zero; left() and right() just turn on the corresponding pins. setPosition() changes the global int pos, getPosition() gives you the current position. forward() simply turns on the middle pin and turns the rest off (yes, you can refactor it to use resetPins() and pinBlink() but I am lazy).

rightDummy() and leftDummy() are wrappers for left() and right() that are there to accept an argument on behalf of the web backend. All web enabled functions except setpos are there for debugging purposes only.

Finally, let's look at the main loop:
 void loop() {  
  // Pos goes from 0 to 100, meaning that 50 is middle so we...  
  int newpos = pos - 50;  
  // now -50 is full on left, 50 is full on right, 0 is straight ahead. we need to have a way of "blinking" the sensor on on a duty cycle.   
  // We'll use a stochastic duty cycle for smoothness -  
  // by that what I mean is that we generate a random number between 1-50, if it's below abs(pos) then we blink the pin high for 10ms, if it's above we keep it low  
   for (int i=0; i<50; i++) {  
     if ( random(50)<abs(newpos)) {  
        if (newpos > 0) left();  
        if (newpos < 0) right();  
        if (newpos == 0)   
        {   
          forward();  
        }  
     }  
     else {   
         forward();  
     }  
     delay(dutyCycleDelay);  
   }  
  // And repeat!  
 }  

What we're doing here is some sort of a stochastic PWM - we're flipping the corresponding sensor on, on average, for abs(pos-50)/50 of the time. If the position is 25, for instance, we will flip the "left" bit on, hopefully, 50% of the time, which would make it turn at 50% duty cycle. We do this other than alternatives (for instance, keep it on 25 ticks and then off 25 ticks) because randomizing it gives a certain smoothness. Keep in mind that we can't actually stop, so we want to spread out the turning and the forward as much as we can to give the illusion of a half turn rather than a full turn for 50% of the time and then stopping for the rest.

Not sure if that makes sense, but even if it doesn't, trust me ;)

Now that you have this - you can compile the backend, flash your particle, and begin your testing! Using curl you can quite simply see if setpos returns the correct results:

 curl https://api.spark.io/v1/devices/[your device id]/setpos -d access_token=[your access token] -d arg="0"  
This should return something like this:
 {  
  "id": "[your id]",  
  "last_app": "",  
  "connected": true,  
  "return_value": 0  
 }  

You can now play with the robot to your heart's content using curl and different arguments to setpos. Make sure that this part works well before proceeding to the next part - your android controller app!


If you're looking for the previous related posts - here's part 2 and here's part 1. Enjoy!



No comments:

Post a Comment