From ea38cf40366ef4b1787f772b6ece74f64ac96ea1 Mon Sep 17 00:00:00 2001 From: WenJY Date: Wed, 21 May 2025 16:57:16 +0800 Subject: [PATCH 1/5] =?UTF-8?q?change=20-=20git=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + .idea/.idea.Sln.Iot/.idea/encodings.xml | 4 ++++ .idea/.idea.Sln.Iot/.idea/indexLayout.xml | 8 +++++++ .../.idea/projectSettingsUpdater.xml | 7 ++++++ .idea/.idea.Sln.Iot/.idea/vcs.xml | 6 +++++ .idea/.idea.Sln.Iot/.idea/workspace.xml | 23 +++++++++++++++++++ Sln.Iot/Program.cs | 3 +++ 7 files changed, 52 insertions(+) create mode 100644 .idea/.idea.Sln.Iot/.idea/encodings.xml create mode 100644 .idea/.idea.Sln.Iot/.idea/indexLayout.xml create mode 100644 .idea/.idea.Sln.Iot/.idea/projectSettingsUpdater.xml create mode 100644 .idea/.idea.Sln.Iot/.idea/vcs.xml create mode 100644 .idea/.idea.Sln.Iot/.idea/workspace.xml diff --git a/.gitignore b/.gitignore index 9491a2f..e37e314 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,7 @@ bld/ # Visual Studio 2015/2017 cache/options directory .vs/ +.idea/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ diff --git a/.idea/.idea.Sln.Iot/.idea/encodings.xml b/.idea/.idea.Sln.Iot/.idea/encodings.xml new file mode 100644 index 0000000..df87cf9 --- /dev/null +++ b/.idea/.idea.Sln.Iot/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/.idea.Sln.Iot/.idea/indexLayout.xml b/.idea/.idea.Sln.Iot/.idea/indexLayout.xml new file mode 100644 index 0000000..7b08163 --- /dev/null +++ b/.idea/.idea.Sln.Iot/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.Sln.Iot/.idea/projectSettingsUpdater.xml b/.idea/.idea.Sln.Iot/.idea/projectSettingsUpdater.xml new file mode 100644 index 0000000..64af657 --- /dev/null +++ b/.idea/.idea.Sln.Iot/.idea/projectSettingsUpdater.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/.idea/.idea.Sln.Iot/.idea/vcs.xml b/.idea/.idea.Sln.Iot/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/.idea.Sln.Iot/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/.idea.Sln.Iot/.idea/workspace.xml b/.idea/.idea.Sln.Iot/.idea/workspace.xml new file mode 100644 index 0000000..e732232 --- /dev/null +++ b/.idea/.idea.Sln.Iot/.idea/workspace.xml @@ -0,0 +1,23 @@ + + + + + + + \ No newline at end of file diff --git a/Sln.Iot/Program.cs b/Sln.Iot/Program.cs index c34cc9e..56010d7 100644 --- a/Sln.Iot/Program.cs +++ b/Sln.Iot/Program.cs @@ -11,6 +11,9 @@ using TouchSocket.Sockets; namespace Sln.Iot { + /// + /// + /// internal class Program { public static IServiceProvider? ServiceProvider = null; From ebc82b90f9a1bc36f6f9cd2161f034fe7d401cdd Mon Sep 17 00:00:00 2001 From: WenJY Date: Wed, 21 May 2025 17:06:59 +0800 Subject: [PATCH 2/5] =?UTF-8?q?change=20-=20git=20=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + Sln.Iot/Program.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index e37e314..3233544 100644 --- a/.gitignore +++ b/.gitignore @@ -98,6 +98,7 @@ StyleCopReport.xml *.pidb *.svclog *.scc +*.idea # Chutzpah Test files _Chutzpah* diff --git a/Sln.Iot/Program.cs b/Sln.Iot/Program.cs index 56010d7..f64080b 100644 --- a/Sln.Iot/Program.cs +++ b/Sln.Iot/Program.cs @@ -12,7 +12,7 @@ using TouchSocket.Sockets; namespace Sln.Iot { /// - /// + /// /// internal class Program { From 4794fdcd2a2677a8b675de962cd68dadfa107d15 Mon Sep 17 00:00:00 2001 From: wenjy Date: Wed, 21 May 2025 17:08:37 +0800 Subject: [PATCH 3/5] change - delete .idea --- .idea/.idea.Sln.Iot/.idea/encodings.xml | 4 ---- .idea/.idea.Sln.Iot/.idea/indexLayout.xml | 8 ------- .../.idea/projectSettingsUpdater.xml | 7 ------ .idea/.idea.Sln.Iot/.idea/vcs.xml | 6 ----- .idea/.idea.Sln.Iot/.idea/workspace.xml | 23 ------------------- 5 files changed, 48 deletions(-) delete mode 100644 .idea/.idea.Sln.Iot/.idea/encodings.xml delete mode 100644 .idea/.idea.Sln.Iot/.idea/indexLayout.xml delete mode 100644 .idea/.idea.Sln.Iot/.idea/projectSettingsUpdater.xml delete mode 100644 .idea/.idea.Sln.Iot/.idea/vcs.xml delete mode 100644 .idea/.idea.Sln.Iot/.idea/workspace.xml diff --git a/.idea/.idea.Sln.Iot/.idea/encodings.xml b/.idea/.idea.Sln.Iot/.idea/encodings.xml deleted file mode 100644 index df87cf9..0000000 --- a/.idea/.idea.Sln.Iot/.idea/encodings.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/.idea.Sln.Iot/.idea/indexLayout.xml b/.idea/.idea.Sln.Iot/.idea/indexLayout.xml deleted file mode 100644 index 7b08163..0000000 --- a/.idea/.idea.Sln.Iot/.idea/indexLayout.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/.idea.Sln.Iot/.idea/projectSettingsUpdater.xml b/.idea/.idea.Sln.Iot/.idea/projectSettingsUpdater.xml deleted file mode 100644 index 64af657..0000000 --- a/.idea/.idea.Sln.Iot/.idea/projectSettingsUpdater.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/.idea.Sln.Iot/.idea/vcs.xml b/.idea/.idea.Sln.Iot/.idea/vcs.xml deleted file mode 100644 index 94a25f7..0000000 --- a/.idea/.idea.Sln.Iot/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/.idea.Sln.Iot/.idea/workspace.xml b/.idea/.idea.Sln.Iot/.idea/workspace.xml deleted file mode 100644 index e732232..0000000 --- a/.idea/.idea.Sln.Iot/.idea/workspace.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - \ No newline at end of file From 1762b1c88a8cd975d7337f7f736d9111ed11edf3 Mon Sep 17 00:00:00 2001 From: WenJY Date: Thu, 22 May 2025 08:59:08 +0800 Subject: [PATCH 4/5] =?UTF-8?q?change=20-=20=E7=AB=AF=E5=8F=A3=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sln.Iot/appsettings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sln.Iot/appsettings.json b/Sln.Iot/appsettings.json index fa27537..8fb0454 100644 --- a/Sln.Iot/appsettings.json +++ b/Sln.Iot/appsettings.json @@ -1,7 +1,7 @@ { "AppConfig": { "logPath": "\\\\Mac\\Home\\Public\\WorkSpace\\Mesnac\\项目资料\\IOT物联网数据采集\\日志信息", - "listernPort": 7001, + "listernPort": 6000, "virtualFlag": true, "virtualValue": 9999999, "electricTimeInterval": 1, //电力数据采集间隔,小于间隔数据不保存,单位:分钟 From f94b3254295c4078ae8295463137436ca9760f68 Mon Sep 17 00:00:00 2001 From: wenjy Date: Tue, 3 Jun 2025 13:47:34 +0800 Subject: [PATCH 5/5] =?UTF-8?q?change=20-=20=E4=B8=B2=E5=8F=A3=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E8=A7=A3=E6=9E=90=E9=80=BB=E8=BE=91=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sln.Iot.Business/IotEnvBusiness.cs | 191 ++++++++++++++++- Sln.Iot.Business/Sln.Iot.Business.csproj | 4 + Sln.Iot.Business/WebSocketBusiness.cs | 63 ++++++ Sln.Iot.Business/base/BaseBusiness.cs | 7 +- Sln.Iot.Config/AppConfig.cs | 5 + Sln.Iot.Config/SerialPortConfig.cs | 41 ++++ Sln.Iot.Model/dao/BaseAlarmRule.cs | 61 ++++++ Sln.Iot.Model/dao/RecordIotEnvInstant.cs | 8 +- Sln.Iot.Model/dto/CommParams.cs | 9 +- Sln.Iot.Model/dto/MonitorAlarmDto.cs | 43 ++++ .../service/IBaseAlarmRuleService.cs | 14 ++ .../service/Impl/BaseAlarmRuleServiceImpl.cs | 17 ++ Sln.Iot.Serial/SerialExtensions.cs | 198 ++++++++++++++++++ Sln.Iot.Serial/Sln.Iot.Serial.csproj | 28 +++ Sln.Iot.Serial/libnserial.so.1 | Bin 0 -> 78992 bytes Sln.Iot.Serilog/SerilogExtensions.cs | 8 +- Sln.Iot.Serilog/SerilogHelper.cs | 20 +- Sln.Iot.Socket/Class1.cs | 5 - Sln.Iot.Socket/Sln.Iot.Socket.csproj | 1 + Sln.Iot.Socket/TcpServer.cs | 28 ++- Sln.Iot.sln | 22 +- Sln.Iot/Program.cs | 25 ++- Sln.Iot/Sln.Iot.csproj | 4 + Sln.Iot/appsettings.json | 16 +- Sln.Iot/libnserial.so.1 | Bin 0 -> 78992 bytes 25 files changed, 761 insertions(+), 57 deletions(-) create mode 100644 Sln.Iot.Business/WebSocketBusiness.cs create mode 100644 Sln.Iot.Config/SerialPortConfig.cs create mode 100644 Sln.Iot.Model/dao/BaseAlarmRule.cs create mode 100644 Sln.Iot.Model/dto/MonitorAlarmDto.cs create mode 100644 Sln.Iot.Repository/service/IBaseAlarmRuleService.cs create mode 100644 Sln.Iot.Repository/service/Impl/BaseAlarmRuleServiceImpl.cs create mode 100644 Sln.Iot.Serial/SerialExtensions.cs create mode 100644 Sln.Iot.Serial/Sln.Iot.Serial.csproj create mode 100644 Sln.Iot.Serial/libnserial.so.1 delete mode 100644 Sln.Iot.Socket/Class1.cs create mode 100644 Sln.Iot/libnserial.so.1 diff --git a/Sln.Iot.Business/IotEnvBusiness.cs b/Sln.Iot.Business/IotEnvBusiness.cs index 06c62d4..fa0c86e 100644 --- a/Sln.Iot.Business/IotEnvBusiness.cs +++ b/Sln.Iot.Business/IotEnvBusiness.cs @@ -25,7 +25,11 @@ using System; using System.Collections.Generic; +using System.Data; using System.Linq; +using System.Linq.Expressions; +using System.Text; +using Dm.util; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Sln.Iot.Business.@base; @@ -35,6 +39,7 @@ using Sln.Iot.Model.dao; using Sln.Iot.Model.dto; using Sln.Iot.Repository.service; using Sln.Iot.Serilog; +using Sln.Iot.Socket; using Sln.Iot.Socket.Adapter; using TouchSocket.Core; using TouchSocket.Sockets; @@ -47,10 +52,26 @@ namespace Sln.Iot.Business public class IotEnvBusiness:BaseBusiness { private readonly IRecordIotEnvInstantService? _service; + + private readonly IBaseAlarmRuleService _alarmRuleService; + + private readonly WebSocketBusiness _webSocket; + + private readonly TcpServer _tcpServer; + + private readonly IBaseMonitorInfoService _monitorInfoService; + - public IotEnvBusiness(SerilogHelper logger, AppConfig appConfig, StringChange stringChange, IRecordIotEnvInstantService? service) : base(logger, appConfig, stringChange) + public IotEnvBusiness(SerilogHelper logger, AppConfig appConfig, StringChange stringChange, IRecordIotEnvInstantService? service, + IBaseAlarmRuleService alarmRuleService, WebSocketBusiness webSocket, TcpServer tcpServer + , IBaseMonitorInfoService monitorInfoService) : base(logger, appConfig, stringChange) { _service = service; + _alarmRuleService = alarmRuleService; + _webSocket = webSocket; + + _tcpServer = tcpServer; + _monitorInfoService = monitorInfoService; } public override FilterResult BufferAnalysis(ISocketClient client, BufferRequestInfo requestInfo, int bodyLength) @@ -139,17 +160,16 @@ namespace Sln.Iot.Business ValueIsNan(ref f_vibrationSpeed); - iotEnvInstant.VibrationSpeed = (decimal) f_vibrationSpeed; + iotEnvInstant.vibrationSpeed = (decimal) f_vibrationSpeed; break; case CommParams.VibrationDisplacement: //振动-位移 - bodyLength = 58; byteBlock.Read(out byte[] vibrationDisplacement, 4); base._stringChange.SwapBytes(ref vibrationDisplacement); float f_vibrationDisplacement = BitConverter.ToSingle(vibrationDisplacement, 0); ValueIsNan(ref f_vibrationDisplacement); - iotEnvInstant.VibrationDisplacement = (decimal)f_vibrationDisplacement; + iotEnvInstant.vibrationDisplacement = (decimal)f_vibrationDisplacement; break; case CommParams.VibrationAcceleration: //振动-加速度 byteBlock.Read(out byte[] vibrationAcceleration, 4); @@ -158,7 +178,7 @@ namespace Sln.Iot.Business ValueIsNan(ref f_vibrationAcceleration); - iotEnvInstant.VibrationAcceleration = (decimal)f_vibrationAcceleration; + iotEnvInstant.vibrationAcceleration = (decimal)f_vibrationAcceleration; break; case CommParams.VibrationTemp: //振动-温度 byteBlock.Read(out byte[] vibrationTemp, 4); @@ -167,7 +187,7 @@ namespace Sln.Iot.Business ValueIsNan(ref f_vibrationTemp); - iotEnvInstant.VibrationTemp = (decimal)f_vibrationTemp; + iotEnvInstant.vibrationTemp = (decimal)f_vibrationTemp; break; case CommParams.CJSJ: byteBlock.Read(out byte[] b_CJSJ, 6); @@ -203,10 +223,14 @@ namespace Sln.Iot.Business { ParamVerification(ref result); } - + + SendData(result); + var inRes = _service.SplitInsert(result,out List insertIds); _logger.Info($"{amount}个物联网数据解析处理完成,保存{result.Count}个物联网数据,保存{(inRes ? "成功" : "失败")}"); + + ParamAlarmFilter(result); } else { @@ -276,28 +300,28 @@ namespace Sln.Iot.Business continue; } - if (item.VibrationSpeed == _appConfig.virtualValue) + if (item.vibrationSpeed == _appConfig.virtualValue) { _logger.Info($"MonitorId:{item.monitorId},振动速度值为 FF FF FF FF,已启用过滤不保存该表数据"); iotEnvInstants.RemoveAt(i); continue; } - if (item.VibrationDisplacement == _appConfig.virtualValue) + if (item.vibrationDisplacement == _appConfig.virtualValue) { _logger.Info($"MonitorId:{item.monitorId},振动位移值为 FF FF FF FF,已启用过滤不保存该表数据"); iotEnvInstants.RemoveAt(i); continue; } - if (item.VibrationAcceleration == _appConfig.virtualValue) + if (item.vibrationAcceleration == _appConfig.virtualValue) { _logger.Info($"MonitorId:{item.monitorId},振动加速度值为 FF FF FF FF,已启用过滤不保存该表数据"); iotEnvInstants.RemoveAt(i); continue; } - if (item.VibrationTemp == _appConfig.virtualValue) + if (item.vibrationTemp == _appConfig.virtualValue) { _logger.Info($"MonitorId:{item.monitorId},振动温度值为 FF FF FF FF,已启用过滤不保存该表数据"); iotEnvInstants.RemoveAt(i); @@ -305,5 +329,150 @@ namespace Sln.Iot.Business } } } + + /// + /// + /// + /// + public void SendData(List iotEnvInstants) + { + lock (string.Empty) + { + foreach (var iotEnvInstant in iotEnvInstants) + { + Thread.Sleep(200); + var monitorInfo = _monitorInfoService.Query(x => x.monitorId == iotEnvInstant.monitorId).FirstOrDefault(); + StringBuilder sb = new StringBuilder(); + if (monitorInfo != null) + { + sb.Append(iotEnvInstant.monitorId); + sb.Append("-"); + sb.Append(monitorInfo.monitorType); + if (monitorInfo.monitorType == 5 || monitorInfo.monitorType == 6) + { + sb.Append("-"); + sb.Append(iotEnvInstant.temperature); + } + if (monitorInfo.monitorType == 6) + { + sb.Append("-"); + sb.Append(iotEnvInstant.humidity); + } + if (monitorInfo.monitorType == 7) + { + sb.Append("-"); + sb.Append(iotEnvInstant.noise); + } + if (monitorInfo.monitorType == 8) + { + sb.Append("-"); + sb.Append(iotEnvInstant.illuminance); + } + + if (monitorInfo.monitorType == 10) + { + sb.Append("-"); + sb.Append(iotEnvInstant.vibrationSpeed); + sb.Append("-"); + sb.Append(iotEnvInstant.vibrationAcceleration); + sb.Append("-"); + sb.Append(iotEnvInstant.vibrationDisplacement); + sb.Append("-"); + sb.Append(iotEnvInstant.vibrationTemp); + } + + sb.Append(";"); + } + + //string str = $"{iotEnvInstant.monitorId}-{iotEnvInstant.temperature}-{iotEnvInstant.humidity}-{iotEnvInstant.noise}-{iotEnvInstant.illuminance}-{iotEnvInstant.vibrationSpeed}-{iotEnvInstant.vibrationAcceleration}-{iotEnvInstant.vibrationDisplacement}-{iotEnvInstant.vibrationTemp}"; + string str = sb.ToString(); + byte[] bytes = StringToBytesUsingASCII(str); + + _tcpServer.SendDataToRecevieDevice(bytes); + } + } + } + + public static byte[] StringToBytesUsingASCII(string input) + { + if (input == null) + { + throw new ArgumentNullException(nameof(input), "输入字符串不能为 null。"); + } + // 使用 ASCII 编码将字符串转换为字节数组 + return Encoding.ASCII.GetBytes(input); + } + + + /// + /// 报警参数过滤 + /// + /// + private void ParamAlarmFilter(List iotEnvInstants) + { + if (iotEnvInstants == null) + { + throw new ArgumentNullException(nameof(iotEnvInstants), "报警参数过滤异常,传入参数为空"); + } + + // 预编译比较委托(避免循环内重复编译) + static Func CreateComparer(int triggerRule) + { + ParameterExpression x = Expression.Parameter(typeof(decimal), "x"); + ParameterExpression y = Expression.Parameter(typeof(decimal), "y"); + BinaryExpression comparison = triggerRule == 0 + ? Expression.GreaterThan(x, y) + : Expression.LessThan(x, y); + return Expression.Lambda>(comparison, x, y).Compile(); + } + + var fieldAccessors = new Dictionary> + { + { 0, item => item.temperature }, + { 1, item => item.humidity }, + { 2, item => item.vibrationSpeed }, + { 3, item => item.vibrationDisplacement }, + { 4, item => item.vibrationAcceleration }, + { 5, item => item.vibrationTemp }, + { 6, item => item.noise }, + { 7, item => item.illuminance } + }; + + foreach (var item in iotEnvInstants) + { + var alarmRules = _alarmRuleService.Query(x => x.monitorId == item.monitorId); + + List ruleRes = new List(); + List alarmContents = new List(); + foreach (var rule in alarmRules) + { + decimal paramValue = fieldAccessors.TryGetValue(rule.monitorField, out var accessor) + ? accessor(item) + : 0; + + var comparer = CreateComparer(rule.triggerRule); + + if (comparer(paramValue, rule.triggerValue)) + { + ruleRes.Add(rule); + alarmContents.Add($"{item.monitorId}传感器数据在{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}触发{rule.ruleName}异常告警,告警规则:{(rule.triggerRule == 0 ? "大于" : "小于") },阈值:{rule.triggerValue},详细信息:{rule.cause}"); + } + } + + MonitorAlarmDto monitorAlarmDto = new MonitorAlarmDto() + { + monitorId = item.monitorId, + isFlag = ruleRes.Count() > 0 ? 1 : 0, + deviceParam = item, + alarmRules = ruleRes, + alarmContents = alarmContents, + recordTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() + }; + string str = JsonConvert.SerializeObject(monitorAlarmDto); + _logger.Alarm($"传感器数据推送:{str}"); + + _webSocket.PushMsg(str); + } + } } } \ No newline at end of file diff --git a/Sln.Iot.Business/Sln.Iot.Business.csproj b/Sln.Iot.Business/Sln.Iot.Business.csproj index a1e49db..4cf19b3 100644 --- a/Sln.Iot.Business/Sln.Iot.Business.csproj +++ b/Sln.Iot.Business/Sln.Iot.Business.csproj @@ -6,6 +6,10 @@ enable + + + + diff --git a/Sln.Iot.Business/WebSocketBusiness.cs b/Sln.Iot.Business/WebSocketBusiness.cs new file mode 100644 index 0000000..18e89d2 --- /dev/null +++ b/Sln.Iot.Business/WebSocketBusiness.cs @@ -0,0 +1,63 @@ +using Fleck; +using Sln.Iot.Serilog; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Sln.Iot.Business +{ + public class WebSocketBusiness + { + private List allSockets = new List(); + + private readonly SerilogHelper _logger; + + public WebSocketBusiness(SerilogHelper serilogHelper) + { + _logger = serilogHelper; + } + + public void Init() + { + var server = new Fleck.WebSocketServer($"ws://0.0.0.0:7181"); + server.Start(socket => + { + socket.OnOpen = () => + { + var data = socket.ConnectionInfo; + _logger.Info("WebSocket Open!"); + allSockets.Add(socket); + }; + + socket.OnClose = () => + { + _logger.Info("WebSocket Close!"); + allSockets.Remove(socket); + }; + + socket.OnMessage = message => + { + //ReceivedMessageRequestInfoEvent?.Invoke(socket, message); + }; + }); + } + + public void PushMsg(string msg) + { + try + { + foreach (var socket in allSockets.ToList()) + { + socket.Send(msg); + _logger.Info($"WebSocket推送信息:{msg}"); + } + } + catch (Exception ex) + { + _logger.Info($"WebSocket推送信息异常:{ex.Message}"); + } + } + } +} diff --git a/Sln.Iot.Business/base/BaseBusiness.cs b/Sln.Iot.Business/base/BaseBusiness.cs index dcdc18c..b65f142 100644 --- a/Sln.Iot.Business/base/BaseBusiness.cs +++ b/Sln.Iot.Business/base/BaseBusiness.cs @@ -23,10 +23,13 @@ #endregion << 版 本 注 释 >> +using Fleck; +using Microsoft.Extensions.DependencyInjection; using Sln.Iot.Common; using Sln.Iot.Config; using Sln.Iot.Model.dto; using Sln.Iot.Serilog; +using Sln.Iot.Socket; using Sln.Iot.Socket.Adapter; using TouchSocket.Core; using TouchSocket.Sockets; @@ -40,12 +43,13 @@ namespace Sln.Iot.Business.@base public AppConfig _appConfig; public StringChange _stringChange; - + public BaseBusiness(SerilogHelper logger,AppConfig appConfig,StringChange stringChange) { _logger = logger; _appConfig = appConfig; _stringChange = stringChange; + } /// @@ -147,5 +151,6 @@ namespace Sln.Iot.Business.@base } } } + } } \ No newline at end of file diff --git a/Sln.Iot.Config/AppConfig.cs b/Sln.Iot.Config/AppConfig.cs index 3ea6d25..60f1c90 100644 --- a/Sln.Iot.Config/AppConfig.cs +++ b/Sln.Iot.Config/AppConfig.cs @@ -70,6 +70,11 @@ namespace Sln.Iot.Config /// public string redisConfig { get; set; } + /// + /// 串口配置 + /// + public SerialPortConfig serialPortConfig { get; set; } + public AppConfig Value => this; } } \ No newline at end of file diff --git a/Sln.Iot.Config/SerialPortConfig.cs b/Sln.Iot.Config/SerialPortConfig.cs new file mode 100644 index 0000000..a82e4f6 --- /dev/null +++ b/Sln.Iot.Config/SerialPortConfig.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Sln.Iot.Config +{ + public class SerialPortConfig + { + /// + /// 串口名称 + /// + public string PortName { get; set; } + + /// + /// 波特率 + /// + public int BaudRate { get; set; } + + /// + /// 奇偶校验 + /// + public int Parity { get; set; } + + /// + /// 停止位 + /// + public int StopBits { get; set; } + + /// + /// 数据位 + /// + public int DataBits { get; set; } + + /// + /// + /// + public int Handshake { get; set; } + } +} diff --git a/Sln.Iot.Model/dao/BaseAlarmRule.cs b/Sln.Iot.Model/dao/BaseAlarmRule.cs new file mode 100644 index 0000000..0117c5c --- /dev/null +++ b/Sln.Iot.Model/dao/BaseAlarmRule.cs @@ -0,0 +1,61 @@ +using SqlSugar; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Sln.Iot.Model.dao +{ + [SugarTable("ems_record_alarm_rule"), TenantAttribute("tao_iot")] + public class BaseAlarmRule + { + /// + /// 编号 + /// + [SugarColumn(ColumnName = "obj_id", IsPrimaryKey = true)] + public long objid { get; set; } + + /// + /// 计量设备编号 + /// + [SugarColumn(ColumnName = "monitor_id")] + public string monitorId { get; set; } + + /// + /// 规则编号 + /// + [SugarColumn(ColumnName = "rule_id")] + public string ruleId { get;set; } + + /// + /// 规则名称 + /// + [SugarColumn(ColumnName = "rule_name")] + public string ruleName { get; set; } + + /// + /// 触发规则(0大于 1小于) + /// + [SugarColumn(ColumnName = "trigger_rule")] + public int triggerRule { get; set; } + + /// + /// 监测字段(0温度,1湿度,2振动-速度(mm/s),3振动-位移(um),4振动-加速度(g),5振动-温度(℃),6-噪音,7-照度) + /// + [SugarColumn(ColumnName = "monitor_field")] + public int monitorField { get;set; } + + /// + /// 阈值 + /// + [SugarColumn(ColumnName = "trigger_value")] + public decimal triggerValue { get; set; } + + /// + /// 备注 + /// + [SugarColumn(ColumnName = "cause")] + public string cause { get; set; } + } +} diff --git a/Sln.Iot.Model/dao/RecordIotEnvInstant.cs b/Sln.Iot.Model/dao/RecordIotEnvInstant.cs index 8958735..69a4239 100644 --- a/Sln.Iot.Model/dao/RecordIotEnvInstant.cs +++ b/Sln.Iot.Model/dao/RecordIotEnvInstant.cs @@ -78,26 +78,26 @@ namespace Sln.Iot.Model.dao /// 振动-速度 /// [SugarColumn(ColumnName = "vibration_speed")] - public decimal VibrationSpeed { get; set; } + public decimal vibrationSpeed { get; set; } /// /// 振动-位移 /// [SugarColumn(ColumnName = "vibration_displacement")] - public decimal VibrationDisplacement { get; set; } + public decimal vibrationDisplacement { get; set; } /// /// 振动-加速度 /// [SugarColumn(ColumnName = "vibration_acceleration")] - public decimal VibrationAcceleration { get; set; } + public decimal vibrationAcceleration { get; set; } /// /// 振动-温度 /// [SugarColumn(ColumnName = "vibration_temp")] - public decimal VibrationTemp { get; set; } + public decimal vibrationTemp { get; set; } /// diff --git a/Sln.Iot.Model/dto/CommParams.cs b/Sln.Iot.Model/dto/CommParams.cs index 94a81a3..5c9c4a7 100644 --- a/Sln.Iot.Model/dto/CommParams.cs +++ b/Sln.Iot.Model/dto/CommParams.cs @@ -46,22 +46,23 @@ namespace Sln.Iot.Model.dto /// /// 振动-速度 /// - public const uint VibrationSpeed = 0x8E54; + public const uint VibrationSpeed = 0x8E55; /// /// 振动-位移 /// - public const uint VibrationDisplacement = 0x8E55; + public const uint VibrationDisplacement = 0x8E56; /// /// 振动-加速度 /// - public const uint VibrationAcceleration = 0x8E56; + public const uint VibrationAcceleration = 0x8E57; /// /// 振动-温度 /// - public const uint VibrationTemp = 0x8E57; + public const uint VibrationTemp = 0x8E54; + /// /// 采集时间 diff --git a/Sln.Iot.Model/dto/MonitorAlarmDto.cs b/Sln.Iot.Model/dto/MonitorAlarmDto.cs new file mode 100644 index 0000000..fa1dd05 --- /dev/null +++ b/Sln.Iot.Model/dto/MonitorAlarmDto.cs @@ -0,0 +1,43 @@ +using Sln.Iot.Model.dao; +using SqlSugar; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Sln.Iot.Model.dto +{ + public class MonitorAlarmDto + { + /// + /// 设备编号 + /// + public string monitorId { get;set; } + + /// + /// 是否报警:1-是;0-否 + /// + public int isFlag { get;set; } + + /// + /// 设备参数 + /// + public RecordIotEnvInstant deviceParam { get;set; } + + /// + /// 告警规则 + /// + public List alarmRules { get;set; } + + /// + /// 报警内容 + /// + public List alarmContents { get;set; } + + /// + /// 记录时间 + /// + public long recordTime { get;set; } + } +} diff --git a/Sln.Iot.Repository/service/IBaseAlarmRuleService.cs b/Sln.Iot.Repository/service/IBaseAlarmRuleService.cs new file mode 100644 index 0000000..4860e2c --- /dev/null +++ b/Sln.Iot.Repository/service/IBaseAlarmRuleService.cs @@ -0,0 +1,14 @@ +using Sln.Iot.Model.dao; +using Sln.Iot.Repository.service.@base; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Sln.Iot.Repository.service +{ + public interface IBaseAlarmRuleService : IBaseService + { + } +} diff --git a/Sln.Iot.Repository/service/Impl/BaseAlarmRuleServiceImpl.cs b/Sln.Iot.Repository/service/Impl/BaseAlarmRuleServiceImpl.cs new file mode 100644 index 0000000..e42d738 --- /dev/null +++ b/Sln.Iot.Repository/service/Impl/BaseAlarmRuleServiceImpl.cs @@ -0,0 +1,17 @@ +using Sln.Iot.Model.dao; +using Sln.Iot.Repository.service.@base; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Sln.Iot.Repository.service.Impl +{ + public class BaseAlarmRuleServiceImpl : BaseServiceImpl, IBaseAlarmRuleService + { + public BaseAlarmRuleServiceImpl(Repository rep) : base(rep) + { + } + } +} diff --git a/Sln.Iot.Serial/SerialExtensions.cs b/Sln.Iot.Serial/SerialExtensions.cs new file mode 100644 index 0000000..1fca02f --- /dev/null +++ b/Sln.Iot.Serial/SerialExtensions.cs @@ -0,0 +1,198 @@ +using Dm.util; +using Microsoft.Extensions.DependencyInjection; +using Newtonsoft.Json; +using RJCP.IO.Ports; +using Sln.Iot.Business; +using Sln.Iot.Common; +using Sln.Iot.Config; +using Sln.Iot.Model.dao; +using Sln.Iot.Model.dto; +using Sln.Iot.Repository.service; +using Sln.Iot.Serilog; +using SqlSugar; +using System; +using System.Linq.Expressions; +using System.Net.WebSockets; +using System.Text; +using TouchSocket.Core; +using TouchSocket.Sockets; + +namespace Sln.Iot.Serial +{ + public static class SerialExtensions + { + + public static void UseSerialPortExtensions(this IServiceProvider service) + { + IRecordIotEnvInstantService instantService = service.GetService(); + + IBaseAlarmRuleService _alarmRuleService = service.GetService(); + + WebSocketBusiness webSocket = service.GetService(); + + var logger = service.GetService(); + try + { + var appConfig = service.GetService(); + var tcpClient = service.GetService(); + var stringChange = service.GetService(); + + SerialPortStream serialPort = new SerialPortStream(appConfig.serialPortConfig.PortName); + serialPort.BaudRate = appConfig.serialPortConfig.BaudRate; + serialPort.Parity = Parity.None; + serialPort.StopBits = StopBits.One; + serialPort.DataBits = appConfig.serialPortConfig.DataBits; + serialPort.Handshake = Handshake.None; + + serialPort.DataReceived += (sender, e) => + { + lock (string.Empty) + { + try + { + byte[] receivedData = new byte[serialPort.BytesToRead]; + serialPort.Read(receivedData, 0, receivedData.Length); + + string str = Encoding.ASCII.GetString(receivedData); + logger.Info($"串口服务接收到数据:{str}"); + + List< RecordIotEnvInstant > iotEnvInstants = new List< RecordIotEnvInstant >(); + string[] monitorInfos = str.Split(';'); + + foreach ( string monitorInfo in monitorInfos ) + { + + if (monitorInfo.Length < 12) + { + continue; + } + logger.Info($"解析串口数据:{monitorInfo}"); + RecordIotEnvInstant iotEnvInstant = new RecordIotEnvInstant(); + string[] strs = monitorInfo.Replace(";","").Split('-'); + + iotEnvInstant.monitorId = strs[0]; + + if (strs[1] == "5" || strs[1] == "6") + { + iotEnvInstant.temperature = decimal.Parse(strs[2].Trim()); + } + if (strs[1] == "6") + { + iotEnvInstant.humidity = decimal.Parse(strs[3].Trim()); + } + if (strs[1] == "7") + { + iotEnvInstant.noise = decimal.Parse(strs[2].Trim()); + } + if (strs[1] == "8") + { + iotEnvInstant.illuminance = decimal.Parse(strs[2].Trim()); + } + + if (strs[1] == "10") + { + iotEnvInstant.vibrationSpeed = decimal.Parse(strs[2].Trim()); + iotEnvInstant.vibrationAcceleration = decimal.Parse(strs[3].Trim()); + iotEnvInstant.vibrationDisplacement = decimal.Parse(strs[4].Trim()); + iotEnvInstant.vibrationTemp = decimal.Parse(strs[5].Trim()); + } + iotEnvInstant.collectTime = DateTime.Now; + iotEnvInstant.recordTime = DateTime.Now; + iotEnvInstants.Add(iotEnvInstant); + + Thread.Sleep(200); + } + + + var inRes = instantService.SplitInsert(iotEnvInstants, out List insertIds); + + logger.Info($"物联网数据保存{(inRes ? "成功" : "失败")}"); + + ParamAlarmFilter(iotEnvInstants,_alarmRuleService,logger, webSocket); + } + catch (Exception ex) + { + logger.Info($"串口接收数据异常:{ex.Message}"); + } + } + }; + + serialPort.Open(); + + logger.Info($"串口服务加载启动成功"); + } + catch (Exception ex) + { + logger.Error($"串口服务加载异常:{ex.Message}"); + } + } + + private static void ParamAlarmFilter(List iotEnvInstants, IBaseAlarmRuleService _alarmRuleService, SerilogHelper _logger, WebSocketBusiness _webSocket) + { + if (iotEnvInstants == null) + { + throw new ArgumentNullException(nameof(iotEnvInstants), "报警参数过滤异常,传入参数为空"); + } + + // 预编译比较委托(避免循环内重复编译) + static Func CreateComparer(int triggerRule) + { + ParameterExpression x = Expression.Parameter(typeof(decimal), "x"); + ParameterExpression y = Expression.Parameter(typeof(decimal), "y"); + BinaryExpression comparison = triggerRule == 0 + ? Expression.GreaterThan(x, y) + : Expression.LessThan(x, y); + return Expression.Lambda>(comparison, x, y).Compile(); + } + + var fieldAccessors = new Dictionary> + { + { 0, item => item.temperature }, + { 1, item => item.humidity }, + { 2, item => item.vibrationSpeed }, + { 3, item => item.vibrationDisplacement }, + { 4, item => item.vibrationAcceleration }, + { 5, item => item.vibrationTemp }, + { 6, item => item.noise }, + { 7, item => item.illuminance } + }; + + foreach (var item in iotEnvInstants) + { + var alarmRules = _alarmRuleService.Query(x => x.monitorId == item.monitorId); + + List ruleRes = new List(); + List alarmContents = new List(); + foreach (var rule in alarmRules) + { + decimal paramValue = fieldAccessors.TryGetValue(rule.monitorField, out var accessor) + ? accessor(item) + : 0; + + var comparer = CreateComparer(rule.triggerRule); + + if (comparer(paramValue, rule.triggerValue)) + { + ruleRes.Add(rule); + alarmContents.Add($"{item.monitorId}传感器数据在{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}触发{rule.ruleName}异常告警,告警规则:{(rule.triggerRule == 0 ? "大于" : "小于")},阈值:{rule.triggerValue},详细信息:{rule.cause}"); + } + } + + MonitorAlarmDto monitorAlarmDto = new MonitorAlarmDto() + { + monitorId = item.monitorId, + isFlag = ruleRes.Count() > 0 ? 1 : 0, + deviceParam = item, + alarmRules = ruleRes, + alarmContents = alarmContents, + recordTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() + }; + string str = JsonConvert.SerializeObject(monitorAlarmDto); + _logger.Alarm($"传感器数据推送:{str}"); + + _webSocket.PushMsg(str); + } + } + + } +} diff --git a/Sln.Iot.Serial/Sln.Iot.Serial.csproj b/Sln.Iot.Serial/Sln.Iot.Serial.csproj new file mode 100644 index 0000000..dae9b9b --- /dev/null +++ b/Sln.Iot.Serial/Sln.Iot.Serial.csproj @@ -0,0 +1,28 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + + + + + + + + Always + + + + diff --git a/Sln.Iot.Serial/libnserial.so.1 b/Sln.Iot.Serial/libnserial.so.1 new file mode 100644 index 0000000000000000000000000000000000000000..5b93c2a735232ad4096d7387e9b2007563cf4157 GIT binary patch literal 78992 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 lD@yfmOO4{6h<#4;&~|GawNLv_ logEvent.Properties.ContainsKey("Module") && logEvent.Properties["Module"].ToString().Contains("Info")) .WriteTo.File(Path.Combine($"{logPath}/Info/", "Info.log"), rollingInterval: RollingInterval.Day)) .WriteTo.Logger(lc => lc - .Filter.ByIncludingOnly(logEvent => logEvent.Properties.ContainsKey("Module") && logEvent.Properties["Module"].ToString().Contains("Plc")) - .WriteTo.File(Path.Combine($"{logPath}/Plc/", "Plc.log"), rollingInterval: RollingInterval.Day)) + .Filter.ByIncludingOnly(logEvent => logEvent.Properties.ContainsKey("Module") && logEvent.Properties["Module"].ToString().Contains("Iot")) + .WriteTo.File(Path.Combine($"{logPath}/Iot/", "Iot.log"), rollingInterval: RollingInterval.Day)) .WriteTo.Logger(lc => lc - .Filter.ByIncludingOnly(logEvent => logEvent.Properties.ContainsKey("Module") && logEvent.Properties["Module"].ToString().Contains("Camera")) - .WriteTo.File(Path.Combine($"{logPath}/Camera/", "Camera.log"), rollingInterval: RollingInterval.Day)) + .Filter.ByIncludingOnly(logEvent => logEvent.Properties.ContainsKey("Module") && logEvent.Properties["Module"].ToString().Contains("Alarm")) + .WriteTo.File(Path.Combine($"{logPath}/Alarm/", "Alarm.log"), rollingInterval: RollingInterval.Day)) .WriteTo.Logger(lc => lc .Filter.ByIncludingOnly(logEvent => logEvent.Properties.ContainsKey("Module") && logEvent.Properties["Module"].ToString().Contains("Error")) .WriteTo.File(Path.Combine($"{logPath}/Error/", "Error.log"), rollingInterval: RollingInterval.Day)) diff --git a/Sln.Iot.Serilog/SerilogHelper.cs b/Sln.Iot.Serilog/SerilogHelper.cs index 7a00349..bb38ae6 100644 --- a/Sln.Iot.Serilog/SerilogHelper.cs +++ b/Sln.Iot.Serilog/SerilogHelper.cs @@ -31,9 +31,9 @@ namespace Sln.Iot.Serilog public class SerilogHelper { private readonly ILogger? Info_logger = Log.ForContext("Module", "Info"); - private readonly ILogger? Plc_logger = Log.ForContext("Module", "Plc"); + private readonly ILogger? Iot_logger = Log.ForContext("Module", "Iot"); private readonly ILogger? Error_logger = Log.ForContext("Module", "Error"); - private readonly ILogger? Camera_logger = Log.ForContext("Module", "Camera"); + private readonly ILogger? Alarm_logger = Log.ForContext("Module", "Alarm"); /// /// Info日志 @@ -48,26 +48,26 @@ namespace Sln.Iot.Serilog } /// - /// Plc日志 + /// Iot日志 /// /// - public void Plc(string msg) + public void Iot(string msg) { - if (Plc_logger != null) + if (Iot_logger != null) { - this.Plc_logger.Information(msg); + this.Iot_logger.Information(msg); } } /// - /// 相机日志 + /// 设备告警日志 /// /// - public void Camera(string msg) + public void Alarm(string msg) { - if (Camera_logger != null) + if (Alarm_logger != null) { - this.Camera_logger.Information(msg); + this.Alarm_logger.Information(msg); } } diff --git a/Sln.Iot.Socket/Class1.cs b/Sln.Iot.Socket/Class1.cs deleted file mode 100644 index 3660e6e..0000000 --- a/Sln.Iot.Socket/Class1.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Sln.Iot.Socket; - -public class Class1 -{ -} \ No newline at end of file diff --git a/Sln.Iot.Socket/Sln.Iot.Socket.csproj b/Sln.Iot.Socket/Sln.Iot.Socket.csproj index 0990bfc..40602ca 100644 --- a/Sln.Iot.Socket/Sln.Iot.Socket.csproj +++ b/Sln.Iot.Socket/Sln.Iot.Socket.csproj @@ -11,6 +11,7 @@ + diff --git a/Sln.Iot.Socket/TcpServer.cs b/Sln.Iot.Socket/TcpServer.cs index 87e3e2a..0a9a153 100644 --- a/Sln.Iot.Socket/TcpServer.cs +++ b/Sln.Iot.Socket/TcpServer.cs @@ -24,6 +24,8 @@ #endregion << 版 本 注 释 >> using System; +using System.Text; +using Sln.Iot.Common; using Sln.Iot.Serilog; using Sln.Iot.Socket.Adapter; using TouchSocket.Core; @@ -35,11 +37,13 @@ namespace Sln.Iot.Socket { private readonly SerilogHelper _logger; private readonly TcpService _service; + private readonly StringChange _stringChange; - public TcpServer(SerilogHelper logger, TcpService service) + public TcpServer(SerilogHelper logger, TcpService service, StringChange stringChange) { _logger = logger; _service = service; + _stringChange = stringChange; } /// @@ -66,7 +70,7 @@ namespace Sln.Iot.Socket _service.Connected = (client, e) => { _logger.Info($"客户端{client.IP}接入服务成功"); RefreshClientInfoEvent?.Invoke(_service); - return EasyTask.CompletedTask; + return EasyTask.CompletedTask; }; _service.Disconnected = (client, e) => { _logger.Info($"客户端{client.IP}断开连接"); @@ -77,13 +81,13 @@ namespace Sln.Iot.Socket { if (e.RequestInfo is BufferRequestInfo request) { + _logger.Iot($"收到客户端:{client.Id};原始指令====>>>>{_stringChange.bytesToHexStr(request.buffer.Buffer, request.buffer.Len)}"); string msg = $"收到客户端:{client.Id};指令====>>>>Header:{BitConverter.ToString(request.header).Replace("-", "")};DataType:{request.DataType.ToString("X2")};BufferLength:{request.BufferLength};Body:{BitConverter.ToString(request.Body).Replace("-", "")};CheckBit:{request.CheckBit.ToString("X2")};Tail:{BitConverter.ToString(request.Tail).Replace("-", "")};"; _logger.Info($"{msg}"); ReceivedBufferRequestInfoEvent?.Invoke(client,request); - } return EasyTask.CompletedTask; @@ -123,5 +127,23 @@ namespace Sln.Iot.Socket _service.Send(item.Id,"heartbeat"); } } + + public void SendDataToRecevieDevice(byte[] bytes) + { + var clientInfos = _service.SocketClients.GetClients().Where(x => x.Id.Contains("450")).ToList(); + + if (clientInfos == null) + { + _logger.Info($"连接中不存在450设备"); + } + else + { + foreach (var item in clientInfos) + { + item.Send(bytes); + _logger.Info($"向接收设备{item.Id};发送数据:{_stringChange.bytesToHexStr(bytes, bytes.Length)}"); + } + } + } } } \ No newline at end of file diff --git a/Sln.Iot.sln b/Sln.Iot.sln index c7f584f..a491fd6 100644 --- a/Sln.Iot.sln +++ b/Sln.Iot.sln @@ -3,21 +3,23 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.9.34714.143 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sln.Iot", "Sln.Iot\Sln.Iot.csproj", "{2140AD68-D4CE-44EC-B9D3-20D18EB59F9D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sln.Iot", "Sln.Iot\Sln.Iot.csproj", "{2140AD68-D4CE-44EC-B9D3-20D18EB59F9D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sln.Iot.Business", "Sln.Iot.Business\Sln.Iot.Business.csproj", "{F59EB67D-66FD-43B9-B6CC-46BF25202C21}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sln.Iot.Business", "Sln.Iot.Business\Sln.Iot.Business.csproj", "{F59EB67D-66FD-43B9-B6CC-46BF25202C21}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sln.Iot.Config", "Sln.Iot.Config\Sln.Iot.Config.csproj", "{2E10DED8-5F53-4ED9-892F-B0007B85806E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sln.Iot.Config", "Sln.Iot.Config\Sln.Iot.Config.csproj", "{2E10DED8-5F53-4ED9-892F-B0007B85806E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sln.Iot.Common", "Sln.Iot.Common\Sln.Iot.Common.csproj", "{899D8A81-D3E3-4599-8A8C-D60280A777F3}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sln.Iot.Common", "Sln.Iot.Common\Sln.Iot.Common.csproj", "{899D8A81-D3E3-4599-8A8C-D60280A777F3}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sln.Iot.Model", "Sln.Iot.Model\Sln.Iot.Model.csproj", "{503E7EAE-6323-4CE2-AAE4-C6A7CBDFC4B2}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sln.Iot.Model", "Sln.Iot.Model\Sln.Iot.Model.csproj", "{503E7EAE-6323-4CE2-AAE4-C6A7CBDFC4B2}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sln.Iot.Repository", "Sln.Iot.Repository\Sln.Iot.Repository.csproj", "{DA193E49-8B4A-4C8D-B44E-844E22983DA8}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sln.Iot.Repository", "Sln.Iot.Repository\Sln.Iot.Repository.csproj", "{DA193E49-8B4A-4C8D-B44E-844E22983DA8}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sln.Iot.Serilog", "Sln.Iot.Serilog\Sln.Iot.Serilog.csproj", "{A9CCC9F6-BE1C-4B73-AFBF-83D363D7F64F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sln.Iot.Serilog", "Sln.Iot.Serilog\Sln.Iot.Serilog.csproj", "{A9CCC9F6-BE1C-4B73-AFBF-83D363D7F64F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sln.Iot.Socket", "Sln.Iot.Socket\Sln.Iot.Socket.csproj", "{5B7C6367-7B41-48A6-9A71-2F191CE14000}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sln.Iot.Socket", "Sln.Iot.Socket\Sln.Iot.Socket.csproj", "{5B7C6367-7B41-48A6-9A71-2F191CE14000}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sln.Iot.Serial", "Sln.Iot.Serial\Sln.Iot.Serial.csproj", "{A030CF57-DAA0-4662-B836-3CFC2CDCD9FE}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -57,6 +59,10 @@ Global {5B7C6367-7B41-48A6-9A71-2F191CE14000}.Debug|Any CPU.Build.0 = Debug|Any CPU {5B7C6367-7B41-48A6-9A71-2F191CE14000}.Release|Any CPU.ActiveCfg = Release|Any CPU {5B7C6367-7B41-48A6-9A71-2F191CE14000}.Release|Any CPU.Build.0 = Release|Any CPU + {A030CF57-DAA0-4662-B836-3CFC2CDCD9FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A030CF57-DAA0-4662-B836-3CFC2CDCD9FE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A030CF57-DAA0-4662-B836-3CFC2CDCD9FE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A030CF57-DAA0-4662-B836-3CFC2CDCD9FE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Sln.Iot/Program.cs b/Sln.Iot/Program.cs index f64080b..cd8b3fa 100644 --- a/Sln.Iot/Program.cs +++ b/Sln.Iot/Program.cs @@ -7,7 +7,10 @@ using Sln.Iot.Config; using Sln.Iot.Repository; using Sln.Iot.Serilog; using Sln.Iot.Socket; +using Sln.Iot.Serial; using TouchSocket.Sockets; +using TouchSocket.Core; +using Fleck; namespace Sln.Iot { @@ -32,6 +35,9 @@ namespace Sln.Iot var _server = ServiceProvider.GetService(); _server.Init(appConfig.listernPort); + var _webSocket = ServiceProvider.GetService(); + _webSocket.Init(); + _server.ReceivedBufferRequestInfoEvent += (client, info) => { bool isRet = false; @@ -67,12 +73,14 @@ namespace Sln.Iot if (_business != null) { Parallel.Invoke( - () => _business.ResponseHandle(client, info.buffer), + //() => _business.ResponseHandle(client, info.buffer), () => _business.BufferAnalysis(client, info, bodyLength) ); } }; + ServiceProvider.UseSerialPortExtensions(); + await Task.Delay(-1); } @@ -88,6 +96,19 @@ namespace Sln.Iot return ap; }); + //services.AddSingleton(provider => + //{ + // TcpClient tcpClient = new TcpClient(); + // tcpClient.Setup(new TouchSocketConfig() + // .SetRemoteIPHost("127.0.0.1:6000") + // .ConfigureContainer(a => + // { + // a.AddConsoleLogger(); + // })); + // tcpClient.Connect(); + // return tcpClient; + //}); + Assembly[] assemblies = { Assembly.LoadFrom("Sln.Iot.Repository.dll"), @@ -100,7 +121,7 @@ namespace Sln.Iot .AddClasses() .AsImplementedInterfaces() .AsSelf() - .WithTransientLifetime()); + .WithSingletonLifetime()); services.AddSingleton(typeof(SerilogHelper)); services.AddSingleton(typeof(TcpService)); diff --git a/Sln.Iot/Sln.Iot.csproj b/Sln.Iot/Sln.Iot.csproj index ee5f903..e953348 100644 --- a/Sln.Iot/Sln.Iot.csproj +++ b/Sln.Iot/Sln.Iot.csproj @@ -14,6 +14,7 @@ + @@ -27,6 +28,9 @@ PreserveNewest + + Always + diff --git a/Sln.Iot/appsettings.json b/Sln.Iot/appsettings.json index 8fb0454..880627a 100644 --- a/Sln.Iot/appsettings.json +++ b/Sln.Iot/appsettings.json @@ -4,16 +4,22 @@ "listernPort": 6000, "virtualFlag": true, "virtualValue": 9999999, - "electricTimeInterval": 1, //电力数据采集间隔,小于间隔数据不保存,单位:分钟 - "fluidTimeInterval": 1, //流体数据采集间隔,小于间隔数据不保存,单位:分钟 + "receiveDevice": "E0020", + "electricTimeInterval": 1, //电力数据采集间隔,小于间隔数据不保存,单位:分钟 + "fluidTimeInterval": 1, //流体数据采集间隔,小于间隔数据不保存,单位:分钟 "SqlConfig": [ { - "configId": "tao_iot", //tao:青岛胶东机场简称 - "dbType": 0, //tidb按照 mysql 去连接 + "configId": "tao_iot", //tao:青岛胶东机场简称 + "dbType": 0, //tidb按照 mysql 去连接 "isFlag": true, //"connStr": "server=127.0.0.1;Port=4000;Database=tao_iot;Uid=root;" //Pwd=haiwei@123; - "connStr": "server=1.13.177.47;Port=3306;Database=tao_iot;Uid=root;Pwd=Haiwei123456;" + "connStr": "server=1.13.177.47;Port=3306;Database=tao_iot;Uid=root;Pwd=Haiwei123456;" } ], + "serialPortConfig": { + "PortName": "COM1", + "BaudRate": 9600, + "DataBits": 8 + } } } diff --git a/Sln.Iot/libnserial.so.1 b/Sln.Iot/libnserial.so.1 new file mode 100644 index 0000000000000000000000000000000000000000..5b93c2a735232ad4096d7387e9b2007563cf4157 GIT binary patch literal 78992 zcmeIb4|r77wKu$H5;T}7AqGLQW`bA|FhoI(iZTHdu&5ZLQcF9T%uL9@WM-I|Xb@Tl zr1vUnWt1w{R_+9CFLL!Qw%(Ss*aV7}Te-J1T1#*JGa#+BTE!M`Nej;VTYIlPvuAQ9 zX!||i^L@`VM-Tg~wb#GB_S$Rjv(K5FyRVslt=(o5`X~`U5Co;pQy3>#th+Ev;}i|z zWZ@KNi?Nb+7}Ho43)U&DE`{V!|DJ@ZeaBV(zT-;1F2hubV=I@M&kh`+4l&;$=F{a~ zHdB{nBm_q(pS!s}zZ@^AgwQ3?)3M;UF)v+vC3#B+C6y|{3rWBMPLw4V2@?TSU^@g)gTF$?Dm zoaC3`IPHx)hm9;18-zV9oMn~&W*e7ut}b0O{^9P`BfWjKJ1(B!m{qz!RMagH&S!QC z+sFy4;*K@DPa1jeJr1#|QJXDgHbS00X7a^sF0Vy759f6_KZkQZ&Kq#ju>j{yIGb?Z zjFXOLoVVb-l>i(Iaef}>7jS+N=a+D{;B3X|!Rf`>hI28_cAS2kbS%Nyfis9Rgp-c2 zD#S7_V<_7lM619ro+M@6PDka>}Ty z*1o(eJj#CCgr?72^TOoczqIii-HRj7{p$6%zJ7f9&u?j&ck7Se{?<3{c>KV30_I_1ZC80SZ0;hcR==GtOf45|1xa)Uk{^f%sQ()g? zx1=}x@T?!dJ?wjXH~pfc_L{FuoHOR$nXzr{|2%H|Z_j#kt>L0UmnDe6`r1Kg#PBH5 zVCO*R>K8DWss@vaGy*>pghu_FV7H?GegQco!(s62%l-m#D4xe@BBPvHfd2H8t?i+> zwX*Z60&>1q0RNW)?fQKI{I&vm9|OIWKd&mFcWi-n)gduiwd>jf^r-@N-dDiRKNi5Z z7OgNTgQu{fIX!d>JM|EBuTC?LD^uzO#V+!wS%UT)=N16u>vZPOJXC zp@81K1^iZ5K+c>3ak8WU{qGC-^P&Rn{c{0(W)+aXtbiPEf&SfI!2S&d@S_UIsVJcL zvI2H)Eg*k#f%xn#;0J#Jy=ND&^9u#+nNonhzJQ&xC4ITrH4BE}*sTh2qqL`7RMW(Y zV>}Hw9H+|oEEl_ZqNX{R4japveYJw)u#b*DnNP~a^g0DUpbF8*ayHYXk7I!<#1}z7 z67z_Y=aIv#_flzpxoEgZ5guXu9jx~~TA<)qqzZ8f>pgaXLO2<}nDJ$k6rz;n3}^fp zra#E^cQC$!IuwVN?~(CeE)L(Q@F`V@D9S2PF4~!1uwB)+q|1_5KbZ$#0&n@Vl7)t4u%lLWS7C_^+^?TWO(&BPIXE+Kc^pj2CWQjDMEp zq*#vD`$e|%f!T_1KIGD|i{*!T!FN=l#b+6RKmv{=QV@n2{83tJUl>%E@+aFiCUIJ6%&upbFWDzPk`k-psK}nENA*eh1kRRkJ-)%Y`^aJU$UJ~u^${v zzmw_Z0vtHqu2D>1K?`df4WOgr0k&sdlY&#K5I<)6HF@%{X8J|ExYzQ3$Nm{}l@hRy zl&A*%k66O z2i*bxT|N=@Mf~o7>keNe>JJ4)G#HNfgRwTzdY3N}(zQ`v%=e$fpG|+8GXqA~Bzr8Ww^6 zsOEqm*;sA~Xibnjg2uM`V9wG9NBpr&J3tf8K|pvk77Clf$<#a) zsM_5@Z?xULG}C&JWpVzHCl=7nfnh-r_J@6<%@fR`AtVxuqQ`v^1cWB0*2O3eapDhKy^*$2`DG>b5{nt zzs;+w?sWTOZJ~(o4qq?^T)^jv~F1S(qQ| z@`Qphf3VXR3U-Cs+B6+nU#N;4>5qEc5pS?FkZ~^ zSso5_DySm9aKP=!v;(>=@ev<5482}Y#t&eL_|2*)Mwdz(sPg2D0ECTEXF!mSKr`wH z(uo9>9(T~=3m~<@<%7_%<%rv;ufr7%MYDbF>&kkH`?4eC^>ygL=+LRC16fjK4lUV( zewKaP;UdnAMrB0?ToUplC90;HA(Nv<#tr!hq0_qD$RNPoNgB{_1)nzdRn>c+<(o{_rljX9ivEdoR|2O`lrxrHh!aZ;$H3P?EiAS-Lv5U39(V?Gr zlwiF@$JWoPGR4m|G`^kjdS6uIHC$gxp1}S;%jx6I>4rS~KE^xq@P`=RmsSfa8|1Kw zQtoa8KU`weKYIKzV(o`WwP%To|`7|l?EP5$;>gv zz{Am*!(rg{y#ZBDFz|Y9O89yMPtP3m(O}?BKR6BiDViANTmxTj;1?M93Io5;z@KX1 zTMYbZ2EN_Ek1+6I1FvI`s=EyQNP|9Z;LUx^dky^Q2K_n%-)P`B82D-fpEU5J4E$yT ze};kIYT!p3_-zJ$jDhbn@MjwM-3I7hQV4g=4(u(Ec7fj`%vuQ%}H4Sa)v zKhMBB4LqkRSv%Lj^Hzt%FEH?Yi!bpD4g3X${1yY>V&K~ie8|9u4Lom|$l5LgKgpnv z8~Div{$2xrp@Cm#;Q1C@)^0HHyoDg~NdwQf+7iFnz+Ys@-)i75Ht^dFJl|@|+CBsC zGU#_3_-O`yuYteE!0$8gd)*oN7 zV{>1L>or4-IE?zkn8rtbJZGTtz>I{$Uh`UwqvV(RMBUzzcIQB)UC25Kp9FZE0iObR zrU9P@xYdBG0IxRSGXQTg;IjblG~japA28r?fbF)d{?7s)XTTEy&otmVz^w*+G2qn( zd@0~f27CqJod$e0-~$Fc6R_Q$)qgGEaRz)H;F$(|1K?HzZUVg8fNur7$$)PMywiZc z1o(gfw*t18Wc9ZJ9%sNy0M9hwAmCO5jsRY5z;^)NWWaX;-f6&J0erxKR|B?}X7&FQ z;Bf}L7Vu01{u;P{H2GS|L|Hs~DefIeIDf`sIAj!b+N^y6`!i}OH< zZHuim-oFR3biK~k+Lq)9F_+<=I?1xE#;r1XbF^swUfHzIKAYp6jQnQY38&=-pR*rFupj&E89!EGEh+sZ%A7qlrQ#yQcJmJO4e|YebTCg6IQD%N z?iZ?)h;8bhRiGzY?YBOA7XAkKSwHIIO_j`d74X!bRA=M3)%+Nb&5f{+>K+3f_^PO# z#Gma;H72QzEMLaSuUM|pzJA=3tM;K^^Z8ShOZrA8v~Jrl(HtKU@3*lWT~9XZYfVdW z`6H$?;}Z93)x9;M2lt}Y;Mr<>Z_YrnCf+07TltDZi2LoZL(8(^TGk!;imcmTV#o3f zeP8435v5{e5_0Ierb{e)`gpoo^N|hNwy3z4{XJTZK|O9B8SIa$sbt?2(Nl}Edvv^q zY_yY&Q#Ss8G~p_odX$~N;zhm(23Q_Dcm;L zP)Ih}U;DRob0f#$D72%+qAe$i`8bVXj#KKd8!cpDuSLd(t-{$uwB&y?*3$QgU!+bJ z18MXH+Memtl+mZukHWd3-yxEJBEPj8elzLFujG@|$=8--c7smOH9FVG{=qo^7w`==#?3q?d5k{&9}AtCKCQOy)1lh@l7&3% z`()bud$jWf3!0pE@?4tVPS-!s&ZLFTOgqPX`gT5GAurR;GO>)>d5;B6PCI$6kl)Vp z{(*M3S?J8P^XyOG&L#_anRcG7+BwgHCa0ae7RqnuyT|_ixSedFGt*8-p>}3-J>_1q zr+d}f{)-?>!*{@@Byy|F&3X-poSV&+xiTnsP;M- zanhV_j{O!M`;_+@9jltADjGF!@Y+=Sv=KS;9n^!ir^cz)b8l;SvRKv)8{c9bNnQuK z*|rBIUA$_x)(5|=f?ueAl23}nhkwHw5o;Saa^x!JiBlcg^Y2*Sn7*R*#>J4+Jqdk) zdK=cw-SD5hZc%;}i<~`ntb<8b1ae3=9+#+fP)oLMDc9{s+qzkAE9-rb^^&aNj1%BJ ziLY(C@5lza&Rf5eo!Ix6xwXct?7EYAzlHXyIZUq!e<;_!`Rk?oL7(4eTOkwrWS?KA z?c%jzBigv1>*`Urg83cUb$M0N=!0&|F`L-;JJ5eN(2*Z&!AJIgooL}_QF?Y2^?3^N zsk|4oh|6UulxKjZCN7r6sjg2f>q9w`b#%iPno~5qgW*%To@}LhJnv9*JoFQc=Po%g z&4sN#-h& z1A4g@2JB{7<{K$HmtN`xy|$y4>7{*?m%u|d|BU4e)*@7{7ZZvWF7PWt=gHBrx072n7<44 zQocpZM^X1<<~L(L41Uc+G5_1Ys+?fPycc{F^FGj0ZV{EMnj8Xa=t(NSw2W9+1^5)1 zyU#~`S4~y&d|Gp2%$&<{7dxwp=%p^KZE(*n#{-oQzz;RmYRu65hS*)!LSx2(T#8c0 z8}t%CjVTqIXJ1S(-K!{gtc3BLPQj(Bp6<2XJa#WeJHE_5FmwGB?E}ow)K77;2kVGF zlydx0o4d}G_Gjdav!p(fzl!Cb1=&6e`J+ib#c}{XAb(KYVckPI9LTR!?k1hk?Vxs- z!Uo*i-vQY*74S<1a9Fb@PZ7<_bo*jwNPCuRSd5YONMCfRI(2Q6bW*>7PqcpO1NfKP zs_NiBT{lhnLBKXu_nB-R^CgK7?T}--LG;{4b^w-R9`MZ!*Wh}Awz1T-@&6sWF3Pj( zLc^|!xR&wkh&KtbIpW}$&ha7j6}P#iMEgVeYP9BW$>TS8vOZKilqa%W&!}w}7eszCK znsX$L6Y*!`J&ZQk_MC-%9@r;i7`YR+PfQ|jyfUEnvdY1qU$+4=D7NuFb@L8x`;EXo zZXtUWvu*tg>wvmmuD}8be zM|ppS5KC=jFY4|Gjcg0~jO)l3wd{*?!5dWkTavH?u>wEGt57OmU|$Sv{LoD?LNXfE z7}9g6n4sVmj42`3^gpKbsePpJYA;Bzj7I2fWWB?na{=p(qrF;h_MTJE8P@wsjm&H2 zyYbp)o+oG?LR*^7Vf=RT7qtQFJ6a#6c&rorAlKQ@cD=5nwOWw|%jZS4QWNk88=cT<1CHu5#$DJBU15oDfZ(N-}us zTG0KHWt($!W*-p#%iarhXV%5t*!$RYx|&zWFZ-eACoIS8_nT-QqCF4j)qOr6IBoY5 z@Fd?pDP#9Wz^2`>wL1BuM#*p5y$E#w#`?^$aS7`ac%5F#;l9&xo}zxGIBVvy0oVaw zoDV)4TgeY>J;}FA6UlePK<~S@9=sb>oqWeR(EToK=&C)ML@8(;hB~^gnJ$(MPh*Ys zwmp%28$9nY&)eX6hj~y+9@HfXr+MHPZKtD@?Sj8;$HYjo-LW-(mRNbx>2D+7wP3v{ z4uAQTZrE1a-f~p70kj87#8OyZH4~?pz54cX*vof1`W_!A#1@lhT)QK~)BQoC%tLFn zFCa&@TiT=Y`X{%?{&(1eefm#g5A_qq|0nhbXg{?-z%$4nz)63cu43fhDZk>{{loj3 z8Y?zMY%6QOQG2ZAm0I+TSXpzrgMR9c{z=}3X9%Ec0i6?cj+KsY2+-L<=K!4(boMV} z-d<@ROFTAtZ5u0jzkq!FBx2|TF+h2?7iALtG>l@+=$97iGsGI_;(m&Ur#^5Fl%U_y zc03aqHu1%y7*oB7wO+&;;)v$J*MSoP?ZKL^ z^XCSfdkm0HNN@g57c=X%zWokxD8<`Fzs>^@x#Y@+8DO;Ky0;enh@_h37`l4Fe60ix%iY+aJbWe0B0N<(teL z1G=9;-f1i^2_5KXT7NwZ8>Mf_UY^tXUnd-R^csxhNO*#hy92s5Ar_DyWLx`%SpRd) zlfMRooTs4s1DxasV-5E3PR+AW$xX5z8)Qg*3oxf(Ji^xdljkbG(p)EV%=vNdfC+;0yg&sY5rpqRq-<61t;>IP2Q0@~gg z*dMu4^nfu}{O?lWz}VhJ4tFb)#$}>90`s(72U8;5bWB zKjixou^y*(GQO7j7dr3`-pEbd=1tJy1ilu&&*-4J2W^&o?E7a){*~w}#4`28?hmo{ zA-PAJa!%ZC znM-KBt;gGk|BHN4gIKM}=8RR8$DrqDs2j!OENP7KG2~aKUyC+XfJV-r*jsnNmt_W= zgilH}jQQ0KSRilG+R@E=wn7hZ4^vJjxyM)R8q$8miOhxXgD*c9UJDtBO=M zPrqlt z-7oHk?c}d~J(=fK;}Q3k_kM-#4p_(=(r&ze^!M1^WT7WxH*(CUwfkxdd2&CK{rv~{ z>JkeY{mel=2cP`+wWY>FZl*1hKYd%uE#&35#cn~9)0X=-iR$hGUv^!p-qp)_KcK)K3XO>$S?J942hDqyW5RmPyV*jP ze5PdVf!q%oBezxI2H$cIWFF|&vpgfWW!AHl$7sHOKmwEsE_ z-mx4vknO9CH?6X0-!8=s@ zt^+;!>xAtqjW><7Xx|m!Jz@Lu)*#C*Y#c>nC~plS#F;l^uNZqwC!cOdNo#J0XuKTn zTu*ff+vPMq^qAvZ(WLin!+%acX7$dalh%#;nJ8q@a~#@_)_JmT_1Y#HYbliay%^Hb zJtf|=340i=uswf_4nS9~4|ER7=5BLuoyKT`g%9eD9CQ#et@`*);4iS?t+mKO(m&M3 z(#A)~kLcrxB-5tu?89!EgKaVgll=TP_Hi5KelWe0xzctz>#eT`==M(pRkA)A+zW1Tts_$LE zZ?WJVvhM})bXS$>tv#zl8a*&iL1S{v7+cAM*8| z&it6iAIj&4AI!FHLjSFTKS#4aPr+XHu|H7^VSU8=2S*J!2|pasaQ0resgH9St%Yor zqUT-E*dT*+(R*3syB~n2_wEw(YbnlQI8VZP^3%x=po{M7^V{(g^<10!`vb@$n(Mf~ zd*4Ajn6D2ucTa_`N7OTG8CUN(p%d?AfUX<+rOC(g>&{Vi*jq||Bfk!N?!YxKlbi^6n$CTwHF3;zZ-W_;ItuY7nFXwNHuk-W!2lA5l7vRI3CVxYO zxH>bR5Bih$6yUpu`4CU#`Fv#OKNsM8g!vFh$MGyWXB?oN^u9mg`l#Qyt<;Czz!N+J z?+>)lxWQUl=iF}i&_VB!z=z3620yo1@&ivk(EM?XYfFB-t3mpcq@UaFB>lh>O#C@} zB(nc(O85DOZf?KS4Ltcn>vk5>O>vD}K{0{$oN|1{&vW<4`1dQxvAAbKztKF3IBEKq z4>4cBuW}6sct2nVO4_@W?>dgfzBJy=m+Kk!?<)8Z_frFS*Qtm0rKR677Ltr7f75Rc zWF2C*26a2|n-by?Eh13?9njtX6_okU?CCdLWf)r-ec&Y@{tdKbBk4l>dRFm0&eKYk zagQ^24wd-@a{mh2e7V2PliSU5A1NreCo@Oo?~nDfEc4#5kModTYnu0WcY*#z<}vSo zzQ;WBo(}DAp_s+k#{O}&Y>QXjf5j=z(C;r|ZS7{eeUP`+!j6r^i#0O#zU3TI%e>#G zb-AHqJ?JPd9!A-Fw;lN~n;%J663?7Sj$8+098-VVK~ML;S3@twzl~-8D`eLqrx4!^ z)KME&avSI!Aw1_Apt`TK4TAabJa2&B@gdpEm@mkDrOuKB@d~^nDh^+qcpL8-(fvR4 z_oqrqw_FAKWC$@CQgN4r?*hQd*a6Og*wJ-1yf@&WSYIl7=zS2aQ;>|spj)hD&^Y{p zv;#2DF}IKS-sRY(e4~r$Tksea;d8CK8EqYHg_U$H=y#4sDn3P`wGc+E%iyHm~oc-+-6?M>)l+p8KVo+mvYo z{M?PPNwERFr*M9(I=z_<6Ez8Rb9H^%`){BY6+z=`{09 z3y)ttXJqen3g6@PavM#*)DGd7#i0K#^O(5=gIVPaT1P&q=1=!MYx_yQY2(jeBdwLl z56!5fIHA4QZkqdy`@TS!{ggN0tKpz0f91zx74z6>PR5v^cr@q!6n=ks1LBPEeeiuFVwvQ91RU9) zjg`Us#K;kn_P;Scv-hgmHc?EIte0}?jabXOuO4sF=7~eJc@JzHZPDh>5D&Li=W-gu zm>>H0uUXqa4m{Lv@?H@3={nn2*Y=O)I=zP5VZ`HB_DRmOfax;t;@Og(@3ZaC-!FL& z{M0{YpPoKM`__a0E#@)fF|*%3J%3E-KFam+8IX}Qe*e86ZTe4^8#i*7Y!{yS$!7<= zzmjd2ArpJiHVc{0Tga5QLZ-auu^7K{-+cu3*+(EY*NA1C@m`Y+zrU?bO;0!$wrq3b z&#^gnJMe0A&1(X9k-8w!2A|1m$Hat3U&kjVklz$OH8Jr;eeIl-xLseTCM9mu*U8C= zW_|6rFmaQ!T5}(u8sk+2meI1{YxJF;M)F)=@Yv)Duw;9;BixboJwV0Z? zTwkZACN9<2ebW+C^>xc73EDrA?MhyfxKLlGE=kZBQ0=`malXDzUYel!MB-DICTjI{ z{IUc+yOsF9%M!G2DzBXli81;*)sUe5D#d$wf^vkSxgtUDn9A$q6$yHlB(EJ;CTLBe z@K+|NPZfST{fS}5PfwsP`S-QiSfTNc-0~s%u^IWKpPqx5@3{WpedHeGGF?ydXzUF~ zEM;RaJEln9PVn^6@7PoFckDgLL;XD@2ft~`<{|aBe_-c2Tk70|rVoLR)*%uWRros% zLO$Ec{=Ere%_+%$5CGaxATv%o$kNDkG2euO||%&BlJ$m4Ek+2_x%|}uW6vyLGQ58 zeIV}51iv37pP@k}-J>N*F6fava%dp?euW1w==6ISIk{v!<}>Vn)S#?@&uG029rE`= zal+Gl2RzLcHPbLB5)H}+U~5gi`uz~ui+6#R!Qag_8`S$ZBY{I4H_S!J^aRPKa?^b2U6psNsbEM~s&%rmk zZ!6DO-JkZCUI(Gszqe6)n?$Q}o5J-)*EH0!&cV!5&+Pq*-TK(m$T&j0ACXCg?0Z#Bxce&2YAW_G-hnHex`i^dT;Fz?;(J9D)Y+wGODNd z&zOIm$zN*lb8g7Un~;@P!}8?32wNTkOg5gW@XEi+r_#UXz6AN7?ssX;PkFje{w7JFg!MGW=F)euA0lMGs`bkW`xEyi zd)!q8>2t}K>dmLuBboBXL}$I zwpYpZ9`H$$KL>F&2XTe=<2w({-GraBj@teU2l9sOU-ZWg$jYw+&*dclNvu~haY4V? z2JSr8`5mQmP(M{x=8l6u<8SVKM0*qzqiC1xt5hZSC@97-F8a{US+>Meh^ekf38K3Uv3^<3*yz8Cd^w|RpqJj&q%ktJY@i#y zr!lw*wC@lfaLK8P?`7mf*t1nQKr@BL4#vlnxdRu;_A(4#BUavZ(|$7QiPzGfd3D;K z)%d$W9cu#Lh~K>$0p>dC$U%CJ7uIC_ZN_?N1*qS z9Dg<)fIkU0kLn*n{X=ZaS+EP&_W5!T2>n7nE=?RnUp|RG)%`h|_(ns==$mA&oje*o z#B&g$J;$zc?8|3B7WUdjMRiq8nRA)D3h-^t0Wa~M4qjY$)AfjvJ-w4g%HLggPfGNp z5HsTF-LK$xd-v13`*QqaUX?J`6y2xaB*!@I>zt6j6!aq$J*{h!lWtP^0{Xgf4ZllS zpTm9;%>}g9GVv)q2RNY){{8Zf5s;7e??B!;7wtlvEOP^QLVn!C5p9xwX9IqlBi{6z zcj1?E_^2HI#X7PD_YahxXdSTw`wN8sHtUE}ECK!lc<3JDMEdCdmG+O5vy|R$=Kmk? zQ~PhB*nqs($OgoBDaZLR#5ut>{H?6yl!W~4IM(@im%9}EggV}bA>K>3+znoggMky- zg8O$lhVo_N?uVc$Mp%R0rFiL=0>~e91V2&IvkCl1^bOPeLcHcgQ!i-l3A#J5-*+Wf_+={}G;k z<4>iIDLua22Qk($?xN&d$X9oJrq%d9?W=>asfXyUC7wy(nWT8)_%xH2 zN%@`dC1zfD5_@@g=ibcoBT2@M6MB+2w#&Tm4BnIP4sGtqAJg3@I}^2gMPf#~V_U&` z%q3K>pQq3oU>Qs1Cn&L=9yWW1ScG5O4e_L%US`v=kg4&(}2 zTirzQf;AfTff}n=o8j-E38z|r$oR;sbEtKR99P(n+w#A#nfk5+eOE{h#jbn?k|(Q{ z@(gfxnR~Ljoxsa^XW^zYCI9$P7LQl5<&eW;UuBY)+uPvP4c zSfBp;N9aeaRmmUwk7Vbb?B2lOy@vhB(=@&)&Wydbe%g;ATLk}2!~K}&#OiqeO!!;J zy)B^Z)igUGtC4>%wH0*83H=kb9RB-nAEDD}H>trp>>&Gvad$Neqsg<;O#$5{ElLpVn9| zwX>Ny^}&#`jOvsge73_h(?K8o@wehjQBg-*B-G)!@n$VY(c}9gh3I1;Nv-JQ*MV{< z{lRc&YzjV^f`Vk4{)1F}sb{eR-wAT)4=_m}*6xRI0&e<(5N_N}KA#ujvuyf%ONH19 z>Qmp>DaclkFVp7*SPQi;^TnqEe6yRI97NMjpNb-ja+2m74oOA3txu8$FXEM zQZzauzF21@2#mW8flK}p;>J$IK$`?;eb83gu1@almO*gwIGlR_Zx3hdzt?2`-Z zn~PeU*jE?WXBWux0&8edj}z;6aVbvBAp-M~n2GZmoR{Ooa{>IgQV2XZ5Sahv4fSm( z@!Uav|GWjI2d5sjSEwx5aPcK1S%@jo<rl4;TRQzb$~`FQ8>i22PNxMH z_#2*1r^ln*hmyV_`V{_Pa~S1j{2{vaC`+G7r?;Uzi1JmGE1ylLkD}a%^6X*wUi|+_ zr=2MKPGVOA6|bOQa6`2LL6qD7kWN?NMsmZuh#!>CqnwYj{ynr4CH7y_-$W_ihksBu ze2`8bLb>NK;syi!nIq}+T$E@31@VS*>ruoT%2x-V2jxMOucE{~t2}IXHHp%$b8KTP zY9x;G!kSI+G1n6vT_=H~JV3{IoKBp3fSZd_G@j;oqI6?PuF^O7K=bVc%#2@AYrKq4*p?CvhGH%|4Wd{HRH@3suixVLlPW=-nZ5$@;EL~r62P}ORDjle*v_aNW=mSNk+XE716UmwengH^G zGnXb~H=!Lk7lCdK==KmI>zlBaMKp2HOhyjD-v==5`ie>O6zVFFR~CW9kkhPbsIPW| zW;SS2Omhdt1`Y?#gQzRpjB!DTtQ{WBPc-y>)s>(rq=RVi8*!P}9>BFBr^S@B05oSm zJ+zz{XqrJYlpOkw`SugYp>LocIf0xMXci&o4%MzwgInXTHTV*>%4jl&6)(?N3t zzC_zKjr}erasWLaG;5H@k3c7ATArxbSdJL8|InZbgQgj|TYg_TOH;Derie(+deC^0 zw|C*%@N>|#XB%ir@#5gRTpG8gp*VdVG-1$uQ_*Ofy_$y54xFW>h!N1mF@Hec;gTn= z-gp%ZFZrS}ifAT)#)-LOH?EELhqNrBq516~XymuD8&6yC#3>txlbA}QZ8Y~)>_E-} zJ>)o_DBE~4Xgai<@j%nOxEVC`GK4{6Z_-3SJ8*6X-K#r??%NdU!`wDx|D)ZrPe3yP zG%HU)Gaoe1gC-2yV7Ja4pVwxS&0+HKuA%K&51Izh3?*k9Xx5xS&g-Dr4jRh6hCR2L z_Q-|di$mKp9_xb!&^)E&=s0TAawz}I1R6>mGg}J<3-(W)Uh3!KgW)HK~wQk zHh=Lv(y4VgAodxOa{?OrF7uk74lRe)JI>uh)6iPz7-)vdQM69-{wLNes?EAxUodT^ zHPklH9L=R!Vbak0>VcmPt!p!Aj(}z;U3)+?7wfR0bR7oG*;t!3QygNf+nzXQ<7a4) zHmN~62I%TCweCtPoAnr#W3C=`d$A^4sOof^Z#LW044P7`*M_3$0!;&GK5Np@g8SFQ4Z)@7fmm4U{Aa~H{?^&YO#CQTR6WJGfaG<#1#Q-Sqm zJJyTOD>-`nE-~fQgXSn`NTxBSuF*7PH?3oj3_(Mz4xB4N_a5j{xHjz0iTNaGHsd2R zYZQ&P=}TIl1NdE_Df>10RMF`6XVN9n90E-PXbSa7HE1fZP7Z+PRpswWd-OE9#v2d( zgx_GDoh$z)Z3CelIOl_I0qCX?BAfrN({w}=22BfS8WfFQ^L@^wSr3{RXgFtUn#_8q z61>|$^9*Rvt@0qh?*T3eT#PVT{kLn`geJNppnDH=2MLj#gEDJ8LX$tv#=3tq?iXGs z1aev@avJeZqx%BfH=M2Tx=&{3>sbW47SOFEx|yeKt!;eb(bA1yFGZZ+{>Gcx_2e4R zJrBAT^83uwQjW$a9w^;-KhfE5WoT!M_%==CQ1L z&>aQcT-@`lS9EdMwyt#JL!@)&BVQ|B|6u9YA1Jkd>5VskDVN6h{wUUe74Qe{k5X7y zP{tleMZbluv=*9q+TjI_Pkil^jSrqeZFsTq|+21`k?EBz?bFWb$zic zdZ6fmq6dl|D0-mifuaYB9w>UC=z*dKiXJF>py+|32Z|mjdZ6fmq6dl|D0-mifuaYB z9w>UC=z*dKiXJF>py+|32Z|mjdZ6fmq6dl|D0-mifuaYB9w>UC=z*dKiXJF>py+|3 z2Z|mjdZ6fmq6dl|D0-mifuaYB9w>UC=z*dKiXJF>py+|32Z|mjdZ6fmq6dl|D0-mi zfuaYB9w>UC=z*dKiXJF>py+|32Z|mjdZ6fmq6dl|D0-mifuaYB9w>UC=z*dKiXJF> zpy+|32Z|mjdZ6fmq6dl|D0-mifuaYB9w>UC=z*dK{{P~Et)EqvwsT3Jc%#FG?^M#k zpAi*zF^o^S$)mnjmH1S)JQm)i$}<_>cU(!LZ`;$MzY}Awyp}Pc&L1sYq4P&yl~SPdUHm)+>wG7;0Xjcw14po3V!yhu>5pOV zR0i5LyhgROM8j-QDZ`F#W#BMf|1;H+lQdkZG@s0{bEeWxx zttrW@KC(EvDiLGEy?OK$Hl^ajbS|Y3GbCcLpJxJZ7g@Ys_bB?o{iyJR`{l+V^$r($ zoM^kW96dHP{=p&Tl#8Q0?lt`eT#)_w_{SMPL8UKEE}oTFgZ;mi>GgV7GrWWgo5=Qy z9+pbZ;5hlEq#ukw#`5*LTFX~*2FD?-BW=ij`SGdfM~FaPJS%+GPAxwQIacwY=m(D@ z9VgG{`R#mZ&lzF?$D4x{Oaq?$e}w7xF<#rF>9sxD{=IqjUo)hgqlA;!3tEo0bNWP$ zQRM=*Gt72U&Za}#qwxx>@gv06H5#MJ_5${3y>l?+GU6)I*{E9))8BKsKOK0=w_QAbd3l~0zZ$QlUB~n* z^XR|C_;?;(`}wF21Qz56J^5MVr!jsx<0tzSqJigazlx zpZm33{5MV@$FVe*UfUVYlk;Q&Ig0*Nq1WkJn!=9~_3TfL{|@BXL>6Dh726r#!1Nk_ zYKfwE=Hay;#ssuvRqloy>KBb)$<;q+{CcKu!MLL14aOhoP;gik;vL3&g9_2W^aG6l zW=P@JGrkOVlHU4d3ZDW@$0){Eb}BdwemZIxAHP$G(pRZ*(`Ro7j6%;>( z*3X0QgOZTLTMJpGxcVOOx{YSV_5$>KCB9rto~o#iaD4u@0R2&>KX$XC?_wpVA@Nor z{wG|o5MjoT1Kz>cW^pysKjl?eO>?Wno0RH0$n<*swuedX*Lco{2O0k;@Z$!H5zk5b z!4*3T;9rw?%%=+!Pl{RJX8dNJ7r0s+W_%y#f1SsMVZA{9d5ZUYc)1|PO8j7Z>I>lK z7QlZ=%l9c+YglBk0R5^0_(uxhzYV-qT>Vhemy0mR!vm~$bpbiMnSOVjBGi8PHRH!z zpzsG-&ijmC*Q5|B#vfz+297r^r}8A_=dDu|p~j!fc)ecQ&T=ke{BG{A7~`*zcm|B( z^GqMOMIm*27c;(og2EqSo~{CN?k|8(7Qp{U0endM!7z|7UzPOb;xMmoQ;a_Vyj4Gb zR6zd8*gkYHuUVV{JjJt^udv$BlO^7yRM%HC{nncloMMvsjGvn~E*2M%vzF;A>XiWP z|BVIcf57yMZcy~v&Yu^cKfv_O(-r+Lw(|qV@0z6$yBS}G0dAFtS0N9K#QdV~L-lj0 zagx4VG+d+v9O3mt9pe{qe@$ljS1^9ITOq2Mej)Hy{pe%*m2HY%-*v zm3Rh>Vgu9b`@uNkjE-jt(DyR^bRO5bU-lNDKM1^){eLWgw+*-Ua~1Gba>h#fa^bjC z**y*MO-DWB_vFRxEXM2mlx9UPZf1P7M>pb6!W}LK+anQ@B;<#V{t!grFSCmG_EVYq!cz_zNcfB#H(xb--QM6{sQ>B z3*aA<_;R85Nj9*OEsQ_P`Btwt`WSzN^M5-)sl8UI`0H4UR=k!Jb_W-IJ` z$wuD_HyCEXI5Nx%i2U*ZVwAscLbV#4}(N%?0r7ET?ReA};0j-dTYDQQ)oo z|1FlYnfv{ltoyr+58tW~+W&n8rS70sjDsc4@e6_8iYn;oo#J(9^v&xe2e|jm@ndrb+|l%P|z0@Q%;@-pgQ3 zlo>9sKk9KuyshX3UqrM*%u-bPB9V^hVq7S}UU$r$11$9|H>v=6z!>w1u%ZV$obB@| zC>o=x4)@X=I1nQF(OASETnz2F4*FtkT`0+)G9Yp7Y4>@SdVP2JJqU;B@~9`^jz(j# z<-#9@#sGOl)dt}r(a{-=NscTY{&ENW?x?~<;f%2KOxPWX`oJ24nF5jGC9P0_f7Mlh z%&rUFj7Yh*sqy-2T-V$%+vNg8cp-y};w~C;aZtEil0~>?fBuHX>*vivEgnC+u9?gH zb7waJ)qMRdEosjD8)r7ocinjHwKreW>}qbDIsY1$=?xM@k zQys2wC>j$nj|_;sN{UMt{E&?t@@l{rw4#f`U*vKXaykfr?FzNE1^gX;G;Ln<^{(u2 zguyfzo87Gl{M?`kb;dYuJZ&%_6b<`)UMsDr96V0Q%Ah-p@j;{9)8!_gqOb1q<%kIT z!?_3=QC@!}hluTho`_!?i69I`j3{-X%^lEySY&c?h6?{+XCNS=!7zEIEl0Aa4OA8_ zi+P~T9g7vRLyjcTCP#dZz&5f!2h)Ls8uH|zcpOo;!l@pAn?EyPRS)VWfB=!~$AB=} zB!^27xlSWN<)Pq~IgEs6dpnDZ#zNs%qz;3Ol1l6H7=|8rvZ+82zCahiJUNb(D#PWm zh_^GG(@5!?L1Hi&i9r1+&ZjVp6JxGjED^lrQZKAssdF@WOKh_qC$O%JWuE!w1!`0zl zlILtzMPaHYARgn4H$}{t4Vfd64ppLW`Zg~vZlL0Tv5)A)#2zSh)is>I-1a zlVd@)3uHg(*vlkAp79k7&4UIp3-CwfSYU`(hPrRi>)GK875G2Thz^W1>1@pcRTjAx z)3T70O=0iI@X}Iv=JE%5cxJ{}Fr=n%y`Hw3z^S*iAhdwiuI5Zh3tn9v!GaZw0vf%+ zkP8!)o7PV`ZFIQ?#{+u{yi&N1Efsx`vNT76u2xa4Rl~A2(@dIJG^G;M85|@ktJv#n zb9V-me%+PSFn`FCH|V$)`gk!(bEl|_F7Lq5MTtBl%XWPg3}Nw)u~ygG=?{1>^m~Pb z+TGE1QRiJA1g9#|sj@~#jS09=7x4w$#K2eKKupw0@7H0b!!_N*pbm@Jm|N8O+WD@f z-3wj~DGqh}qB!(51iL%1bf{Y#f-L+~Clx4RtAfsE zwEdb+m$a=#@A_+4zfY-42QlLy`$?3xpKx?|aZ;V;*Y92GGGD*u*X<87|4fv0=-(^o zQvbez?61a|&))?c*~QzdnfEyL`<%pM9%QR|P!O8@`R{|T%VW@cGrIgr9>0FySC?IS zBbSwG`?U>g^7!@pqq@|3G<}{qVlDG)|LgZmb@?~mH`o06`XA2Y_r?{aF7@+Rg;jm6 zuc=JpB>!ps`n^?M9_9E4i9GW4Zvpl+oH{=B`-A(AThnWPU49>ABMg51dz{oK4zkaU1Elph5?^i|UU(K(}y{Iwy_3wKQG5-SX zSeBsqbouK%e*Hc{gIyV3S%6;~^cF5imbPEN&pVelDxI2;ORZO<58{F}<+oGwtGD+x zwW1|RStrCFaZUMKANu{^MXZ0HuHaJlgGQ5_ye7)3hG9y>gtHVS6_4W7_UrtE#}k>h lD@yfmOO4{6h<#4;&~|GawNLv_