C#标准Mes接口框架(持续更新)
前言
由于近期我做了好几个客户的接入工厂Mes系统的需求。但是每个客户的Mes都有不同程度的定制需求,原有的代码复用难度其实很大。所以打算将整个接入Mes系统的框架单独拿出来作为一个项目使用,同时因为不同的设备接入同一个Mes系统,所以代码的迁移规范同样非常重要。
1.需求分析
这部分的需求分析,主要来自于我在接入不同客户Mes系统时发现的一些问题和解决方案,同时也了解过工厂Mes系统供应商的朋友们。列举了一些比较实际的功能(主要是代码方面的)。
- 需要有同一个的接入方式,方便接入不同客户的Mes系统。
- 需要有全局的参数列表。
- 只允许存在一个Mes系统的入口。
- 保证数据统一性,在多线程访问,设备不同轨道运行时,数据需要做区分。
- 尽可能的减少后续的代码修改。
- UI上,不同的客户的选项卡要做区分,但是只显示一个选项卡。
- 所有关于Mes的操作需要独立于设备内容,方便在不同设备软件上迁移,设备软件只做传入数据的功能。
- 需要考虑由Mes控制设备的功能
2.设计项目内容
- 首先会有一个MesApp的入口,用来访问整个Mes系统,但是这个入口只能有一个,所有MesApp应当使用单例模式。
- 不同的客户需要继承于同一个接口,根据客户的名称信息去访问指定的客户类,所以MesApp应当具有工厂模式,通过工厂生产客户类。
- 根据全局参数列表,需要一个可通过MesApp访问的全局静态类Const,和枚举类
- 保证数据做好区分,但是又保证数据统一性,所以Mes需要全部公用一个数据类对象,并且可以在外部写入参数,并作为类对象进行传参
- UI上面仍然使用MVVM框架去实现,同时使用Visibility的binding的形式来控制在选项卡中显示UI(根据实际情况可以选择公用选项卡或者,多选项卡的形式)
- 考虑数据的通用性,所以数据应到以类对象的形式存在(类型为String的Name,类型为Object的Value)
- 需要独立的数据存储部分,通讯方式需要独立,
- 在MesApp中需要由一个队列跟软件的框架进行通讯,用来控制软件执行某些内容
3.代码内容
3.1MesApp入口
首先MesApp是整个项目的入口,除了数据结构类以外,全部数据都应该从MesApp的端口中进入。所以MesApp有一个单例的入口。其中包含,config配置文件,const在软件运行时的不用存储的变量,IMesSend接口与Mes交互的主要代码,Enum需要使用的枚举值。同时在进入Mes前,需要根据不同的客户去选择我们需要的使用的Mes内容,所以有一个创建mes的函数。同时还有保存配置和获取配置的部分
namespace Mes
{public class MesApp{#region 单例部分private static MesApp _instance = null;private static readonly object Lock = new object();private MesConst _Const = new MesConst();private IMesSend _Mes = null;private MesEnum _MesEnum = new MesEnum();private MyMesConfig _MesConfig = new MyMesConfig();private string EnvironmentAddress = Path.Combine(Environment.CurrentDirectory, "Config");private string ConfigPath = Path.Combine(Environment.CurrentDirectory, "Config\\MesConfig.txt");//mes接收控制软件的队列public Queue<MesProcessData> MesQueueAccept = new Queue<MesProcessData>();//mes接收控制软件结束后的反馈队列public Queue<MesProcessData> MesQueueSend = new Queue<MesProcessData>();public MyMesConfig MyMesConfig{get => _MesConfig;set => _MesConfig = value;}public MesEnum MesEnum{get => _MesEnum;}public MesConst Const{get => _Const;set => _Const = value;}public IMesSend Mes{get => _Mes;set => _Mes = value;}public static MesApp Instance{get{if (_instance == null){lock (Lock){if (_instance == null){_instance = new MesApp();}}}return _instance;}}#endregion/// <summary>/// 创建Mes对象/// </summary>public bool CreatMes(){try{MesEnum.MesCustomer customer = new MesEnum.MesCustomer();customer = MesEnum.GetEnumValueFromDescription<MesEnum.MesCustomer>(MesApp.Instance.MyMesConfig.SelectCustomer);if (!MesApp.Instance.MyMesConfig.IsEnableMes){customer = MesEnum.MesCustomer.None;}switch (customer){case MesEnum.MesCustomer.CustomerA:Mes = new CustomerA();break;default:Mes = new DefaultMes();break;}return true;}catch (Exception){return false;}}public bool SaveMesConfig(){// 检查 config 文件夹是否存在if (!Directory.Exists(EnvironmentAddress)){try{// 创建 config 文件夹Directory.CreateDirectory(EnvironmentAddress);string json = JsonConvert.SerializeObject(MesApp.Instance.MyMesConfig, Formatting.Indented);File.WriteAllText(ConfigPath, json);return true;}catch (Exception ex){MesLog.Error("配置参数序列化失败:" + ex.ToString());return false;}}else{string json = JsonConvert.SerializeObject(MesApp.Instance.MyMesConfig, Formatting.Indented);File.WriteAllText(ConfigPath, json);return true;}}public bool GetMesConfig(){if (Directory.Exists(EnvironmentAddress)){try{string json = File.ReadAllText(ConfigPath);MesApp.Instance.MyMesConfig = MesJson.DeserializeObject<MyMesConfig>(json);}catch (Exception ex){MesLog.Error("配置参数反序列化失败:" + ex.ToString());}}else{return false;}return true;}}
}
3.2IMesSend接口
IMesSend接口是项目主要的内容,在创建Mes时,会使用工厂模式,通过IMesSend接口去生产指定的客户类,客户类通常包含我们自有设备通常需要上传的函数方法。同时包含一个动态接口,因为在某些客户需要定制一些独特的功能,但是大部分客户都是没有的,可以使用这个Task Dynamic(MesDynamic dynamic);的接口。
using System.Threading.Tasks;namespace Mes
{public interface IMesSend{/// <summary>/// 用户登录/// </summary>/// <returns></returns>Task<MesProcessData> MesLogin(MesDynamic dynamic);/// <summary>/// 上报拿板情况/// </summary>Task<bool> RemovePCB(MesDynamic data);/// <summary>/// 上传工艺参数/// </summary>void ProcessParameters();/// <summary>/// 上传整板测试结果/// </summary>Task<bool> Result(MesDynamic dynamic);/// <summary>/// Mes启用/// </summary>/// <returns></returns>bool MesEnable();/// <summary>/// 上传报警信息,/// </summary>/// <param name="message"></param>/// <param name="Level">级别:1为警告黄灯,2为红灯报警</param>Task<bool> AlarmInformation(string message, int Level);/// <summary>/// 消除报警信息/// </summary>/// <param name="message"></param>Task<bool> CancelAlarmInformation(string message);/// <summary>/// 发送设备状态/// </summary>Task<bool> ProcessStop(MesEnum.MachineState on);/// <summary>/// 切换程序/// </summary>void SwitchPrograms();/// <summary>/// 过站检测/// </summary>/// <param name="BoardCode"></param>/// <returns></returns>Task<bool> CheckBoard(MesDynamic dynamic);/// <summary>/// 设备出板/// </summary>/// <returns></returns>Task<bool> OutBoard(MesDynamic dynamic);/// <summary>/// 关闭Mes/// </summary>void CloseMes();/// <summary>/// 动态接口,用于在特殊情况下调用的接口/// </summary>/// <param name="dynamic"></param>/// <returns></returns>Task<MesProcessData> Dynamic(MesDynamic dynamic);}
}
3.3通讯类
通讯类所需要做的内容并不是很多,需要有创建通讯的步骤,发送数据,监听端口,关闭通讯就可以了。
using JOJO.Mes.Log;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;namespace JOJO.Mes.CommModel
{internal class MesHttp{public string MesUrlAddress { get; set; } = @"http:\\Send";public string AccessInterface { get; set; } = "";public string MesUrlAcceptAddress { get; set; } = @"http:\\Accept";public int MesUrlTimeOut { get; set; } = 5000;public bool UseToken { get; set; } = false;public string Token { get; set; } = "";private string url { get; set; } = "";public TimeSpan CtsTimeOut = TimeSpan.FromSeconds(10);HttpClient Client;HttpListener Listener;public Queue<string> GetHttpQueue = new Queue<string>();public Queue<string> ResponseHttpQueue = new Queue<string>();public bool CreatHttpClient(){Client = new HttpClient();Client.Timeout = TimeSpan.FromSeconds(MesUrlTimeOut);Listener = new HttpListener();Listener.Prefixes.Add(MesUrlAcceptAddress); // 监听的 URL 前缀Listener.Start();return true;}public async Task<string> MesUrlSendAndAccept(string obj){try{string dataOut = "";url = MesUrlAddress + "/" + AccessInterface;MesLog.Info("当前访问的URL地址:" + url);// 创建 HTTP 客户端实例if (UseToken){Client.DefaultRequestHeaders.Add("token", Token);}// 构造要发送的内容var content = new StringContent(obj, Encoding.UTF8, "application/json");MesLog.Info("MesUrl数据上传:" + obj);// 发送POST请求var response = await Client.PostAsync(url, content);// 确保响应成功if (response.IsSuccessStatusCode){dataOut = await response.Content.ReadAsStringAsync().ConfigureAwait(false);MesLog.Info("Mes数据接受:" + dataOut);}content = null;response = null;return dataOut;}catch (Exception ex){MesLog.Error("发送Http数据失败:" + ex.ToString());return null;}}public async void AcceptHttp(){while (true){HttpListenerContext context = await Listener.GetContextAsync();HttpListenerRequest request = context.Request;HttpListenerResponse response = context.Response;if (request.HttpMethod == "POST"){using (StreamReader reader = new StreamReader(request.InputStream, request.ContentEncoding)){string requestContent = await reader.ReadToEndAsync();GetHttpQueue.Enqueue(requestContent);string responseString = "";//等待内容响应try{Task ResponseTask = Task.Run(async () =>{while (true){if (ResponseHttpQueue.Count > 0){responseString = ResponseHttpQueue.Dequeue();break;}await Task.Delay(10);}}, new CancellationTokenSource(CtsTimeOut).Token);}catch (Exception ex){MesLog.Error("Http接收响应失败:" + ex.ToString());MesApp.Instance.Const.SetMachineLog("Http接收响应失败");return;}byte[] buffer = Encoding.UTF8.GetBytes(responseString);response.ContentLength64 = buffer.Length;using (Stream output = response.OutputStream){await output.WriteAsync(buffer, 0, buffer.Length);}}}else{response.StatusCode = (int)HttpStatusCode.MethodNotAllowed;response.Close();}}}public void Close(){Client.Dispose();}}
}
using JOJO.Mes.Log;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;namespace JOJO.Mes.CommModel
{internal class MesSocket{private readonly byte[] buffer = new byte[1024 * 1024 * 100];public Socket listener;public Socket handler;public int Point = 8888;public string Address = "192.168.8.88";public Queue<string> SocketQueue = new Queue<string>();public bool CreatSocket(){listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);long address = new long();try{IPAddress ipAddress = IPAddress.Parse(Address);address = BitConverter.ToInt32(ipAddress.GetAddressBytes(), 0);IPEndPoint localEndPoint = new IPEndPoint(address, Point);listener.Bind(localEndPoint);listener.Listen(10);}catch (FormatException){return false;}MesLog.Info("Socket,等待客户端连接...");// 开始异步接受客户端连接listener.BeginAccept(AcceptCallback, listener);return true;}private void AcceptCallback(IAsyncResult ar){Socket listener = (Socket)ar.AsyncState;// 完成接受客户端连接handler = listener.EndAccept(ar);MesLog.Info($"连接Socket成功:" + handler.AddressFamily);// 开始异步接收数据handler.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, ReceiveCallback, handler); 继续监听新的连接listener.BeginAccept(AcceptCallback, listener);}private void ReceiveCallback(IAsyncResult ar){Socket handler = (Socket)ar.AsyncState;try{int bytesRead = handler.EndReceive(ar);if (bytesRead > 0){byte[] data = new byte[bytesRead];Array.Copy(buffer, data, bytesRead);string message = System.Text.Encoding.UTF8.GetString(data);MesLog.Info($"接收Socket数据: {message}");SocketQueue.Enqueue(message);handler.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, ReceiveCallback, handler);}}catch (SocketException e){MesLog.Warn($"接收Socket数据出错: {e.Message}");}finally{handler.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, ReceiveCallback, handler);}}public async void SendObject(string SendString){try{if (!handler.Connected){MesApp.Instance.Const.SetMachineLog("Mes所在的Socket端口未连接,无法发送数据");return;}byte[] SendBytes = Encoding.UTF8.GetBytes(SendString);await Task.Run(() =>{// 通过Socket发送数据handler.Send(SendBytes, 0, SendBytes.Length, SocketFlags.None);});}catch (Exception ex){MesLog.Error("发送不带反馈的Socket数据失败:" + ex.ToString());}}public void Close(){try{handler.Shutdown(SocketShutdown.Both);//listener.Shutdown(SocketShutdown.Both);handler.Close();listener.Close();}catch (Exception){}}}
}
3.4日志类
using System;
using System.IO;
using System.Threading.Tasks;namespace Mes.Log
{internal static class MesLog{public enum LogLevel{Trace,Debug,Info,Warn,Error,Fatal}private static string logBasePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "log", "Meslog");private static long maxFileSize = 5 * 1024 * 1024; // 5MBprivate static LogLevel minimumLevel = LogLevel.Trace;static MesLog(){if (!Directory.Exists(logBasePath)){Directory.CreateDirectory(logBasePath);}}private static string GetLogFilePath(){string date = DateTime.Now.ToString("yyyy-MM-dd");return Path.Combine(logBasePath, $"{date}.txt");}private static async Task AppendTextAsync(string text, string filePath){var fileOptions = FileOptions.Asynchronous;using (var fileStream = new FileStream(filePath,FileMode.Append, // 使用FileMode.Append以追加模式打开文件FileAccess.Write,FileShare.ReadWrite,bufferSize: 4096 * 10,fileOptions)){using (var streamWriter = new StreamWriter(fileStream)){// 异步写入文本到文件await streamWriter.WriteAsync(text);}}}public static async void Write(LogLevel level, string message){if (level < minimumLevel){return;}string timestamp = DateTime.Now.ToString("yyyy - MM - dd HH:mm:ss");string logMessage = $"{timestamp} [{level}]: {message}{Environment.NewLine}";string filePath = GetLogFilePath();if (File.Exists(filePath) && new FileInfo(filePath).Length >= maxFileSize){filePath = Path.Combine(logBasePath, $"{DateTime.Now.ToString("yyyyMMddHHmmss")}.txt");}await AppendTextAsync(logMessage, filePath);}public static void Trace(string message){Write(LogLevel.Trace, message);}public static void Debug(string message){
#if DEBUGWrite(LogLevel.Debug, message);
#endif}public static void Info(string message){Write(LogLevel.Info, message);}public static void Warn(string message){Write(LogLevel.Warn, message);}public static void Error(string message){Write(LogLevel.Error, message);}public static void Fatal(string message){Write(LogLevel.Fatal, message);}}
}
3.5:配置参数类(使用Json格式)
using System;
using System.Windows;namespace Mes.Config
{[Serializable]public class MyMesConfig{/// <summary>/// 是否需要Mes控制软件,不需要情况下,减少线程开辟/// </summary>public bool IsMesControMachine { get; set; } = false;/// <summary>/// 是否显示选择客户页面/// </summary>public string IsShowSelectCustomer { get; set; } = Visibility.Visible.ToString();/// <summary>/// 是否开启Mes/// </summary>public bool IsEnableMes { get; set; } = false;/// <summary>/// Mes超时时间/// </summary>public int MesTimeOut { get; set; } = 5000;public string EquipmentID { get; set; } = "SMT01";public string MesAddress { get; set; } = "192.168.1.1";/// <summary>/// 选择客户选项卡的ID/// </summary>public int SelectedTabIndex { get; set; } = 0;/// <summary>/// 是否显示客户页面/// </summary>public string IsShowCustomer { get; set; } = Visibility.Collapsed.ToString();/// <summary>/// 选择的客户名称/// </summary>public string SelectCustomer { get; set; } = "选择Mes客户";public CustomerConfig.CustomerA CustomerA { get; set; } = new CustomerConfig.CustomerA();public CustomerConfig.CustomerB CustomerB { get; set; } = new CustomerConfig.CustomerB();}
}
3.6,实际使用的用户类参考
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;namespace Mes.Client
{/// <summary>/// 客户A/// </summary>public class CustomerA : IMesSend{private readonly byte[] buffer = new byte[1024 * 1024 * 100];private MesSocket socket = new MesSocket();private static readonly object _lockObject = new object();private Dictionary<string, CustomerARec> _recDic = new Dictionary<string, CustomerARec>();private Dictionary<string, CustomerARec> RecDic{get => _recDic;set{lock (_lockObject){_recDic = value;}}}DateTime HeartTime = DateTime.Now;private bool CustomerAIsConnect = false;private bool IsAlarm = false;private string Heade { get; } = "Header";public CustomerA(){socket.Address = MesApp.Instance.MyMesConfig.MesAddress;socket.Point = MesApp.Instance.MyMesConfig.CustomerA.Port;socket.CreatSocket();Receive();HeartTimeAndIsConnect();}private object lockObj = new object();private void Receive(){Task MesContralMachine = Task.Run(async () =>{while (true){lock (lockObj){if (socket.SocketQueue.Count > 0){string message = socket.SocketQueue.Dequeue();MesLog.Info($"接收客户AMes数据: {message}");MesProcessData ProcessData = MesXml.DeserializeXml(message);string MesInterface = ProcessData.FindValueByPath(new string[] { "Header", "MESSAGENAME" }, 0).ToString();switch (MesInterface){case "EAP_LinkTest_Request":EAP_LinkTest_Request_Accept(ProcessData);break;case "DATE_TIME_CALIBREATION_COMMAND":ChangeTime(ProcessData);break;case "ALARM_REPORT_R":case "EQP_STATUS_REPORT_R":case "JOB_RECEIVE_REPORT_R":case "JOB_SEND_REPORT_R":case "EDC_REPORT_R":case "JOB_REMOVE_RECOVERY_REPORT_R":CheckSend(ProcessData);break;case "123"://预留Mes控制设备部分MesApp.Instance.MesQueueAccept.Enqueue(ProcessData); break;default:break;}}}// 等待一段时间,避免忙等待await Task.Delay(10);}});}public bool MesEnable(){return MesApp.Instance.MyMesConfig.IsEnableMes && socket.handler.Connected;}public async Task<bool> AlarmInformation(string message, int Level){List<MesProcessData> datas = new List<MesProcessData>();datas.Add(new MesProcessData { MesName = "EquipmentID", MesValue = MesApp.Instance.MyMesConfig.EquipmentID });datas.Add(new MesProcessData { MesName = "AlarmStatus", MesValue = 0 });datas.Add(new MesProcessData { MesName = "AlarmLevel", MesValue = 0 });datas.Add(new MesProcessData { MesName = "AlarmCode", MesValue = "00" });datas.Add(new MesProcessData { MesName = "AlarmText", MesValue = message });MesDynamic dynamic = new MesDynamic();dynamic.AddressInterface = "ALARM_REPORT";IsAlarm = true;return await MesSend(datas, dynamic);}public void ProcessParameters(){return;}/// <summary>/// 过站检查/// </summary>/// <param name="BoardCode"></param>/// <returns></returns>public async Task<bool> CheckBoard(MesDynamic dynamic){List<MesProcessData> datas = new List<MesProcessData>();if (dynamic.BoardCode == "" || dynamic.BoardCode == null){dynamic.BoardCode = MesApp.Instance.Const.NowTimeF;}datas.Add(new MesProcessData { MesName = "EquipmentID", MesValue = MesApp.Instance.MyMesConfig.EquipmentID });datas.Add(new MesProcessData { MesName = "JobID", MesValue = dynamic.BoardCode });dynamic.AddressInterface = "JOB_RECEIVE_REPORT";bool result = await MesSend(datas, dynamic);return result;}/// <summary>/// 发送设备状态/// </summary>/// <param name="on"></param>public async Task<bool> ProcessStop(MesEnum.MachineState on){if (!MesApp.Instance.Mes.MesEnable()){return true;}int EquipmentStatus = -1;switch (on){case MesEnum.MachineState.stop:EquipmentStatus = 3;break;case MesEnum.MachineState.start:EquipmentStatus = 1;break;case MesEnum.MachineState.RedLight:EquipmentStatus = 2;break;case MesEnum.MachineState.AwaitEnterBoard:EquipmentStatus = 4;break;case MesEnum.MachineState.AwaitOutBoard:EquipmentStatus = 5;break;default:EquipmentStatus = 2;break;}List<MesProcessData> datas = new List<MesProcessData>();datas.Add(new MesProcessData { MesName = "EquipmentID", MesValue = MesApp.Instance.MyMesConfig.EquipmentID });List<MesProcessData> StationInfoList = new List<MesProcessData>();List<MesProcessData> StationInfo = new List<MesProcessData>();StationInfo.Add(new MesProcessData { MesName = "Station", MesValue = MesApp.Instance.MyMesConfig.CustomerA.CustomerAStatueName });StationInfo.Add(new MesProcessData { MesName = "EquipmentStatus", MesValue = EquipmentStatus });StationInfoList.Add(new MesProcessData { MesName = "StationInfo", MesValue = StationInfo.ToArray() });datas.Add(new MesProcessData { MesName = "StationInfoList", MesValue = StationInfoList.ToArray() });MesDynamic dynamic = new MesDynamic();dynamic.AddressInterface = "EQP_STATUS_REPORT";return await MesSend(datas, dynamic);}public async Task<bool> RemovePCB(MesDynamic data){if (!MesApp.Instance.Mes.MesEnable()){return true;}List<MesProcessData> datas = new List<MesProcessData>();datas.Add(new MesProcessData { MesName = "EquipmentID", MesValue = MesApp.Instance.MyMesConfig.EquipmentID });datas.Add(new MesProcessData { MesName = "JobID", MesValue = data.BoardCode });datas.Add(new MesProcessData { MesName = "RemoveFlag", MesValue = 0 });data.AddressInterface = "JOB_REMOVE_RECOVERY_REPORT";return await MesSend(datas, data);}public async Task<bool> Result(MesDynamic data){if (!MesApp.Instance.Mes.MesEnable()){return true;}List<MesProcessData> datas = new List<MesProcessData>();datas.Add(new MesProcessData { MesName = "EquipmentID", MesValue = MesApp.Instance.MyMesConfig.EquipmentID.ToString() });datas.Add(new MesProcessData { MesName = "JobID", MesValue = data.BoardCode.ToString() });datas.Add(new MesProcessData { MesName = "ProcessTime", MesValue = data.TimeCost.ToString() });datas.Add(new MesProcessData { MesName = "ProcessStartTime", MesValue = data.ProcessStartTime.ToString() });datas.Add(new MesProcessData { MesName = "ProcessEndTime", MesValue = data.ProcessEndTime.ToString() });List<MesProcessData> MesProcessDataList = new List<MesProcessData>();List<MesProcessData> ProcessData1 = new List<MesProcessData>();ProcessData1.Add(new MesProcessData { MesName = "Station", MesValue = MesApp.Instance.MyMesConfig.CustomerA.CustomerAStatueName.ToString() });ProcessData1.Add(new MesProcessData { MesName = "Name", MesValue = "TotalResult" });ProcessData1.Add(new MesProcessData { MesName = "value", MesValue = int.Parse(data.VerifiedBoardResult) == 0 ? "NG" : "OK" });List<MesProcessData> ProcessData2 = new List<MesProcessData>();ProcessData2.Add(new MesProcessData { MesName = "Station", MesValue = MesApp.Instance.MyMesConfig.CustomerA.CustomerAStatueName.ToString() });ProcessData2.Add(new MesProcessData { MesName = "Name", MesValue = "ProductCode" });ProcessData2.Add(new MesProcessData { MesName = "value", MesValue = data.BoardCode.ToString() });List<MesProcessData> ProcessData3 = new List<MesProcessData>();ProcessData3.Add(new MesProcessData { MesName = "Station", MesValue = MesApp.Instance.MyMesConfig.CustomerA.CustomerAStatueName.ToString() });ProcessData3.Add(new MesProcessData { MesName = "Name", MesValue = "ProcessName" });ProcessData3.Add(new MesProcessData { MesName = "value", MesValue = data.ProduceName.ToString() });List<MesProcessData> ProcessData4 = new List<MesProcessData>();ProcessData4.Add(new MesProcessData { MesName = "Station", MesValue = MesApp.Instance.MyMesConfig.CustomerA.CustomerAStatueName.ToString() });ProcessData4.Add(new MesProcessData { MesName = "Name", MesValue = "PartNum" });ProcessData4.Add(new MesProcessData { MesName = "value", MesValue = data.TPNumber.ToString() });List<MesProcessData> ProcessData5 = new List<MesProcessData>();ProcessData5.Add(new MesProcessData { MesName = "Station", MesValue = MesApp.Instance.MyMesConfig.CustomerA.CustomerAStatueName.ToString() });ProcessData5.Add(new MesProcessData { MesName = "Name", MesValue = "NGNum" });ProcessData5.Add(new MesProcessData { MesName = "value", MesValue = data.NGTPNumber.ToString() });JArray Detailes = new JArray();foreach (var item in data.TPNGs){string codeName = string.Join(",", item.NGCodeName);JObject Detailedata = new JObject{{"Code", item.SubBoardCode},{"Results", "NG"},{"TEST_ITEM", item.TagNumber},{"Result", codeName},{"PartName", item.PartNumber}};Detailes.Add(Detailedata);}List<MesProcessData> ProcessData6 = new List<MesProcessData>();ProcessData6.Add(new MesProcessData { MesName = "Station", MesValue = MesApp.Instance.MyMesConfig.CustomerA.CustomerAStatueName });ProcessData6.Add(new MesProcessData { MesName = "Name", MesValue = "Details" });ProcessData6.Add(new MesProcessData { MesName = "value", MesValue = Detailes.ToString() });MesProcessDataList.Add(new MesProcessData { MesName = "ProcessData", MesValue = ProcessData1.ToArray() });MesProcessDataList.Add(new MesProcessData { MesName = "ProcessData", MesValue = ProcessData2.ToArray() });MesProcessDataList.Add(new MesProcessData { MesName = "ProcessData", MesValue = ProcessData3.ToArray() });MesProcessDataList.Add(new MesProcessData { MesName = "ProcessData", MesValue = ProcessData4.ToArray() });MesProcessDataList.Add(new MesProcessData { MesName = "ProcessData", MesValue = ProcessData5.ToArray() });MesProcessDataList.Add(new MesProcessData { MesName = "ProcessData", MesValue = ProcessData6.ToArray() });datas.Add(new MesProcessData { MesName = "ProcessDataList", MesValue = MesProcessDataList.ToArray() });MesDynamic dynamic = new MesDynamic{AddressInterface = "EDC_REPORT"};return await MesSend(datas, dynamic, true); ;}public void SwitchPrograms(){return;}private static DateTime _lastCallTime = DateTime.MinValue;/// <summary>/// 发送客户A数据/// </summary>/// <param name="datas">数据源</param>/// <param name="dynamic">接口</param>/// <returns></returns>public async Task<bool> MesSend(List<MesProcessData> datas, MesDynamic dynamic, bool IsUseFix = false){Random random = new Random();int number = GetUniqueFiveDigitNumber();List<MesProcessData> Message = new List<MesProcessData>();List<MesProcessData> Head = new List<MesProcessData>();Head.Add(new MesProcessData { MesName = "MESSAGENAME", MesValue = dynamic.AddressInterface });Head.Add(new MesProcessData { MesName = "TRANSACTIONID", MesValue = MesApp.Instance.Const.NowTime.ToString("yyyyMMddHHmmssffff") + number });Head.Add(new MesProcessData { MesName = "MESSAGEID", MesValue = number.ToString() });Head.Add(new MesProcessData { MesName = "REPLYSUBJECTNAME", MesValue = MesApp.Instance.MyMesConfig.MesAddress + ":" + MesApp.Instance.MyMesConfig.CustomerA.Port });Message.Add(new MesProcessData { MesName = "Header", MesValue = Head.ToArray() });Message.Add(new MesProcessData { MesName = "Body", MesValue = datas.ToArray() });List<MesProcessData> MessageReturn = new List<MesProcessData>();MessageReturn.Add(new MesProcessData { MesName = "ReturnCode", MesValue = "" });MessageReturn.Add(new MesProcessData { MesName = "ReturnMessage", MesValue = "" });Message.Add(new MesProcessData { MesName = "Return", MesValue = MessageReturn.ToArray() });MesProcessData Top = new MesProcessData();Top.MesName = "Message";Top.MesValue = Message.ToArray();if ((DateTime.Now - _lastCallTime).TotalMilliseconds >= 500){_lastCallTime = DateTime.Now;MesSend_Accept(Top);}return await AwaitReceive(Top);}/// <summary>/// 发送数据后,将参数存储到字典中,等待超时和反馈信号/// </summary>/// <param name="mesData"></param>/// <returns></returns>private async Task<bool> AwaitReceive(MesProcessData Data){CancellationTokenSource cts1 = new CancellationTokenSource();CustomerARec meiDiRec = new CustomerARec();string Time = DateTime.Now.ToString();meiDiRec.Cts = cts1;string MESSAGEID = Data.FindValueByPath(new string[] { Heade, "MESSAGEID" }).ToString();RecDic.Add(MESSAGEID, meiDiRec);bool Result = false;Task task1 = Task.Run(async () =>{int timeoutCount = 0;while (true){try{Task.Delay(TimeSpan.FromMilliseconds(MesApp.Instance.MyMesConfig.MesTimeOut), cts1.Token).Wait(cts1.Token);}catch (OperationCanceledException ex){if (ex.CancellationToken == cts1.Token){if (RecDic[MESSAGEID].Result){RecDic.Remove(MESSAGEID);MesLog.Info(MESSAGEID + "return true");Result = true;return;}RecDic.Remove(MESSAGEID);Result = false;return;}timeoutCount++;if (timeoutCount >= MesApp.Instance.MyMesConfig.CustomerA.CustomerAReNumber){RecDic.Remove(MESSAGEID);MesLog.Error("Mes重新发送3次失败。接口为:" + Data.FindValueByPath(new string[] { Heade, "MESSAGENAME" }).ToString() + " 时间为:" + Time +"随机ID为:" + MESSAGEID);CustomerAIsConnect = false;MesApp.Instance.Const.SetMachinAlarm();break;}else{MesSend_Accept(Data);continue;}}await Task.Delay(5000 * 100);}Result = false;return;}, cts1.Token);await task1;return Result;}private void CheckSend(MesProcessData data){string MESSAGEID = data.FindValueByPath(new string[] { "Header", "MESSAGEID" }).ToString();if (RecDic.ContainsKey(MESSAGEID)){if (!data.FindValueByPath(new string[] { "Return", "ReturnCode" }).ToString().Contains("1")){MesLog.Error("Mes执行失败。 接口为:" + data.FindValueByPath(new string[] { "Header", "MESSAGENAME" }).ToString());}RecDic[MESSAGEID].Result = true;RecDic[MESSAGEID].Cts.Cancel();}else{MesLog.Error("Mes没有此发送数据");}}public void MesSend_Accept(MesProcessData Data){string data = MesXml.SerializeToXml(Data);data = Regex.Replace(data, @"<\?.*?\?>", "");//整理XMl的缩进XmlDocument xmlDoc = new XmlDocument();xmlDoc.LoadXml(data);StringBuilder sb = new StringBuilder();XmlWriterSettings settings = new XmlWriterSettings();settings.Indent = true; // 设置缩进为 truesettings.IndentChars = " "; // 设置缩进字符,这里使用两个空格using (XmlWriter writer = XmlWriter.Create(sb, settings)){xmlDoc.Save(writer);}data = sb.ToString();if (!socket.handler.Connected){MesApp.Instance.Const.SetMachineLog("客户AMes 没有连接");MesApp.Instance.Const.SetMachinAlarm();return;}socket.SendObject(data);}/// <summary>/// 接收心跳,反馈接收的心跳/// </summary>public void EAP_LinkTest_Request_Accept(MesProcessData Data){HeartTime = DateTime.Now;Data = Data.ModifyValueByPath(new string[] { "Header", "MESSAGENAME" }, "EAP_LinkTest_Request_R");Data = Data.ModifyValueByPath(new string[] { "Header", "REPLYSUBJECTNAME" }, MesApp.Instance.MyMesConfig.MesAddress + ":" + MesApp.Instance.MyMesConfig.CustomerA.Port);Data = Data.ModifyValueByPath(new string[] { "Return", "ReturnCode" }, "01");Data = Data.ModifyValueByPath(new string[] { "Return", "ReturnMessage" }, "指令执行成功");MesSend_Accept(Data);}public bool AwaitHeartTime(TimeSpan time){if (HeartTime + time < DateTime.Now){return false;}return true;}public async Task<bool> OutBoard(MesDynamic dynamic){List<MesProcessData> datas = new List<MesProcessData>();if (dynamic.BoardCode == "" || dynamic.BoardCode == null){dynamic.BoardCode = MesApp.Instance.Const.NowTimeF;}datas.Add(new MesProcessData { MesName = "EquipmentID", MesValue = MesApp.Instance.MyMesConfig.EquipmentID });datas.Add(new MesProcessData { MesName = "JobID", MesValue = dynamic.BoardCode });dynamic.AddressInterface = "JOB_SEND_REPORT";bool result = await MesSend(datas, dynamic);return result;}/// <summary>/// Mes校准系统时间/// </summary>/// <param name="data"></param>public void ChangeTime(MesProcessData data){data = data.ModifyValueByPath(new string[] { "Header", "MESSAGENAME" }, "DATE_TIME_CALIBREATION_COMMAND_R");data = data.ModifyValueByPath(new string[] { "Header", "REPLYSUBJECTNAME" }, MesApp.Instance.MyMesConfig.MesAddress + ":" + MesApp.Instance.MyMesConfig.CustomerA.Port);try{string input = data.FindValueByPath(new string[] { "Body", "DateTime" }).ToString();string format = "yyyyMMddHHmmss";DateTime result = DateTime.ParseExact(input, format, null);DateTime dt = result;bool r = UpdateTime.SetDate(dt);if (r){data = data.ModifyValueByPath(new string[] { "Return", "ReturnCode" }, "01");data = data.ModifyValueByPath(new string[] { "Return", "ReturnMessage" }, "指令执行成功");}else{data = data.ModifyValueByPath(new string[] { "Return", "ReturnCode" }, "02");data = data.ModifyValueByPath(new string[] { "Return", "ReturnMessage" }, "指令执行失败");}}catch (Exception ex){data = data.ModifyValueByPath(new string[] { "Return", "ReturnCode" }, "02");data = data.ModifyValueByPath(new string[] { "Return", "ReturnMessage" }, "指令执行失败");MesLog.Error("Mes矫正时间失败:" + ex.Message);}MesSend_Accept(data);}private static Random _random = new Random();private static HashSet<int> _uniqueNumbers = new HashSet<int>();/// <summary>/// 获取五位的随机数/// </summary>/// <returns></returns>static int GetUniqueFiveDigitNumber(){int fiveDigitNumber;do{fiveDigitNumber = _random.Next(10000, 100000);} while (!_uniqueNumbers.Add(fiveDigitNumber));return fiveDigitNumber;}public async Task<bool> CancelAlarmInformation(string message){if (!IsAlarm){return true;}IsAlarm = false;message += ",报警已消除";List<MesProcessData> datas = new List<MesProcessData>();datas.Add(new MesProcessData { MesName = "EquipmentID", MesValue = MesApp.Instance.MyMesConfig.EquipmentID });datas.Add(new MesProcessData { MesName = "AlarmStatus", MesValue = 1 });datas.Add(new MesProcessData { MesName = "AlarmLevel", MesValue = 0 });datas.Add(new MesProcessData { MesName = "AlarmCode", MesValue = "10" });datas.Add(new MesProcessData { MesName = "AlarmText", MesValue = message });MesDynamic dynamic = new MesDynamic();dynamic.AddressInterface = "ALARM_REPORT";return await MesSend(datas, dynamic);}/// <summary>/// 监听心跳/// </summary>private void HeartTimeAndIsConnect(){Task task1 = Task.Run(() =>{while (true){if (!AwaitHeartTime(MesApp.Instance.MyMesConfig.CustomerA.HeartTime)){MesLog.Error("Mes心跳异常");MesApp.Instance.Const.SetMachinAlarm();CloseMes();break;}}});}public void CloseMes(){socket.Close();}public async Task<MesProcessData> MesLogin(MesDynamic dynamic){List<MesProcessData> resultList1 = new List<MesProcessData>();resultList1.Add(new MesProcessData { MesName = "IsEnable", MesValue = true });return new MesProcessData { MesValue = resultList1.ToArray() };}public Task<MesProcessData> Dynamic(MesDynamic dynamic){throw new NotImplementedException();}public class CustomerARec{public CancellationTokenSource Cts { get; set; } = new CancellationTokenSource();public bool Result { get; set; } = false;}}
}
3.7,框架整体解析
如下图所示:整体框架包含7个部分。
1:Client,客户类。由于每个客户都有独特的定制需求,所以所有的客户定制的内容都存放再Client中,便于管理
2:CommModel,通讯类。存放每种不同通讯方式的方法,通常最常使用的是Http和Socket,如果有另外特殊的通讯模式还可以单独编写。
3:Config,配置文件类。推荐使用可读的Json格式。一开始编写Mes时候就有一个客户是在登陆工程师级别账户时要求Mes通讯同意,但是由于客户Mes在升级无法通讯,所有软件登陆不了工程师级别,无法关闭Mes,造成需要软件重新配置参数。所以需要在特殊情况下,可以手动配置Mes参数。不同的客户Mes配置也是单独做出区分即可,在打开软件时将json反序列化到MesApp下面的Config字段中,即可全局使用。
4.Const,参数类。MesConst,用于存放软件中需要使用但是不需要保存的配置参数。MesData,用于存放自定义的数据结构。MesDynamic,一个动态数据结构类,这个类存在的意义在于,所有的接口传入传出都可以使用这个类对象,所有的数据都可以在这个类中建立新的字段,这个是考虑在,某些定制的客户中,一些框架满足不了的需求,可以在这里做新增内容。MesEnum,枚举类,用来存入Mes的枚举,例如客户枚举,设备状态代码等。其中包含,访问Description参数的方法。
5.Log,日志类,将mes的日志与原有的软件日志做区分防止单日的日志过多。
6.Model,方法类,这里存放着Json和Xml的序列化和反序列化的方法类,就暂时来说,客户的序列化都是Json或者Xml的形式。
7.接口类和MesApp的单例入口
3.8,举例部分方法使用方式
报警和取消报警的调用
public async void Alarm(bool enable, int emgLight = -1, string message = "Alarm")
{ChangeEMGLight(enable ? MachineConsts.EMG_ERROR : emgLight == -1 ? MachineConsts.EMG_RUNNING : emgLight);if (MesApp.Instance.Mes.MesEnable() && !enable){MesApp.Instance.Const.ProcessState = MesEnum.MachineState.GreenLight;if (!await MesApp.Instance.Mes.CancelAlarmInformation(message)){LogController.Instance.Error("上传Mes取消报警信息失败");}}if (MesApp.Instance.Mes.MesEnable() && enable){MesApp.Instance.Const.ProcessState = MesEnum.MachineState.RedLight;if (!await MesApp.Instance.Mes.AlarmInformation(message, 2)){LogController.Instance.Error("上传Mes报警信息失败");}}_machine.EnableBuzzer(enable);
}
3.9,UI部分
由于我使用的WPF框架,对winform,QT的框架并不是很熟悉,所以这里只使用WPF框架的内容作为参考
1.选择厂商的UI。WPF的UI其实编写很简单,核心在于Visibility=“{Binding }”>的使用,例如,在选择厂商前,需要将选择厂商的选项卡显示,厂商选项卡隐藏,那么将选择厂商选项卡的binding设置为显示,其他全部厂商选项卡设置为隐藏。同理选择完厂商后就将指定厂商的选项卡显示即可。
2.参数的Binding,如果是需要保存在配置文件中的,可以使用Binding的Value指向Config的类即可。
3.由于UI部分难度不高,并且大家UI都是不一样的,所以这里仅说一下我是怎么使用UI的
<TabItemWidth="200"Height="24"FontSize="15"Header="请选择Mes厂商"Style="{StaticResource OverrideMaterialDesignNavigationRailTabItem}"Visibility="{Binding IsShowSelectCustomer}"><Grid IsEnabled="{Binding Source={x:Static service:ApplicationStateService.Instance}, Path=LoginUser.UserGroupKey, Converter={StaticResource LoginUserAuthorityToIsEnableConverter}, ConverterParameter={x:Static enums:AuthorityKeys.SoftwareOptions}}"><Grid.RowDefinitions><RowDefinition Height="auto" /><RowDefinition Height="auto" /></Grid.RowDefinitions><StackPanel><Grid Margin="8"><Grid.ColumnDefinitions><ColumnDefinition Width="200" /><ColumnDefinition Width="*" /></Grid.ColumnDefinitions><TextBlockHorizontalAlignment="Right"VerticalAlignment="Center"Text="选择需要使用的Mes系统:" /><ComboBoxName="客户名称"Grid.Column="1"Height="30"Margin="0,0,8,0"Cursor="IBeam"ItemsSource="{Binding Path=CustomerName}"Style="{DynamicResource MaterialDesignComboBox}"Text="{Binding SelectCustomer, UpdateSourceTrigger=PropertyChanged}" /></Grid><ButtonMargin="5"HorizontalAlignment="Center"VerticalAlignment="Center"Command="{Binding OpenMesUI}"CommandParameter="ALM"Content="开启Mes配置界面" /></StackPanel></Grid>
</TabItem>
4.总结
整个标准Mes框架并不是很难。难点只有一个是如果将数据灵活应用,在常见编写Mes时,由于每个客户的数据结构都是不一样的,每个客户都需要单独开多个数据结构类去序列化和反序列化,当然这个并没有错,只是这样子会导致代码冗杂量非常大,而且维护难度大,代码命名混乱的问题。所以标准框架主要还是提供一个如何解决数据灵活性的思路而已。除去这个以外,其他内容并不是很难,剩下的就是如何规范后续客户的扩展性,和如何高效的实现解耦和代码迁移。
最后,如果有什么想法可以持续交流。有时间的话,这个标准接口框架还会随着我接入Mes的次数而优化。
相关文章:

C#标准Mes接口框架(持续更新)
前言 由于近期我做了好几个客户的接入工厂Mes系统的需求。但是每个客户的Mes都有不同程度的定制需求,原有的代码复用难度其实很大。所以打算将整个接入Mes系统的框架单独拿出来作为一个项目使用,同时因为不同的设备接入同一个Mes系统,所以代…...

【Uniapp-Vue3】动态设置页面导航条的样式
1. 动态修改导航条标题 uni.setNavigationBarTitle({ title:"标题名称" }) 点击修改以后顶部导航栏的标题会从“主页”变为“动态标题” 2. 动态修改导航条颜色 uni.setNavigationBarColor({ backgroundColor:"颜色" }) 3. 动态添加导航加载动画 // 添加加…...

SQL 递归 ---- WITH RECURSIVE 的用法
SQL 递归 ---- WITH RECURSIVE 的用法 开发中遇到了一个需求,传递一个父类id,获取父类的信息,同时获取其所有子类的信息。 首先想到的是通过程序中去递归查,但这种方法着实孬了一点,于是想,sql能不能递归查…...

期权帮|如何利用股指期货进行对冲套利?
锦鲤三三每日分享期权知识,帮助期权新手及时有效地掌握即市趋势与新资讯! 如何利用股指期货进行对冲套利? 对冲就是通过股指期货来平衡投资组合的风险。它分为正向与反向两种策略: (1)正向对冲ÿ…...

INCOSE需求编写指南-第1部分:介绍
第1部分:介绍Section 1: Introduction 1.1 目的和范围 Purpose and Scope 本指南专门介绍如何在系统工程背景下以文本形式表达需求和要求陈述。其目的是将现有标准(如 ISO/IEC/IEEE 29148)中的建议以及作者、主要贡献者和审稿员的最佳实践结…...

FFPlay命令全集合
FFPlay是以FFmpeg框架为基础,外加渲染音视频的库libSDL构建的媒体文件播放器。 ffplay工具下载并播放视频,可以辅助卡看流信息。 官网下载地址:http://ffmpeg.org/download.html#build-windows 下载build好的exe程序: 此处下载…...
Mono里运行C#脚本34—内部函数调用的过程
本文来分析Mono运行脚本时,会调用一些C实现的函数代码。 而这个过程又是怎么样实现的呢? 比如前面分析的脚本: IL_0000: call string class MonoEmbed::gimme() 在这里会调用C函数实现的MonoEmbed::gimme()函数。 而这个函数是在C程序内部实现,通过下面的代码来注册到运行…...
rust feature h和 workspace相关知识 (十一)
feature 相关作用和描述 在 Rust 中,features(特性) 是一种控制可选功能和依赖的机制。它允许你在编译时根据不同的需求启用或禁用某些功能,优化构建,甚至改变代码的行为。Rust 的特性使得你可以轻松地为库提供不同的…...
-bash: ./uninstall.command: /bin/sh^M: 坏的解释器: 没有那个文件或目录
终端报错: -bash: ./uninstall.command: /bin/sh^M: 坏的解释器: 没有那个文件或目录原因:由于文件行尾符不匹配导致的。当脚本文件在Windows环境中创建或编辑后,行尾符为CRLF(即回车和换行,\r\n)…...

【Redis】Redis入门以及什么是分布式系统{Redis引入+分布式系统介绍}
文章目录 介绍redis的引入 分布式系统单机架构应用服务和数据库服务分离【负载均衡】引入更多的应用服务器节点 单机架构 分布式是什么 数据库分离和负载均衡 理解负载均衡 数据库读写分离 引入缓存 数据库分库分表 引入微服务 介绍 The open source, in-memory data store us…...
C#高级:常用的扩展方法大全
1.String public static class StringExtensions {/// <summary>/// 字符串转List(中逗 英逗分隔)/// </summary>public static List<string> SplitCommaToList(this string data){if (string.IsNullOrEmpty(data)){return new List&…...

Consul持久化配置报错1067---consul_start
报错都是文件写的有问题或者格式问题,直接复制我的这个改改地址就行 先创建文本文件consul_start.txt--->再复制代码保存---->再把.txt改成.bat 持久化存储的地址在:mydata 注:D:\consul\consul_1.20.2_windows_386改成自己consul的…...
「 机器人 」扑翼飞行器控制策略浅谈
1. 研究背景 • 自然界中的蜂鸟以极高的机动能力著称,能够在短至0.2秒内完成如急转弯、快速加速、倒飞、躲避威胁等极限机动。这种表现对微型飞行器(Flapping Wing Micro Air Vehicles, FWMAVs)具有重要的仿生启示。 • 目前的微型飞行器距离自然生物的飞行能力仍有相当差距…...
Qt信号与槽底层实现原理
在Qt中,信号与槽是实现对象间通信的核心机制, 类似于观察者模式。当某个事件发生后,比如按钮被点击,就会发出一个信号(signal)。这种发出是没有目的的,类似广播。如果有对象对这个信号感兴趣,它就会使用连接(connect)函数,将想要处理的信号和自己的一个函数(称为槽…...

QT QTableWidget控件 全面详解
本系列文章全面的介绍了QT中的57种控件的使用方法以及示例,包括 Button(PushButton、toolButton、radioButton、checkBox、commandLinkButton、buttonBox)、Layouts(verticalLayout、horizontalLayout、gridLayout、formLayout)、Spacers(verticalSpacer、horizontalSpacer)、…...

Flutter_学习记录_基本组件的使用记录
1.TextWidge的常用属性 1.1TextAlign: 文本对齐属性 常用的样式有: TextAlign.center 居中TextAlign.left 左对齐TextAlign.right 有对齐 使用案例: body: Center(child: Text(开启 TextWidget 的旅程吧,珠珠, 开启 TextWidget 的旅程吧&a…...

基于JAVA的微信点餐小程序设计与实现(LW+源码+讲解)
专注于大学生项目实战开发,讲解,毕业答疑辅导,欢迎高校老师/同行前辈交流合作✌。 技术范围:SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:…...

计算机毕业设计hadoop+spark+hive民宿推荐系统 酒店推荐系统 民宿价格预测 酒店价格 预测 机器学习 深度学习 Python爬虫 HDFS集群
温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 作者简介:Java领…...

亲测有效!解决PyCharm下PyEMD安装报错 ModuleNotFoundError: No module named ‘PyEMD‘
解决PyCharm下PyEMD安装报错 PyEMD安装报错解决方案 PyEMD安装报错 PyCharm下通过右键自动安装PyEMD后运行报错ModuleNotFoundError: No module named ‘PyEMD’ 解决方案 通过PyCharm IDE python package搜索EMD-signal,选择版本后点击“install”执行安装...
Gin 应用并注册 pprof
pprof 配置与使用步骤 1. 引言 通过下面操作,你可以顺利集成和使用 pprof 来收集和分析 Gin 应用的性能数据。你可以查看 CPU 使用情况、内存占用、以及其他运行时性能数据,并通过图形化界面进行深度分析。 1. 安装依赖 首先,确保安装了 gi…...
CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型
CVPR 2025 | MIMO:支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题:MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者:Yanyuan Chen, Dexuan Xu, Yu Hu…...

Appium+python自动化(十六)- ADB命令
简介 Android 调试桥(adb)是多种用途的工具,该工具可以帮助你你管理设备或模拟器 的状态。 adb ( Android Debug Bridge)是一个通用命令行工具,其允许您与模拟器实例或连接的 Android 设备进行通信。它可为各种设备操作提供便利,如安装和调试…...

剑指offer20_链表中环的入口节点
链表中环的入口节点 给定一个链表,若其中包含环,则输出环的入口节点。 若其中不包含环,则输出null。 数据范围 节点 val 值取值范围 [ 1 , 1000 ] [1,1000] [1,1000]。 节点 val 值各不相同。 链表长度 [ 0 , 500 ] [0,500] [0,500]。 …...
【python异步多线程】异步多线程爬虫代码示例
claude生成的python多线程、异步代码示例,模拟20个网页的爬取,每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程:允许程序同时执行多个任务,提高IO密集型任务(如网络请求)的效率…...

selenium学习实战【Python爬虫】
selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...

使用Spring AI和MCP协议构建图片搜索服务
目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式(本地调用) SSE模式(远程调用) 4. 注册工具提…...
嵌入式常见 CPU 架构
架构类型架构厂商芯片厂商典型芯片特点与应用场景PICRISC (8/16 位)MicrochipMicrochipPIC16F877A、PIC18F4550简化指令集,单周期执行;低功耗、CIP 独立外设;用于家电、小电机控制、安防面板等嵌入式场景8051CISC (8 位)Intel(原始…...
API网关Kong的鉴权与限流:高并发场景下的核心实践
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 引言 在微服务架构中,API网关承担着流量调度、安全防护和协议转换的核心职责。作为云原生时代的代表性网关,Kong凭借其插件化架构…...

Ubuntu系统多网卡多相机IP设置方法
目录 1、硬件情况 2、如何设置网卡和相机IP 2.1 万兆网卡连接交换机,交换机再连相机 2.1.1 网卡设置 2.1.2 相机设置 2.3 万兆网卡直连相机 1、硬件情况 2个网卡n个相机 电脑系统信息,系统版本:Ubuntu22.04.5 LTS;内核版本…...
第八部分:阶段项目 6:构建 React 前端应用
现在,是时候将你学到的 React 基础知识付诸实践,构建一个简单的前端应用来模拟与后端 API 的交互了。在这个阶段,你可以先使用模拟数据,或者如果你的后端 API(阶段项目 5)已经搭建好,可以直接连…...