> 文章列表 > 物联网WEB大屏数据可视化

物联网WEB大屏数据可视化

物联网WEB大屏数据可视化

物联网WEB大屏数据可视化

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

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

物联网WEB大屏数据可视化

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

  1. 大屏实现方案

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

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

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

物联网WEB大屏数据可视化

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

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

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

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

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

缺点,费时费力。

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

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

缺点,难找合适的。

  1. 推荐方案

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

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

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

物联网WEB大屏数据可视化
  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

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

物联网WEB大屏数据可视化

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:反正是测试玩的,不重要)

物联网WEB大屏数据可视化

代码

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){                                                               //将可用端口号添加到ComboBoxstring 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. 测试效果

物联网WEB大屏数据可视化

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

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

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

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

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

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

物联网WEB大屏数据可视化

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