双节长假一晃就已余额不足了。 其实,我节前立的Flag也早就完成了。而且对“小目标”做了些简化处理:
- 原计划的客户端控制器,以App小游戏或者小车的遥控器的形式去实现。现在的版本是手机去App控制发光二极管开关和闪烁。
- 考虑到我们的重点旨在驱动树莓派的GPIO做信号输出,以小游戏的形式虽然趣味性会高一些,但是似乎有点喧宾夺主。所以大好的时光,我还是决定还是花在刀刃上,在树莓派上所能显现的内容是一样的。
- 至于遥控小车也是挺好玩的,不过手头上的配件不充足,留着下回补充好配件模块回来,再动手去弄吧。
GPIO控制器
上回讲过了GPIO的接线以及如何C#代码驱动它。回到我的Demo,还稍微做了一点封装,以便不用款的Led灯的重用。上一篇说到的 GpioController
的静态实例,是在LedModule构造方法中注进来的。
LedModule
是基于GPIO对Led模块的控制器,主要方法有:
SetOn
亮灯SetOff
灭灯Blink
闪烁BlinkWith
与另一个Led交替闪烁
/// <summary>
/// Led 模块的封装
/// </summary>
public class LedModule
{
/// <summary>
/// Pin针脚编号,采用来Board方式来自定
/// </summary>
public int PinIndex { get; private set; }
/// <summary>
/// Pin是否开启
/// </summary>
public bool IsOpenned { get; private set; }
/// <summary>
/// GpioController 实例
/// </summary>
public GpioController GPIO { get; private set; }
/// <summary>
/// LedModule构造方法
/// </summary>
/// <param name=\"i\">PinIndex</param>
/// <param name=\"ctrl\">GpioController</param>
public LedModule(int i, GpioController ctrl)
{
PinIndex = i;
GPIO = ctrl;
}
/// <summary>
/// OpenPin
/// </summary>
public void Open()
{
if (PinIndex < 1 || PinIndex > 40) throw new ArgumentException(\"index must between 1 - 40.\");
try
{
if (!GPIO.IsPinOpen(PinIndex))
{
GPIO.OpenPin(PinIndex, PinMode.Output);
}
IsOpenned = true;
}
catch (Exception ex)
{
IsOpenned = false;
throw ex;
}
}
/// <summary>
/// ClosePin
/// </summary>
public void Close()
{
if (IsOpenned) GPIO.ClosePin(PinIndex);
IsOpenned = false;
}
/// <summary>
/// 闪烁
/// </summary>
/// <param name=\"interval\">闪烁的时间间隔</param>
/// <param name=\"times\">闪烁次数,0-代表无限次数</param>
public void Blink(int interval, int times = 0)
{
if (!IsOpenned) return;
int loop = 0;
ThreadPool.QueueUserWorkItem((x) =>
{
while (IsOpenned)
{
if (loop % 2 == 0) SetOn();
else SetOff();
Thread.Sleep(interval);
loop++;
if (times >= loop) break;
}
});
}
/// <summary>
/// 与另外一个LED灯一起闪烁
/// </summary>
/// <param name=\"led\">另一个灯实例</param>
/// <param name=\"interval\">闪烁的时间间隔</param>
/// <param name=\"times\">闪烁次数,0-代表无限次数</param>
public void BlinkWith(LedModule led, int interval, int times = 0)
{
var led1 = this;
var led2 = led;
int loop = 0;
ThreadPool.QueueUserWorkItem((x) =>
{
while (true)
{
if ((!led1.IsOpenned && !led2.IsOpenned) || times > loop) break;
if (loop % 2 == 0)
{
led1.SetOn();
led2.SetOff();
}
else
{
led2.SetOn();
led1.SetOff();
}
Thread.Sleep(interval);
loop++;
}
});
}
/// <summary>
/// LED亮
/// </summary>
public void SetOn()
{
if (IsOpenned) GPIO.Write(PinIndex, PinValue.High);
}
/// <summary>
/// LED灭
/// </summary>
public void SetOff()
{
if (IsOpenned) GPIO.Write(PinIndex, PinValue.Low);
}
}
手机端APP
手机端应用,我提供用的是一个简单的H5页面。通过调用服务端WebAPI,发出控制LED的命令。
H5代码
<!DOCTYPE html>
<html>
<head>
<meta charset=\"utf-8\" />
<meta name=\"viewport\" content=\"width=device-width,initial-scale=1,user-scalable=0,viewport-fit=cover\">
<title>Led控制器</title>
<link href=\"//smallsea2016.gitee.io/lui/css/lui.css\" rel=\"stylesheet\" />
</head>
<body>
<div class=\"ui_page_wrap\">
<header class=\"ui_page_hd\">
<a id=\"go—back\" href=\"javascript:goBack();\" class=\"ui_back\" style=\"display: none;\"></a>
<a>LED控制器</a>
</header>
<div class=\"ui_page_bd\">
<h2 class=\"ui_list_hd\">控制器</h2>
<ul class=\"ui_list ui_list_arrow\">
<li><a href='javascript:resetLed()'>重置LED</a></li>
<li><a href='javascript:setLed(\"red\",2)'>红灯亮</a></li>
<li><a href='javascript:setLed(\"red\",0)'>红灯关闭</a></li>
<li><a href='javascript:setLed(\"blue\",2)'>蓝灯亮</a></li>
<li><a href='javascript:setLed(\"blue\",0)'>蓝灯关闭</a></li>
<li><a href='javascript:blinkLed(\"red\")'>红灯闪烁</a></li>
<li><a href='javascript:blinkLed(\"blue\")'>蓝灯闪烁</a></li>
<li><a href='javascript:blinkLed(\"all\")'>红蓝闪烁</a></li>
</ul>
</div>
</div>
</body>
</html>
<script src=\"//smallsea2016.gitee.io/lui/js/lui.js\"></script>
<script>
function resetLed() {
lui.request({
type: 'POST', url: \"/api/led/reset\", data: \"\",
success: function (res) {
console.log(res);
}
});
}
function setLed(color,cmd) {
lui.request({
type: 'POST', url: \"/api/led/set\", data: { color, cmd },
success: function (res) {
console.log(res);
}
});
}
function blinkLed(color) {
lui.request({
type: 'POST', url: \"/api/led/flash\", data: { color },
success: function (res) {
console.log(res);
}
});
}
</script>
WebAPI 服务端代码
[Controller(BaseUrl = \"/api/led\")]
public class Led : BaseController
{
const string LedControl = \"LedControl\";
[Post]
public JsonResult Reset()
{
Console.WriteLine(\"Led Reset\");
var result = new MsgResult();
Dispatcher.Call(LedControl, new LedEvent(\"reset\", \"\"));
return Json(result);
}
[Post]
public JsonResult Set(string color, int cmd)
{
Console.WriteLine($\"Led Set [{color},{cmd}]\");
var result = new MsgResult();
if (!(color == \"red\" || color == \"blue\"))
result.SetMessage(\"led color is not found\");
if(result.ret == 0)
{
var evt = new LedEvent(\"\", color);
switch (cmd)
{
case 0:
case 1:
evt.action = \"off\";
break;
case 2:
evt.action = \"on\";
break;
}
if(!string.IsNullOrEmpty(evt.action))
result = Dispatcher.Call(LedControl, evt);
}
return Json(result);
}
[Post]
public JsonResult Flash(string color)
{
Console.WriteLine($\"Led Flash [{color}]\");
var result = new MsgResult();
if (!(color == \"red\" || color == \"blue\" || color == \"all\"))
result.SetMessage(\"led color is not found\");
if (result.ret == 0)
{
result = Dispatcher.Call(LedControl, new LedEvent(\"blink\", color));
}
return Json(result);
}
}
代码粘贴到这里,似乎要稍微解释一下,WebAPI并没有直接操控GPIO,而是通将命令封装成LedEvent
,然后通过Dispatcher.Call
事件派发出去。因此,实际上真正执行GPIO命令的是隐藏在应用内的另外一个服务——LedServerHost
/// <summary>
/// 树莓派GPIO Led灯操控服务
/// </summary>
public class LedServerHost : IHostedService, IDisposable
{
private GpioController controller;
private LedModule ledRed;
private LedModule ledBlue;
public const string LedControl = \"LedControl\";
public LedServerHost()
{
}
public void Dispose()
{
ledRed.Close();
ledBlue.Close();
controller.Dispose();
Dispatcher.Removesync(LedControl, OnLedCommand);
}
public Task StartAsync(CancellationToken cancellationToken)
{
controller = new GpioController(PinNumberingScheme.Board);
ledRed = new LedModule(11, controller);
ledBlue = new LedModule(16, controller);
Dispatcher.AddAsync(LedControl, OnLedCommand);// 监听WebApi控制事件
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
this.Dispose();
return Task.CompletedTask;
}
/// <summary>
/// WebApi监听事件的回调方法
/// </summary>
/// <param name=\"obj\"></param>
/// <param name=\"evt\"></param>
/// <returns></returns>
private Task<MsgResult> OnLedCommand(object obj, EventArgs evt)
{
var ledEvt = obj as LedEvent;
var result = new MsgResult();
Console.WriteLine($\"action:{ledEvt.action} | target:{ledEvt.target}\");
switch (ledEvt.action)
{
case \"on\":
SetLedOn(ledEvt);
break;
case \"off\":
SetLedOff(ledEvt);
break;
case \"blink\":
SetLedBlink(ledEvt);
break;
case \"reset\":
ledRed.Close();
ledBlue.Close();
break;
}
return Task.FromResult(result);
}
/// <summary>
/// 设置Led闪烁
/// </summary>
/// <param name=\"ledEvt\"></param>
private void SetLedBlink(LedEvent ledEvt)
{
int interval = (ledEvt.interval == 0) ? 500 : ledEvt.interval;
switch (ledEvt.target)
{
case \"red\":
ledRed.Open();
ledRed.Blink(interval);
break;
case \"blue\":
ledBlue.Open();
ledBlue.Blink(interval);
break;
case \"all\":
ledRed.Open();
ledBlue.Open();
ledRed.BlinkWith(ledBlue, interval);
break;
}
}
/// <summary>
/// 设置Led熄灭
/// </summary>
/// <param name=\"ledEvt\"></param>
private void SetLedOff(LedEvent ledEvt)
{
LedModule led = null;
if (ledEvt.target == \"red\") led = ledRed;
else if (ledEvt.target == \"blue\") led = ledBlue;
led?.Open();
led?.SetOff();
}
/// <summary>
/// 设置Led点亮
/// </summary>
/// <param name=\"ledEvt\"></param>
private void SetLedOn(LedEvent ledEvt)
{
LedModule led = null;
if (ledEvt.target == \"red\") led = ledRed;
else if (ledEvt.target == \"blue\") led = ledBlue;
led?.Open();
led?.SetOn();
}
}
至此,我们熟悉的GpioController
终于又显现出来了。我的代码也宣告粘贴完毕。国庆几天假期,我对着树莓派也就折腾这了个这样的东西出来……
此刻,我的心情有点小兴奋,也有点小失望。兴奋的自然是自己向IoT领域的探索,终于迈出成功的小半步;失望的是个人能力所限,出来的作品确实也不够高端不够精彩。树莓派的系列就此暂告一段落,相信不久我又会回来的:)