随着社会的发展,工厂智能化的步骤越来越快,信息和数据越来越重要,MES,SCADA等智能化系统逐渐成为工厂管理的标配,于是数据采集与维护也逐渐成为了工厂工作的重点,本人把工作中学习的项目分享给大家,希望能抛砖引玉,得到大家的指点和帮助!
项目预览与概述:
预览
电脑测试预览
手机测试预览
设计思路
概述
项目分为3个部分,硬件基础(数据收集),数据采集上传,信息展示(微信小程序)
一.硬件基础 西门子S71500 PLC
1.设备选型,组态
2.数据块结构设计
3.OPC UA服务开启
4.仿真环境搭建
二.数据采集上传 OPC客户端
1.OPC UA 通信连接
2.数据采集(采用订阅方式)
3.数据处理或上传微信云数据库
三.信息展示(微信小程序)
1.选择云开发,云模板
2.修改界面
2.修改云函数和数据展示逻辑
项目实施:
一.硬件基础 西门子S71500 PLC
1.设备选型,组态
2.数据块结构设计
为了方便数据数据采集,数据块一定要设计合理,尽量模块化
3.OPC UA服务开启
西门子1500PLC配置OPC相当简单,选择好授权激活OPC服务器功能即可,具体几个容易出错的小细节见图片
尽量启用访客认证,这样方便测试
4.仿真环境搭建
我为了方便测试,电脑上安装了VM虚拟机,虚拟机里安装博途V15.1和模拟器S7-PLCSIM Advanced V3.0,如果大家也和我一样那么要注意IP设置,否则OPC客户端在本机无法采集数据,详细见下图
仿真时一定要选上支持仿真的功能,许可证类型要选上
二.数据采集上传 OPC客户端
1.OPC UA 通信连接
1.引用OPC官方的Opc.Ua.Client.dll,Opc.Ua.Core.dll
2.引用开源的OPCHelper(编译后引用)
网上搜索的帮助类,自己修改了一下,具体代码如图
OPC UA详细使用方法见下面这位大牛的文章
C#使用OpcUaHelper开源库开发客户端实现读取、订阅OPC UA服务器节点信息_牛奶咖啡13的博客-CSDN博客_c# opc ua客户端
//声明对象
private OpcHleperTest opctest;
//实例化对象
opctest = new OpcHleperTest();
//连接
try
{
//判断连接状态后连接
if (!opctest.ConnectStatus)
{
//注意连接地址
opctest.OpenConnectOfAnonymous("opc.tcp://192.168.40.8:4840");
}
//判断连接状态后按钮改变颜色
if (opctest.ConnectStatus)
{
button1.BackColor = Color.YellowGreen;
button1.Text = "已连接";
label9.BackColor = Color.Yellow;
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
2.数据采集(采用订阅方式),数据处理
订阅
//定义需要订阅的数据
string str1 = "ns=3;s=\"opctest\".\"station1\".\"equipment\".\"speed\"";
string str2 = "ns=3;s=\"opctest\".\"station1\".\"equipment\".\"state\"";
string str3 = "ns=3;s=\"opctest\".\"station1\".\"operator\".\"name\"";
string str4 = "ns=3;s=\"opctest\".\"station1\".\"operator\".\"state\"";
//加入数组
string[] nolds = new string[] { str1, str2 , str3 , str4 };
//批量订阅
opctest.BatchNodeIdDatasSubscription("monitor", nolds, SubCallback);
3.数据处理或上传微信云数据库
//数据订阅的回调函数
private void SubCallback(string key, MonitoredItem monitoredItem, MonitoredItemNotificationEventArgs args)
{
if (InvokeRequired)
{
Invoke(new Action<string, MonitoredItem, MonitoredItemNotificationEventArgs>(SubCallback), key, monitoredItem, args);
return;
}
// 如果有多个的订阅值都关联了当前的方法,可以通过key和monitoredItem来区分
MonitoredItemNotification notification = args.NotificationValue as MonitoredItemNotification;
if (notification != null)
{
//运行速度
if(monitoredItem.StartNodeId.ToString()== "ns=3;s=\"opctest\".\"station1\".\"equipment\".\"speed\"")
{
//取出订阅返回的值
textBox2.Text = notification.Value.WrappedValue.Value.ToString();
//数据上传到微信云数据库
UpData("Speed_Station1", textBox2.Text);
}
//设备状态
if (monitoredItem.StartNodeId.ToString() == "ns=3;s=\"opctest\".\"station1\".\"equipment\".\"state\"")
{
//判断取回的值
if(notification.Value.WrappedValue.Value.ToString()=="0")
{
//改变标签的颜色
label6.BackColor = Color.Yellow;
label5.BackColor = Control.DefaultBackColor;
label7.BackColor = Control.DefaultBackColor;
//数据上传到微信云数据库
UpData("State_Station1", "0");
}
if (notification.Value.WrappedValue.Value.ToString() == "1")
{
label5.BackColor = Color.YellowGreen;
label6.BackColor = Control.DefaultBackColor;
label7.BackColor = Control.DefaultBackColor;
UpData("State_Station1", "1");
}
if (notification.Value.WrappedValue.Value.ToString() == "3")
{
label7.BackColor = Color.Red;
label5.BackColor = Control.DefaultBackColor;
label6.BackColor = Control.DefaultBackColor;
UpData("State_Station1", "3");
}
}
//工号
if (monitoredItem.StartNodeId.ToString() == "ns=3;s=\"opctest\".\"station1\".\"operator\".\"name\"")
{
textBox1.Text = notification.Value.WrappedValue.Value.ToString();
UpData("Code_OP", textBox1.Text);
}
//人员状态
if (monitoredItem.StartNodeId.ToString() == "ns=3;s=\"opctest\".\"station1\".\"operator\".\"state\"")
{
if (notification.Value.WrappedValue.Value.ToString()== "True")
{
label3.BackColor = Color.YellowGreen;
label8.BackColor = Control.DefaultBackColor;
UpData("State_OP", "true");
}
if (notification.Value.WrappedValue.Value.ToString() == "False")
{
label8.BackColor = Color.Red;
label3.BackColor = Control.DefaultBackColor;
UpData("State_OP", "false");
}
}
}
}
3.微信云数据库修改
获取修改数据库的官方获取测试地址
// 通过Get请求获取access_token
HttpWebResponse httpWebResponse = GetRequest("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="你的appid"&secret="你的secret"");
string resultJson = HttpWebResponseToString(httpWebResponse);
var resultObj = DesAnonymousType(resultJson, new { access_token = "", expires_in = "" });
return resultObj.access_token;
修改微信云数据库
string accessToken = GetAccessToken();
string accessTokenqueryString = $"{{\"env\":\"你的环境名称\", \"query\": \"db.collection(\\\"你的数据库名\\\").doc(\\\"你的数据库ID\\\")" + ".update({data:" + "{" + name + ":" + value + "}})\"}";
HttpWebResponse httpWebResponse = PostRequest("https://api.weixin.qq.com/tcb/databaseupdate?access_token=" + accessToken, accessTokenqueryString);
string data = HttpWebResponseToString(httpWebResponse);
return data;
全部代码
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Opc.Ua;
using Opc.Ua.Client;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace SimChart
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
//实例化对象
opctest = new OpcHleperTest();
}
//声明对象
private OpcHleperTest opctest;
private void Button1_Click(object sender, EventArgs e)
{
//云数据库的accessToken
accessToken = GetAccessToken();
try
{
//判断连接状态后连接
if (!opctest.ConnectStatus)
{
opctest.OpenConnectOfAnonymous("opc.tcp://192.168.40.8:4840");
}
//判断连接状态后按钮改变颜色
if (opctest.ConnectStatus)
{
button1.BackColor = Color.YellowGreen;
button1.Text = "已连接";
label9.BackColor = Color.Yellow;
}
string str1 = "ns=3;s=\"opctest\".\"station1\".\"equipment\".\"speed\"";
string str2 = "ns=3;s=\"opctest\".\"station1\".\"equipment\".\"state\"";
string str3 = "ns=3;s=\"opctest\".\"station1\".\"operator\".\"name\"";
string str4 = "ns=3;s=\"opctest\".\"station1\".\"operator\".\"state\"";
string[] nolds = new string[] { str1, str2 , str3 , str4 };
opctest.BatchNodeIdDatasSubscription("monitor", nolds, SubCallback);
//opctest.SingleNodeIdDatasSubscription("opctest888", str1, SubCallback2);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
//单节点数据订阅的回调函数
private void SubCallback2(string key, MonitoredItem monitoredItem, MonitoredItemNotificationEventArgs args)
{
if (InvokeRequired)
{
Invoke(new Action<string, MonitoredItem, MonitoredItemNotificationEventArgs>(SubCallback2), key, monitoredItem, args);
return;
}
// 如果有多个的订阅值都关联了当前的方法,可以通过key和monitoredItem来区分
MonitoredItemNotification notification = args.NotificationValue as MonitoredItemNotification;
if (notification != null)
{
textBox1.Text = notification.Value.WrappedValue.Value.ToString();
}
}
//数据订阅的回调函数
private void SubCallback(string key, MonitoredItem monitoredItem, MonitoredItemNotificationEventArgs args)
{
if (InvokeRequired)
{
Invoke(new Action<string, MonitoredItem, MonitoredItemNotificationEventArgs>(SubCallback), key, monitoredItem, args);
return;
}
// 如果有多个的订阅值都关联了当前的方法,可以通过key和monitoredItem来区分
MonitoredItemNotification notification = args.NotificationValue as MonitoredItemNotification;
if (notification != null)
{
//运行速度
if(monitoredItem.StartNodeId.ToString()== "ns=3;s=\"opctest\".\"station1\".\"equipment\".\"speed\"")
{
textBox2.Text = notification.Value.WrappedValue.Value.ToString();
UpData("Speed_Station1", textBox2.Text);
}
//设备状态
if (monitoredItem.StartNodeId.ToString() == "ns=3;s=\"opctest\".\"station1\".\"equipment\".\"state\"")
{
if(notification.Value.WrappedValue.Value.ToString()=="0")
{
label6.BackColor = Color.Yellow;
label5.BackColor = Control.DefaultBackColor;
label7.BackColor = Control.DefaultBackColor;
UpData("State_Station1", "0");
}
if (notification.Value.WrappedValue.Value.ToString() == "1")
{
label5.BackColor = Color.YellowGreen;
label6.BackColor = Control.DefaultBackColor;
label7.BackColor = Control.DefaultBackColor;
UpData("State_Station1", "1");
}
if (notification.Value.WrappedValue.Value.ToString() == "3")
{
label7.BackColor = Color.Red;
label5.BackColor = Control.DefaultBackColor;
label6.BackColor = Control.DefaultBackColor;
UpData("State_Station1", "3");
}
}
//工号
if (monitoredItem.StartNodeId.ToString() == "ns=3;s=\"opctest\".\"station1\".\"operator\".\"name\"")
{
textBox1.Text = notification.Value.WrappedValue.Value.ToString();
UpData("Code_OP", textBox1.Text);
}
//人员状态
if (monitoredItem.StartNodeId.ToString() == "ns=3;s=\"opctest\".\"station1\".\"operator\".\"state\"")
{
if (notification.Value.WrappedValue.Value.ToString()== "True")
{
label3.BackColor = Color.YellowGreen;
label8.BackColor = Control.DefaultBackColor;
UpData("State_OP", "true");
}
if (notification.Value.WrappedValue.Value.ToString() == "False")
{
label8.BackColor = Color.Red;
label3.BackColor = Control.DefaultBackColor;
UpData("State_OP", "false");
}
}
}
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
//关闭连接
opctest.CloseConnect();
}
///云数据库相关
///
/// <summary>
/// 发送http Get请求
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
public static HttpWebResponse GetRequest(string url)
{
HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
request.Method = "GET";
request.ContentType = "application/x-www-form-urlencoded";//链接类型
return request.GetResponse() as HttpWebResponse;
}
/// <summary>
/// 发送http Post请求
/// </summary>
/// <returns></returns>
public static HttpWebResponse PostRequest(string url, string messsage)
{
byte[] byteData = Encoding.UTF8.GetBytes(messsage);
HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(url);
webRequest.Method = "POST";
webRequest.ContentType = "application/json;charset=UTF-8";
webRequest.ContentLength = byteData.Length;
using (Stream stream = webRequest.GetRequestStream())
{
stream.Write(byteData, 0, byteData.Length);
}
HttpWebResponse response = (HttpWebResponse)webRequest.GetResponse();
return response;
}
/// <summary>
/// 从HttpWebResponse对象中提取响应的数据转换为字符串
/// </summary>
/// <param name="webresponse"></param>
/// <returns></returns>
public static string HttpWebResponseToString(HttpWebResponse webresponse)
{
using (Stream s = webresponse.GetResponseStream())
{
StreamReader reader = new StreamReader(s, Encoding.UTF8);
return reader.ReadToEnd();
}
}
/// <summary>
/// Json字符串转为匿名对象
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="json"></param>
/// <param name="anonymousTypeObject"></param>
/// <returns></returns>
public static T DesAnonymousType<T>(string json, T anonymousTypeObject)
{
return JsonConvert.DeserializeAnonymousType(json, anonymousTypeObject);
}
/// <summary>
/// 获取access_token
/// </summary>
/// <returns></returns>
public static string GetAccessToken()
{
// 通过Get请求获取access_token
HttpWebResponse httpWebResponse = GetRequest("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=wx017496cac8389210&secret=51c000bc2fa6d32fa8969e9cb8f71072");
string resultJson = HttpWebResponseToString(httpWebResponse);
var resultObj = DesAnonymousType(resultJson, new { access_token = "", expires_in = "" });
return resultObj.access_token;
}
/// <summary>
/// 查询数据
/// </summary>
/// <param name="queryString">形如: $"{{\"env\":\"小程序环境id\", \"query\": \"db.collecti(\\\"数据集合名称\\\").where({{集合中字段:\\\"集合中字段值\\\"}}).limit(10).get()\"}}"</param>
/// <returns></returns>
//public static string GetData(string queryString)
public static string GetData()
{
string accessToken = GetAccessToken();
string accessTokenqueryString = $"{{\"env\":\"zunzhi-5gthrfdrd0504463\", \"query\": \"db.collection(\\\"Test\\\").where({{_id:\\\"TestByClient\\\"}}).limit(10).get()\"}}";
HttpWebResponse httpWebResponse = PostRequest("https://api.weixin.qq.com/tcb/databasequery?access_token=" + accessToken, accessTokenqueryString);
string data = HttpWebResponseToString(httpWebResponse);
return data;
}
static string accessToken = "";
/// <summary>
/// 更新数据
/// </summary>
/// <param name="queryString">形如: $"{{\"env\":\"小程序环境id\", \"query\": \"db.collecti(\\\"数据集合名称\\\").where({{集合中字段:\\\"集合中字段值\\\"}}).update({data:{集合中字段:\\\"集合中字段值\\\"})"</param>
/// <returns></returns>
//public static string GetData(string queryString)
public static string UpData(string name, string value)
{
//string accessToken = GetAccessToken();
string accessTokenqueryString = $"{{\"env\":\"zunzhi-5gthrfdrd0504463\", \"query\": \"db.collection(\\\"Test\\\").doc(\\\"TestByClient\\\")" + ".update({data:" + "{" + name + ":" + value + "}})\"}";
HttpWebResponse httpWebResponse = PostRequest("https://api.weixin.qq.com/tcb/databaseupdate?access_token=" + accessToken, accessTokenqueryString);
string data = HttpWebResponseToString(httpWebResponse);
return data;
}
/// <summary>
/// 通过FileId获取文件下载URL
/// </summary>
/// <param name="queryString">形如:$"{{\"env\": \"环境ID\",\"file_list\": [{{\"fileid\":文件ID\",\"max_age\":7200 }}]}}"</param>
/// <returns></returns>
public static string GetDownFileUrl(string queryString)
{
string accessToken = GetAccessToken();
string url = $"https://api.weixin.qq.com/tcb/batchdownloadfile?access_token={accessToken}";
HttpWebResponse httpWebResponse = PostRequest(url, queryString);
string downFileUrl = HttpWebResponseToString(httpWebResponse);
return downFileUrl;
}
//提取数据
public static string GetValue(string JsonString, string Jsonvalue)
{
JObject JsonTemp = JObject.Parse(JsonString);
string str1 = JsonTemp["data"].ToString().Replace("[", "").Replace("]", "").Trim();
str1 = str1.Substring(1, str1.Length - 2).Replace(@"\", "");
JObject JsonTemp2 = JObject.Parse(str1);
string str2 = JsonTemp2[Jsonvalue].ToString();
return str2;
}
}
}
正常后返回的数据见下图
三.信息展示(微信小程序)
1.选择云开发,云模板
打开微信的官方开发软件,建立项目,选择云开发,选择官方的云模板
2.简单修改一下界面
把模板中不需要的注释掉,加入自己的代码
<view class="title">XX车间状态</view>
<view class="top_tip">努力工作!好好学习!</view>
<view class="app">
员工
<view class="horizontal">
<view style="color: lightsalmon;">员工工号</view>
<view style="border-style:solid; border-color: darkorchid; height: 38px;width: 200px;color: olivedrab;text-align: center;">{{record[0]["Code_OP"]}}</view>
</view>
<view class="horizontal">
<text wx:if="{{record[0]['State_OP']}}" style="border-style:solid;border-color: turquoise; background-color: rgb(30, 235, 23);">在岗</text>
<text wx:else="" style="border-style:solid;border-color: turquoise; background-color: slategray;">在岗</text>
<text wx:if="{{!record[0]['State_OP']}}"style="border-style:solid;border-color: turquoise; background-color:red;">离岗</text>
<text wx:else="" style="border-style:solid;border-color: turquoise; background-color: slategray;">离岗</text>
</view>
</view>
<view class="app">
设备
<view class="horizontal">
<view style="color: lightsalmon;">运行速度</view>
<view style="border-style:solid; border-color: darkorchid; height: 38px;width: 200px;color: olivedrab;text-align: center;">{{record[0]["Speed_Station1"]}}</view>
</view>
<view class="horizontal">
<text wx:if="{{record[0]['State_Station1']==1}}" style="border-style:solid;border-color: turquoise; background-color: rgb(30, 235, 91);">运行</text>
<text wx:else="" style="border-style:solid;border-color: turquoise; background-color: slategray;">运行</text>
<text wx:if="{{record[0]['State_Station1']==0}}" style="border-style:solid;border-color: turquoise; background-color: yellow;">停止</text>
<text wx:else="" style="border-style:solid;border-color: turquoise; background-color: slategray;">停止</text>
<text wx:if="{{record[0]['State_Station1']==3}}" style="border-style:solid;border-color: turquoise; background-color:red;">故障</text>
<text wx:else="" style="border-style:solid;border-color: turquoise; background-color: slategray;">故障</text>
</view>
</view>
<button style="border-style: solid; border-color: springgreen;background-color: palegreen;margin-top: 30px;" bindtap="getRecord">刷新</button>
3..修改云函数和数据展示逻辑
添加云数据库信息
修改微信云函数,修改后一定上传部署
在页面的js文件中加入查询云数据库的方法
//查询数据
getRecord() {
wx.showLoading({
title: '',
});
wx.cloud.callFunction({
name: 'quickstartFunctions',
config: {
env: this.data.envId
},
data: {
//注意这个命令,不要选错
type: 'selectRecord'
}
}).then((resp) => {
this.setData({
haveGetRecord: true,
record: resp.result.data
});
wx.hideLoading();
}).catch((e) => {
console.log(e);
this.setData({
showUploadTip: true
});
wx.hideLoading();
});
},
修改后保存编译,模拟器就会显示查询的结果,是不是很惊喜?整个项目比较简单,稍微有些编程知识即可入手
我在页面中加入了定时器可以实现数据实时更新,但很快就把我的数据库测试次数用完了,所以就注释掉了,大家打开注释用定时器刷新页面时一定要注意,每天的次数用完微信官方就不允许访问数据库了!页面写满了,无法再写了,代码见附件吧,我已分类打包,完整上传!最后谢谢大家的观看,大家互相交流,共同进步,谢谢!
项目资源下载