標籤:

遙控迷你巡線小車的Windows程序

最近拿到了小喵科技的迷你巡線小車。

作為「尋常不走路」的DIY人員,關注的絕對不是尋線功能,而是研究如何接下來研究如何實現電腦對小車的遙控。

在經過一番折騰之後,我成功的連接了手機和小車能夠實現手機的遙控。從原理上來說,WIFI 模塊在這個過程中充當了透明網關的角色,對於手機遙控端來說,它在和TCP/IP

設備打交道;對於小車的主控來說,它是在接受串口指令而已。反編譯他們的

App只能看懂他們用了TCP 做連接,代碼中使用到的UDP大約只是用來掃描而已。接著找技術支持群,疑似開發人員留下了一句話使用:23埠,就不見了。想像中,他剛說完這句就因為管理員擔心泄密直接打暈拖走…….仍然留下一頭霧水的我。

忽然想起來,1024以下的埠都是有固定用途的,比如:ftp 是21。而23是 Telnet的。然後直接用系統自帶的直接 telnet 上去。每次我在Telnet

上發送消息,小車的串口都會收到對應的消息。為了便於實驗,我先刷上默認的代碼,其中有一些控制命令可以從代碼中看出來:

M0 顯示當前版本

M6 後面帶2個參數,控制前方的LED開關

M8 返回當前電池電壓

M13 後面帶4個參數 第一個LED 然後是 R G

B的色彩分量

M18 後面帶2個參數 第一個是頻率,第二個是播放時長

M19 和上面的M18類似

M200 後面帶2個參數,設置左馬達和右馬達的速度

M202 後面三個參數,左馬達和右側馬達速度,持續時間

下面就實驗一下直接使用 Windows自帶的Telnet來實現控制,小車當前的IP可以從控制的APP中看到,當然也可以從你家路由器的配置界面看到:

下面就連接上了,我輸入M0(無回顯),小車返回下面的字元給我

接下來,就可以像電影的黑客一樣輸入字元來控制小車啦。

退出當前 telnet 連接的方法是使用 ctrl+],再輸入quit。當然直接關閉窗口也可以。

雖然這樣的方法看起來很酷,但是比較麻煩,所以接下來使用C#編寫一個 Windows程序來進行控制。選擇 C# 的最主要是因為語言簡單,特別是在界面開發所見即所得。如果說20年前的Delphi 是 VB Killer的話,眼下 C# 也能稱得上 Delphi Killer了。不由得發出「風輪流轉」的感嘆。

從界面入手,介紹功能:左右放置兩個TrackBar用來控制左右輪子的速度,上方放置2個控制LED的Button(具體的做法是Button 上顯示圖片,通過 ImageList來進行切換),一個 Text輸入框用來輸入小車的IP,剩下的大按鈕是用來控制車前燈。

除了這些可見的控制項之外,還有下面三個不可見的控制項,2個ImageList 是給Button顯示圖標用的,一個 Timer是用來每隔100ms發送命令用到(發出的命令是左右輪子轉動加持續100ms,

這樣就能做到連續運行)。同時Trackbar如果有一個數值低於30就直接停止。

完整代碼如下:

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

using System.Windows.Forms;

using System.Net.Sockets;

using System.IO;

namespace Tempshow

{

public partial class Form1 : Form

{

// Telnet 對象

TelnetConnection tc;

// 前燈是否有改變的標記,如果有改變我們才發送命令給小車

Boolean LedChanged = false;

public Form1()

{

InitializeComponent();

}

//左車燈按鈕

private void

button1_Click(object sender, EventArgs e)

{

// ImageIndex 為 0 表示關,1表示開

if (this.button1.ImageIndex

== 0) { this.button1.ImageIndex = 1; }

else { this.button1.ImageIndex

= 0; }

LedChanged = true;

}

//右車燈按鈕

private void

button2_Click(object sender, EventArgs e)

{

// ImageIndex 為 0 表示關,1表示開

if (this.button2.ImageIndex

== 0) { this.button2.ImageIndex = 1; }

else { this.button2.ImageIndex

= 0; }

LedChanged = true;

}

//連接小車的按鈕

private void

button3_Click(object sender, EventArgs e)

{

if (this.button3.ImageIndex

== 0) {

this.button3.ImageIndex = 1;

this.textBox1.Enabled = true;

timer1.Enabled = false;

if (tc.IsConnected) {

tc.DisConnect();

}

}

else {

// 創建 Telnet 連接

tc = new TelnetConnection(textBox1.Text, 23);

string s = tc.Login(" ", " ", 100);

// 正確連接之後

if (tc.IsConnected) {

// 改變連接的圖標

button3.ImageIndex = 0;

// 不可以輸入 Ip

textBox1.Enabled = false;

//打開定時器

timer1.Enabled = true;

}

}

}

//每隔100ms發生一次的定時器

private void

timer1_Tick(object sender, EventArgs e)

{

string led="M6 ";

if (LedChanged) {

if (button1.ImageIndex==0) {

//發送開左燈

led += "0 ";

} else

{

//發送關左燈

led += "1 ";

}

if (button2.ImageIndex == 0)

{

//發送開右燈

led += "0";

}else

{

//發送關右燈

led += "1";

}

tc.WriteLine(led);

LedChanged = false;

}

if ((trackBar1.Value<30) || (trackBar2.Value <30)) {

tc.WriteLine("M200 0 0 0");

}

else {

string Motor = "M200 " + trackBar1.Value.ToString() + "

"+trackBar2.Value.ToString() + " 100";

tc.WriteLine(Motor);

}

}

}

enum Verbs

{

WILL = 251,

WONT = 252,

DO = 253,

DONT = 254,

IAC = 255

}

enum Options

{

SGA = 3

}

class TelnetConnection

{

TcpClient tcpSocket;

int TimeOutMs = 1 * 1000;

public TelnetConnection(String Hostname, int Port)

{

tcpSocket = new TcpClient(Hostname, Port);

}

public string Login(string Username, string Password, int LoginTimeOutMs)

{

int oldTimeOutMs = TimeOutMs;

TimeOutMs = LoginTimeOutMs;

// string s;

// string s =

Read();

// if

(!s.TrimEnd().EndsWith(":"))

// throw new Exception("Failed to

connect : no login prompt");

//

WriteLine(Username);

//s += Read();

//if

(!s.TrimEnd().EndsWith(":"))

// throw new Exception("Failed to connect

: no password prompt");

//

WriteLine(Password);

WriteLine("M0");

string s = Read();

TimeOutMs = oldTimeOutMs;

return s;

}

public void

DisConnect()

{

if (tcpSocket != null)

{

if (tcpSocket.Connected)

{

tcpSocket.Client.Disconnect(true);

}

}

}

public void

WriteLine(string cmd)

{

Write(cmd + "
");

}

public void Write(string cmd)

{

if (!tcpSocket.Connected) return;

byte[] buf = System.Text.ASCIIEncoding.ASCII.GetBytes(cmd.Replace("xFF", "xFFxFF"));

tcpSocket.GetStream().Write(buf, 0,

buf.Length);

}

public string Read()

{

if (!tcpSocket.Connected) return null;

StringBuilder sb = new StringBuilder();

do

{

ParseTelnet(sb);

System.Threading.Thread.Sleep(TimeOutMs);

} while (tcpSocket.Available > 0);

return ConvertToGB2312(sb.ToString());

}

public bool

IsConnected

{

get { return

tcpSocket.Connected; }

}

void ParseTelnet(StringBuilder sb)

{

while (tcpSocket.Available > 0)

{

int input = tcpSocket.GetStream().ReadByte();

switch (input)

{

case -1:

break;

case (int)Verbs.IAC:

// interpret as command

int inputverb =

tcpSocket.GetStream().ReadByte();

if (inputverb == -1) break;

switch (inputverb)

{

case (int)Verbs.IAC:

//literal IAC = 255 escaped, so append char 255 to string

sb.Append(inputverb);

break;

case (int)Verbs.DO:

case (int)Verbs.DONT:

case (int)Verbs.WILL:

case (int)Verbs.WONT:

// reply to all commands with "WONT", unless it is SGA

(suppres go ahead)

int inputoption =

tcpSocket.GetStream().ReadByte();

if (inputoption == -1) break;

tcpSocket.GetStream().WriteByte((byte)Verbs.IAC);

if (inputoption == (int)Options.SGA)

tcpSocket.GetStream().WriteByte(inputverb == (int)Verbs.DO ? (byte)Verbs.WILL : (byte)Verbs.DO);

else

tcpSocket.GetStream().WriteByte(inputverb == (int)Verbs.DO ? (byte)Verbs.WONT : (byte)Verbs.DONT);

tcpSocket.GetStream().WriteByte((byte)inputoption);

break;

default:

break;

}

break;

default:

sb.Append((char)input);

break;

}

}

}

private string

ConvertToGB2312(string

str_origin)

{

char[] chars = str_origin.ToCharArray();

byte[] bytes = new byte[chars.Length];

for (int i = 0; i

< chars.Length; i++)

{

int c = (int)chars[i];

bytes[i] = (byte)c;

}

Encoding Encoding_GB2312 = Encoding.GetEncoding("GB2312");

string str_converted = Encoding_GB2312.GetString(bytes);

return str_converted;

}

}

}

Windows平板和普通的PC 筆記本沒有差別,只是更輕薄

實驗的視頻

https://www.zhihu.com/video/930443224208707584
推薦閱讀:

【圖形化學習 Arduino】(二)輸入與輸出
arduino與電腦利用wifi數據互通,是怎麼實現的?
基於arduino的環境雜訊測試儀需要哪些東西?
【Arduino 鉤沉】(一)Arduino IDE 和 Makeblock 庫的安裝
Arduino 是什麼?

TAG:Arduino |