Применяем технологию «Интернета вещей» (IoT) для дистанционного радиоуправления машинкой.

IOT является одной из самых популярных областей в мире технологий. Как правило в IOT используются web-технологии, RTOS, языки HTML, CSS, JavaScript, JSON, XML, C++ или Python.

Компания Espressif Systems из Шанхая создала для IOT микроконтроллер ESP8266 с поддержкой Wi-Fi.

Характеристики м-к ESP8266:

  • 32-разрядный RISC микропроцессор LX106
  • Разработчик Tensilica Xtensa®Т
  • Тактовая частота от 80 до 160 МГц1
  • RAM 128 кб
  • Flash ext. 4 МБ (внешняя) 
  • Wi-Fi 802.11b/g/n

Схема радиоуправления устройством в зоне прямой видимости может быть построена на микроконтроллере с поддержкой Wi-Fi. В таком случае  в качестве пульта управления можно будет использовать браузер смартфона. 

Рис. 1. Принципиальная электрическая схема машинки.

Программировать микроконтроллер ESP8266 будем в среде Arduino IDE. 

Рис. 2. Параметры настройки Arduino IDE.

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <Servo.h>

const char index_page[] PROGMEM = R"=====(
<!DOCTYPE html>
<head>
  <meta charset="utf-8">
  <title>Управление машиной</title>
  <script type="text/javascript">
    let StickStatus={xPosition:0,yPosition:0,x:0,y:0,cardinalDirection:"C"};var JoyStick=(function(container,parameters,callback){parameters=parameters||{};var title=(typeof parameters.title==="undefined"?"joystick":parameters.title),width=(typeof parameters.width==="undefined"?0:parameters.width),height=(typeof parameters.height==="undefined"?0:parameters.height),internalFillColor=(typeof parameters.internalFillColor==="undefined"?"#00AA00":parameters.internalFillColor),internalLineWidth=(typeof parameters.internalLineWidth==="undefined"?2:parameters.internalLineWidth),internalStrokeColor=(typeof parameters.internalStrokeColor==="undefined"?"#003300":parameters.internalStrokeColor),externalLineWidth=(typeof parameters.externalLineWidth==="undefined"?2:parameters.externalLineWidth),externalStrokeColor=(typeof parameters.externalStrokeColor==="undefined"?"#008000":parameters.externalStrokeColor),autoReturnToCenter=(typeof parameters.autoReturnToCenter==="undefined"?true:parameters.autoReturnToCenter);callback=callback||function(StickStatus){};var objContainer=document.getElementById(container);objContainer.style.touchAction="none";var canvas=document.createElement("canvas");canvas.id=title;if(width===0){width=objContainer.clientWidth}if(height===0){height=objContainer.clientHeight}canvas.width=width;canvas.height=height;objContainer.appendChild(canvas);var context=canvas.getContext("2d");var pressed=0;var circumference=2*Math.PI;var internalRadius=(canvas.width-((canvas.width/2)+10))/2;var maxMoveStick=internalRadius+5;var externalRadius=internalRadius+30;var centerX=canvas.width/2;var centerY=canvas.height/2;var directionHorizontalLimitPos=canvas.width/10;var directionHorizontalLimitNeg=directionHorizontalLimitPos*-1;var directionVerticalLimitPos=canvas.height/10;var directionVerticalLimitNeg=directionVerticalLimitPos*-1;var movedX=centerX;var movedY=centerY;if("ontouchstart"in document.documentElement){canvas.addEventListener("touchstart",onTouchStart,false);document.addEventListener("touchmove",onTouchMove,false);document.addEventListener("touchend",onTouchEnd,false)}else{canvas.addEventListener("mousedown",onMouseDown,false);document.addEventListener("mousemove",onMouseMove,false);document.addEventListener("mouseup",onMouseUp,false)}drawExternal();drawInternal();function drawExternal(){context.beginPath();context.arc(centerX,centerY,externalRadius,0,circumference,false);context.lineWidth=externalLineWidth;context.strokeStyle=externalStrokeColor;context.stroke()}function drawInternal(){context.beginPath();if(movedX<internalRadius){movedX=maxMoveStick}if((movedX+internalRadius)>canvas.width){movedX=canvas.width-(maxMoveStick)}if(movedY<internalRadius){movedY=maxMoveStick}if((movedY+internalRadius)>canvas.height){movedY=canvas.height-(maxMoveStick)}context.arc(movedX,movedY,internalRadius,0,circumference,false);var grd=context.createRadialGradient(centerX,centerY,5,centerX,centerY,200);grd.addColorStop(0,internalFillColor);grd.addColorStop(1,internalStrokeColor);context.fillStyle=grd;context.fill();context.lineWidth=internalLineWidth;context.strokeStyle=internalStrokeColor;context.stroke()}function onTouchStart(event){pressed=1}function onTouchMove(event){if(pressed===1&&event.targetTouches[0].target===canvas){movedX=event.targetTouches[0].pageX;movedY=event.targetTouches[0].pageY;if(canvas.offsetParent.tagName.toUpperCase()==="BODY"){movedX-=canvas.offsetLeft;movedY-=canvas.offsetTop}else{movedX-=canvas.offsetParent.offsetLeft;movedY-=canvas.offsetParent.offsetTop}context.clearRect(0,0,canvas.width,canvas.height);drawExternal();drawInternal();StickStatus.xPosition=movedX;StickStatus.yPosition=movedY;StickStatus.x=(100*((movedX-centerX)/maxMoveStick)).toFixed();StickStatus.y=((100*((movedY-centerY)/maxMoveStick))-1).toFixed();StickStatus.cardinalDirection=getCardinalDirection();callback(StickStatus)}}function onTouchEnd(event){pressed=0;if(autoReturnToCenter){movedX=centerX;movedY=centerY}context.clearRect(0,0,canvas.width,canvas.height);drawExternal();drawInternal();StickStatus.xPosition=movedX;StickStatus.yPosition=movedY;StickStatus.x=(100((movedX-centerX)/maxMoveStick)).toFixed();StickStatus.y=((100*((movedY-centerY)/maxMoveStick))-1).toFixed();StickStatus.cardinalDirection=getCardinalDirection();callback(StickStatus)}function onMouseDown(event){pressed=1}function onMouseMove(event){if(pressed===1){movedX=event.pageX;movedY=event.pageY;if(canvas.offsetParent.tagName.toUpperCase()==="BODY"){movedX-=canvas.offsetLeft;movedY-=canvas.offsetTop}else{movedX-=canvas.offsetParent.offsetLeft;movedY-=canvas.offsetParent.offsetTop}context.clearRect(0,0,canvas.width,canvas.height);drawExternal();drawInternal();StickStatus.xPosition=movedX;StickStatus.yPosition=movedY;StickStatus.x=(100((movedX-centerX)/maxMoveStick)).toFixed();StickStatus.y=((100*((movedY-centerY)/maxMoveStick))-1).toFixed();StickStatus.cardinalDirection=getCardinalDirection();callback(StickStatus)}}function onMouseUp(event){pressed=0;if(autoReturnToCenter){movedX=centerX;movedY=centerY}context.clearRect(0,0,canvas.width,canvas.height);drawExternal();drawInternal();StickStatus.xPosition=movedX;StickStatus.yPosition=movedY;StickStatus.x=(100((movedX-centerX)/maxMoveStick)).toFixed();StickStatus.y=((100*((movedY-centerY)/maxMoveStick))-1).toFixed();StickStatus.cardinalDirection=getCardinalDirection();callback(StickStatus)}function getCardinalDirection(){let result="";let orizontal=movedX-centerX;let vertical=movedY-centerY;if(vertical>=directionVerticalLimitNeg&&vertical<=directionVerticalLimitPos){result="C"}if(vertical<directionVerticalLimitNeg){result="N"}if(vertical>directionVerticalLimitPos){result="S"}if(orizontal<directionHorizontalLimitNeg){if(result==="C"){result="W"}else{result+="W"}}if(orizontal>directionHorizontalLimitPos){if(result==="C"){result="E"}else{result+="E"}}return result}this.GetWidth=function(){return canvas.width};this.GetHeight=function(){return canvas.height};this.GetPosX=function(){return movedX};this.GetPosY=function(){return movedY};this.GetX=function(){return(100((movedX-centerX)/maxMoveStick)).toFixed()};this.GetY=function(){return((100*((movedY-centerY)/maxMoveStick))*-1).toFixed()};this.GetDir=function(){return getCardinalDirection()}});
  </script>
</head>
<body>
  <center>
  <div id="joyDiv" style="width:400px;height:400px;"></div>
  </center>
  <script type="text/javascript">
    var Joy1 = new JoyStick('joyDiv', {});
    setInterval(() => {
      var xmlHttp = new XMLHttpRequest();
      let y = Math.floor(((Joy1.GetPosX()-100)/100-1)*255);
      let x = -1*Math.floor(((Joy1.GetPosY()-100)/100-1)*255);
      xmlHttp.open( "GET", "/data?x="+x.toString()+"&y="+y.toString(), false );
      xmlHttp.send( null );}, 50);
  </script>
</body>
)=====";

String ssid = "Машинка ";
const int motorA = 4;
const int motorB = 5;
const int servoPin = 12;

ESP8266WebServer server(80);
Servo servo;
IPAddress ip(192, 168, 1, 1);

void setSpeed(int power) {
  digitalWrite(motorB, power < 0);
  analogWrite(motorA, (power < 0) ? (255+power) : power);
}

void setup() {
  pinMode(motorA, OUTPUT);
  pinMode(motorB, OUTPUT);
  setSpeed(0);
  servo.attach(servoPin);
  servo.write(90);
  ssid += String(WiFi.macAddress()).substring(12);
  WiFi.mode(WIFI_AP);
  WiFi.softAPConfig(ip, ip, IPAddress(255, 255, 255, 0));
  WiFi.softAP(ssid);
  server.on("/", [] {
    server.send(200, "text/html", index_page);
  });
  server.on("/data", getData);
  server.begin();
}

void getData() {
  setSpeed(server.arg("x").toInt());
  servo.write(map(server.arg("y").toInt(), -255, 255, 45, 105)); //Корректировка прямо и диапазон углов поворота
  server.send(200, "text/plain", "ok");
}

void loop() {
  server.handleClient();
  delay(5);
}

Лист. 1.

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <Servo.h>

const char index_page[] PROGMEM = R"=====(
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<!--title>Управление машиной</title-->
<script type="text/javascript">
  let StickStatus={xPosition:0,yPosition:0,x:0,y:0,cardinalDirection:"C"};
  function JoyStick(container,parameters,callback){
    parameters=parameters||{};
    let title=(typeof parameters.title==="undefined"?"joystick":parameters.title),
        width=(typeof parameters.width==="undefined"?0:parameters.width),
        height=(typeof parameters.height==="undefined"?0:parameters.height),
        internalFillColor=(typeof parameters.internalFillColor==="undefined"?"#00AA00":parameters.internalFillColor),
        internalLineWidth=(typeof parameters.internalLineWidth==="undefined"?2:parameters.internalLineWidth),
        internalStrokeColor=(typeof parameters.internalStrokeColor==="undefined"?"#003300":parameters.internalStrokeColor),
        externalLineWidth=(typeof parameters.externalLineWidth==="undefined"?2:parameters.externalLineWidth),
        externalStrokeColor=(typeof parameters.externalStrokeColor==="undefined"?"#008000":parameters.externalStrokeColor),
        autoReturnToCenter=(typeof parameters.autoReturnToCenter==="undefined"?true:parameters.autoReturnToCenter);
    callback=callback||function(StickStatus){};
    let objContainer=document.getElementById(container);
    objContainer.style.touchAction="none";
    let canvas=document.createElement("canvas");
    canvas.id=title;
    if(width===0){width=objContainer.clientWidth}
    if(height===0){height=objContainer.clientHeight}
    canvas.width=width;
    canvas.height=height;
    objContainer.appendChild(canvas);
    let context=canvas.getContext("2d"),
        pressed=0,
        circumference=2*Math.PI,
        internalRadius=(canvas.width-((canvas.width/2)+10))/2,
        maxMoveStick=internalRadius+5,
        externalRadius=internalRadius+30,
        centerX=canvas.width/2,
        centerY=canvas.height/2,
        directionHorizontalLimitPos=canvas.width/10,
        directionHorizontalLimitNeg=directionHorizontalLimitPos*-1,
        directionVerticalLimitPos=canvas.height/10,
        directionVerticalLimitNeg=directionVerticalLimitPos*-1,
        movedX=centerX;var movedY=centerY;
    if("ontouchstart"in document.documentElement){
      canvas.addEventListener("touchstart",onTouchStart,false);
      document.addEventListener("touchmove",onTouchMove,false);
      document.addEventListener("touchend",onTouchEnd,false)
      }
    else{
      canvas.addEventListener("mousedown",onMouseDown,false);
      document.addEventListener("mousemove",onMouseMove,false);
      document.addEventListener("mouseup",onMouseUp,false)
      }
    drawExternal();
    drawInternal();
    function drawExternal(){
      context.beginPath();
      context.arc(centerX,centerY,externalRadius,0,circumference,false);
      context.lineWidth=externalLineWidth;
      context.strokeStyle=externalStrokeColor;
      context.stroke()
      }
    function drawInternal(){
      context.beginPath();
      if(movedX<internalRadius){movedX=maxMoveStick}
      if((movedX+internalRadius)>canvas.width){movedX=canvas.width-(maxMoveStick)}
      if(movedY<internalRadius){movedY=maxMoveStick}
      if((movedY+internalRadius)>canvas.height){movedY=canvas.height-(maxMoveStick)}
      context.arc(movedX,movedY,internalRadius,0,circumference,false);
      var grd=context.createRadialGradient(centerX,centerY,5,centerX,centerY,200);
      grd.addColorStop(0,internalFillColor);
      grd.addColorStop(1,internalStrokeColor);
      context.fillStyle=grd;context.fill();
      context.lineWidth=internalLineWidth;
      context.strokeStyle=internalStrokeColor;context.stroke()
      }
    function onTouchStart(event){pressed=1}
    function onTouchMove(event){
      if(pressed===1&&event.targetTouches[0].target===canvas){
        movedX=event.targetTouches[0].pageX;
        movedY=event.targetTouches[0].pageY;
        if(canvas.offsetParent.tagName.toUpperCase()==="BODY"){
          movedX-=canvas.offsetLeft;movedY-=canvas.offsetTop
          }
        else{
          movedX-=canvas.offsetParent.offsetLeft;
          movedY-=canvas.offsetParent.offsetTop
          }
        context.clearRect(0,0,canvas.width,canvas.height);
        drawExternal();
        drawInternal();
        StickStatus.xPosition=movedX;
        StickStatus.yPosition=movedY;
        StickStatus.x=(100*((movedX-centerX)/maxMoveStick)).toFixed();
        StickStatus.y=((100*((movedY-centerY)/maxMoveStick))-1).toFixed();
        StickStatus.cardinalDirection=getCardinalDirection();
        callback(StickStatus)
        }
      }
    function onTouchEnd(event){
      pressed=0;
      if(autoReturnToCenter){movedX=centerX;movedY=centerY}
      context.clearRect(0,0,canvas.width,canvas.height);
      drawExternal();
      drawInternal();
      StickStatus.xPosition=movedX;
      StickStatus.yPosition=movedY;
      StickStatus.x=(100((movedX-centerX)/maxMoveStick)).toFixed();
      StickStatus.y=((100*((movedY-centerY)/maxMoveStick))-1).toFixed();
      StickStatus.cardinalDirection=getCardinalDirection();
      callback(StickStatus)
      }
    function onMouseDown(event){pressed=1}
    function onMouseMove(event){
      if(pressed===1){
        movedX=event.pageX;movedY=event.pageY;
        if(canvas.offsetParent.tagName.toUpperCase()==="BODY"){
          movedX-=canvas.offsetLeft;movedY-=canvas.offsetTop
          }
        else{
          movedX-=canvas.offsetParent.offsetLeft;
          movedY-=canvas.offsetParent.offsetTop
          }
        context.clearRect(0,0,canvas.width,canvas.height);
        drawExternal();
        drawInternal();
        StickStatus.xPosition=movedX;
        StickStatus.yPosition=movedY;
        StickStatus.x=(100((movedX-centerX)/maxMoveStick)).toFixed();
        StickStatus.y=((100*((movedY-centerY)/maxMoveStick))-1).toFixed();
        StickStatus.cardinalDirection=getCardinalDirection();
        callback(StickStatus)
        }
      }
    function onMouseUp(event){
      pressed=0;
      if(autoReturnToCenter){
        movedX=centerX;
        movedY=centerY
        }
      context.clearRect(0,0,canvas.width,canvas.height);
      drawExternal();
      drawInternal();
      StickStatus.xPosition=movedX;
      StickStatus.yPosition=movedY;
      StickStatus.x=(100((movedX-centerX)/maxMoveStick)).toFixed();
      StickStatus.y=((100*((movedY-centerY)/maxMoveStick))-1).toFixed();
      StickStatus.cardinalDirection=getCardinalDirection();
      callback(StickStatus)
      }
    function getCardinalDirection(){
      let result="";
      let orizontal=movedX-centerX;
      let vertical=movedY-centerY;
      if(vertical>=directionVerticalLimitNeg&&vertical<=directionVerticalLimitPos){result="C"}
      if(vertical<directionVerticalLimitNeg){result="N"}
      if(vertical>directionVerticalLimitPos){result="S"}
      if(orizontal<directionHorizontalLimitNeg){
        if(result==="C"){result="W"}
        else{result+="W"}
        }
      if(orizontal>directionHorizontalLimitPos){
        if(result==="C"){result="E"}
        else{result+="E"}
        }
      return result}
    this.GetWidth=function(){return canvas.width};
    this.GetHeight=function(){return canvas.height};
    this.GetPosX=function(){return movedX};
    this.GetPosY=function(){return movedY};
    this.GetX=function(){return(100((movedX-centerX)/maxMoveStick)).toFixed()};
    this.GetY=function(){return((100*((movedY-centerY)/maxMoveStick))*-1).toFixed()};
    this.GetDir=function(){return getCardinalDirection()}
    };
</script>
</head>
<body>
  <center>
  <div id="joyDiv" style="width:400px;height:400px;"></div>
  </center>
  <script type="text/javascript">
    var Joy1 = new JoyStick('joyDiv', {externalLineWidth:5});
    setInterval(() => {
      var xmlHttp = new XMLHttpRequest();
      let y = Math.floor(((Joy1.GetPosX()-100)/100-1)*255),
          x = -1*Math.floor(((Joy1.GetPosY()-100)/100-1)*255);
      xmlHttp.open( "GET", "/data?x="+x.toString()+"&y="+y.toString(), false );
      xmlHttp.send( null );}, 500);
  </script>
</body>
)=====";

String ssid = "Машинка ";
const int motorA = 4;
const int motorB = 5;
const int servoPin = 12;

ESP8266WebServer server(80);
Servo servo;
IPAddress ip(192, 168, 1, 1);

void setSpeed(int power) {
  digitalWrite(motorB, power < 0);
  analogWrite(motorA, (power < 0) ? (255+power) : power);
}

void setup() {
  pinMode(motorA, OUTPUT);
  pinMode(motorB, OUTPUT);
  setSpeed(0);
  servo.attach(servoPin);
  servo.write(90);
  ssid += String(WiFi.macAddress()).substring(12);
  WiFi.mode(WIFI_AP);
  WiFi.softAPConfig(ip, ip, IPAddress(255, 255, 255, 0));
  WiFi.softAP(ssid);
  server.on("/", [] {
    server.send(200, "text/html", index_page);
  });
  server.on("/data", getData);
  server.begin();
}

void getData() {
  setSpeed(server.arg("x").toInt());
  servo.write(map(server.arg("y").toInt(), -255, 255, 45, 105)); //Корректировка прямо и диапазон углов поворота
  server.send(200, "text/plain", "ok");
}

void loop() {
  server.handleClient();
  delay(5);
}

Лист. 2.

ааеп