Применяем технологию «Интернета вещей» (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.
ааеп