65.9K
CodeProject 正在变化。 阅读更多。
Home

[OoB] Arduino、C#、JavaScript 和 HTML5 声纳 (第 2 部分)

starIconstarIconstarIconstarIconstarIcon

5.00/5 (6投票s)

2014 年 9 月 26 日

CPOL

7分钟阅读

viewsIcon

21628

第一部分描述了 Sonar 项目的总体思路、使用的硬件组件和 Arduino 草图……“Out of Boredom”系列的第二篇文章是关于 C# 和 JavaScript 程序,它们可以显示超声波测距传感器数据在浏览器中。

第一部分描述了 Sonar 项目的总体思路、使用的硬件组件和 Arduino 草图……“Out of Boredom”系列的第二篇文章是关于C# 和 JavaScript 程序,它们可以显示超声波测距传感器数据在浏览器中。 .NET 应用程序的作用是通过串行端口接收来自 Arduino 的消息,并使用 SignalR 库将其广播给客户端。JS/HTML5 客户端使用jquery.signalR库来获取有关伺服器位置和到障碍物距离的信息,并使用这些数据在canvas上渲染声纳图像:

Sonar client image...

这些链接在上一篇文章中,但仅供您参考

1. SonarServer

SonarServer 是一个.NET 4.5 控制台应用程序,在 Visual Studio Express 2013 for Windows Desktop 中创建。它使用Microsoft.AspNet.SignalR.SelfHostMicrosoft.Owin.Cors NuGet 包来创建自托管的 SignalR 服务器。ASP.NET SignalR 是一个库,旨在轻松创建能够将数据推送到 Web 浏览器中运行的客户端的应用程序。这与常规的网页/应用程序行为相反,在常规行为中,客户端(浏览器)通过发出请求(如 GET 或 POST)来请求服务器执行操作。SignalR 允许客户端监听服务器发送的消息……如果可能,SignalR 将使用WebSockets来实现高效的双向连接。如果由于浏览器或服务器限制而无法使用此选项,它将自动切换到其他推送技术,如长轮询服务器发送事件。当我用 Windows 7 Home Premium SP1 的笔记本电脑测试代码时,IE 11 使用长轮询,Chrome 37 使用 SSE。服务器每秒发送约 20 条消息,客户端处理这些消息(通信在本地主机上)没有任何问题。自托管意味着 SignalR 服务器不必运行在 Web 服务器(如 IIS)上——它可以存在于普通的控制台项目中!如果您是 SignalR 的新手,请查看教程……

这是 SonarServer 项目结构

SonarServer solution...

SonarData.cs 文件包含这样的结构

namespace SonarServer
{
    public struct SonarData
    {
        public byte Angle { get; set; }
        public byte Distance { get; set; }
    }
}

服务器会将此类对象的列表发送给客户端。

SonarHub.cs 包含一个派生自 Hub 的类。它不声明任何方法,但仍然很有用。库将使用它来生成 JavaScript 代理对象……

using Microsoft.AspNet.SignalR;

namespace SonarServer
{
    public class SonarHub : Hub
    {

    }
}

Startup.cs 文件如下所示

using Microsoft.Owin.Cors;
using Owin;

namespace SonarServer
{
    class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.UseCors(CorsOptions.AllowAll);
            app.MapSignalR();
        }
    }
}

它配置了 SignalR 服务器,通过UseCors调用使其支持跨域连接。

以下是Program.cs文件中最重要的部分。这段代码

using (SerialPort sp = new SerialPort())
 {
     sp.PortName = "COM3";
     sp.ReceivedBytesThreshold = 3;
     sp.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);

     sp.Open();

     Console.WriteLine("Serial port opened!");

     using (WebApp.Start<Startup>("https://:8080/"))
     {
         Console.WriteLine("Server running!");
         Console.ReadKey();
     }
 }

负责通过名为“COM3”的串行端口打开与 Arduino 的连接,并在localhost:8080启动 SignalR 服务器ReceivedBytesThreshold属性允许我们控制在调用DataReceivedHandler之前从 Arduino 接收的字节数。如果您希望服务器广播更大的数据包并通过客户端渲染,可以增加此值。这是DataReceivedHandler方法中将串行端口数据加载到字节数组的部分

int count = sp.BytesToRead;
[] data = new byte[count];
sp.Read(data, 0, count);

这种字节数组之后会被添加到自定义缓冲区并进行处理,以创建发送给 SignalR 客户端的SonarData对象列表。第一部分提到 Arduino 以字节(数组)包的形式向 PC 发送数据,其中包含三个元素:[255, angle, distance]。特殊255值的目的是分隔用于创建声纳图像的角度-距离对。我们不能仅仅从 Arduino 发送[angle, distance]流到 PC,因为服务器很容易丢失哪个值是角度,哪个值是距离。这可能是由于延迟、缓冲等原因。当然,它不是一个万无一失的协议,但在我测试时效果很好。不要对此过于纠结——这是个爱好项目,还记得吗?:) 查看存储库中的ProcessSonarData方法,如果您想了解字节数组如何转换为SonarData列表(考虑到缓冲)……

SonarServer 拼图中最后缺失的一块是SendSonarDataToClients方法

private static void SendSonarDataToClients(List<SonarData> sonarDataForClients)
{
    var hub = GlobalHost.ConnectionManager.GetHubContext<SonarHub>();
    hub.Clients.All.sonarData(sonarDataForClients);

    Console.WriteLine("Sonar data items sent to clients. Samples count=" + sonarDataForClients.Count);
}

这是实际将数据广播给 Web 浏览器中运行的客户端的内容。您可能想知道为什么不直接使用new运算符创建SonarHub实例,而是使用GetHubContext方法。这是因为 SignalR 负责其 Hub 的生命周期。这样的代码

SonarHub sonarHub = new SonarHub();
sonarHub.Clients.All.sonarData(sonarDataForClients);

将导致异常:“System.InvalidOperationException: Using a Hub instance not created by the HubPipeline is unsupported.”

2. SonarClient

SonarClient 是负责绘制声纳图像的子项目。它不是 Visual Studio 解决方案——只是一些文件

SonarClient files...

我在 IE 11 和 Chrome 37 中测试了 SonarClient 代码,效果很好。假设您也使用现代浏览器。我没有费心进行任何功能检测,我必须为 IE9 编写代码已经够了——至少不是 IE 6,嗯?;) 但如果您想这样做,我可以推荐 Modernizr 库……

这是index.html文件的内容(为了简洁起见,省略了一些枯燥的部分)

<!DOCTYPE html>
<html>
<head>
    <title>Sonar - sample code from morzel.net blog post</title>
    <style>
        /* more */
    </style>
</head>
<body>
    <div>
        <canvas id="sonarImage" width="410" height="210"></canvas>

        <table>
              <!-- more -->            
        </table>
    </div>

    <a href="http://morzel.net" target="_blank">morzel.net</a>

    <script src="lib/jquery-1.6.4.js"></script>
    <script src="lib/jquery.signalR-2.1.1.js"></script>
    <script src="https://:8080/signalr/hubs"></script>
    <script src="sonarStats.js"></script>
    <script src="sonarImage.js"></script>
    <script src="sonarConnection.js"></script>

    <script>
        $(function () {          
            sonarImage.init('sonarImage');
            sonarConnection.init('https://:8080/signalr');
        });
    </script>
</body>
</html>

这个 HTML5 标记中最重要的部分是用于创建声纳图像的canvas元素。该页面导入了jqueryjquery.signalR库,它们使得与服务器通信成为可能。那一行尤其有趣

 <script src="https://:8080/signalr/hubs"></script>

SingalR 会自动为服务器-客户端消息创建 JavaScript 代理对象,该行允许我们将它们加载到页面中。最后三个脚本引用是负责显示从 SonarServer 接收的数据信息、渲染声纳图像以及与服务器通信的 JS 模块。之后有一个简短的脚本,在页面 DOM 就绪后初始化模块。

我将跳过对sonarStats.js文件的描述(没什么特别的——只是填充一些表格单元格)。但sonarConnection.js应该很有趣。这是全部内容

var sonarConnection = (function () {
    'use strict';

    var sonarHub, startTime, numberOfMessages, numberOfSamples;

    var processSonarData = function (sonarData) {
        numberOfMessages++;
        $.each(sonarData, function (index, item) {
            numberOfSamples++;
            sonarImage.draw(item.Angle, item.Distance);
            sonarStats.fillTable(item.Angle, item.Distance, startTime, numberOfMessages, numberOfSamples);
        });
    };

    return {
        init: function (url) {
            $.connection.hub.url = url;

            sonarHub = $.connection.sonarHub;

            if (sonarHub) {
                sonarHub.client.sonarData = processSonarData;
                               
                startTime = new Date();
                numberOfMessages = 0;
                numberOfSamples = 0;

                $.connection.hub.start();
            } else {
                alert('Sonar hub not found! Are you sure the server is working and URL is set correctly?');
            }
        }
    };
}());

init方法设置了 SignalR中心 URL以及sonarData处理程序与 .NET 应用程序建立连接。还有一个非常基础的中心可用性检查(jquery.signalR库对连接相关的事件有广泛的支持,但我们还是保持简单吧)。当服务器调用hub.Clients.All.sonarData(sonarDataForClients)时,会调用processSonarDataprocessSonarData函数接收一个对象数组,其中包含有关伺服器角度和到障碍物距离的信息。SignalR 负责数据的正确序列化/反序列化——您无需自己处理 JSON。$.each函数(jQuery 的一部分)用于为sonarData数组中的每个项目调用sonarImage.drawsonarStats.fillTable方法……

现在是改变角度-距离对并生成精美的声纳图像的模块(sonarImage.js的全部代码)

var sonarImage = (function () {
    'use strict';

    var maxDistance = 100;
    var canvas, context;

    var fadeSonarLines = function () {
        var imageData = context.getImageData(0, 0, canvas.width, canvas.height),
            pixels = imageData.data,
            fadeStep = 1,
            green,
            fadedGreen;

        for (var i = 0; i < pixels.length; i += 4) {
            green = pixels[i + 1];

            fadedGreen = green - fadeStep;
            pixels[i + 1] = fadedGreen;
        }

        context.putImageData(imageData, 0, 0);
    };

    return {
        init: function (canvasId) {
            canvas = document.getElementById(canvasId);
            context = canvas.getContext('2d');

            context.lineWidth = 2;
            context.strokeStyle = '#00FF00';
            context.fillStyle = "#000000";

            context.fillRect(0, 0, canvas.width, canvas.height);

            context.translate(canvas.width / 2, 0);
            context.scale(2, 2);
        },

        draw: function (angle, distance) {
            context.save();

            context.rotate((90 - angle) * Math.PI / 180);
            context.beginPath();
            context.moveTo(0, 0);
            context.lineTo(0, distance || maxDistance); // Treat 0 as above range
            context.stroke();

            context.restore();

            fadeSonarLines();
        }
    };
}());

我们再次有一个init方法。它从canvas获取2d 绘图上下文,并使用它来设置线条宽度和颜色(绿色)。它还设置了内部颜色(黑色)并用它填充整个canvascontext.translate调用用于将坐标系的原点移动到canvas的中心(水平方向)。默认情况下,它位于左上角。context.scale用于使图像比默认设置绘制的大小大两倍。如果您想了解更多关于canvas坐标的信息,请阅读这篇帖子

draw方法是为 Arduino 生成的每个数据样本(角度-距离对)调用的,并由 SonarServer 广播。HC-SR04 传感器测量的到障碍物的距离表示为一条线。距离越大,线越长。这要归功于beginPathmoveTolineTostroke调用。context.rotate方法负责显示传感器在测量距离时所指的角度(伺服器臂的角度)。当伺服器移动时,我们希望改变绘制距离线的方向。请注意,负责绘制线的代码被包含在context.savecontext.restore调用中。这两个调用确保在draw方法调用之间,旋转变换不会累积……

fadeSonarLines函数负责创建旧声纳线以渐进方式消失的效果。context.getImageData方法返回一个 RGBA 值数组,代表构成当前 canvas 图像的像素。该函数循环遍历图像数据并逐渐减小绿色颜色分量的强度。这样,声纳线就会淡入黑色。

瞧——声纳图像可以在浏览器中渲染了:)

Web 平台现在能够做到这些,难道不令人惊叹吗?我不是恐龙,但我还记得当时要在页面上显示一个 div 叠加层需要使用隐藏的 iframe(因为否则 select 元素会渲染在叠加层之上)……真是疯狂的时代;)

© . All rights reserved.