再玩树莓派(四)小实验Demo

双节长假一晃就已余额不足了。
其实,我节前立的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的命令。


H5App

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领域的探索,终于迈出成功的小半步;失望的是个人能力所限,出来的作品确实也不够高端不够精彩。树莓派的系列就此暂告一段落,相信不久我又会回来的:)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,546评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,224评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,911评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,737评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,753评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,598评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,338评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,249评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,696评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,888评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,013评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,731评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,348评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,929评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,048评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,203评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,960评论 2 355