物联网WEB大屏数据可视化

最近了解WEB大屏显示。一般像嵌入式这类的,MQTT协议会走的多一些,走订阅和发布的策略,网上走了一圈之后,目前有几个实现方案。

这里对比一下几个物联网协议,相对而言MQTT更合适物联网,其它几个协议不是干这个的,不过我推荐一下DDS,这玩意还挺好用的。

(ps:最近了解到一个团队的实现方案是tcp。。。什么魔鬼设计,想的啥呢)

  1. 大屏实现方案

1.买物联网网关,附带WEB大屏服务,这里就不打广告了,自己找一圈,蛮多有支持的。

优势是,有些基础服务是免费的,硬件是现成的,有详细的指导文件。

缺点是,高级一些的服务是要收费的,不合适大批量生产,毕竟从底层硬件到前端程序都是人家的,要找到契合自己的网关也麻烦,比如我遇到的几个,都不支持MQTT协议接入。还有就是,数据都得去人家服务器,多少有点膈应。

2.买云服务器,比如阿里云的可视化数据服务,我整了个15天试用。

优点是,数据都是自己的。

缺点是,要买一堆的配套服务,比如协议转换,服务器啥的,死贵死贵的。

3.技术牛皮,那就自己搭建服务器,然后写前端后台嵌入式,我能搞,但这玩意费劲,要时间,而且中间肯定有那么两个环节自己得摸索一段时间。

优点,啥都是自己的,可定制化程度高,省钱,只要有个服务器就行。

缺点,费时费力。

4.找个低代码平台,看了一下,网上这种公司有,只是要掏钱

优点,省时省力,不花多少钱。

缺点,难找合适的。

  1. 推荐方案

对比一圈之后,还是选择了低代码平台,仔细想想,我是干嵌入式的,后台前端这些,对我来说是行业外的东西,可以去了解,但不需要深入,关键省钱就行。

私有部署 - 使用教程-免费低代码数据可视化平台-触达云屏 (topthink.com)

上面的方案个人觉得蛮合适,哔哩哔哩有教程,作者有个群,现在对大家都有技术支持,可以部署到局域网,重点是对个人免费,我觉得不花钱就可以打败一切了,这些都是他的资料。

  1. 服务器搭建

这部分其实是抄官方资料了,自己可以去看,我就抄linux搭建部分

全新安装说明 | 免费低代码数据可视化平台-触达云屏 (chudayun.com)

1,基础环境安装

安装以下基础环境(参考各官网)

Nginx

Mysql 5.7+

2,创建/导入数据库

初始化数据库:创建数据库 chudy_data_visual,执行 chudy_visual_[版本号] /doc 目录下的 初始化SQL文件

3,创建安装目录

分别执行以下三行命令,创建安装目录

cd /
mkdir app
mkdir app/java

4,部署安装

上传以下文件夹到服务器 /app 目录下

chudy_visual_[版本号]

filesystem

chudy_designer

上传 jdk-8u221-linux-x64.tar.gz文件 到服务器 /app/java目录下,解压。

cd /app/java
tar -zxvf jdk-8u221-linux-x64.tar.gz

修改数据库连接配置文件:

/app/chudy_visual_[版本号] /mgr/config 目录下 application-dev.properties

修改 数据库端口、名称、帐号、密码 等信息。

5,配置nginx

上传 chudy_designer 目录下的 chudy_visual.conf 文件到 服务器 nginx的 conf.d 目录下(一般在 /etc/nginx/conf.d/)

重载Nginx配置使配置生效。

防火墙中配置,放行端口 18088

6,启动应用

cd /app/chudy_visual_[版本号]/mgr/
./mgr.sh start
tail -f visual_cms.out

mgr.sh 支持启动、停止、重启、查看状态

./mgr.sh start | stop | restart | status

7,访问系统

http://IP:18088/

初始帐号 sadmin 密码 111111

  1. 设备端实现

目前测试,我是找了个温湿度传感器在折腾,实现方案是

传感器->C#上位机->MQTT协议->云屏

这里放出C#的代码(ps:反正是测试玩的,不重要)

代码

using System;
using System.Text;
using System.Windows.Forms;

using System.IO.Ports;

using System.Net;
using System.Net.Sockets;

using MQTTnet;
using MQTTnet.Client;
using MQTTnet.Protocol;
using System.Threading.Tasks;
using System.Threading;

namespace 开发快上位机
{
    public partial class Form1 : Form
    {
        public static IMqttClient _mqttClient;

        byte[] uart_data = new byte[100];
        byte uart_addr = 0;
        byte uart_start = 0;
        byte uart_time = 0;
        float wendu, shidu;

        public Form1()
        {
            InitializeComponent();
            System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = false;
        }
        
        private void Form1_Load(object sender, EventArgs e)
        {
            button8.Enabled = false;
            serialPort1.DataReceived += new SerialDataReceivedEventHandler(port_DataReceived);//必须手动添加事件处理程序
        }

        private void port_DataReceived(object sender, SerialDataReceivedEventArgs e)//串口数据接收事件
        {
            try
            {
                uart_start = 1;
                uart_time = 0;
                int ilen = serialPort1.BytesToRead;
                byte[] bytes = new byte[ilen];
                serialPort1.Read(bytes, 0, ilen);
                for (int i = 0; i < ilen; i++)
                {
                    uart_data[uart_addr] = bytes[i];
                    uart_addr++;
                }

                /*                if (!radioButton6.Checked)//如果接收模式为字符模式
                                {
                                    int ilen = serialPort1.BytesToRead;
                                    byte[] bytes = new byte[ilen];
                                    serialPort1.Read(bytes, 0, ilen);
                                    //string str = System.Text.Encoding.Default.GetString(bytes); //xx="中文";
                                    //textBox1.AppendText(str);//添加内容
                                }
                                else
                                { //如果接收模式为数值接收
                                    byte data;
                                    data = (byte)serialPort1.ReadByte();//此处需要强制类型转换,将(int)类型数据转换为(byte类型数据,不必考虑是否会丢失数据
                                    //string str = Convert.ToString(data, 16).ToUpper();//转换为大写十六进制字符串
                                    //textBox1.AppendText("0x" + (str.Length == 1 ? "0" + str : str) + " ");//空位补“0”   
                                }
                */
            }
            catch
            {
                textBox1.AppendText("串口数据接收出错,请检查!\r\n");
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            if (button1.Text == "串口连接")
            {
                try
                {
                    serialPort1.PortName = comboBox1.Text;
                    serialPort1.BaudRate = Convert.ToInt32(comboBox2.Text);
                    serialPort1.Open();
                    button1.Text = "断开连接";
                    button2.Enabled = false;
                    panel2.Enabled = false;
                    comboBox1.Enabled = false;
                    comboBox2.Enabled = false;
                    comboBox3.Enabled = false;
                    comboBox4.Enabled = false;
                    textBox1.AppendText("串口已连接\r\n");
                }
                catch
                {
                    if (serialPort1.IsOpen)
                        serialPort1.Close();

                    button1.Text = "串口连接";
                    button2.Enabled = true;
                    comboBox1.Enabled = true;
                    panel2.Enabled = true;
                    comboBox2.Enabled = true;
                    comboBox3.Enabled = true;
                    comboBox4.Enabled = true;
                    textBox1.AppendText("请检查串口连接\r\n");
                }
            }
            else if (button1.Text == "断开连接")
            {
                try
                {
                    serialPort1.Close();
                    button1.Text = "串口连接";
                    button2.Enabled = true;
                    comboBox1.Enabled = true;
                    comboBox2.Enabled = true;
                    comboBox3.Enabled = true;
                    panel2.Enabled = true;
                    comboBox4.Enabled = true;
                    textBox1.AppendText("串口已断开\r\n");
                }
                catch { }
            }
        }

        private void SearchAndAddSerialToComboBox(SerialPort MyPort, ComboBox MyBox)
        {                                                               //将可用端口号添加到ComboBox
            string Buffer;                                              //缓存
            string[] MyString = new string[Convert.ToInt32(comboBox3.Text)];                         //最多容纳20个,太多会影响调试效率        
            MyBox.Items.Clear();                                        //清空ComboBox内容
            for (int i = 1; i < Convert.ToInt32(comboBox3.Text); i++)                                //循环
            {
                try
                {                                   //核心原理是依靠try和catch完成遍历
                    progressBar1.Value = i * (100 / Convert.ToInt32(comboBox3.Text));
                    Buffer = "COM" + i.ToString();
                    MyPort.PortName = Buffer;
                    MyPort.Open();                                      //如果失败,后面的代码不会执行
                    MyString[i - 1] = Buffer;
                    MyBox.Items.Add(Buffer);                            //打开成功,添加至下俩列表
                    comboBox1.Text = Buffer.ToString();
                    MyPort.Close();                                     //关闭
                }
                catch { }
            }
        }

        private void button2_Click(object sender, EventArgs e)
        {
            textBox1.AppendText("开始自动配置串口\r\n");//出错提示
            textBox1.AppendText("串口扫描\r\n");//出错提示
            SearchAndAddSerialToComboBox(serialPort1, comboBox1);       //扫描并讲课用串口添加至下拉列表
            textBox1.AppendText("端口扫描完毕\r\n");//出错提示
            textBox1.AppendText("正在配置波特率\r\n");//出错提示
            comboBox2.Text = comboBox4.Text;
            serialPort1.BaudRate = Convert.ToInt32(comboBox2.Text);
            progressBar1.Value = 0;
            textBox1.AppendText("自动配置完成\r\n");//出错提示
            button1_Click(sender, e);
        }

        private void button3_Click(object sender, EventArgs e)
        {
            textBox1.Clear();
        }

        private void button5_Click(object sender, EventArgs e)
        {
            textBox3.Clear();
        }

        private void button4_Click(object sender, EventArgs e)
        {
            System.IO.File.WriteAllText(@"C:\Users\Administrator\Desktop\发送区数据.txt", textBox1.Text);
            System.IO.File.WriteAllText(@"C:\Users\Administrator\Desktop\接收区数据.txt", textBox3.Text);
            textBox1.AppendText("数据保存完成!\r\n");
        }

        void uart_send(object sender, EventArgs e,string data)
        {
            byte[] Data = new byte[1];//作用同上集
            if (serialPort1.IsOpen)//判断串口是否打开,如果打开执行下一步操作
            {
                try
                {
                    if (data != "")
                    {
                        if (!radioButton6.Checked)//如果发送模式是字符模式
                        {
                            try
                            {//实现串口发送汉字
                                Encoding gb = System.Text.Encoding.GetEncoding("gb2312");
                                byte[] bytes = gb.GetBytes(data);
                                serialPort1.Write(bytes, 0, bytes.Length);
                            }
                            catch
                            {
                                textBox1.AppendText("串口数据写入错误\r\n");//出错提示
                                serialPort1.Close();
                                button1_Click(sender, e);
                            }
                        }
                        else
                        {
                            for (int i = 0; i < (data.Length - data.Length % 2) / 2; i++)//取余3运算作用是防止用户输入的字符为奇数个
                            {
                                Data[0] = Convert.ToByte(data.Substring(i * 2, 2), 16);
                                serialPort1.Write(Data, 0, 1);//循环发送(如果输入字符为0A0BB,则只发送0A,0B)
                            }
                            if (data.Length % 2 != 0)//剩下一位单独处理
                            {
                                Data[0] = Convert.ToByte(data.Substring(data.Length - 1, 1), 16);//单独发送B(0B)
                                serialPort1.Write(Data, 0, 1);//发送
                            }
                        }
                    }
                }
                catch
                {
                    textBox1.AppendText("串口数据写入错误\r\n");//出错提示
                }
            }
        }

        private void button6_Click(object sender, EventArgs e)
        {
            uart_send(sender,e, textBox3.Text);
        }

        private void button9_Click(object sender, EventArgs e)
        {
            var optionsBuilder = new MqttClientOptionsBuilder()
                .WithTcpServer(textBox2.Text, Convert.ToInt16(textBox4.Text)) // 要访问的mqtt服务端的 ip 和 端口号
                .WithCredentials("admin", "123456") // 要访问的mqtt服务端的用户名和密码
                .WithClientId("testclient02") // 设置客户端id
                .WithCleanSession()
                .WithTls(new MqttClientOptionsBuilderTlsParameters
                {
                    UseTls = false  // 是否使用 tls加密
                });

            var clientOptions = optionsBuilder.Build();
            _mqttClient = new MqttFactory().CreateMqttClient();

            _mqttClient.ConnectedAsync += _mqttClient_ConnectedAsync; // 客户端连接成功事件
            _mqttClient.DisconnectedAsync += _mqttClient_DisconnectedAsync; // 客户端连接关闭事件
            _mqttClient.ApplicationMessageReceivedAsync += _mqttClient_ApplicationMessageReceivedAsync; // 收到消息事件

            _mqttClient.ConnectAsync(clientOptions);
        }

        /// <summary>
        /// 客户端连接关闭事件
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        private Task _mqttClient_DisconnectedAsync(MqttClientDisconnectedEventArgs arg)
        {
            button8.Enabled = false;
            button9.Text = "连接";
            textBox6.AppendText($"客户端已断开与服务端的连接……\n");
            
            return Task.CompletedTask;
        }

        /// <summary>
        /// 客户端连接成功事件
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        private Task _mqttClient_ConnectedAsync(MqttClientConnectedEventArgs arg)
        {
            button8.Enabled = Enabled;
            button9.Text = "断开";
            textBox6.AppendText($"客户端已连接服务端……\n");

            // 订阅消息主题
            // MqttQualityOfServiceLevel: (QoS):  0 最多一次,接收者不确认收到消息,并且消息不被发送者存储和重新发送提供与底层 TCP 协议相同的保证。
            // 1: 保证一条消息至少有一次会传递给接收方。发送方存储消息,直到它从接收方收到确认收到消息的数据包。一条消息可以多次发送或传递。
            // 2: 保证每条消息仅由预期的收件人接收一次。级别2是最安全和最慢的服务质量级别,保证由发送方和接收方之间的至少两个请求/响应(四次握手)。
            _mqttClient.SubscribeAsync("sub", MqttQualityOfServiceLevel.AtLeastOnce);

            return Task.CompletedTask;
        }

        /// <summary>
        /// 收到消息事件
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        private Task _mqttClient_ApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs arg)
        {
            textBox6.AppendText($"Topic主题=【{arg.ApplicationMessage.Topic}】 消息={Encoding.UTF8.GetString(arg.ApplicationMessage.Payload)}");
            string str = Encoding.UTF8.GetString(arg.ApplicationMessage.Payload);
            if (str.Contains("on") || str.Contains("off"))
            {
                textBox6.AppendText("\r\n控制继电器\r\n");//出错提示
                byte[] Data = { 0x01, 0x03, 0x00, 0x00, 0x00, 0x02, 0xc4, 0x0b };//作用同上集
                if (serialPort1.IsOpen)//判断串口是否打开,如果打开执行下一步操作
                {
                    try
                    {
                        serialPort1.Write(Data, 0, 8);//循环发送(如果输入字符为0A0BB,则只发送0A,0B)
                    }
                    catch
                    {
                        textBox1.AppendText("串口数据写入错误\r\n");//出错提示
                    }
                }
            }
            //textBox6.AppendText($"ApplicationMessageReceivedAsync:客户端ID=【{arg.ClientId}】接收到消息。 Topic主题=【{arg.ApplicationMessage.Topic}】 消息=【{Encoding.UTF8.GetString(arg.ApplicationMessage.Payload)}】 qos等级=【{arg.ApplicationMessage.QualityOfServiceLevel}】");
            return Task.CompletedTask;
        }

        public void Publish(string topic,string data)
        {
            var message = new MqttApplicationMessage
            {
                Topic = topic,
                Payload = Encoding.Default.GetBytes(data),
                QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
                Retain = true  // 服务端是否保留消息。true为保留,如果有新的订阅者连接,就会立马收到该消息。
            };
            _mqttClient.PublishAsync(message);
        }

        private void button8_Click(object sender, EventArgs e)
        {
            if(textBox8.Text != "")
                Publish(textBox7.Text,textBox8.Text);
        }

        private void button7_Click(object sender, EventArgs e)
        {
            textBox6.Text = "";
            textBox8.Text = "";
        }

        private void button10_Click(object sender, EventArgs e)
        {
            textBox6.AppendText("\r\n读取温度\r\n");//出错提示
            byte[] Data = { 0x01, 0x03, 0x00, 0x00, 0x00, 0x02, 0xc4, 0x0b };//作用同上集
            if (serialPort1.IsOpen)//判断串口是否打开,如果打开执行下一步操作
            {
                try
                {
                    serialPort1.Write(Data, 0, 8);//循环发送(如果输入字符为0A0BB,则只发送0A,0B)
                }
                catch
                {
                    textBox1.AppendText("串口数据写入错误\r\n");//出错提示
                }
            }
        }

        string zhongliang1 = "{\"value\":";
        string zhongliang2 = ",\"unit\": \"Kg\",\"min\": 0,\"max\": 100,\"label\": \"11\"}";
        string wenshidu1 = "[{\"extObj\": {},\"value\": \"";
        string wenshidu2 = "\",\"prefixText\": \"\",\"suffixText\": \"\",\"descText\": \"\",\"backgroundColor\": \"\",\"icon\": \"\",\"color\": \"\"}]";


        private void button11_Click(object sender, EventArgs e)
        {
            string data;
            data = wenshidu1 + textBox9.Text + wenshidu2;
            Publish("wendu", data);
        }

        private void button12_Click(object sender, EventArgs e)
        {
            string data;
            data = wenshidu1 + textBox10.Text + wenshidu2;
            Publish("shidu", data);
        }

        private void button13_Click(object sender, EventArgs e)
        {
            string data;
            data = zhongliang1 + textBox11.Text + zhongliang2;
            Publish("zhongliang", data);
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            if (uart_start == 1)
            {
                uart_time++;
                if (uart_time >= 100)
                {
                    textBox1.AppendText(uart_addr.ToString());//空位补“0”
                    for (int i = 0; i < uart_addr; i++)
                    {
                        string str = Convert.ToString(uart_data[i], 16).ToUpper();//转换为大写十六进制字符串
                        textBox1.AppendText("0x" + (str.Length == 1 ? "0" + str : str) + " ");//空位补“0”
                    }

                    if (uart_data[0] == 0x01 && uart_data[1] == 0x03 && uart_data[2] == 0x04)
                    {
                        shidu = uart_data[3] << 8 | uart_data[4];
                        wendu = uart_data[5] << 8 | uart_data[6];
                        shidu = shidu / 10;
                        wendu = wendu / 10;

                        string str1,str2;
                        textBox9.Text = wendu.ToString();
                        textBox10.Text = shidu.ToString();
                        button11_Click(sender,  e);
                        button12_Click(sender,  e);
                    }

                    uart_start = 0;
                    uart_time = 0;
                    uart_addr = 0;
                }
            }
        }
    }
}
  1. 测试效果

简化操作,直接搞了几个按键去发

当然实际项目不会这么干,实际项目的构成应该是

  1. 单片机接传感器,以MODBUS/无线等方式提供数据访问接口

  1. linux网关实现数据转发,比如NXP的IMX6,上面实现MQTT客户端

  1. 云服务器做MQTT服务器,实现数据转发

  1. WEB云屏/微信小程序/桌面应用等订阅MQTT数据

上面是我的微信和QQ群,欢迎新朋友的加入。

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值