遙控迷你巡線小車的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", " xFF xFF"));
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(stringstr_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 |