Simple Pi Car – Making a website

NOTE: This is an incomplete tutorial. I am seeking feedback, and in doing so published prior to completion.

Introduction: 

Interacting with robots–or whatever you’re building using Pi–is probably the primary reason you’re using a Pi over other SBMC‘s. The intention of this article is to walk-through setting up a single-page application that will display some information about your Pi, and then execute commands for you.

Assumptions: 

  • Basic knowledge of how to connect to your Pi via SSH or VNC
  • You have a running Raspberry Pi with an operating system installed and an operating network device on the Pi ( Raspbian download )
  • You have a running webserver and PHP installed

To Begin:

There is a lot of noise out there about which language to use or learn. Let me offer a piece of advice for anyone on the fence, just pick one and learn it all the way. PHP was my choice many years ago, and it has served me well. It’s also nice because you can write quick scripts, or full-on OOP applications.

Here’s what the we’re building – and here’s the repo on GitHub

picar-interface

This is a very primitive program, served by the Pi itself, which allows a user to:

  • set the heading in degrees [-90, 90]
  • set the speed [0, 255]
  • set execution time [0, infinity]

After setting these variables the user then chooses to click click to immediately execute the command, or you can drag one of the drag arrows to put the command in the queue, or if on a touch device click either click to add the command to the queue.  Clicking run cycles through the queue, while click clears the queue.

When there are commands in the queue it looks like this:

click

Step 1

Let’s drop in some boilerplate HTML to give our page a decent look. We’re not worried about styling, just function, so we can get this done easily.

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="">
    <meta name="author" content="">

    <title>Robo Controller</title>

    <!-- Bootstrap Core CSS -->
    <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">  
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue-resource/1.0.2/vue-resource.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.14.0/babel.min.js"></script>
</head> 
<body>
    <div id="app"> 
        <div id="wrapper"> 
        </div> 
    </div> 
</body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.14.0/babel.min.js"></script>
<script type="text/babel">
// our app will live here 
</script> 
</html>

This boilerplate sets the basic HTML template for our webpage. The template includes Bootstrap CSS styling for us, and the javascript libraries we’ll be using to create an interactive page. You can visit the page in your browser to see it working ( a blank page with the title Robo Controller )

Let’s put in the navigation bar, paste this in right after the opening <body> tag

<!-- Navigation -->
<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
    <div class="navbar-header">
        <a class="navbar-brand" href="#">
           Welcome, my IP is <?php echo empty($_SERVER['SERVER_ADDR']) ?: $_SERVER['SERVER_ADDR']; ?>
        </a>
 </div>
 <!-- /.navbar-collapse -->
</nav>

If you look you can see that I’m using PHP inside the HTML, this is a very common practice in hobby PHP applications. It’s not a great idea, though, as programs grow to do this. Things quickly become too complicated to update or troubleshoot. The rule of thumb is to separate business logic from presentation. We can leave this here, as we won’t be doing much PHP on this page.

Next inside the <div id="page-wrapper"></div> tag insert this:

<div id="page-wrapper">
    <div class="container-fluid" style="min-height:800px;">
        <div class="panel panel-default" id="robo-controller">
            <div class="panel-heading">
                <h3 class="panel-title">Controller</h3>
            </div>
            <div class="panel-body">
                <div class="row">
                    <div class="col-md-12">
                        <div class="panel panel-default">
                            <div class="panel-body">
                                <div class="col-md-6">
                                    <div class="input-group">
                                        <button class="btn btn-primary"
                                                @click="sendCommand('FORWARD')"
                                        >
                                                    <span class="glyphicon glyphicon-chevron-up"
                                                          aria-hidden="true"></span>
                                        </button>
                                    </div>

                                    <div class="input-group">
                                        <button class="btn btn-primary"
                                                @click="sendCommand('REVERSE')"
                                        >
                                                    <span class="glyphicon glyphicon-chevron-down"
                                                          aria-hidden="true"></span>
                                        </button>
                                    </div>
                                    <div class="input-group">
                                        <label>Heading</label>
                                        <input
                                                v-model="heading"
                                                class="form-control"
                                                type="range"
                                                min="-90"
                                                max="90"
                                                step="1"
                                        />
                                        <div class="input-group">
                                            <input type="number" min="-90" max="90" step="1" class="form-control" v-model="heading" >
                                            <span class="input-group-addon" >°</span>
                                        </div>
                                    </div>
                                </div>
                                <div class="col-md-6">
                                    <div class="input-group">
                                        <label>Speed</label>
                                        <input class="form-control" type="number" min="0" max="255" step="1"
                                               v-model="speed">
                                    </div>
                                    <div class="input-group">
                                        <label>Time</label>
                                        <input class="form-control" type="number" min="0" max="255" step="0.1"
                                               v-model="moment">
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
                <div class="row">
                    <div class="col-md-12">
                        <div class="panel panel-default">
                            <div class="panel-body">
                                <div class="row">
                                    <div class="btn-group">
                                        <button draggable="true" @touchStare="dragStart" @dragstart="dragStart" id="up" class="btn btn-primary">
                                            <span class="glyphicon glyphicon-chevron-up" aria-hidden="true"></span>
                                        </button>
                                        <button draggable="true" @dragstart="dragStart" @touchStart="dragStart" id="down" class="btn btn-primary">
                                            <span class="glyphicon glyphicon-chevron-down" aria-hidden="true"></span>
                                        </button>
                                    </div>
                                    <div class="btn-group">
                                        <button class="btn btn-primary" @click="run"><span
                                                class="glyphicon glyphicon-ok" aria-hidden="true"> Run</span>
                                        </button>
                                        <button class="btn btn-primary" @click="clear"><span
                                                class="glyphicon glyphicon-remove" aria-hidden="true"></span>
                                        </button>
                                    </div>
                                    <div class="btn-group">
                                        <button class="btn btn-primary" @click="addManual('up')">
                                            <span class="glyphicon glyphicon-chevron-up" aria-hidden="true"> Add </span>
                                        </button>
                                        <button class="btn btn-primary" @click="addManual('down')">
                                            <span class="glyphicon glyphicon-chevron-down" aria-hidden="true"> Add </span>
                                        </button>
                                    </div>
                                    <p></p>
                                    <div class="well" @dragover.prevent @drop="onDrop" id="queue">
                                        <span v-if="queue.length == 0">{{ prompt }}</span>
                                        <span v-for="item in queue">
                                        <button class="btn btn-primary">
                                            <span class="glyphicon glyphicon-chevron-{{command}}"
                                                  aria-hidden="true"
                                            >
                                                Command: go {{item.command == 'up' ? 'forward' : 'reverse'}} at angle {{item.heading}}° for {{item.moment}} at speed {{item.speed}}
                                            </span>
                                        </button>
                                        <span v-if="queue.length > 1 && $index !== queue.length - 1" >
                                            <code>then--></code>
                                        </span>
                                    </span>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
                <div class="row">
                    <div class="col-md-12">
                        <div class="panel panel-default">
                            <div class="panel-body">
                                <ul class="list-group">
                                    <li class="list-group-item" v-for="item in responses" >
                                        {{item.body | json}}
                                    </li>
                                </ul>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
    <!-- /.container-fluid -->
</div>
<!-- /#page-wrapper -->

That’s quite a bit, but I want to move us along to getting commands working. In a very short summary, what was inserted was the remaining HTML with the page’s layout, and the Vue.js javascript library {{ variables }} inside curly braces.

Visiting the page now will reveal what we’re going to program to connect to your Pi.

vuebrackets

A small caveat: when I made this I wasn’t anticipating showing/sharing/teaching anyone about it, so the structure and naming conventions aren’t exactly optimal–feel free to change variable and/or method names as you see fit.

Next replace // our app will live here with:

var controller = new Vue({
        el: '#app',
        data: {},
        methods: {},
    });

cc

Give the page a refresh and see that the bracket variables shown above are now gone. Vue was loaded before, but wasn’t aware of where it should attach to the page. Since we defined the <div id=”#app”> as the Vue’s el it knows where to attach, parse, and bind.

Most browsers have an inspection console. For example hit F12 in chrome to launch theirs. I recommend getting familiar with web inspectors and various debugging tools and extensions. Anything that can provide insight into a program is always helpful!

 

Remember that PHP from earlier?

<?php echo empty($_SERVER['SERVER_ADDR']) ?: $_SERVER['SERVER_ADDR']; ?>

We’re going to make use of Vue and PHP.

Replace that PHP echo statement with:

{{ ip_address }}

Change Vue’s data: {} property to this:

var controller = new Vue({
        el: '#app',
        data: {
          ip_address: "<?php echo empty($_SERVER['SERVER_ADDR']) ?: $_SERVER['SERVER_ADDR']; ?>",
        }, 
        methods: {}, 
});

So now what we’re doing it passing that echo statement directly to Vue. If you refresh the page you should see the Pi’s IP address in the top bar. Vue automatically searches within it’s element ( <div id=”app”> ) for curly braces. If it has a property or method that matches the {{ variable_name }} it automatically binds it’s value to that spot in the DOM

The remaining steps include:

  • “Hooking” each button to a method
  • Triggering a request to server when we want to execute a command
  • Listening for, verifying and executing the command server side
  • responding to our webpage with the results of the command’s execution

Each of these steps is relatively in-depth, so I’ll be creating a tutorial for each. As they’re finished I will link the bullet points to each.

As always, please leave any questions or comments below.

 

Tags: , , ,