Large Rover

 
It is about 50x50cm with 20cm wheels and uses PiBorg Diable DC motor controller.
It has the same control system as the Small Rover but it do not use the internal DC motor controller.
  
  • Drag on the right side of the screen with your mouse or your finger to control the movement.
  • And drag up and down on the left side to tilt the camera.
  • Double tap on the screen to toggle the light.
 

Two videos where i drive it out in the snow

Below is the HTML and python code, it is not so much because the Steelsquid Kiss OS gives a lot of functionality automatically. All this code is already in Steelsquid Kiss OS you can enable it by executing:
steelsquid module kiss_rover on small
But for it to work you need this:
See the newest file on GitHub

kiss_rover.py
#!/usr/bin/python -OO


'''.
Fuctionality for my rover controller
Also see rover.html (called from utils.html)

For this to start on boot you need to enable it, you can do it like this:
Command line: steelsquid module kiss_expand on
Python: steelsquid_kiss_global.module_status("kiss_expand", True)
Syncrinization program: Press E and then enter

You can omit all the undelaying methods and classes and this module will just be imported...

Varibale is_started
 Is this module started
 This is set by the system automatically.

If a method named enable exist:
 When this module is enabled what needs to be done (execute: steelsquid module XXX on)
 Maybe you need create some files or enable other stuff.

If a method named disable exist:
 When this module is disabled what needs to be done (execute: steelsquid module XXX off)
 Maybe you need remove some files or disable other stuff.

If Class with name SETTINGS exist:
 The system will try to load settings with the same name as all variables in the class SETTINGS.
 If the variable value is Boolean: steelsquid_utils.get_flag("variable_name")
 If the variable value is Integer, Float, String: steelsquid_utils.get_parameter("variable_name")
 If the variable value is Array []: steelsquid_utils.get_list("variable_name")
 The variable value will also be used as default value if the paramater or list not is found
 When the system shutdowen the value of the variable will also be saved to disk
 EX: 
   class SETTINGS(object):
       this_is_a_flag = False
       this_is_a_parameter = "a_default_value"
       this_is_a_list = []
   System try to read: steelsquid_utils.get_flag("this_is_a_flag")
   System try to read: steelsquid_utils.get_parameter("this_is_a_parameter", "a_default_value")
   System try to read: steelsquid_utils.get_list("this_is_a_list", [])
 If you want to disable save and read the settings from disk add a variable like this.
 This is usefull under development if you wan to test different values when you restart the module,
 otherwise the value from the first execution to be used ...
   _persistent_off = True
 To sum up: Variables in class SETTINGS that has value: Boolean, Array, Integer, Float, String will be persistent.

If Class with name SYSTEM has this staticmethods
 on_start() exist it will be executed when system starts (boot)
 on_stop() exist it will be executed when system stops (shutdown)
 on_network(status, wired, wifi_ssid, wifi, wan) exist it will be execute on network up or down
 on_vpn(status, name, ip) This will fire when a VPN connection is enabled/disabled.
 on_bluetooth(status) exist it will be execute on bluetooth enabled
 on_mount(type_of_mount, remote, local) This will fire when USB, Samba(windows share) or SSH is mounted.
 on_umount(type_of_mount, remote, local) This will fire when USB, Samba(windows share) or SSH is unmounted.
 on_event_data(key, value) exist it will execute when data is changed with steelsquid_kiss_global.set_event_data(key, value)

If Class with name LOOP
 Every static method with no inparameters will execute over and over again untill it return None or -1
 If it return a number larger than 0 it will sleep for that number of seconds before execute again.
 If it return 0 it will not not sleep, will execute again immediately.
 Every method will execute in its own thread

Class with name EVENTS
 Create staticmethods in this class to listen for asynchronous events.
 Example: If you have a method like this:
   @staticmethod
   def this_is_a_event(a_parameter, another_parameter):
      print a_parameter+":"+another_parameter
 Then if a thread somewhere in the system execute this: steelsquid_kiss_global.broadcast_event("this_is_a_event", ("para1", "para2",))
 The method def this_is_a_event will execute asynchronous

Class with name WEB:
 Methods in this class will be executed by the webserver if module is activated and the webserver is enabled
 If is a GET it will return files and if it is a POST it executed commands.
 It is meant to be used as follows.
 1. Make a call from the browser (GET) and a html page is returned back.
 2. This html page then make AJAX (POST) call to the server to retrieve or update data.
 3. The data sent to and from the server can just be a simple list of strings.
 See steelsquid_http_server.py for more examples how it work

Class with name SOCKET:
 Methods in this class will be executed by the socket connection if module is activated and the socket connection is enabled
 A simple class that i use to sen async socket command to and from client/server.
 A request can be made from server to client or from client to server
 See steelsquid_connection.py and steelsquid_socket_connection.py
 - on_connect(remote_address): When a connection is enabled
 - on_disconnect(error_message): When a connection is lost

If this is a PIIO board
Methods in this class will be executed by the system if module is enabled and this is a PIIO board
Enebale this module like this: steelsquid piio-on
 on_voltage_change(voltage) Will fire when in voltage to the PIIO board i changed 
 on_low_bat(voltage) exist it will execute when voltage is to low.
 on_button(button_nr) exist it will execute when button 1 to 6 is clicken on the PIIO board
 on_button_info() exist it will execute when info button clicken on the PIIO board
 on_switch(dip_nr, status) exist it will execute when switch 1 to 6 is is changed on the PIIO board
 on_movement(x, y, z) will execute if Geeetech MPU-6050 is connected and the device is moved.
 on_rotation(x, y) will execute if Geeetech MPU-6050 is connected and the device is tilted.

The class with name GLOBAL
 Put global staticmethods in this class, methods you use from different part of the system.
 Maybe the same methods is used from the WEB, SOCKET or other part, then put that method her.
 It is not necessary to put it her, you can also put it direcly in the module or use a nother name (but i think it is kind of nice to have it inside this class)

@organization: Steelsquid
@author: Andreas Nilsson
@contact: steelsquid@gmail.com
@license: GNU Lesser General Public License v2.1
@change: 2014-12-26 Created
'''


import steelsquid_utils
import steelsquid_pi
import steelsquid_piio
import steelsquid_kiss_global
import time
import datetime


# Is this module started
# This is set by the system automatically.
is_started = False


def enable(argument=None):
    '''
    When this module is enabled what needs to be done (execute: steelsquid module XXX on)
    Maybe you need create some files or enable other stuff.
    argument: Send data to the enable or disable method in the module
              Usually a string to tell the start/stop something
    '''
        
    #Clear any saved settings for this module
    steelsquid_kiss_global.clear_modules_settings("kiss_rover")
    # Get what tyoe of rover this is from the start argument
    if argument!=None and argument=="large":
        steelsquid_utils.set_parameter("rover_type", "large")
        steelsquid_utils.set_parameter("servo_position_start", "70")
        steelsquid_utils.set_parameter("servo_position_max", "160")
        steelsquid_utils.set_parameter("servo_position_min", "10")
        steelsquid_utils.set_parameter("motor_forward_max", "1000")
        steelsquid_utils.set_parameter("motor_backward_max", "-1000")
        steelsquid_utils.set_parameter("motor_forward_start", "75")
        steelsquid_utils.set_parameter("motor_backward_start", "-75")
    elif argument!=None and argument=="mini":
        steelsquid_utils.set_parameter("rover_type", "mini")
        steelsquid_utils.set_parameter("servo_position_start", "85")
        steelsquid_utils.set_parameter("servo_position_max", "140")
        steelsquid_utils.set_parameter("servo_position_min", "30")
        steelsquid_utils.set_parameter("motor_forward_max", "800")
        steelsquid_utils.set_parameter("motor_backward_max", "-800")
        steelsquid_utils.set_parameter("motor_forward_start", "170")
        steelsquid_utils.set_parameter("motor_backward_start", "-170")
    else:
        steelsquid_utils.set_parameter("rover_type", "small")
        steelsquid_utils.set_parameter("servo_position_start", "210")
        steelsquid_utils.set_parameter("servo_position_max", "230")
        steelsquid_utils.set_parameter("servo_position_min", "80")
        steelsquid_utils.set_parameter("motor_forward_max", "1023")
        steelsquid_utils.set_parameter("motor_backward_max", "-1023")
        steelsquid_utils.set_parameter("motor_forward_start", "200")
        steelsquid_utils.set_parameter("motor_backward_start", "-200")
    # Enable the PIIO board
    if not steelsquid_kiss_global.is_module_enabled("kiss_piio") and not steelsquid_kiss_global.stream():
        steelsquid_kiss_global.module_status("kiss_piio", True, restart=False) # Not trigger reboot
    elif not steelsquid_kiss_global.is_module_enabled("kiss_piio") and steelsquid_kiss_global.stream():
        steelsquid_kiss_global.module_status("kiss_piio", True, restart=True) # Trigger reboot
    # Enable PI camera streaming (if necessary)
    if not steelsquid_kiss_global.stream() == "pi":
        steelsquid_kiss_global.stream_pi() #Will trigger reboot


def disable(argument=None):
    '''
    When this module is disabled what needs to be done (execute: steelsquid module XXX off)
    Maybe you need remove some files or disable other stuff.
    argument: Send data to the enable or disable method in the module
              Usually a string to tell the start/stop something
    '''
    # Disable the PIIO board and PI camera streaming (if necessary)
    if steelsquid_kiss_global.is_module_enabled("kiss_piio") and steelsquid_kiss_global.stream():
        steelsquid_kiss_global.module_status("kiss_piio", False, restart=False) # Not trigger reboot
    elif steelsquid_kiss_global.is_module_enabled("kiss_piio") and steelsquid_kiss_global.stream():
        steelsquid_kiss_global.module_status("kiss_piio", False, restart=True) # Trigger reboot
    if steelsquid_kiss_global.stream() == "pi":
        steelsquid_kiss_global.stream_off() #Will trigger reboot


class SETTINGS(object):
    '''
    The system will try to load settings with the same name as all variables in the class SETTINGS.
    If the variable value is Boolean: steelsquid_utils.get_flag("variable_name")
    If the variable value is Integer, Float, String: steelsquid_utils.get_parameter("variable_name")
    If the variable value is Array []: steelsquid_utils.get_list("variable_name")
    The variable value will also be used as default value if the paramater or list not is found
    When the system shutdowen the value of the variable will also be saved to disk
    EX: this_is_a_flag = False
        this_is_a_parameter = "a_default_value"
        this_is_a_list = []
    System try to read: steelsquid_utils.get_flag("this_is_a_flag")
    System try to read: steelsquid_utils.get_parameter("this_is_a_parameter", "a_default_value")
    System try to read: steelsquid_utils.get_list("this_is_a_list", [])
    If you want to disable save and read the settings from disk add a variable like this.
    This is usefull under development if you wan to test different values when you restart the module,
    otherwise the value from the first execution to be used ...
      _persistent_off = True
    To sum up: Variables in class SETTINGS that has value: Boolean, Array, Integer, Float, String will be will be persistent.
    '''
    
    # This will tell the system not to save and read the settings from disk
    _persistent_off = True

    # What type of rover is this. This is set by the enable module (Small or large, default mini)
    rover_type = "mini"

    # The lamp is connected to this POWER PIN
    lamp_power_pin = 1

    # The front edge detection sensor is connected to this GPIO
    front_edge_gpio = 14

    # The front edge detection sensor is connected to this GPIO
    left_edge_gpio = 15

    # The front edge detection sensor is connected to this GPIO
    right_edge_gpio = 13

    # The front edge detection sensor is connected to this GPIO
    back_edge_gpio = 16

    # The distance sensor gpios
    front_echo_gpio = 1
    front_trig_gpio = 17
    front_right_echo_gpio = 2
    front_right_trig_gpio = 18
    right_echo_gpio = 3
    right_trig_gpio = 19
    back_right_echo_gpio = 4
    back_right_trig_gpio = 20
    back_echo_gpio = 5
    back_trig_gpio = 21
    back_left_echo_gpio = 6
    back_left_trig_gpio = 22
    left_echo_gpio = 7
    left_trig_gpio = 9
    front_left_echo_gpio = 8
    front_left_trig_gpio = 10

    # Number of seconds until drive stop if no commands from client (connection lost, stop the rover)
    max_drive_delta = 1

    # When system start move servo here
    servo_position_start = 85

    # Max Servo position
    servo_position_max = 140

    # Min Servo position
    servo_position_min = 30

    # Motor max forward
    motor_forward_max = 800

    # Motor max backward
    motor_backward_max = -800

    # start with this value when drive forward (if lower than this the motor do not turn)
    motor_forward_start = 170

    # start with this value when drive backward (if lower than this the motor do not turn)
    motor_backward_start = -170


class SYSTEM(object):
    '''
    Methods in this class will be executed by the system if module is enabled
    on_start() exist it will be executed when system starts (boot)
    on_stop() exist it will be executed when system stops (shutdown)
    on_network(status, wired, wifi_ssid, wifi, wan) exist it will be execute on network up or down
    on_vpn(status, name, ip) This will fire when a VPN connection is enabled/disabled.
    on_bluetooth(status) exist it will be execute on bluetooth enabled
    on_mount(type_of_mount, remote, local) This will fire when USB, Samba(windows share) or SSH is mounted.
    on_umount(type_of_mount, remote, local) This will fire when USB, Samba(windows share) or SSH is unmounted.
    on_event_data(key, value) exist it will execute when data is changed with steelsquid_kiss_global.set_event_data(key, value)
    '''

    @staticmethod
    def on_start():
        '''
        This will execute when system starts
        Do not execute long running stuff here, do it in on_loop...
        '''
        steelsquid_utils.shout("Steelsquid Rover enabled ("+SETTINGS.rover_type+")")
        # Set servo start position
        WEB.servo_position = SETTINGS.servo_position_start
        # Move the sevo to start position
        steelsquid_piio.servo(1, WEB.servo_position)
        # The mini rover has edge detection sensors
        if SETTINGS.rover_type=="mini":
            steelsquid_piio.gpio_event(SETTINGS.front_edge_gpio, SYSTEM.on_edge, bouncetime_ms=0, resistor=steelsquid_piio.PULL_NONE)         
            steelsquid_piio.gpio_event(SETTINGS.left_edge_gpio, SYSTEM.on_edge, bouncetime_ms=0, resistor=steelsquid_piio.PULL_NONE)         
            steelsquid_piio.gpio_event(SETTINGS.right_edge_gpio, SYSTEM.on_edge, bouncetime_ms=0, resistor=steelsquid_piio.PULL_NONE)         
            steelsquid_piio.gpio_event(SETTINGS.back_edge_gpio, SYSTEM.on_edge, bouncetime_ms=0, resistor=steelsquid_piio.PULL_NONE)         


    @staticmethod
    def on_stop():
        '''
        This will execute when system stops
        Do not execute long running stuff here
        '''
        pass
        

    @staticmethod
    def on_edge(gpio, state):
        '''
        On edge detection
        If front edge detect: state=True
        '''
        # Light different PIIO leds
        if gpio == SETTINGS.front_edge_gpio:
            steelsquid_piio.led(4, state)
        elif gpio == SETTINGS.left_edge_gpio:
            steelsquid_piio.led(3, state)
        elif gpio == SETTINGS.right_edge_gpio:
            steelsquid_piio.led(2, state)
        elif gpio == SETTINGS.back_edge_gpio:
            steelsquid_piio.led(1, state)
        

class LOOP(object):
    '''
    Every static method with no inparameters will execute over and over again untill it return None or -1
    If it return a number larger than 0 it will sleep for that number of seconds before execute again.
    If it return 0 it will not not sleep, will execute again immediately.
    Every method will execute in its own thread
    '''
    
    # Last distance read
    front_distance=999
    front_right_distance=999
    right_distance=999
    back_right_distance=999
    back_distance=999
    back_left_distance=999
    left_distance=999
    front_left_distance=999
    
    @staticmethod
    def on_loop():
        '''
        Execute every 0.5 second
        '''    
        # If more than "configurable" second since last drive command stop the drive (connection may be lost)
        drive_delta = datetime.datetime.now() - WEB.last_drive_command
        if drive_delta.total_seconds()>1:
            steelsquid_piio.motor(0, 0)
        # Read front sensor distance
        LOOP.front_distance = steelsquid_piio.hcsr04_distance(SETTINGS.front_trig_gpio, SETTINGS.front_echo_gpio)
        LOOP.front_right_distance = steelsquid_piio.hcsr04_distance(SETTINGS.front_right_trig_gpio, SETTINGS.front_right_echo_gpio)
        LOOP.right_distance = steelsquid_piio.hcsr04_distance(SETTINGS.right_trig_gpio, SETTINGS.right_echo_gpio)
        LOOP.back_right_distance = steelsquid_piio.hcsr04_distance(SETTINGS.back_right_trig_gpio, SETTINGS.back_right_echo_gpio)
        LOOP.back_distance = steelsquid_piio.hcsr04_distance(SETTINGS.back_trig_gpio, SETTINGS.back_echo_gpio)
        LOOP.back_left_distance = steelsquid_piio.hcsr04_distance(SETTINGS.back_left_trig_gpio, SETTINGS.back_left_echo_gpio)
        LOOP.left_distance = steelsquid_piio.hcsr04_distance(SETTINGS.left_trig_gpio, SETTINGS.left_echo_gpio)
        LOOP.front_left_distance = steelsquid_piio.hcsr04_distance(SETTINGS.front_left_trig_gpio, SETTINGS.front_left_echo_gpio)
        return 0.3 # Execute this method again in half a second


class WEB(object):
    '''
    Methods in this class will be executed by the webserver if module is enabled and the webserver is enabled
    If is a GET it will return files and if it is a POST it executed commands.
    It is meant to be used as follows.
    1. Make a call from the browser (GET) and a html page is returned back.
    2. This html page then make AJAX (POST) call to the server to retrieve or update data.
    3. The data sent to and from the server can just be a simple list of strings.
    For more examples how it work:
     - steelsquid_http_server.py
     - steelsquid_kiss_http_server.py
     - web/index.html
    '''
    
    # Current servo position
    servo_position = 210

    # Is the lamp on or off
    lamp_status = False
    
    # Last time the client send a drive command
    last_drive_command = datetime.datetime.now()

    
    @staticmethod
    def rover_settings(session_id, parameters):
        '''
        Get info on the rover
        '''
        return [WEB.servo_position, SETTINGS.servo_position_max, SETTINGS.servo_position_min, SETTINGS.motor_forward_max, SETTINGS.motor_backward_max, SETTINGS.motor_forward_start, SETTINGS.motor_backward_start, WEB.lamp_status, SETTINGS.rover_type]


    @staticmethod
    def rover_status(session_id, parameters):
        '''
        Get status
        '''
        return [steelsquid_piio.volt_last(), LOOP.front_distance, LOOP.front_right_distance, LOOP.right_distance, LOOP.back_right_distance, LOOP.back_distance, LOOP.back_left_distance, LOOP.left_distance, LOOP.front_left_distance]
    
    
    @staticmethod
    def rover_camera(session_id, parameters):
        '''
        Tilt camera up and down
        '''
        position = int(parameters[0])
        if position>SETTINGS.servo_position_max:
            position=SETTINGS.servo_position_max
        elif position<SETTINGS.servo_position_min:
            position=SETTINGS.servo_position_min
        WEB.servo_position = position
        steelsquid_piio.servo(1, position)


    @staticmethod
    def rover_drive(session_id, parameters):
        '''
        Tilt camera up and down
        '''
        left = int(parameters[0])
        right = int(parameters[1])
        if left>SETTINGS.motor_forward_max:
            left=SETTINGS.motor_forward_max
        elif left<SETTINGS.motor_backward_max:
            left=SETTINGS.motor_backward_max
        if right>SETTINGS.motor_forward_max:
            right=SETTINGS.motor_forward_max
        elif right<SETTINGS.motor_backward_max:
            right=SETTINGS.motor_backward_max
        WEB.last_drive_command = datetime.datetime.now()
        
        
        
        # The large rover uses Piborg diablo motor controller
        if SETTINGS.rover_type=="large":
            steelsquid_pi.diablo_motor_1(left);
            steelsquid_pi.diablo_motor_2(right);
        else:
            steelsquid_piio.motor(left, right)
    
        
    @staticmethod
    def rover_lamp(session_id, parameters):
        '''
        Turn the lamp on and off
        '''
        status = steelsquid_utils.to_boolean(parameters[0])
        steelsquid_piio.power(SETTINGS.lamp_power_pin, status)
        steelsquid_piio.power(SETTINGS.lamp_power_pin+1, status) 
        


rover.html
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" /> 
        <title>Kiss OS Rover</title>
        <meta name="Steelsquid Kiss OS" content="Controll a rover with Steelsquid PIIO board" />
        <meta name="keywords" content="steelsquid, steelsquid-kiss-os, raspberry, pi, debian, linux, distribution, administrator, configure, rover" />
        <meta name="author" content="Andreas Nilsson (Steelsquid)" />
        <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"/>     
        <meta name="mobile-web-app-capable" content="yes"/>
        <meta name="apple-mobile-web-app-capable" content="yes"/>
        <link rel="icon" href="favicon.ico" type="image/x-icon"/>
        <link rel="shortcut icon" href="favicon.ico" type="image/x-icon"/>
        <link rel="stylesheet" type="text/css" href="steelsquid.css"/>
        <script type="text/javascript" src="jquery.js"></script>
        <script type="text/javascript" src="steelsquid.js"></script>     
        <style type="text/css">
            /* Necessary for correct mouse co-ords in Firefox */
            #app {
                -webkit-touch-callout: none;
                -webkit-user-select: none;
                -khtml-user-select: none;
                -moz-user-select: none;
                -ms-user-select: none;
                user-select: none;
            }
            /* Necessary for correct mouse co-ords in Firefox */
            #pad {
                position:relative;
            }
        </style>
        <script type="text/javascript">
            //<![CDATA[ 

            // Is this a touch device
            var isTouch = (('ontouchstart' in window) || (navigator.msMaxTouchPoints > 0));
            
            // Variables for referencing the canvas and 2dcanvas context
            var canvas;
            var ctx;

            // Video location
            videoLeft=0;
            videoTop=0;
            videoWidth=0;
            videoHeight=0;

            /**
             * Draws a dot at a specific position on the canvas
             */
            function drawDot(x, y ,size) {
                // Let's use black by setting RGB values to 0, and 255 alpha (completely opaque)
                r=0; g=0; b=0; a=255;

                // Select a fill style
                ctx.fillStyle = "rgba("+r+","+g+","+b+","+(a/255)+")";

                // Draw a filled circle
                ctx.beginPath();
                ctx.arc(x, y, size, 0, Math.PI*2, true); 
                ctx.closePath();
                ctx.fill();           
            } 
            
            /**
             * Draws a line
             */
            function drawLine(x_start, y_start, x_stop, y_stop) {
                ctx.beginPath();
                ctx.moveTo(x_start, y_start);
                ctx.lineTo(x_stop, y_stop);
                ctx.lineWidth = 4;
                ctx.strokeStyle = '#000000';
                ctx.stroke();                
            } 
            
            /**
             * Draws drawDistance
             */
            function drawDistance() {
                if(videoWidth>0){
                    //Top left
                    fromLeft = videoLeft;
                    fromTop = videoTop-1;
                    startAngle = 0;
                    endAngle = Math.PI/2;
                    drawPay(fromLeft, fromTop, startAngle, endAngle, front_left_distance);
                    //Top
                    fromLeft = videoLeft + videoWidth/2;
                    fromTop = videoTop-1;
                    startAngle = 0;
                    endAngle = Math.PI;
                    drawPay(fromLeft, fromTop, startAngle, endAngle, front_distance)
                    //Top right
                    fromLeft = videoLeft+videoWidth;
                    fromTop = videoTop-1;
                    startAngle = Math.PI/2;
                    endAngle = Math.PI;
                    drawPay(fromLeft, fromTop, startAngle, endAngle, front_right_distance);
                    //Right
                    fromLeft = videoLeft+videoWidth;
                    fromTop = videoTop-36 + videoHeight/2;
                    startAngle = Math.PI/2;
                    endAngle = Math.PI*1.5;
                    drawPay(fromLeft, fromTop, startAngle, endAngle, right_distance);
                    //Back Right
                    fromLeft = videoLeft+videoWidth;
                    fromTop = videoTop + videoHeight-36;
                    startAngle = Math.PI;
                    endAngle = Math.PI*1.5;
                    drawPay(fromLeft, fromTop, startAngle, endAngle, back_right_distance);
                    //Back
                    fromLeft = videoLeft+videoWidth/2;
                    fromTop = videoTop + videoHeight-36;
                    startAngle = Math.PI;
                    endAngle = 0;
                    drawPay(fromLeft, fromTop, startAngle, endAngle, back_distance);
                    //Back left
                    fromLeft = videoLeft;
                    fromTop = videoTop + videoHeight-36;
                    startAngle = Math.PI*1.5;
                    endAngle = 0;
                    drawPay(fromLeft, fromTop, startAngle, endAngle, back_left_distance);
                    //left
                    fromLeft = videoLeft;
                    fromTop = videoTop-36 + videoHeight/2;
                    startAngle = Math.PI*1.5;
                    endAngle = Math.PI*0.5;
                    drawPay(fromLeft, fromTop, startAngle, endAngle, left_distance);
                }
 
            } 

            function drawPay(fromLeft, fromTop, startAngle, endAngle, distance) {
                radius = videoHeight/15;
                dest_rad = remap(distance, 4, 80, radius-3, 0)
                ctx.beginPath();
                ctx.strokeStyle = '#000000';
                ctx.lineWidth = 2;
                ctx.arc(fromLeft, fromTop, radius, startAngle, endAngle, false); 
                ctx.stroke();      
                ctx.beginPath();
                ctx.strokeStyle = '#ffffff';
                ctx.lineWidth = 2;
                ctx.arc(fromLeft, fromTop, radius-2, startAngle, endAngle, false); 
                ctx.stroke();      
                ctx.beginPath();
                ctx.moveTo(fromLeft, fromTop);
                ctx.fillStyle = '#ff0000';
                ctx.arc(fromLeft, fromTop, dest_rad, startAngle, endAngle, false); 
                ctx.closePath();
                ctx.fill();                          
            }
            
            
            /**
             * Draws text on canvas
             */
            function drawText(text, x, y) {
                ctx.fillStyle = '#000000';
                ctx.font = "13px Arial";
                ctx.fillText(text, x, y);                           
            } 


            /**
             * Draws text vertical on canvas
             */
            function drawTextVerical(text, toLeft) {
                var x = canvas.offsetWidth-8;
                var y = (canvas.offsetHeight / 2)-20;
                ctx.save();
                if (toLeft){
                    ctx.translate(16, y);
                }
                else{
                    ctx.translate(x, y);
                }
                ctx.fillStyle = '#000000';
                ctx.rotate(-Math.PI/2);
                ctx.textAlign = "center";
                ctx.font = "13px Arial";
                ctx.fillText(text, 0, 0);
                ctx.restore();
            } 

            /**
             * Draws text vertical in canvas
             */
            function drawDoubleText(verticalText1, verticalText2) {
                var x = canvas.offsetWidth-8;
                var y = (canvas.offsetHeight / 2)-20;
                ctx.save();
                ctx.fillStyle = '#000000';
                ctx.translate(x, y);
                ctx.rotate(-Math.PI/2);
                ctx.textAlign = "center";
                ctx.font = "13px Arial";
                ctx.fillText(verticalText1, 0, 0);
                ctx.restore();
                ctx.save();
                ctx.translate(x-20, y);
                ctx.rotate(-Math.PI/2);
                ctx.textAlign = "center";
                ctx.font = "13px Arial";
                ctx.fillText(verticalText2, 0, 0);
                ctx.restore();
            } 

            /**
             * Expand canvas to max size
             */
            function setSizeOfCanvas(){
                heightObj = document.getElementById('fill');
                canvas.style.width = "100%";
                canvas.style.height = (heightObj.offsetHeight)+"px";
                canvas.width  = canvas.offsetWidth;
                canvas.height = canvas.offsetHeight;
                //Set size of video
                $("#stream_img").height(canvas.offsetHeight);
                max_value = parseInt(canvas.height/2);
                min_value = max_value*-1;
            }

            // Variables to keep track of the mouse position and left-button status 
            var drive_is_mouseDown=0;
            var drive_mouseX_now;
            var drive_mouseY_now;
            var drive_mouseX_press;
            var drive_mouseY_press;
            var camera_is_mouseDown=0;
            var camera_mouseX_now;
            var camera_mouseY_now;
            var camera_mouseX_press;
            var camera_mouseY_press;
            var last_tap = new Date().getTime();
            
            /**
             * Remap one serie to another
             */
            function remap( x, oMin, oMax, nMin, nMax ){
                //range check
                if (oMin == oMax){
                    return None;
                }
    
                if (nMin == nMax){
                    return None;
                }

                //check reversed input range
                var reverseInput = false;
                oldMin = Math.min( oMin, oMax );
                oldMax = Math.max( oMin, oMax );
                if (oldMin != oMin){
                    reverseInput = true;
                }

                //check reversed output range
                var reverseOutput = false;  
                newMin = Math.min( nMin, nMax );
                newMax = Math.max( nMin, nMax );
                if (newMin != nMin){
                    reverseOutput = true;
                }

                var portion = (x-oldMin)*(newMax-newMin)/(oldMax-oldMin);
                if (reverseInput){
                    portion = (oldMax-x)*(newMax-newMin)/(oldMax-oldMin);
                }

                var result = portion + newMin;
                if (reverseOutput){
                    result = newMax - portion;
                    if(result<nMax){
                        result = nMax;
                    }
                    else if(result>nMin){
                        result = nMin;
                    }
                }
                else{
                    if(result>nMax){
                        result = nMax;
                    }
                    else if(result<nMin){
                        result = nMin;
                    }
                }

                return result;
            }

            /**
             * Keep track of the mouse button being pressed and draw a dot at current location
             */
            function mouseDown(e) {
                if (drive_mouseX_now>=($(document).width()/2)){
                    drive_is_mouseDown=1;
                    drive_mouseX_press = drive_mouseX_now;
                    drive_mouseY_press = drive_mouseY_now;
                    //Stop the movement
                    sendDrive(0, 0);
                }
                else{
                    camera_is_mouseDown=1;
                    camera_mouseX_press = camera_mouseX_now;
                    camera_mouseY_press = camera_mouseY_now;
                    servo_position_press = servo_position;
                }
                // Draw canvas
                drawCanvas();
                // Check if it is a double tap
                tapTheScreen();
            }

            /**
             * Keep track of the mouse button being released
             */
            function mouseUp(e) {
                drive_is_mouseDown=0;
                camera_is_mouseDown=0;
                // Draw canvas
                drawCanvas();
                //Stop the movement
                sendDrive(0, 0);
            }

            /**
             * Keep track of the mouse position.
             */
            function mouseMove(e) { 
                if (!e)
                    var e = event;
                e.preventDefault();
                //Get current location
                if (e.offsetX) {
                    drive_mouseX_now = e.offsetX;
                    drive_mouseY_now = e.offsetY;
                    camera_mouseX_now = e.offsetX;
                    camera_mouseY_now = e.offsetY;
                }
                else if (e.layerX) {
                    drive_mouseX_now = e.layerX;
                    drive_mouseY_now = e.layerY;
                    camera_mouseX_now = e.layerX;
                    camera_mouseY_now = e.layerY;
                }
                if(drive_is_mouseDown){
                    // Draw canvas
                    drawCanvas();
                    //Send a drive command to the rover
                    sendDrive(drive_mouseX_now- drive_mouseX_press, drive_mouseY_press - drive_mouseY_now)
                }
                if(camera_is_mouseDown){
                    // Draw canvas
                    drawCanvas();
                    //Send a camera move command to the rover
                    if(rover_type=="large"){
                        sendCamera(camera_mouseY_press-camera_mouseY_now);
                    }
                    else{
                        sendCamera(camera_mouseY_now-camera_mouseY_press);
                    }
                }
            }
            
            /**
             * When user touch screen
             */
            function touchStart(e) {
                if (!e)
                    var e = event;
                e.preventDefault();
                var arrayLength = e.changedTouches.length;
                //Check all touches, touch on the left side is camera movement
                //And on the right side to drive the rover.
                for (var i = 0; i < arrayLength; i++) {
                    t = e.changedTouches[i];
                    if (t.pageX>=($(document).width()/2)){
                        drive_is_mouseDown=1;
                        drive_mouseX_press = t.pageX;
                        drive_mouseY_press = t.pageY - canvas.offsetTop;
                        // Draw canvas
                        drawCanvas();
                        //Stop the movement
                        sendDrive(0, 0);
                    }
                    else{
                        camera_is_mouseDown=1;
                        camera_mouseX_press = t.pageX;
                        camera_mouseY_press = t.pageY - canvas.offsetTop;
                        // Draw canvas
                        drawCanvas();
                        servo_position_press = servo_position;
                    }
                }
                // Check if it is a double tap
                tapTheScreen();
            }

            /**
             * When user stop to touch screen
             */
            function touchEnd(e) {
                if (!e)
                    var e = event;
                e.preventDefault();
                var arrayLength = e.changedTouches.length;
                for (var i = 0; i < arrayLength; i++) {
                    t = e.changedTouches[i];
                    if (t.pageX>=($(document).width()/2)){
                        drive_is_mouseDown=0;
                        // Draw canvas
                        drawCanvas();
                        //Stop the movement
                        sendDrive(0, 0);
                    }
                    else{
                        camera_is_mouseDown=0;
                        // Draw canvas
                        drawCanvas();
                    }
                }
            }

            /**
             * When user move the finger on screen
             */
            function touchMove(e) { 
                if (!e)
                    var e = event;
                e.preventDefault();
                var arrayLength = e.changedTouches.length;
                for (var i = 0; i < arrayLength; i++) {
                    t = e.changedTouches[i];
                    if (t.pageX>=($(document).width()/2)){
                        drive_mouseX_now = t.pageX;
                        drive_mouseY_now = t.pageY - canvas.offsetTop;
                    }
                    else{
                        camera_mouseX_now = t.pageX;
                        camera_mouseY_now = t.pageY - canvas.offsetTop;
                    }
                }

                //Draw the drive
                if (drive_is_mouseDown==1) {
                    // Draw canvas
                    drawCanvas();
                    //Send a drive command to the rover
                    sendDrive(drive_mouseX_now-drive_mouseX_press, drive_mouseY_press - drive_mouseY_now)
                }
                if (camera_is_mouseDown==1) {
                    // Draw canvas
                    drawCanvas();
                    //Send a camera move command to the rover
                    if(rover_type=="large"){
                        sendCamera(camera_mouseY_press-camera_mouseY_now);
                    }
                    else{
                        sendCamera(camera_mouseY_now-camera_mouseY_press);
                    }
                }
                
            }
            
            var rover_is_enabled = false
            var stream_port="8080";
            var servo_position = 210;
            var servo_position_press = servo_position;
            var servo_position_last=servo_position;
            var servo_position_max = 230;
            var servo_position_min = 80;
            var motor_forward_max = 1023;
            var motor_backward_max = -1023;
            var motor_forward_start = 200;
            var motor_backward_start = -200;
            var motor_left = 0;
            var motor_right = 0;
            var max_value = 0;
            var min_value = 0;
            var send_drive = false;
            var lamp_status = false;
            var rover_type = "medium"; 
            var every_second_time = true;
            var voltage = 0;
            var front_distance = 999;
            var front_right_distance=999
            var right_distance=999
            var back_right_distance=999
            var back_distance=999
            var back_left_distance=999
            var left_distance=999
            var front_left_distance=999
            
            /**
             * Initialise all the drawing on the canvas here
             */
            function drawCanvas() {
                //Clear canvas
                ctx.clearRect(0, 0, canvas.width, canvas.height);
                //Draw voltage
                drawText("Voltage: "+voltage, canvas.offsetWidth-130, 15)
                //Draw help texts
                drawTextVerical("Drag up and down to tilt camera", true);
                drawDoubleText("Drag in all directions to drive the rover", "Double tap to toggle the light");
                if(drive_is_mouseDown){
                    //Draw start dott
                    drawDot(drive_mouseX_press, drive_mouseY_press, 12);
                    //Draw new dott
                    drawDot(drive_mouseX_now, drive_mouseY_now, 12);
                    //Draw line from new to start dott
                    drawLine(drive_mouseX_now, drive_mouseY_now, drive_mouseX_press, drive_mouseY_press);
                }
                if(camera_is_mouseDown){
                    //Draw start dott
                    drawDot(camera_mouseX_press, camera_mouseY_press, 12);
                    //Draw new dott
                    drawDot(camera_mouseX_press, camera_mouseY_now, 12);
                    //Draw line from new to start dott
                    drawLine(camera_mouseX_press, camera_mouseY_now, camera_mouseX_press, camera_mouseY_press);
                }
                //Draw the distance stuff
                drawDistance();
            }
                        
            /**
             * When the page is loaded
             */
            function onPageLoad(){
                //Prevent scrolling
                document.body.addEventListener('touchmove', function(event) {
                  event.preventDefault();
                }, false);                 
                
                // Get the specific canvas element from the HTML document
                canvas = document.getElementById('pad');
                
                // fit the canvas to container
                setSizeOfCanvas();

                // If the browser supports the canvas tag, get the 2d drawing context for this canvas
                ctx = canvas.getContext('2d');
                
                // Draw the canvas
                drawCanvas();
                
                // Handle touch and desktop different
                if(isTouch){
                    canvas.addEventListener('touchstart', touchStart, false);
                    canvas.addEventListener('touchmove', touchMove, false);
                    canvas.addEventListener('touchend', touchEnd, false);
                }
                else{
                    canvas.addEventListener('mousedown', mouseDown, false);
                    canvas.addEventListener('mousemove', mouseMove, false);
                    window.addEventListener('mouseup', mouseUp, false);
                }
                
                // Set fit the canvas to container
                window.addEventListener('resize', function(event){
                    setSizeOfCanvas();
                    try{
                        // Draw the canvas
                        drawCanvas();
                    }
                    catch(e){
                    }
                });

                // This thread sending the command to server
                setTimeout(function(){
                    if (servo_position_last!=servo_position){
                        servo_position_last = servo_position;
                        submitAsync('rover_camera', servo_position);
                    }
                    if (motor_left!=0 || motor_right!=0){
                        send_drive=true;
                    }
                    if (send_drive){
                        m_l=motor_left;
                        m_r=motor_right;
                        if (m_l > 0){
                            m_l = m_l + motor_forward_start;
                        }
                        else if(m_l < 0){
                            m_l = m_l + motor_backward_start;
                        }
                        if (m_r > 0){
                            m_r = m_r + motor_forward_start;
                        }
                        else if(m_r < 0){
                            m_r = m_r + motor_backward_start;
                        }
                        submitAsync('rover_drive', m_l, m_r);
                    }
                    if (motor_left==0 && motor_right==0){
                        send_drive=false;
                    }
                    
                    if(every_second_time){
                        submitNoPW('rover_status');
                    }
                    every_second_time=!every_second_time;
                    
                    setTimeout(arguments.callee, 300);
                }, 300);
            }
            
            /**
             * This will execute every 1 second
             */
            function onEvery01s(){
                //Send utill is enabled
                if(!rover_is_enabled){
                    //Send a command to the servor to get settings
                    submitNoPW('rover_settings');
                }
            }
            
            /**
             * This will execute every 5 second
             */
            function onEvery05s(){
                if (rover_is_enabled && !is_top_menu_showing()){
                    $("#the_video").show();
                    $("#stream_img").attr("src", "http://"+window.location.hostname+":"+stream_port+"/?action=stream");         
                    img_o = $("#stream_img");
                    p = img_o.position();
                    videoLeft=40;
                    videoTop=1;
                    videoWidth=img_o.width();
                    videoHeight=img_o.height();
                }
            }
            
            /**
             * When the page is in front
             */
            function onVisible(){
                if (rover_is_enabled && !is_top_menu_showing()){
                    drawCanvas();
                    $("#the_video").show();
                    $("#stream_img").attr("src", "http://"+window.location.hostname+":"+stream_port+"/?action=stream");         
                    img_o = $("#the_video");
                    p = img_o.position();
                    videoLeft=p.left;
                    videoTop=p.left;
                    videoWidth=img_o.width();
                    videoHeight=img_o.height();
                }
            }

            /**
             * When the page is in background
             */
            function onHidden(){
                if (rover_is_enabled){
                    $("#the_video").hide();
                    $("#stream_img").attr("src", "");         
                }
            }
                        
            /**
             * This will execute when the black top menu is expanded
             */
            function on_top_menu_show(){
                onHidden();
            }

            /**
             * This will execute when the black top menu is hidden
             */
            function on_top_menu_hide(){
                onVisible();
            }
                        
            /**
             * Get settings from the server
             */
            function on_rover_settings(paramaters){
                servo_position = parseInt(paramaters[0]);
                servo_position_press = servo_position;
                servo_position_last = servo_position;
                servo_position_max = parseInt(paramaters[1]);
                servo_position_min = parseInt(paramaters[2]);
                motor_forward_max = parseInt(paramaters[3]);
                motor_backward_max = parseInt(paramaters[4]);
                motor_forward_start = parseInt(paramaters[5]);
                motor_backward_start = parseInt(paramaters[6]);
                lamp_status = paramaters[7]=="True";
                rover_type = paramaters[8];
                //Rotate camera if a mini rover
                if(rover_type=="mini"){
                    $('#stream_img').removeClass('rotate-180').addClass('rotate-180');
                }
                rover_is_enabled=true;
                $("#not_enabled").hide();
                onEvery05s();
            }
            
            /**
             * Get status
             */
            function on_rover_status(paramaters){
                voltage = paramaters[0];
                front_distance = parseInt(paramaters[1]);
                front_right_distance=parseInt(paramaters[2]);
                right_distance=parseInt(paramaters[3]);
                back_right_distance=parseInt(paramaters[4]);
                back_distance=parseInt(paramaters[5]);
                back_left_distance=parseInt(paramaters[6]);
                left_distance=parseInt(paramaters[7]);
                front_left_distance=parseInt(paramaters[8]);
                drawCanvas();
            }

            /**
             * Error when get settings from the server
             */
            function on_rover_settings_err(error){
                rover_is_enabled=false;
                $("#not_enabled").show();
            }

            /**
             * On enable the rover functionality OK
             */
            function on_module_status(paramaters){
                showAlertMedium("Enabling...please wait!!!", false);
            }
                        
            /**
             * Send drive command to rover
             * x and y how many pixelsn the finger is drawn (top to bottom +, bottom to top -)
             */
            function sendDrive(x, y) {
                drive = parseInt(remap(y, min_value, max_value, motor_backward_max, motor_forward_max));
                motor_left = drive;
                motor_right = drive;
                steer = parseInt((remap(x, min_value, max_value, motor_backward_max, motor_forward_max)));
                if (steer>0){ //Right
                    motor_right = motor_right - steer;
                    motor_left = motor_left + steer;
                }
                else if (steer<0){ //Left
                    motor_right = motor_right - steer;
                    motor_left = motor_left + steer;
                }
                if (motor_right>motor_forward_max){
                    motor_right = motor_forward_max;
                }
                else if (motor_right<motor_backward_max){
                    motor_right = motor_backward_max;
                }
                if (motor_left>motor_forward_max){
                    motor_left = motor_forward_max;
                }
                else if (motor_left<motor_backward_max){
                    motor_left = motor_backward_max;
                }
            }

            /**
             * Send camera command to rover
             * y how many pixelsn the finger is drawn (top to bottom +, bottom to top -)
             */
            function sendCamera(new_camera) {
                if (new_camera<0){
                    add_servo_position = (remap(new_camera*-1, 0, max_value, 0, servo_position_max-servo_position_min ))*-1;
                }
                else{
                    add_servo_position = remap(new_camera, 0, max_value, 0, servo_position_max-servo_position_min );
                }
                servo_position = parseInt(servo_position_press + add_servo_position);
                if (servo_position<servo_position_min){
                    servo_position=servo_position_min;
                }
                else if (servo_position>servo_position_max){
                    servo_position=servo_position_max;
                }
            }

            
            /**
             * Check if it is a double tap, then toggle lamp
             */
            function tapTheScreen(){
                new_tap = new Date().getTime();
                if(new_tap-last_tap<300){
                    lamp_status = !lamp_status;
                    submitAsync('rover_lamp', lamp_status);
                }
                last_tap = new_tap;
            }
            //]]>
        </script>
        
    </head>
    <body id="the_body">
        <div id="included_top_bar">Kiss OS Utils</div>
        <div class="fill" id="fill">
            <div id="app">
                <canvas id="pad" style="z-index: 10">

                </canvas>
            </div>
        </div>
        <div id="the_video" style="position:absolute; left:40px;top:37px;display: none;z-index: 5">
            <img alt="The stream" id="stream_img" src="" style="height:100%">
        </div>
        <div id="not_enabled" style="position:absolute; left:10px;top:60px;display: none;border:1px solid black;background-color: white;padding:8px">
            The rover fuctionality is not enabled.<br/>
            <!-- Send command to server to enable the rover module -->
            <button onclick="javascript:submitSync('module_status', 'kiss_rover', 'True');">Enable</button>
        </div>
    </body>
</html>