Control Spider Robot? Sounds interesting. Control Spider Robot via WebSocket? Sound even more interesting.
In this project, I will show how to control the robot via WebSocket, using PHPoC DC motor controller board.


Demo



Hardware components:

Spider Robot × 1
PHPoC Blue × 1
PES-2404 - PHPoC DC Motor Controller × 1
Battery × 1

This project is similar to this one https://www.hackster.io/gyusik-song/...r-robot-06ea9f, but the hardware components are different. Here I use DC Motor Controller expansion board for controlling spider robot. It seems that PES-2404 DC Motor Controller is a useful board to control DC motor.


Wiring

- Stack DC Motor Controller board to PHPoC Blue
- Connect DC Motor Controller to Spider Robot

Click image for larger version

Name:	components_TGWusuFnO4.jpg
Views:	147
Size:	85.1 KB
ID:	372

Here, instead of connecting to GND and PWR pin of PHPoC Blue, I connect the GND and VM pins of DC Motor Controller directly to the battery in order to supply higher current.

Connect Spider Robot's Motors <---> DC Motor controller

A-1 ------ M1+
A-2 ------ M1-
B-1 ------ M2+
B-2 ------ M2-

Click image for larger version

Name:	dc_motor_controller_d6JKK2BAD6.jpg
Views:	125
Size:	82.4 KB
ID:	373


Data Flow

Web browser ---> PHPoC ---> Spider Robot

First, a WebSocket connection is established between PHPoC Blue and Web browser, in which PHPoC Blue is WS server, and Web browser is client. User interacts with Web browser via Web remote slide interface, to control the movement of Spider Robot. Then, Web browser sends those data via WebSocket. After receiving those control data from the client, PHPoC (WS server), via DC Motor Controller, send the commands to control the motors on Spider Robot.


Source Code

Source code includes two files:

index.php: This file contains client-side code.
PHP Code:
<?php
   $wrs_width 
"400";
   
$wrs_length "300";
   
$wrs_value_min "-100";
   
$wrs_value_max "100";
?>
<!DOCTYPE html>
<html>
<head>
<title>PHPoC - Web Remote Control for Spider Robot</title>
<meta name="viewport" content="width=device-width, initial-scale=0.7, maximum-scale=0.7">
<style>
body { text-align: center; font-size: 15pt; }
h1 { font-weight: bold; font-size: 25pt; }
h2 { font-weight: bold; font-size: 15pt; }
button { font-weight: bold; font-size: 15pt; }
</style>
<script>
var SLIDE_WIDTH = <?echo(int)$wrs_width/2?>;
var SLIDE_LENGTH = <?echo(int)$wrs_length?>;
var VALUE_MIN = <?echo(int)$wrs_value_min?>;
var VALUE_MAX = <?echo(int)$wrs_value_max?>;
var BUTTON_WIDTH = parseInt(SLIDE_WIDTH * 0.8);
var BUTTON_HEIGHT = parseInt(BUTTON_WIDTH / 2);
var SLIDE_HEIGHT = parseInt(SLIDE_LENGTH + BUTTON_HEIGHT * 1.1);
var slide_info = [ null, null ];
var ws;
function init()
{
   var remote = document.getElementById("remote");

   remote.width = SLIDE_WIDTH * 2;
   remote.height = SLIDE_HEIGHT;
   remote.style = "border:1px solid black";

   slide_info[0] = {x:0, y:0, offset:0, state:false, identifier:null, ws_value:0};
   slide_info[1] = {x:0, y:0, offset:0, state:false, identifier:null, ws_value:0};

   slide_info[0].x = parseInt(SLIDE_WIDTH / 2);
   slide_info[0].y = parseInt(SLIDE_HEIGHT / 2);

   slide_info[1].x = parseInt(SLIDE_WIDTH + SLIDE_WIDTH / 2);
   slide_info[1].y = parseInt(SLIDE_HEIGHT / 2);

   update_slide(0, SLIDE_HEIGHT / 2);
   update_slide(1, SLIDE_HEIGHT / 2);

   remote.addEventListener("touchstart", mouse_down);
   remote.addEventListener("touchend", mouse_up);
   remote.addEventListener("touchmove", mouse_move);

   remote.addEventListener("mousedown", mouse_down);
   remote.addEventListener("mouseup", mouse_up);
   remote.addEventListener("mousemove", mouse_move);
   remote.addEventListener("mouseout", mouse_up);
}
function connect_onclick()
{
   if(ws == null)
   {
      var ws_host_addr = "<?echo _SERVER("HTTP_HOST")?>";
      var debug = document.getElementById("debug");

      if((navigator.platform.indexOf("Win") != -1) && (ws_host_addr.charAt(0) == "["))
      {
         // network resource identifier to UNC path name conversion
         ws_host_addr = ws_host_addr.replace(/[\[\]]/g, '');
         ws_host_addr = ws_host_addr.replace(/:/g, "-");
         ws_host_addr += ".ipv6-literal.net";
      }

      //debug.innerHTML = "<br>" + navigator.platform + " " + ws_host_addr;
      ws = new WebSocket("ws://" + ws_host_addr + "/rc_spider", "text.phpoc");

      document.getElementById("ws_state").innerHTML = "CONNECTING";

      ws.onopen = ws_onopen;
      ws.onclose = ws_onclose;
      ws.onmessage = ws_onmessage;
   }
   else
      ws.close();
}
function ws_onopen()
{
   document.getElementById("ws_state").innerHTML = "<font color='blue'>CONNECTED</font>";
   document.getElementById("bt_connect").innerHTML = "Disconnect";

   // draw active slide button
   for(slide_id = 0; slide_id < 2; slide_id++)
   {
      var slide = slide_info[slide_id];

      update_slide(slide_id, slide.y - slide.offset);
   }
}
function ws_onclose()
{
   document.getElementById("ws_state").innerHTML = "<font color='gray'>CLOSED</font>";
   document.getElementById("bt_connect").innerHTML = "Connect";

   ws.onopen = null;
   ws.onclose = null;
   ws.onmessage = null;
   ws = null;

   // draw inactive slide button
   for(slide_id = 0; slide_id < 2; slide_id++)
   {
      var slide = slide_info[slide_id];

      update_slide(slide_id, slide.y - slide.offset);
      slide.ws_value = 0;
   }
}
function ws_onmessage(e_msg)
{
   e_msg = e_msg || window.event; // MessageEvent

   alert("msg : " + e_msg.data);
}
function update_slide(slide_id, y)
{
   var debug = document.getElementById("debug");
   var remote = document.getElementById("remote");
   var ctx = remote.getContext("2d");
   var slide = slide_info[slide_id];
   var slide_top, slide_ratio, slide_value;

   slide_top = (SLIDE_HEIGHT - SLIDE_LENGTH) / 2;

   slide.y = y + slide.offset;

   if(slide.y < slide_top)
      slide.y = slide_top;

   if(slide.y > (slide_top + SLIDE_LENGTH))
      slide.y = slide_top + SLIDE_LENGTH;

   ctx.clearRect(SLIDE_WIDTH * slide_id, 0, SLIDE_WIDTH, SLIDE_HEIGHT);

   ctx.fillStyle = "silver";
   ctx.beginPath();
   ctx.rect(slide.x - 5, slide_top, 10, SLIDE_LENGTH);
   ctx.fill();

   if(ws && (ws.readyState == 1))
   {
      ctx.strokeStyle = "blue";
      if(slide.state)
         ctx.fillStyle = "blue";
      else
         ctx.fillStyle = "skyblue";
   }
   else
   {
      ctx.strokeStyle = "gray";
      if(slide.state)
         ctx.fillStyle = "gray";
      else
         ctx.fillStyle = "silver";
   }

   ctx.beginPath();
   ctx.rect(slide.x - BUTTON_WIDTH / 2, slide.y - BUTTON_HEIGHT / 2, BUTTON_WIDTH, BUTTON_HEIGHT);
   ctx.fill();
   ctx.stroke();

   ctx.font = "30px Arial";
   ctx.textBaseline = "top";
   ctx.fillStyle = "white";

   slide_ratio = (SLIDE_LENGTH - (slide.y - slide_top)) / SLIDE_LENGTH;       // 0 ~ 1
   slide_value = parseInt(slide_ratio * (VALUE_MAX - VALUE_MIN) + VALUE_MIN); // VALUE_MIN ~ VALUE_MAX

   if(slide_id == 0)
   {
      ctx.textAlign = "right";
      ctx.fillText(slide_value.toString(), slide.x + BUTTON_WIDTH / 2 - 5, slide.y - BUTTON_HEIGHT / 2);
   }
   else
   {
      ctx.textAlign = "left";
      ctx.fillText(slide_value.toString(), slide.x - BUTTON_WIDTH / 2 + 5, slide.y - BUTTON_HEIGHT / 2);
   }

   if(ws && (ws.readyState == 1))
   {
      //debug.innerHTML = slide.ws_value + "/" + slide_value;

      if(slide.ws_value != slide_value)
      {
         if(slide_id == 0)
            ws.send("A" + slide_value.toString() + "\r\n");
         else
            ws.send("B" + slide_value.toString() + "\r\n");

         slide.ws_value = slide_value;
      }
   }
}
function find_slide_id(x, y)
{
   var button_left, button_right, button_top, button_bottom;
   var slide_id, slide;

   if(x < SLIDE_WIDTH)
      slide_id = 0;
   else
      slide_id = 1;

   slide = slide_info[slide_id];

   button_left = slide.x - BUTTON_WIDTH / 2;
   button_right = slide.x + BUTTON_WIDTH / 2;
   button_top = slide.y - BUTTON_HEIGHT / 2;
   button_bottom = slide.y + BUTTON_HEIGHT / 2;

   if((x > button_left) && (x < button_right) && (y > button_top) && (y < button_bottom))
      return slide_id;
   else
      return 2;
}
function mouse_down(event)
{
   var debug = document.getElementById("debug");
   var x, y, slide_id;

   //debug.innerHTML = "";

   if(event.changedTouches)
   {
      for(var id = 0; id < event.changedTouches.length; id++)
      {
         var touch = event.changedTouches[id];

         x = touch.pageX - touch.target.offsetLeft;
         y = touch.pageY - touch.target.offsetTop;

         slide_id = find_slide_id(x, y);

         //debug.innerHTML += slide_id + "/" + x + "/" + y + " ";
         //debug.innerHTML += slide_id + "/" + touch.identifier + " ";

         if(slide_id < 2)
         {
            var slide = slide_info[slide_id];

             if(!slide.state)
            {
               slide.offset = slide.y - y;
               slide.identifier = touch.identifier;
               slide.state = true;

               update_slide(slide_id, y);
            }
         }
      }
   }
   else
   {
      x = event.offsetX;
      y = event.offsetY;

      slide_id = find_slide_id(x, y);

      if(slide_id < 2)
      {
         var slide = slide_info[slide_id];

         slide.offset = slide.y - y;
         slide.state = true;

         update_slide(slide_id, y);
      }
   }

   event.preventDefault();
}
function mouse_up(event)
{
   var debug = document.getElementById("debug");
   var slide_id;

   //debug.innerHTML = "";

   if(event.changedTouches)
   {
      for(var id = 0; id < event.changedTouches.length; id++)
      {
         var touch = event.changedTouches[id];

         if(touch.identifier == slide_info[0].identifier)
            slide_id = 0;
         else
         if(touch.identifier == slide_info[1].identifier)
            slide_id = 1;
         else
            slide_id = 2;

         if(slide_id < 2)
         {
            var slide = slide_info[slide_id];

            slide.state = false;
            slide.identifier = null;

            if(document.getElementById("bt_center").checked == true)
            {
               slide.offset = 0;
               update_slide(slide_id, SLIDE_HEIGHT / 2);
            }
            else
               update_slide(slide_id, slide.y - slide.offset);
         }
      }
   }
   else
   {
      if(slide_info[0].state)
         slide_id = 0;
      else
      if(slide_info[1].state)
         slide_id = 1;
      else
         slide_id = 2;

      if(slide_id < 2)
      {
         var slide = slide_info[slide_id];

         slide.state = false;

         if(document.getElementById("bt_center").checked == true)
         {
            slide.offset = 0;
            update_slide(slide_id, SLIDE_HEIGHT / 2);
         }
         else
            update_slide(slide_id, slide.y - slide.offset);
      }
   }

   event.preventDefault();
}
function mouse_move(event)
{
   var debug = document.getElementById("debug");
   var x, y, slide_id, offset;

   //debug.innerHTML = "";

   if(event.changedTouches)
   {
      for(var id = 0; id < event.changedTouches.length; id++)
      {
         var touch = event.changedTouches[id];

         if(touch.identifier == slide_info[0].identifier)
            slide_id = 0;
         else
         if(touch.identifier == slide_info[1].identifier)
            slide_id = 1;
         else
            slide_id = 2;

         if(slide_id < 2)
         {
            x = touch.pageX - touch.target.offsetLeft;
            y = touch.pageY - touch.target.offsetTop;

            update_slide(slide_id, y);
         }
      }
   }
   else
   {
      if(slide_info[0].state)
         slide_id = 0;
      else
      if(slide_info[1].state)
         slide_id = 1;
      else
         slide_id = 2;

      if(slide_id < 2)
      {
         x = event.offsetX;
         y = event.offsetY;

         update_slide(slide_id, y);
      }
   }

   event.preventDefault();
}
function bt_center_change()
{
   if(document.getElementById("bt_center").checked == true)
   {
      for(slide_id = 0; slide_id < 2; slide_id++)
      {
         var slide = slide_info[slide_id];

         slide.offset = 0;
         update_slide(slide_id, SLIDE_HEIGHT / 2);
      }
   }
}
window.onload = init;
</script>
</head>

<body>

<p>
<h1>Web Remote Control / Slide</h1>
</p>

<canvas id="remote"></canvas>

<h2>WebSocket <font id="ws_state" color="gray">CLOSED</font></h2>
<button id="bt_connect" type="button" onclick="connect_onclick();">Connect</button>
&nbsp;&nbsp;&nbsp;Return to Center<input id="bt_center" type="checkbox" onchange="bt_center_change()">
<span id="debug"></span>

</body>
</html>



task0.php: This file contains server side code.
PHP Code:
<?php

if(_SERVER("REQUEST_METHOD"))
   exit;   
// avoid php execution via http request

include_once "/lib/sd_340.php";
include_once 
"/lib/sn_tcp_ws.php";
include_once 
"/lib/sd_spc.php";
define("WIDTH_MIN"420);
define("WIDTH_MAX"1000);
define("PWM_PERIOD"1000);
define ("RC_SPIDER_SID" 14);

function 
dc_init()
{
   
spc_reset();
   
spc_sync_baud(115200);
}

function 
dc_pwm_width($dc_id$pwm_width$pwm_period$pol_dir "+")
{
   if((
$dc_id 1) || ($dc_id 2))
      exit(
"dc_pwm_width: dc_id out of range $dc_id\r\n");

   
spc_request_dev(RC_SPIDER_SID"dc$dc_id pwm set pol $pol_dir");
   
spc_request_dev(RC_SPIDER_SID"dc$dc_id pwm set period $pwm_period");
   
spc_request_dev(RC_SPIDER_SID"dc$dc_id pwm set width $pwm_width");

}

ws_setup(0"rc_spider""text.phpoc");
dc_init();
$rwbuf "";

while(
1)
{
   if(
ws_state(0) == TCP_CONNECTED)
    {
      
$rlen ws_read_line(0$rwbuf);
      if(
$rlen)
        {
         
$slide_name $rwbuf[0];
         
$slide_value = (int)substr($rwbuf,1);
         
$slide_motor 0;
         
$abs_pwm_value 0;

         switch (
$slide_name)
         {
            case 
'A':           
               
$slide_motor 1;
               break;
            case 
'B':            
               
$slide_motor 2;
               
$slide_value = -$slide_value;
               break;
            default:
               echo 
"Unknown input slide[$slide_name] \r\n";
               break;
         }

         if (
$slide_value == 0)
         {            
            
dc_pwm_width($slide_motor,   0PWM_PERIOD);
         }
         else
         {
            if (
$slide_value<0)
            {               
               
$abs_pwm_value WIDTH_MIN + (int)round((WIDTH_MAX WIDTH_MIN)*(-1)*$slide_value/100) ;                   
               
dc_pwm_width($slide_motor,   $abs_pwm_value1000,"-");
            }
            else
            {
               
$abs_pwm_value WIDTH_MIN + (int)round((WIDTH_MAX WIDTH_MIN)*$slide_value/100) ;                           
               
dc_pwm_width($slide_motor,   $abs_pwm_valuePWM_PERIOD,"+");
            }
         }               
      }
   }

   else 
   {
      
dc_pwm_width(1,   0PWM_PERIOD);
      
dc_pwm_width(2,   0PWM_PERIOD);
   }
}

?>

You can get the full source code in the attachment.
Hope it is helpful to you
Attached Files