From f4f8c6e023037b04843190569681db3a3df43856 Mon Sep 17 00:00:00 2001 From: wenjy Date: Wed, 21 May 2025 16:47:40 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=A1=B9=E7=9B=AE=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .DS_Store | Bin 0 -> 6148 bytes .idea/.idea.Sln.Iot/.idea/.gitignore | 13 + .idea/.idea.Sln.Iot/.idea/encodings.xml | 4 + .idea/.idea.Sln.Iot/.idea/indexLayout.xml | 8 + Sln.Iot.Business/CheckTimeBusiness.cs | 76 ++++ Sln.Iot.Business/ElectricBusiness.cs | 299 +++++++++++++++ Sln.Iot.Business/FluidBusiness.cs | 276 ++++++++++++++ Sln.Iot.Business/HeartBusiness.cs | 79 ++++ Sln.Iot.Business/IotEnvBusiness.cs | 309 +++++++++++++++ Sln.Iot.Business/LoginBusiness.cs | 98 +++++ Sln.Iot.Business/Sln.Iot.Business.csproj | 17 + Sln.Iot.Business/base/BaseBusiness.cs | 151 ++++++++ Sln.Iot.Common/Class1.cs | 5 + Sln.Iot.Common/Sln.Iot.Common.csproj | 13 + Sln.Iot.Common/StringChange.cs | 320 ++++++++++++++++ Sln.Iot.Config/AppConfig.cs | 75 ++++ Sln.Iot.Config/Sln.Iot.Config.csproj | 13 + Sln.Iot.Config/SqlConfig.cs | 50 +++ Sln.Iot.Model/Class1.cs | 5 + Sln.Iot.Model/Sln.Iot.Model.csproj | 13 + Sln.Iot.Model/dao/BaseMonitorInfo.cs | 209 ++++++++++ Sln.Iot.Model/dao/RecordDnbInstant.cs | 132 +++++++ Sln.Iot.Model/dao/RecordFluidInstant.cs | 121 ++++++ Sln.Iot.Model/dao/RecordIotEnvInstant.cs | 116 ++++++ Sln.Iot.Model/dto/CommParams.cs | 100 +++++ Sln.Iot.Model/dto/ResponsePack.cs | 40 ++ Sln.Iot.Model/dto/TagInfo.cs | 42 ++ Sln.Iot.Repository/Class1.cs | 5 + Sln.Iot.Repository/Repository.cs | 44 +++ Sln.Iot.Repository/Sln.Iot.Repository.csproj | 18 + Sln.Iot.Repository/SqlsugarSetup.cs | 73 ++++ Sln.Iot.Repository/service/.DS_Store | Bin 0 -> 6148 bytes .../service/IBaseMonitorInfoService.cs | 35 ++ .../service/IRecordDnbInstantService.cs | 42 ++ .../service/IRecordFluidInstantService.cs | 42 ++ .../service/IRecordIotEnvInstantService.cs | 42 ++ .../Impl/BaseMonitorInfoServiceImpl.cs | 37 ++ .../Impl/RecordDnbInstantServiceImpl.cs | 73 ++++ .../Impl/RecordFluidInstantServiceImpl.cs | 73 ++++ .../Impl/RecordIotEnvInstantServiceImpl.cs | 74 ++++ .../service/base/BaseServiceImpl.cs | 359 ++++++++++++++++++ .../service/base/IBaseService.cs | 130 +++++++ .../service/split/MonitorIdToSplitService.cs | 97 +++++ Sln.Iot.Serilog/Class1.cs | 5 + Sln.Iot.Serilog/SerilogExtensions.cs | 65 ++++ Sln.Iot.Serilog/SerilogHelper.cs | 105 +++++ Sln.Iot.Serilog/Sln.Iot.Serilog.csproj | 18 + Sln.Iot.Socket/Adapter/BufferRequestInfo.cs | 72 ++++ .../Adapter/CustomDataHandlingAdapter.cs | 117 ++++++ Sln.Iot.Socket/Class1.cs | 5 + Sln.Iot.Socket/Sln.Iot.Socket.csproj | 17 + Sln.Iot.Socket/TcpServer.cs | 127 +++++++ Sln.Iot.sln | 67 ++++ Sln.Iot/Program.cs | 108 ++++++ Sln.Iot/Sln.Iot.csproj | 32 ++ Sln.Iot/appsettings.json | 19 + Sln.Iot/readme.md | 210 ++++++++++ Sln.Iot/sql.md | 144 +++++++ 58 files changed, 4839 insertions(+) create mode 100644 .DS_Store create mode 100644 .idea/.idea.Sln.Iot/.idea/.gitignore create mode 100644 .idea/.idea.Sln.Iot/.idea/encodings.xml create mode 100644 .idea/.idea.Sln.Iot/.idea/indexLayout.xml create mode 100644 Sln.Iot.Business/CheckTimeBusiness.cs create mode 100644 Sln.Iot.Business/ElectricBusiness.cs create mode 100644 Sln.Iot.Business/FluidBusiness.cs create mode 100644 Sln.Iot.Business/HeartBusiness.cs create mode 100644 Sln.Iot.Business/IotEnvBusiness.cs create mode 100644 Sln.Iot.Business/LoginBusiness.cs create mode 100644 Sln.Iot.Business/Sln.Iot.Business.csproj create mode 100644 Sln.Iot.Business/base/BaseBusiness.cs create mode 100644 Sln.Iot.Common/Class1.cs create mode 100644 Sln.Iot.Common/Sln.Iot.Common.csproj create mode 100644 Sln.Iot.Common/StringChange.cs create mode 100644 Sln.Iot.Config/AppConfig.cs create mode 100644 Sln.Iot.Config/Sln.Iot.Config.csproj create mode 100644 Sln.Iot.Config/SqlConfig.cs create mode 100644 Sln.Iot.Model/Class1.cs create mode 100644 Sln.Iot.Model/Sln.Iot.Model.csproj create mode 100644 Sln.Iot.Model/dao/BaseMonitorInfo.cs create mode 100644 Sln.Iot.Model/dao/RecordDnbInstant.cs create mode 100644 Sln.Iot.Model/dao/RecordFluidInstant.cs create mode 100644 Sln.Iot.Model/dao/RecordIotEnvInstant.cs create mode 100644 Sln.Iot.Model/dto/CommParams.cs create mode 100644 Sln.Iot.Model/dto/ResponsePack.cs create mode 100644 Sln.Iot.Model/dto/TagInfo.cs create mode 100644 Sln.Iot.Repository/Class1.cs create mode 100644 Sln.Iot.Repository/Repository.cs create mode 100644 Sln.Iot.Repository/Sln.Iot.Repository.csproj create mode 100644 Sln.Iot.Repository/SqlsugarSetup.cs create mode 100644 Sln.Iot.Repository/service/.DS_Store create mode 100644 Sln.Iot.Repository/service/IBaseMonitorInfoService.cs create mode 100644 Sln.Iot.Repository/service/IRecordDnbInstantService.cs create mode 100644 Sln.Iot.Repository/service/IRecordFluidInstantService.cs create mode 100644 Sln.Iot.Repository/service/IRecordIotEnvInstantService.cs create mode 100644 Sln.Iot.Repository/service/Impl/BaseMonitorInfoServiceImpl.cs create mode 100644 Sln.Iot.Repository/service/Impl/RecordDnbInstantServiceImpl.cs create mode 100644 Sln.Iot.Repository/service/Impl/RecordFluidInstantServiceImpl.cs create mode 100644 Sln.Iot.Repository/service/Impl/RecordIotEnvInstantServiceImpl.cs create mode 100644 Sln.Iot.Repository/service/base/BaseServiceImpl.cs create mode 100644 Sln.Iot.Repository/service/base/IBaseService.cs create mode 100644 Sln.Iot.Repository/service/split/MonitorIdToSplitService.cs create mode 100644 Sln.Iot.Serilog/Class1.cs create mode 100644 Sln.Iot.Serilog/SerilogExtensions.cs create mode 100644 Sln.Iot.Serilog/SerilogHelper.cs create mode 100644 Sln.Iot.Serilog/Sln.Iot.Serilog.csproj create mode 100644 Sln.Iot.Socket/Adapter/BufferRequestInfo.cs create mode 100644 Sln.Iot.Socket/Adapter/CustomDataHandlingAdapter.cs create mode 100644 Sln.Iot.Socket/Class1.cs create mode 100644 Sln.Iot.Socket/Sln.Iot.Socket.csproj create mode 100644 Sln.Iot.Socket/TcpServer.cs create mode 100644 Sln.Iot.sln create mode 100644 Sln.Iot/Program.cs create mode 100644 Sln.Iot/Sln.Iot.csproj create mode 100644 Sln.Iot/appsettings.json create mode 100644 Sln.Iot/readme.md create mode 100644 Sln.Iot/sql.md diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..e4d2e266e2e2c58c60ee3b069e05b20ce32c1c32 GIT binary patch literal 6148 zcmeHKyJ`bL3>+nf7}B^*xnE$&A1uZxQr{1x2&6G*2ubRz@?HM4j2?uTvzve$GXiOL zwVFL{iqi>zZ9ZQgffaxy-4Wj&=H}<_GrOsb5$U|+8GC$u4e#S_KdU~SaPA!j>@Z^e zmp{Jct*2?qN&zV#1*Cu!kOKcyzmri^msa2hV}KPV literal 0 HcmV?d00001 diff --git a/.idea/.idea.Sln.Iot/.idea/.gitignore b/.idea/.idea.Sln.Iot/.idea/.gitignore new file mode 100644 index 0000000..c8996d7 --- /dev/null +++ b/.idea/.idea.Sln.Iot/.idea/.gitignore @@ -0,0 +1,13 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/contentModel.xml +/projectSettingsUpdater.xml +/.idea.Sln.Iot.iml +/modules.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml 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/Sln.Iot.Business/CheckTimeBusiness.cs b/Sln.Iot.Business/CheckTimeBusiness.cs new file mode 100644 index 0000000..097323b --- /dev/null +++ b/Sln.Iot.Business/CheckTimeBusiness.cs @@ -0,0 +1,76 @@ +#region << 版 本 注 释 >> + +/*-------------------------------------------------------------------- +* 版权所有 (c) 2025 WenJY 保留所有权利。 +* CLR版本:4.0.30319.42000 +* 机器名称:Mr.Wen's MacBook Pro +* 命名空间:Sln.Iot.Business +* 唯一标识:8BCBBC2D-F2EB-45A3-9DBA-24217C772093 +* +* 创建者:WenJY +* 电子邮箱: +* 创建时间:2025-05-07 13:44:30 +* 版本:V1.0.0 +* 描述: +* +*-------------------------------------------------------------------- +* 修改人: +* 时间: +* 修改说明: +* +* 版本:V1.0.0 +*--------------------------------------------------------------------*/ + +#endregion << 版 本 注 释 >> + +using System; +using Sln.Iot.Business.@base; +using Sln.Iot.Common; +using Sln.Iot.Config; +using Sln.Iot.Model.dto; +using Sln.Iot.Serilog; +using Sln.Iot.Socket.Adapter; +using TouchSocket.Core; +using TouchSocket.Sockets; + +namespace Sln.Iot.Business +{ + /// + /// 校时指令 + /// + public class CheckTimeBusiness:BaseBusiness + { + public CheckTimeBusiness(SerilogHelper logger, AppConfig appConfig, StringChange stringChange) : base(logger, appConfig, stringChange) + { + } + + public override FilterResult BufferAnalysis(ISocketClient client, BufferRequestInfo requestInfo, int bodyLength) + { + ResponsePack sendResponsePackInfo = new ResponsePack() + { + m_MessageType = 0x08, + m_PackLen = new byte[] {0x00, 0x06} + }; + base.GetMessagePack(ref sendResponsePackInfo,requestInfo.buffer); + DateTime currentTime = DateTime.Now; + byte[] timeBuffer = new byte[] + { + base._stringChange.HexStrTorbytes(currentTime.ToString("ss"))[0], + base._stringChange.HexStrTorbytes(currentTime.ToString("mm"))[0], + base._stringChange.HexStrTorbytes(currentTime.ToString("HH"))[0], + base._stringChange.HexStrTorbytes(currentTime.ToString("dd"))[0], + base._stringChange.HexStrTorbytes(currentTime.ToString("MM"))[0], + base._stringChange.HexStrTorbytes(currentTime.ToString("yy"))[0], + }; + + base.SendMessageAsync(client, sendResponsePackInfo,timeBuffer); + + return FilterResult.Success; + } + + public override void ResponseHandle(ISocketClient client, byte[] buffer) + { + //校时指令通过业务数据返回 + } + } +} \ No newline at end of file diff --git a/Sln.Iot.Business/ElectricBusiness.cs b/Sln.Iot.Business/ElectricBusiness.cs new file mode 100644 index 0000000..0812c0a --- /dev/null +++ b/Sln.Iot.Business/ElectricBusiness.cs @@ -0,0 +1,299 @@ +#region << 版 本 注 释 >> + +/*-------------------------------------------------------------------- +* 版权所有 (c) 2025 WenJY 保留所有权利。 +* CLR版本:4.0.30319.42000 +* 机器名称:Mr.Wen's MacBook Pro +* 命名空间:Sln.Iot.Business +* 唯一标识:F35152F4-9983-4881-AB1C-F9561EFAF534 +* +* 创建者:WenJY +* 电子邮箱: +* 创建时间:2025-05-20 10:30:12 +* 版本:V1.0.0 +* 描述: +* +*-------------------------------------------------------------------- +* 修改人: +* 时间: +* 修改说明: +* +* 版本:V1.0.0 +*--------------------------------------------------------------------*/ + +#endregion << 版 本 注 释 >> + +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using Sln.Iot.Business.@base; +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 Sln.Iot.Socket.Adapter; +using TouchSocket.Core; +using TouchSocket.Sockets; + +namespace Sln.Iot.Business +{ + /// + /// 电能数据 + /// + public class ElectricBusiness:BaseBusiness + { + + private Dictionary _lastCollectTimeDict = new Dictionary(); + + private readonly IRecordDnbInstantService? _service; + + + public ElectricBusiness(SerilogHelper logger, AppConfig appConfig, StringChange stringChange, IRecordDnbInstantService? service) : base(logger, appConfig, stringChange) + { + _service = service; + } + + public override FilterResult BufferAnalysis(ISocketClient client, BufferRequestInfo requestInfo, int bodyLength) + { + ByteBlock byteBlock = new ByteBlock(requestInfo.Body); + + if (byteBlock.CanReadLen < 1) + { + return FilterResult.Cache; + } + int pos = byteBlock.Pos; + + try + { + List result = new List(); + + var amount = requestInfo.BufferLength / bodyLength; + + _logger.Info($"收到{amount}个电表数据,开始循环解析......"); + + for (int i = 0; i < amount; i++) + { + RecordDnbInstant dnbInstant = new RecordDnbInstant(); + + #region 表号解析 + byteBlock.Read(out byte[] b_MeterID, 2); + var decParts = b_MeterID.Select(b => $"{b:D2}").ToArray(); + var equipId = $"{requestInfo.ColletEquipCode}_{decParts[0]}{decParts[1]}"; + #endregion + + dnbInstant.monitorId = equipId; + + do + { + byteBlock.Read(out byte[] b_UA_flag, 2); + base._stringChange.ConvertBytesToUInt16(b_UA_flag, out uint flag); + switch (flag) + { + case CommParams.AU: //A项电压 + byteBlock.Read(out byte[] b_UA, 4); + base._stringChange.SwapBytes(ref b_UA); + float f_UA = BitConverter.ToSingle(b_UA, 0); + + ValueIsNan(ref f_UA); + + dnbInstant.vA = (decimal) f_UA; + break; + + case CommParams.BU: //B项电压 + byteBlock.Read(out byte[] b_UB, 4); + base._stringChange.SwapBytes(ref b_UB); + float f_UB = BitConverter.ToSingle(b_UB, 0); + + ValueIsNan(ref f_UB); + + dnbInstant.vB = (decimal) f_UB; + break; + case CommParams.CU: //C项电压 + byteBlock.Read(out byte[] b_UC, 4); + base._stringChange.SwapBytes(ref b_UC); + float f_UC = BitConverter.ToSingle(b_UC, 0); + + ValueIsNan(ref f_UC); + + dnbInstant.vC = (decimal) f_UC; + break; + case CommParams.AI: //A项电流 + byteBlock.Read(out byte[] b_IA, 4); + base._stringChange.SwapBytes(ref b_IA); + float f_IA = BitConverter.ToSingle(b_IA, 0); + + ValueIsNan(ref f_IA); + + dnbInstant.iA = (decimal) f_IA; + break; + case CommParams.BI: //B项电流 + byteBlock.Read(out byte[] b_IB, 4); + base._stringChange.SwapBytes(ref b_IB); + float f_IB = BitConverter.ToSingle(b_IB, 0); + + ValueIsNan(ref f_IB); + + dnbInstant.iB = (decimal) f_IB; + break; + case CommParams.CI: //C项电流 + byteBlock.Read(out byte[] b_IC, 4); + base._stringChange.SwapBytes(ref b_IC); + float f_IC = BitConverter.ToSingle(b_IC, 0); + + ValueIsNan(ref f_IC); + + dnbInstant.iC = (decimal) f_IC; + break; + case CommParams.GLYS: //功率因数 + byteBlock.Read(out byte[] b_GLYS, 4); + base._stringChange.SwapBytes(ref b_GLYS); + float f_GLYS = BitConverter.ToSingle(b_GLYS, 0); + + ValueIsNan(ref f_GLYS); + + dnbInstant.powerFactor = (decimal) f_GLYS; + break; + case CommParams.YGGL: //有功功率 + byteBlock.Read(out byte[] b_YGGL, 4); + base._stringChange.SwapBytes(ref b_YGGL); + float f_YGGL = BitConverter.ToSingle(b_YGGL, 0); + + ValueIsNan(ref f_YGGL); + + dnbInstant.activePower = Convert.ToDecimal(f_YGGL / 1000); + break; + case CommParams.WGGL: //无功功率 + byteBlock.Read(out byte[] b_WGGL, 4); + base._stringChange.SwapBytes(ref b_WGGL); + float f_WGGL = BitConverter.ToSingle(b_WGGL, 0); + + ValueIsNan(ref f_WGGL); + + dnbInstant.reactivePower = (decimal) f_WGGL; + break; + case CommParams.ZXYGZ: //正向有功 + byteBlock.Read(out byte[] b_ZXYGZ, 4); + base._stringChange.SwapBytes(ref b_ZXYGZ); + float f_ZXYGZ = BitConverter.ToSingle(b_ZXYGZ, 0); + + ValueIsNan(ref f_ZXYGZ); + + dnbInstant.positiveActive = (decimal) f_ZXYGZ; + break; + + case CommParams.CJSJ: //采集时间 + byteBlock.Read(out byte[] b_CJSJ, 6); + string strDateTime = "20" + b_CJSJ[5].ToString("x2") + + "-" + b_CJSJ[4].ToString("x2") + + "-" + b_CJSJ[3].ToString("x2") + + " " + b_CJSJ[2].ToString("x2") + + ":" + b_CJSJ[1].ToString("x2") + + ":" + b_CJSJ[0].ToString("x2"); + dnbInstant.collectTime = Convert.ToDateTime(strDateTime); + break; + } + + } while (byteBlock.Pos % bodyLength != 0); + + dnbInstant.recordTime = DateTime.Now; + + var serializeObject = JsonConvert.SerializeObject(dnbInstant); + + _logger.Info($"第{i+1}个电表{dnbInstant.monitorId}解析完成:{serializeObject}"); + + DateTime? lastCollectTime = GetLastCollectTime(dnbInstant.monitorId); + if (lastCollectTime != null && DateTime.Now -lastCollectTime < TimeSpan.FromMinutes(_appConfig.electricTimeInterval)) + { + //时间间隔小于采集间隔,不保存 + continue; + } + + // 如果字典中已有该键,则更新;否则新增 + _lastCollectTimeDict[dnbInstant.monitorId] = DateTime.Now; + + result.Add(dnbInstant); + } + + if (result.Count > 0) + { + //是否开启 FF 异常值过滤 + if (_appConfig.virtualFlag) + { + ParamVerification(ref result); + } + + var inRes = _service.SplitInsert(result,out List insertIds); + _logger.Info($"{result.Count}个电表数据解析处理完成,数据保存{(inRes ? "成功" : "失败")}"); + } + else + { + _logger.Info($"{amount}个电表数据解析处理完成,没有需要保存的数据"); + } + return FilterResult.Success; + } + catch (Exception e) + { + base._logger.Error($"电能数据解析异常:{e.Message}"); + } + + return FilterResult.Cache; + } + + /// + /// FF FF参数过滤 + /// + /// + /// + private void ParamVerification(ref List dnbInstants) + { + if (dnbInstants == null) + { + throw new ArgumentNullException($"过滤参数方法异常,传入参数为空"); + } + + for (int i = dnbInstants.Count - 1; i >= 0; i--) + { + var item = dnbInstants[i]; + + if (item.positiveActive == _appConfig.virtualValue) + { + _logger.Info($"MonitorId:{item.monitorId},正向有功为 FF FF FF FF,已启用过滤不保存该表数据"); + dnbInstants.RemoveAt(i); + continue; + } + } + } + + private DateTime? GetLastCollectTime(string key) + { + // 检查键是否存在 + if (_lastCollectTimeDict.TryGetValue(key, out DateTime collectTime)) + { + return collectTime; + } + else + { + return null; + } + } + + /// + /// 应答处理 + /// + /// + /// + public override void ResponseHandle(ISocketClient client, byte[] buffer) + { + ResponsePack sendResponsePackInfo = new ResponsePack() + { + m_MessageType = 0xB3 + }; + base.GetMessagePack(ref sendResponsePackInfo, buffer); + + base.SendMessageAsync(client,sendResponsePackInfo); + } + } +} \ No newline at end of file diff --git a/Sln.Iot.Business/FluidBusiness.cs b/Sln.Iot.Business/FluidBusiness.cs new file mode 100644 index 0000000..d0974a2 --- /dev/null +++ b/Sln.Iot.Business/FluidBusiness.cs @@ -0,0 +1,276 @@ +#region << 版 本 注 释 >> + +/*-------------------------------------------------------------------- +* 版权所有 (c) 2025 WenJY 保留所有权利。 +* CLR版本:4.0.30319.42000 +* 机器名称:Mr.Wen's MacBook Pro +* 命名空间:Sln.Iot.Business +* 唯一标识:D5F7092D-7C5C-42B9-A373-1332CFD77169 +* +* 创建者:WenJY +* 电子邮箱: +* 创建时间:2025-05-20 14:45:41 +* 版本:V1.0.0 +* 描述: +* +*-------------------------------------------------------------------- +* 修改人: +* 时间: +* 修改说明: +* +* 版本:V1.0.0 +*--------------------------------------------------------------------*/ + +#endregion << 版 本 注 释 >> + +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using Sln.Iot.Business.@base; +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 Sln.Iot.Socket.Adapter; +using TouchSocket.Core; +using TouchSocket.Sockets; + +namespace Sln.Iot.Business +{ + public class FluidBusiness:BaseBusiness + { + private Dictionary _lastCollectTimeDict = new Dictionary(); + + private readonly IRecordFluidInstantService? _service; + + public FluidBusiness(SerilogHelper logger, AppConfig appConfig, StringChange stringChange, IRecordFluidInstantService? service) : base(logger, appConfig, stringChange) + { + _service = service; + } + + public override FilterResult BufferAnalysis(ISocketClient client, BufferRequestInfo requestInfo, int bodyLength) + { + ByteBlock byteBlock = new ByteBlock(requestInfo.Body); + + if (byteBlock.CanReadLen < 1) + { + return FilterResult.Cache; + } + int pos = byteBlock.Pos; + + try + { + List result = new List(); + + var amount = requestInfo.BufferLength / bodyLength; + + _logger.Info($"收到{amount}个流体数据,开始循环解析......"); + + for (int i = 0; i < amount; i++) + { + RecordFluidInstant fluidInstant = new RecordFluidInstant(); + + #region 表号解析 + byteBlock.Read(out byte[] b_MeterID, 2); + var decParts = b_MeterID.Select(b => $"{b:D2}").ToArray(); + var equipId = $"{requestInfo.ColletEquipCode}_{decParts[0]}{decParts[1]}"; + #endregion + + fluidInstant.monitorId = equipId; + + do + { + byteBlock.Read(out byte[] b_UA_flag, 2); + base._stringChange.ConvertBytesToUInt16(b_UA_flag, out uint flag); + switch (flag) + { + case CommParams.Press: //压力值 + byteBlock.Read(out byte[] b_Press, 4); + base._stringChange.SwapBytes(ref b_Press); + float f_Press = BitConverter.ToSingle(b_Press, 0); + + ValueIsNan(ref f_Press); + + fluidInstant.press = (decimal)f_Press; + break; + case CommParams.STemperature: //温度值 + byteBlock.Read(out byte[] b_Temperature, 4); + base._stringChange.SwapBytes(ref b_Temperature); + float f_Temperature = BitConverter.ToSingle(b_Temperature, 0); + + ValueIsNan(ref f_Temperature); + + fluidInstant.temperature = (decimal)f_Temperature; + break; + case CommParams.Frequency: //频率值 + byteBlock.Read(out byte[] b_Frequency, 4); + base._stringChange.SwapBytes(ref b_Frequency); + float f_Frequency = BitConverter.ToSingle(b_Frequency, 0); + + ValueIsNan(ref f_Frequency); + + fluidInstant.frequency = (decimal)f_Frequency; + break; + + case CommParams.Density: //密度值 + byteBlock.Read(out byte[] b_Density, 4); + base._stringChange.SwapBytes(ref b_Density); + float f_Density = BitConverter.ToSingle(b_Density, 0); + + ValueIsNan(ref f_Density); + + fluidInstant.density = (decimal)f_Density; + break; + + case CommParams.FluxInstantValue: //瞬时流值 + byteBlock.Read(out byte[] b_FluxInstantValue, 4); + base._stringChange.SwapBytes(ref b_FluxInstantValue); + float f_FluxInstantValue = BitConverter.ToSingle(b_FluxInstantValue, 0); + + ValueIsNan(ref f_FluxInstantValue); + + fluidInstant.instantFlow = (decimal)f_FluxInstantValue; + break; + case CommParams.FluxEyeableTotalValue: //累计流量值 + byteBlock.Read(out byte[] b_FluxEyeableTotalValue, 4); + base._stringChange.SwapBytes(ref b_FluxEyeableTotalValue); + float f_FluxEyeableTotalValue = BitConverter.ToSingle(b_FluxEyeableTotalValue, 0); + + ValueIsNan(ref f_FluxEyeableTotalValue); + + fluidInstant.totalFlow = (decimal)f_FluxEyeableTotalValue; + break; + case CommParams.HeatInstantValue: //瞬时热量 + byteBlock.Read(out byte[] b_HeatInstantValue, 4); + base._stringChange.SwapBytes(ref b_HeatInstantValue); + float f_HeatInstantValue = BitConverter.ToSingle(b_HeatInstantValue, 0); + + ValueIsNan(ref f_HeatInstantValue); + + fluidInstant.instantHeat = (decimal)f_HeatInstantValue; + break; + case CommParams.HeatToftalValue: //累计热量值 + byteBlock.Read(out byte[] b_HeatToftalValue, 4); + base._stringChange.SwapBytes(ref b_HeatToftalValue); + float f_HeatToftalValue = BitConverter.ToSingle(b_HeatToftalValue, 0); + + ValueIsNan(ref f_HeatToftalValue); + + fluidInstant.totalHeat = (decimal)f_HeatToftalValue; + break; + + case CommParams.CJSJ: //采集时间 + byteBlock.Read(out byte[] b_CJSJ, 6); + string strDateTime = "20" + b_CJSJ[5].ToString("x2") + + "-" + b_CJSJ[4].ToString("x2") + + "-" + b_CJSJ[3].ToString("x2") + + " " + b_CJSJ[2].ToString("x2") + + ":" + b_CJSJ[1].ToString("x2") + + ":" + b_CJSJ[0].ToString("x2"); + fluidInstant.collectTime = Convert.ToDateTime(strDateTime); + break; + } + + } while (byteBlock.Pos % bodyLength != 0); + + fluidInstant.recordTime = DateTime.Now; + + var serializeObject = JsonConvert.SerializeObject(fluidInstant); + + _logger.Info($"第{i+1}个流体仪表{fluidInstant.monitorId}解析完成:{serializeObject}"); + + DateTime? lastCollectTime = GetLastCollectTime(fluidInstant.monitorId); + if (lastCollectTime != null && DateTime.Now -lastCollectTime < TimeSpan.FromMinutes(_appConfig.fluidTimeInterval)) + { + //时间间隔小于采集间隔,不保存 + continue; + } + + // 如果字典中已有该键,则更新;否则新增 + _lastCollectTimeDict[fluidInstant.monitorId] = DateTime.Now; + + result.Add(fluidInstant); + } + + if (result.Count > 0) + { + //是否开启 FF 异常值过滤 + if (_appConfig.virtualFlag) + { + ParamVerification(ref result); + } + + var inRes = _service.SplitInsert(result,out List insertIds); + _logger.Info($"{result.Count}个流体数据解析处理完成,数据保存{(inRes ? "成功" : "失败")}"); + } + else + { + _logger.Info($"{amount}个流体数据解析处理完成,没有需要保存的数据"); + } + return FilterResult.Success; + } + catch (Exception e) + { + base._logger.Error($"流体数据解析异常:{e.Message}"); + } + + return FilterResult.Cache; + } + + /// + /// FF FF参数过滤 + /// + /// + /// + private void ParamVerification(ref List fluidInstants) + { + if (fluidInstants == null) + { + throw new ArgumentNullException($"过滤参数方法异常,传入参数为空"); + } + + for (int i = fluidInstants.Count - 1; i >= 0; i--) + { + var item = fluidInstants[i]; + + if (item.totalFlow == _appConfig.virtualValue) + { + _logger.Info($"MonitorId:{item.monitorId},累计流量为 FF FF FF FF,已启用过滤不保存该表数据"); + fluidInstants.RemoveAt(i); + continue; + } + } + } + + private DateTime? GetLastCollectTime(string key) + { + // 检查键是否存在 + if (_lastCollectTimeDict.TryGetValue(key, out DateTime collectTime)) + { + return collectTime; + } + else + { + return null; + } + } + + /// + /// 应答处理 + /// + /// + /// + public override void ResponseHandle(ISocketClient client, byte[] buffer) + { + ResponsePack sendResponsePackInfo = new ResponsePack() + { + m_MessageType = 0xB4 + }; + base.GetMessagePack(ref sendResponsePackInfo, buffer); + base.SendMessageAsync(client, sendResponsePackInfo); + } + } +} \ No newline at end of file diff --git a/Sln.Iot.Business/HeartBusiness.cs b/Sln.Iot.Business/HeartBusiness.cs new file mode 100644 index 0000000..696fbd2 --- /dev/null +++ b/Sln.Iot.Business/HeartBusiness.cs @@ -0,0 +1,79 @@ +#region << 版 本 注 释 >> + +/*-------------------------------------------------------------------- +* 版权所有 (c) 2025 WenJY 保留所有权利。 +* CLR版本:4.0.30319.42000 +* 机器名称:Mr.Wen's MacBook Pro +* 命名空间:Sln.Iot.Business +* 唯一标识:AF7CD4B2-87AF-4F91-BECA-13779B05170C +* +* 创建者:WenJY +* 电子邮箱: +* 创建时间:2025-04-11 16:04:07 +* 版本:V1.0.0 +* 描述: +* +*-------------------------------------------------------------------- +* 修改人: +* 时间: +* 修改说明: +* +* 版本:V1.0.0 +*--------------------------------------------------------------------*/ + +#endregion << 版 本 注 释 >> + +using Sln.Iot.Business.@base; +using Sln.Iot.Common; +using Sln.Iot.Config; +using Sln.Iot.Model.dto; +using Sln.Iot.Serilog; +using Sln.Iot.Socket.Adapter; +using TouchSocket.Core; +using TouchSocket.Sockets; + +namespace Sln.Iot.Business +{ + /// + /// 心跳指令 + /// + public class HeartBusiness:BaseBusiness + { + public HeartBusiness(SerilogHelper logger, AppConfig appConfig,StringChange stringChange) : base(logger, appConfig,stringChange) + { + } + + public override FilterResult BufferAnalysis(ISocketClient client, BufferRequestInfo requestInfo, int bodyLength) + { + //心跳没有业务逻辑处理 + return FilterResult.Success; + } + + /// + /// 应答处理 + /// + /// + /// + public override void ResponseHandle(ISocketClient client, byte[] buffer) + { + + ResponsePack sendResponsePackInfo = new ResponsePack() + { + m_MessageType = 0xA4 + }; + base.GetMessagePack(ref sendResponsePackInfo,buffer); + + //ByteBlock byteBlock = new ByteBlock(requestInfo.Body); + //byteBlock.Read(out byte[] b_MeterID, 2); + //var MeterID_1 = "00" + Convert.ToInt32(b_MeterID[0]).ToString(); + //MeterID_1 = MeterID_1.Substring(MeterID_1.Length - 2, 2); + //var MeterID_2 = "00" + Convert.ToInt32(b_MeterID[1]).ToString(); + //MeterID_2 = MeterID_2.Substring(MeterID_2.Length - 2, 2); + //var equipId = requestInfo.ColletEquipCOde + "_" + MeterID_1 + MeterID_2; + + //Console.WriteLine($"心跳:::::{}"); + + base.SendMessageAsync(client, sendResponsePackInfo); + } + } +} \ No newline at end of file diff --git a/Sln.Iot.Business/IotEnvBusiness.cs b/Sln.Iot.Business/IotEnvBusiness.cs new file mode 100644 index 0000000..06c62d4 --- /dev/null +++ b/Sln.Iot.Business/IotEnvBusiness.cs @@ -0,0 +1,309 @@ +#region << 版 本 注 释 >> + +/*-------------------------------------------------------------------- +* 版权所有 (c) 2025 WenJY 保留所有权利。 +* CLR版本:4.0.30319.42000 +* 机器名称:Mr.Wen's MacBook Pro +* 命名空间:Sln.Iot.Business +* 唯一标识:7C26094C-5352-4997-866A-FA618F2E5D27 +* +* 创建者:WenJY +* 电子邮箱: +* 创建时间:2025-04-11 15:47:06 +* 版本:V1.0.0 +* 描述: +* +*-------------------------------------------------------------------- +* 修改人: +* 时间: +* 修改说明: +* +* 版本:V1.0.0 +*--------------------------------------------------------------------*/ + +#endregion << 版 本 注 释 >> + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Sln.Iot.Business.@base; +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 Sln.Iot.Socket.Adapter; +using TouchSocket.Core; +using TouchSocket.Sockets; + +namespace Sln.Iot.Business +{ + /// + /// 物联网环境:温度、湿度、照度、噪音、振动 + /// + public class IotEnvBusiness:BaseBusiness + { + private readonly IRecordIotEnvInstantService? _service; + + public IotEnvBusiness(SerilogHelper logger, AppConfig appConfig, StringChange stringChange, IRecordIotEnvInstantService? service) : base(logger, appConfig, stringChange) + { + _service = service; + } + + public override FilterResult BufferAnalysis(ISocketClient client, BufferRequestInfo requestInfo, int bodyLength) + { + ByteBlock byteBlock = new ByteBlock(requestInfo.Body); + + if (byteBlock.CanReadLen < 1) + { + return FilterResult.Cache; + } + int pos = byteBlock.Pos; + + try + { + List result = new List(); + + var amount = requestInfo.BufferLength / bodyLength; + + _logger.Info($"收到{amount}个物联网环境数据,开始循环解析......"); + + for (int i = 0; i < amount; i++) + { + RecordIotEnvInstant iotEnvInstant = new RecordIotEnvInstant(); + + #region 表号解析 Add By Wenjy 2024-04-18 + + // byteBlock.Read(out byte[] b_MeterID, 2); + // var MeterID_1 = "00" + Convert.ToInt32(b_MeterID[0]).ToString(); + // MeterID_1 = MeterID_1.Substring(MeterID_1.Length - 2, 2); + // var MeterID_2 = "00" + Convert.ToInt32(b_MeterID[1]).ToString(); + // MeterID_2 = MeterID_2.Substring(MeterID_2.Length - 2, 2); + // var equipId = requestInfo.ColletEquipCode + "_" + MeterID_1 + MeterID_2; + + #endregion + + #region 表号解析 + byteBlock.Read(out byte[] b_MeterID, 2); + var decParts = b_MeterID.Select(b => $"{b:D2}").ToArray(); + var equipId = $"{requestInfo.ColletEquipCode}_{decParts[0]}{decParts[1]}"; + #endregion + + iotEnvInstant.monitorId = equipId; + + #region 物联网参数解析 Edit By Wenjy 2025-05-07 修改 Nan 值过滤 + + do + { + byteBlock.Read(out byte[] b_UA_flag, 2); + base._stringChange.ConvertBytesToUInt16(b_UA_flag, out uint flag); + switch (flag) + { + case CommParams.TTempreture: //温度 + byteBlock.Read(out byte[] tempreture, 4); + base._stringChange.SwapBytes(ref tempreture); + float f_tempreture = BitConverter.ToSingle(tempreture, 0); + + ValueIsNan(ref f_tempreture); + + iotEnvInstant.temperature = (decimal) f_tempreture; + break; + + case CommParams.Humidity: //湿度 + byteBlock.Read(out byte[] humidity, 4); + base._stringChange.SwapBytes(ref humidity); + float f_humidity = BitConverter.ToSingle(humidity, 0); + + ValueIsNan(ref f_humidity); + + iotEnvInstant.humidity = (decimal) f_humidity; + break; + + case CommParams.Noise: //噪音 + byteBlock.Read(out byte[] noise, 4); + base._stringChange.SwapBytes(ref noise); + float f_noise = BitConverter.ToSingle(noise, 0); + + ValueIsNan(ref f_noise); + + iotEnvInstant.noise = (decimal) f_noise; + break; + + case CommParams.VibrationSpeed: //振动-速度 + byteBlock.Read(out byte[] vibrationSpeed, 4); + base._stringChange.SwapBytes(ref vibrationSpeed); + float f_vibrationSpeed = BitConverter.ToSingle(vibrationSpeed, 0); + + ValueIsNan(ref 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; + break; + case CommParams.VibrationAcceleration: //振动-加速度 + byteBlock.Read(out byte[] vibrationAcceleration, 4); + base._stringChange.SwapBytes(ref vibrationAcceleration); + float f_vibrationAcceleration = BitConverter.ToSingle(vibrationAcceleration, 0); + + ValueIsNan(ref f_vibrationAcceleration); + + iotEnvInstant.VibrationAcceleration = (decimal)f_vibrationAcceleration; + break; + case CommParams.VibrationTemp: //振动-温度 + byteBlock.Read(out byte[] vibrationTemp, 4); + base._stringChange.SwapBytes(ref vibrationTemp); + float f_vibrationTemp = BitConverter.ToSingle(vibrationTemp, 0); + + ValueIsNan(ref f_vibrationTemp); + + iotEnvInstant.VibrationTemp = (decimal)f_vibrationTemp; + break; + case CommParams.CJSJ: + byteBlock.Read(out byte[] b_CJSJ, 6); + string strDateTime = "20" + b_CJSJ[5].ToString("x2") + + "-" + b_CJSJ[4].ToString("x2") + + "-" + b_CJSJ[3].ToString("x2") + + " " + b_CJSJ[2].ToString("x2") + + ":" + b_CJSJ[1].ToString("x2") + + ":" + b_CJSJ[0].ToString("x2"); + iotEnvInstant.collectTime = Convert.ToDateTime(strDateTime); + break; + } + + } while (byteBlock.Pos % bodyLength != 0); + + #endregion + + iotEnvInstant.recordTime = DateTime.Now; + + var serializeObject = JsonConvert.SerializeObject(iotEnvInstant); + + _logger.Info($"第{i+1}个物联网表{iotEnvInstant.monitorId}解析完成:{serializeObject}"); + + result.Add(iotEnvInstant); + + } + + if (result.Count > 0) + { + + //是否开启 FF 异常值过滤 + if (_appConfig.virtualFlag) + { + ParamVerification(ref result); + } + + var inRes = _service.SplitInsert(result,out List insertIds); + + _logger.Info($"{amount}个物联网数据解析处理完成,保存{result.Count}个物联网数据,保存{(inRes ? "成功" : "失败")}"); + } + else + { + _logger.Info($"{amount}个物联网数据解析处理完成,没有需要保存的数据"); + } + + return FilterResult.Success; + } + catch (Exception e) + { + base._logger.Error($"物联网数据解析异常:{e.Message}"); + } + + return FilterResult.Cache; + } + + /// + /// 回复指令 + /// + /// + /// + public override void ResponseHandle(ISocketClient client, byte[] buffer) + { + ResponsePack sendResponsePackInfo = new ResponsePack() + { + m_MessageType = 0xB5 + }; + base.GetMessagePack(ref sendResponsePackInfo, buffer); + + base.SendMessageAsync(client,sendResponsePackInfo); + } + + /// + /// FF FF参数过滤 + /// + /// + /// + private void ParamVerification(ref List iotEnvInstants) + { + if (iotEnvInstants == null) + { + throw new ArgumentNullException($"过滤参数方法异常,传入参数为空"); + } + + for (int i = iotEnvInstants.Count - 1; i >= 0; i--) + { + var item = iotEnvInstants[i]; + + if (item.temperature == _appConfig.virtualValue) + { + _logger.Info($"MonitorId:{item.monitorId},温度值为 FF FF FF FF,已启用过滤不保存该表数据"); + iotEnvInstants.RemoveAt(i); + continue; + } + + if (item.humidity == _appConfig.virtualValue) + { + _logger.Info($"MonitorId:{item.monitorId},湿度值为 FF FF FF FF,已启用过滤不保存该表数据"); + iotEnvInstants.RemoveAt(i); + continue; + } + + if (item.noise == _appConfig.virtualValue) + { + _logger.Info($"MonitorId:{item.monitorId},噪音值为 FF FF FF FF,已启用过滤不保存该表数据"); + iotEnvInstants.RemoveAt(i); + continue; + } + + if (item.VibrationSpeed == _appConfig.virtualValue) + { + _logger.Info($"MonitorId:{item.monitorId},振动速度值为 FF FF FF FF,已启用过滤不保存该表数据"); + iotEnvInstants.RemoveAt(i); + continue; + } + + if (item.VibrationDisplacement == _appConfig.virtualValue) + { + _logger.Info($"MonitorId:{item.monitorId},振动位移值为 FF FF FF FF,已启用过滤不保存该表数据"); + iotEnvInstants.RemoveAt(i); + continue; + } + + if (item.VibrationAcceleration == _appConfig.virtualValue) + { + _logger.Info($"MonitorId:{item.monitorId},振动加速度值为 FF FF FF FF,已启用过滤不保存该表数据"); + iotEnvInstants.RemoveAt(i); + continue; + } + + if (item.VibrationTemp == _appConfig.virtualValue) + { + _logger.Info($"MonitorId:{item.monitorId},振动温度值为 FF FF FF FF,已启用过滤不保存该表数据"); + iotEnvInstants.RemoveAt(i); + continue; + } + } + } + } +} \ No newline at end of file diff --git a/Sln.Iot.Business/LoginBusiness.cs b/Sln.Iot.Business/LoginBusiness.cs new file mode 100644 index 0000000..b6a7bbd --- /dev/null +++ b/Sln.Iot.Business/LoginBusiness.cs @@ -0,0 +1,98 @@ +#region << 版 本 注 释 >> + +/*-------------------------------------------------------------------- +* 版权所有 (c) 2025 WenJY 保留所有权利。 +* CLR版本:4.0.30319.42000 +* 机器名称:Mr.Wen's MacBook Pro +* 命名空间:Sln.Iot.Business +* 唯一标识:71034873-2FF6-4081-AC87-DFCCCCCF51F2 +* +* 创建者:WenJY +* 电子邮箱: +* 创建时间:2025-04-11 16:03:28 +* 版本:V1.0.0 +* 描述: +* +*-------------------------------------------------------------------- +* 修改人: +* 时间: +* 修改说明: +* +* 版本:V1.0.0 +*--------------------------------------------------------------------*/ + +#endregion << 版 本 注 释 >> + +using System; +using Microsoft.Extensions.Logging; +using Sln.Iot.Business.@base; +using Sln.Iot.Common; +using Sln.Iot.Config; +using Sln.Iot.Model.dto; +using Sln.Iot.Serilog; +using Sln.Iot.Socket.Adapter; +using TouchSocket.Core; +using TouchSocket.Sockets; + +namespace Sln.Iot.Business +{ + /// + /// 登录指令 + /// + public class LoginBusiness:BaseBusiness + { + public LoginBusiness(SerilogHelper logger, AppConfig appConfig,StringChange stringChange) : base(logger, appConfig,stringChange) + { + } + + /// + /// 指令解析 + /// + /// + /// + /// + /// + public override FilterResult BufferAnalysis(ISocketClient client, BufferRequestInfo requestInfo, int bodyLength) + { + var flag = ""; + byte[] bDeviceType = new byte[1]; + byte[] bDeviceID = new byte[2]; + Array.Copy(requestInfo.buffer, 1, bDeviceType, 0, 1); + Array.Copy(requestInfo.buffer, 2, bDeviceID, 0, 2); + + flag = base._stringChange.ConverToString(bDeviceType); + flag += base._stringChange.ConverToString(bDeviceID); + + string clientIdStr = flag.ToString(); + + if (clientIdStr.Contains("45")) + { + if (client.Id != clientIdStr) + { + client.ResetId(clientIdStr); + + //更新客户端状态 + } + } + + return FilterResult.Success; + + } + + /// + /// 应答处理 + /// + /// + /// + public override void ResponseHandle(ISocketClient client, byte[] buffer) + { + ResponsePack sendResponsePackInfo = new ResponsePack() + { + m_MessageType = 0xA1 + }; + base.GetMessagePack(ref sendResponsePackInfo,buffer); + + base.SendMessageAsync(client, sendResponsePackInfo); + } + } +} \ No newline at end of file diff --git a/Sln.Iot.Business/Sln.Iot.Business.csproj b/Sln.Iot.Business/Sln.Iot.Business.csproj new file mode 100644 index 0000000..a1e49db --- /dev/null +++ b/Sln.Iot.Business/Sln.Iot.Business.csproj @@ -0,0 +1,17 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + diff --git a/Sln.Iot.Business/base/BaseBusiness.cs b/Sln.Iot.Business/base/BaseBusiness.cs new file mode 100644 index 0000000..dcdc18c --- /dev/null +++ b/Sln.Iot.Business/base/BaseBusiness.cs @@ -0,0 +1,151 @@ +#region << 版 本 注 释 >> + +/*-------------------------------------------------------------------- +* 版权所有 (c) 2025 WenJY 保留所有权利。 +* CLR版本:4.0.30319.42000 +* 机器名称:Mr.Wen's MacBook Pro +* 命名空间:Sln.Iot.Business.base +* 唯一标识:AA8CCDD9-6D6B-47B7-8309-5C9D41FFC822 +* +* 创建者:WenJY +* 电子邮箱: +* 创建时间:2025-04-11 15:04:43 +* 版本:V1.0.0 +* 描述: +* +*-------------------------------------------------------------------- +* 修改人: +* 时间: +* 修改说明: +* +* 版本:V1.0.0 +*--------------------------------------------------------------------*/ + +#endregion << 版 本 注 释 >> + +using Sln.Iot.Common; +using Sln.Iot.Config; +using Sln.Iot.Model.dto; +using Sln.Iot.Serilog; +using Sln.Iot.Socket.Adapter; +using TouchSocket.Core; +using TouchSocket.Sockets; + +namespace Sln.Iot.Business.@base +{ + public abstract class BaseBusiness + { + public SerilogHelper _logger; + + public AppConfig _appConfig; + + public StringChange _stringChange; + + public BaseBusiness(SerilogHelper logger,AppConfig appConfig,StringChange stringChange) + { + _logger = logger; + _appConfig = appConfig; + _stringChange = stringChange; + } + + /// + /// 指令解析方法 + /// + /// + /// + public abstract FilterResult BufferAnalysis(ISocketClient client,BufferRequestInfo requestInfo,int bodyLength); + + /// + /// 应答响应 + /// + /// + /// + public abstract void ResponseHandle(ISocketClient client, byte[] buffer); + + /// + /// 封装回复指令 + /// + /// + /// + public void GetMessagePack(ref ResponsePack SendMessagePackInfo,byte[] buffer) + { + SendMessagePackInfo.m_EnergyType = buffer[1]; + Array.Copy(buffer, 2, SendMessagePackInfo.m_Meteraddr, 0, 2); + Array.Copy(buffer, 4, SendMessagePackInfo.m_Msta, 0, 2); + SendMessagePackInfo.m_StartFlag = buffer[6]; + } + + /// + /// 发送接收成功指令 + /// + /// + /// + /// + public bool SendMessageAsync(ISocketClient client, ResponsePack pMessagePack,byte[] buffer = null) + { + ushort num = 0; + try + { + byte[] SendBuffer = new byte[12]; + + if (buffer != null) + { + SendBuffer = new byte[12 + buffer.Length]; + } + SendBuffer[num] = pMessagePack.m_BeginChar; + num = (ushort)(num + 1); + SendBuffer[num] = pMessagePack.m_EnergyType; + num = (ushort)(num + 1); + Array.Copy(pMessagePack.m_Meteraddr, 0, SendBuffer, num, pMessagePack.m_Meteraddr.Length); + num = (ushort)(num + 2); + Array.Copy(pMessagePack.m_Msta, 0, SendBuffer, num, pMessagePack.m_Msta.Length); + num = (ushort)(num + 2); + SendBuffer[num] = pMessagePack.m_StartFlag; + num = (ushort)(num + 1); + SendBuffer[num] = pMessagePack.m_MessageType; + num = (ushort)(num + 1); + Array.Copy(pMessagePack.m_PackLen, 0, SendBuffer, num, pMessagePack.m_PackLen.Length); + num = (ushort)(num + 2); + + if (buffer != null) + { + Array.Copy(buffer, 0, SendBuffer, num, buffer.Length); + num = (ushort)(num + buffer.Length); + } + + pMessagePack.m_Verify = _stringChange.CalculateVerifyToArray(SendBuffer, SendBuffer.Length - 1)[0]; + SendBuffer[num] = pMessagePack.m_Verify; + num = (ushort)(num + 1); + SendBuffer[num] = pMessagePack.m_EndChar; + _logger.Info($"向客户端:{client.Id};地址:{client.GetIPPort()};发送终端消息:{_stringChange.bytesToHexStr(SendBuffer, SendBuffer.Length)}"); + + client.SendAsync(SendBuffer); + return true; + } + catch (Exception ex) + { + _logger.Error($"SendMessageToClient异常:{ex.Message}"); + return false; + } + } + + /// + /// Nan 值处理 + /// + /// + public void ValueIsNan(ref float value) + { + if (double.IsNaN(value)) + { + if (_appConfig.virtualFlag) + { + value = _appConfig.virtualValue; + } + else + { + value = 0; + } + } + } + } +} \ No newline at end of file diff --git a/Sln.Iot.Common/Class1.cs b/Sln.Iot.Common/Class1.cs new file mode 100644 index 0000000..6644cec --- /dev/null +++ b/Sln.Iot.Common/Class1.cs @@ -0,0 +1,5 @@ +namespace Sln.Iot.Common; + +public class Class1 +{ +} \ No newline at end of file diff --git a/Sln.Iot.Common/Sln.Iot.Common.csproj b/Sln.Iot.Common/Sln.Iot.Common.csproj new file mode 100644 index 0000000..9af301d --- /dev/null +++ b/Sln.Iot.Common/Sln.Iot.Common.csproj @@ -0,0 +1,13 @@ + + + + net6.0 + enable + enable + + + + + + + diff --git a/Sln.Iot.Common/StringChange.cs b/Sln.Iot.Common/StringChange.cs new file mode 100644 index 0000000..962a0b9 --- /dev/null +++ b/Sln.Iot.Common/StringChange.cs @@ -0,0 +1,320 @@ +#region << 版 本 注 释 >> + +/*-------------------------------------------------------------------- +* 版权所有 (c) 2025 WenJY 保留所有权利。 +* CLR版本:4.0.30319.42000 +* 机器名称:Mr.Wen's MacBook Pro +* 命名空间:Sln.Iot.Common +* 唯一标识:78FCCE90-A4C0-4DCC-AB23-7C21EC7ECE05 +* +* 创建者:WenJY +* 电子邮箱: +* 创建时间:2025-04-11 15:06:46 +* 版本:V1.0.0 +* 描述: +* +*-------------------------------------------------------------------- +* 修改人: +* 时间: +* 修改说明: +* +* 版本:V1.0.0 +*--------------------------------------------------------------------*/ + +#endregion << 版 本 注 释 >> + +using System; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Text; + +namespace Sln.Iot.Common +{ + public class StringChange + { + /// + /// 将字符串强制转换成int,转换失败则返回0 + /// + /// + /// + public int ParseToInt(string str) + { + int returnInt = 0; + if (str == null || str.Trim().Length < 1) + { + return returnInt; + } + if (int.TryParse(str, out returnInt)) + { + return returnInt; + } + else + { + return 0; + } + } + + /// + /// char数组转Array + /// + /// + /// + /// + public string CharArrayToString(char[] cha, int len) + { + string str = ""; + for (int i = 0; i < len; i++) + { + str += string.Format("{0}", cha[i]); + } + + return str; + } + + + + public byte[] HexStrTorbytes(string strHex)//e.g. " 01 01" ---> { 0x01, 0x01} + { + strHex = strHex.Replace(" ", ""); + if ((strHex.Length % 2) != 0) + strHex += " "; + byte[] returnBytes = new byte[strHex.Length / 2]; + for (int i = 0; i < returnBytes.Length; i++) + returnBytes[i] = Convert.ToByte(strHex.Substring(i * 2, 2), 16); + return returnBytes; + } + + public string StringToHexString(string s, Encoding encode) + { + byte[] b = encode.GetBytes(s); //按照指定编码将string编程字节数组 + string result = string.Empty; + for (int i = 0; i < b.Length; i++) //逐字节变为16进制字符,以%隔开 + { + result += "%" + Convert.ToString(b[i], 16); + } + return result; + } + + public string HexStringToString(string hs, Encoding encode) + { + //以%分割字符串,并去掉空字符 + string[] chars = hs.Split(new char[] { '%' }, StringSplitOptions.RemoveEmptyEntries); + byte[] b = new byte[chars.Length]; + //逐个字符变为16进制字节数据 + for (int i = 0; i < chars.Length; i++) + { + b[i] = Convert.ToByte(chars[i], 16); + } + //按照指定编码将字节数组变为字符串 + return encode.GetString(b); + } + + public byte[] Swap16Bytes(byte[] OldU16) + { + byte[] ReturnBytes = new byte[2]; + ReturnBytes[1] = OldU16[0]; + ReturnBytes[0] = OldU16[1]; + return ReturnBytes; + } + + + /// 64Base码 + /// 保存路径 + /// 文件名称 + /// + public bool Base64ToImage(string strbase64, string path, string filename) + { + bool Flag = false; + try + { + //base64编码的文本 转为 图片 + //图片名称 + byte[] arr = Convert.FromBase64String(strbase64);//将指定的字符串(它将二进制数据编码为 Base64 数字)转换为等效的 8 位无符号整数数组。 + using (MemoryStream ms = new MemoryStream(arr)) + { + Bitmap bmp = new Bitmap(ms);//加载图像 + if (!Directory.Exists(path))//判断保存目录是否存在 + { + Directory.CreateDirectory(path); + } + bmp.Save((path + "\\" + filename + ".png"), System.Drawing.Imaging.ImageFormat.Png);//将图片以JPEG格式保存在指定目录(可以选择其他图片格式) + ms.Close();//关闭流并释放 + if (File.Exists(path + "\\" + filename + ".png"))//判断是否存在 + { + Flag = true; + } + } + } + catch (Exception ex) + { + Console.WriteLine("图片保存失败:" + ex.Message); + } + return Flag; + } + + /// + /// 获取时间戳 + /// + /// + public long GetTimeStamp() + { + TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0); + return Convert.ToInt64(ts.TotalSeconds); + } + + public byte[] ConvertFloatToINt(byte[] floatBytes) + { + byte[] intBytes = new byte[floatBytes.Length / 2]; + for (int i = 0; i < intBytes.Length; i++) + { + intBytes[i] = floatBytes[i * 2]; + } + return intBytes; + } + + //CRC异或校验 + public byte CalculateVerify(byte[] pMessage, int iLength) + { + UInt16 i; + byte iVerify = 0; + + iVerify = pMessage[0]; + for (i = 1; i < iLength; i++) + { + iVerify = (byte)(iVerify ^ pMessage[i]); + } + return iVerify; + } + + public int HexStringToNegative(string strNumber) + { + + int iNegate = 0; + int iNumber = Convert.ToInt32(strNumber, 16); + if (iNumber > 127) + { + int iComplement = iNumber - 1; + string strNegate = string.Empty; + char[] binchar = Convert.ToString(iComplement, 2).PadLeft(8, '0').ToArray(); + foreach (char ch in binchar) + { + if (Convert.ToInt32(ch) == 48) + { + strNegate += "1"; + } + else + { + strNegate += "0"; + } + } + iNegate = -Convert.ToInt32(strNegate, 2); + } + return iNegate; + } + + + /// + /// Byte[] 转 uint16 + /// + /// + /// + /// + public void ConvertBytesToUInt16(byte[] buffer,out uint falg) + { + if (buffer == null || buffer.Length < 2) + { + throw new ArgumentException("Input array length must be at least 2."); + } + + var input = buffer.Reverse().ToArray(); + + falg = (uint) ((input[1] << 8) | input[0]); + } + + /// + /// Byte[] 移位转换 + /// + /// + /// + public void SwapBytes(ref byte[] input) + { + if (input == null || input.Length % 2 != 0) + { + throw new ArgumentException("Input array length must be a multiple of 2."); + } + + byte[] result = new byte[input.Length]; + + for (int j = 0; j < input.Length; j += 2) + { + ushort swapped = (ushort)((input[j + 1] << 8) | input[j]); + result[j] = (byte)(swapped >> 8); + result[j + 1] = (byte)swapped; + } + input = result; + } + + /// + /// Byte[] 转string + /// + /// + /// + public string ConverToString(byte[] data) + { + string str; + StringBuilder stb = new StringBuilder(); + for (int i = 0; i < data.Length; i++) + { + if ((int)data[i] > 15) + { + stb.Append(Convert.ToString(data[i], 16).ToUpper()); //添加字符串 + } + else //如果是小于0F需要加个零 + { + stb.Append("0" + Convert.ToString(data[i], 16).ToUpper()); + } + } + str = stb.ToString(); + return str; + } + + /// + /// Byte[] 转 Hex + /// + /// + /// + /// + public string bytesToHexStr(byte[] bytes, int iLen) + { + StringBuilder sb = new StringBuilder(); + if (bytes != null) + { + for (int i = 0; i < iLen; i++) + { + sb.Append(bytes[i].ToString("X2")); + } + } + return sb.ToString(); + } + + /// + /// 校验计算 + /// + /// + /// + /// + public byte[] CalculateVerifyToArray(byte[] pMessage, int iLength) + { + UInt16 i; + int iVerify = 0; + + iVerify = pMessage[0]; + for (i = 0; i < iLength - 1; i++) + { + iVerify = iVerify + pMessage[i + 1]; + } + return BitConverter.GetBytes(Convert.ToUInt16(iVerify)); + } + } +} \ No newline at end of file diff --git a/Sln.Iot.Config/AppConfig.cs b/Sln.Iot.Config/AppConfig.cs new file mode 100644 index 0000000..3ea6d25 --- /dev/null +++ b/Sln.Iot.Config/AppConfig.cs @@ -0,0 +1,75 @@ +#region << 版 本 注 释 >> + +/*-------------------------------------------------------------------- +* 版权所有 (c) 2025 WenJY 保留所有权利。 +* CLR版本:4.0.30319.42000 +* 机器名称:Mr.Wen's MacBook Pro +* 命名空间:Sln.Iot.Config +* 唯一标识:132376EA-435B-4340-B8F3-44D61AAE1E99 +* +* 创建者:WenJY +* 电子邮箱: +* 创建时间:2025-04-11 10:54:31 +* 版本:V1.0.0 +* 描述: +* +*-------------------------------------------------------------------- +* 修改人: +* 时间: +* 修改说明: +* +* 版本:V1.0.0 +*--------------------------------------------------------------------*/ + +#endregion << 版 本 注 释 >> + +using System.Collections.Generic; +using Microsoft.Extensions.Options; + +namespace Sln.Iot.Config +{ + public class AppConfig: IOptions + { + /// + /// 日志文件路径 + /// + public string logPath { get; set; } + + /// + /// 监听端口 + /// + public int listernPort { get; set; } + + /// + /// 虚拟值,用于过滤FFFFFF + /// + public long virtualValue { get; set; } + + /// + /// 是否启用虚拟值 + /// + public bool virtualFlag { get; set; } + + /// + /// 电力数据采集间隔 + /// + public int electricTimeInterval { get; set; } + + /// + /// 流体数据采集间隔 + /// + public int fluidTimeInterval { get; set; } + + /// + /// Sql连接配置 + /// + public List sqlConfig { get; set; } + + /// + /// Redis配置 + /// + public string redisConfig { get; set; } + + public AppConfig Value => this; + } +} \ No newline at end of file diff --git a/Sln.Iot.Config/Sln.Iot.Config.csproj b/Sln.Iot.Config/Sln.Iot.Config.csproj new file mode 100644 index 0000000..2a5999f --- /dev/null +++ b/Sln.Iot.Config/Sln.Iot.Config.csproj @@ -0,0 +1,13 @@ + + + + net6.0 + enable + enable + + + + + + + diff --git a/Sln.Iot.Config/SqlConfig.cs b/Sln.Iot.Config/SqlConfig.cs new file mode 100644 index 0000000..33ba963 --- /dev/null +++ b/Sln.Iot.Config/SqlConfig.cs @@ -0,0 +1,50 @@ +#region << 版 本 注 释 >> + +/*-------------------------------------------------------------------- +* 版权所有 (c) 2025 WenJY 保留所有权利。 +* CLR版本:4.0.30319.42000 +* 机器名称:Mr.Wen's MacBook Pro +* 命名空间:Sln.Iot.Config +* 唯一标识:E59D09B8-4750-4486-BB59-5983CD7FB394 +* +* 创建者:WenJY +* 电子邮箱: +* 创建时间:2025-04-11 13:40:21 +* 版本:V1.0.0 +* 描述: +* +*-------------------------------------------------------------------- +* 修改人: +* 时间: +* 修改说明: +* +* 版本:V1.0.0 +*--------------------------------------------------------------------*/ + +#endregion << 版 本 注 释 >> + +namespace Sln.Iot.Config +{ + public class SqlConfig + { + /// + /// Sql 配置ID,实体通过该ID关联数据源 + /// + public string configId { get; set; } + + /// + /// 数据库类型,MySql-0;SqlServer-1;Sqlite-2;Oracle-3 + /// + public int dbType { get; set; } + + /// + /// 是否启用:true-是;false-否 + /// + public bool isFlag{get;set;} + + /// + /// 连接字符串 + /// + public string connStr { get; set; } + } +} \ No newline at end of file diff --git a/Sln.Iot.Model/Class1.cs b/Sln.Iot.Model/Class1.cs new file mode 100644 index 0000000..636c8d9 --- /dev/null +++ b/Sln.Iot.Model/Class1.cs @@ -0,0 +1,5 @@ +namespace Sln.Iot.Model; + +public class Class1 +{ +} \ No newline at end of file diff --git a/Sln.Iot.Model/Sln.Iot.Model.csproj b/Sln.Iot.Model/Sln.Iot.Model.csproj new file mode 100644 index 0000000..9c85367 --- /dev/null +++ b/Sln.Iot.Model/Sln.Iot.Model.csproj @@ -0,0 +1,13 @@ + + + + net6.0 + enable + enable + + + + + + + diff --git a/Sln.Iot.Model/dao/BaseMonitorInfo.cs b/Sln.Iot.Model/dao/BaseMonitorInfo.cs new file mode 100644 index 0000000..41ce6e3 --- /dev/null +++ b/Sln.Iot.Model/dao/BaseMonitorInfo.cs @@ -0,0 +1,209 @@ +#region << 版 本 注 释 >> + +/*-------------------------------------------------------------------- +* 版权所有 (c) 2025 WenJY 保留所有权利。 +* CLR版本:4.0.30319.42000 +* 机器名称:Mr.Wen's MacBook Pro +* 命名空间:Sln.Iot.Model.dao +* 唯一标识:74702D18-55B3-4AA3-91EB-E02ADC9AEBC8 +* +* 创建者:WenJY +* 电子邮箱: +* 创建时间:2025-04-11 13:23:39 +* 版本:V1.0.0 +* 描述: +* +*-------------------------------------------------------------------- +* 修改人: +* 时间: +* 修改说明: +* +* 版本:V1.0.0 +*--------------------------------------------------------------------*/ + +#endregion << 版 本 注 释 >> + +using System; +using SqlSugar; + +namespace Sln.Iot.Model.dao +{ + /// + ///计量设备信息 + /// + [SugarTable("ems_base_monitor_info"), TenantAttribute("tao_iot")] + public partial class BaseMonitorInfo + { + public BaseMonitorInfo(){ + + + } + /// + /// Desc:自增标识 + /// Default: + /// Nullable:False + /// + [SugarColumn(IsPrimaryKey=true,IsIdentity=true,ColumnName="obj_id")] + public int objid {get;set;} + + /// + /// Desc:父级编号 + /// Default: + /// Nullable:True + /// + [SugarColumn(ColumnName="parent_id")] + public int? parentId {get;set;} + + /// + /// Desc:计量设备编号 + /// Default: + /// Nullable:False + /// + [SugarColumn(IsPrimaryKey=true,ColumnName="monitor_code")] + public string monitorId {get;set;} + + /// + /// Desc:计量设备名称 + /// Default: + /// Nullable:True + /// + [SugarColumn(ColumnName="monitor_name")] + public string monitorName {get;set;} + + /// + /// Desc:计量设备位置 + /// Default: + /// Nullable:True + /// + [SugarColumn(ColumnName="monitor_addr")] + public string monitorAddr {get;set;} + + /// + /// Desc:计量设备类型 + /// Default: + /// Nullable:True + /// + [SugarColumn(ColumnName="monitor_type")] + public int? monitorType {get;set;} + + /// + /// Desc:计量设备状态 + /// Default:0 + /// Nullable:True + /// + [SugarColumn(ColumnName="monitor_status")] + public int? monitorStatus {get;set;} + + /// + /// Desc:采集设备编号 + /// Default: + /// Nullable:True + /// + [SugarColumn(ColumnName="collect_device_id")] + public string collectDeviceId {get;set;} + + /// + /// Desc:祖级列表 + /// Default: + /// Nullable:True + /// + [SugarColumn(ColumnName="ancestors")] + public string ancestors {get;set;} + + /// + /// Desc:等级 + /// Default: + /// Nullable:True + /// + [SugarColumn(ColumnName="grade")] + public int? grade {get;set;} + + /// + /// Desc:传感器仪表 + /// Default: + /// Nullable:True + /// + [SugarColumn(ColumnName="meter_type_id")] + public string meterTypeId {get;set;} + + /// + /// Desc:修正值 + /// Default: + /// Nullable:True + /// + [SugarColumn(ColumnName="correct_value")] + public decimal? correctValue {get;set;} + + /// + /// Desc:PT值 + /// Default: + /// Nullable:True + /// + [SugarColumn(ColumnName="pt")] + public int? pt {get;set;} + + /// + /// Desc:CT值 + /// Default: + /// Nullable:True + /// + [SugarColumn(ColumnName="ct")] + public int? ct {get;set;} + + /// + /// Desc:是否虚拟 + /// Default:false + /// Nullable:True + /// + [SugarColumn(ColumnName="is_ammeter")] + public string isAmmeter {get;set;} + + /// + /// Desc:通断复位 + /// Default: + /// Nullable:True + /// + [SugarColumn(ColumnName="is_key_monitor")] + public int? isKeyMonitor {get;set;} + + /// + /// Desc:是否断路 + /// Default: + /// Nullable:True + /// + [SugarColumn(ColumnName="is_circuit")] + public int? isCircuit {get;set;} + + /// + /// Desc:创建人 + /// Default: + /// Nullable:True + /// + [SugarColumn(ColumnName="create_by")] + public string createBy {get;set;} + + /// + /// Desc:创建时间 + /// Default:CURRENT_TIMESTAMP + /// Nullable:True + /// + [SugarColumn(ColumnName="create_time")] + public DateTime? createTime {get;set;} + + /// + /// Desc:更新人 + /// Default: + /// Nullable:True + /// + [SugarColumn(ColumnName="update_by")] + public string updateBy {get;set;} + + /// + /// Desc:更新时间 + /// Default: + /// Nullable:True + /// + [SugarColumn(ColumnName="update_time")] + public DateTime? updateTime {get;set;} + } +} \ No newline at end of file diff --git a/Sln.Iot.Model/dao/RecordDnbInstant.cs b/Sln.Iot.Model/dao/RecordDnbInstant.cs new file mode 100644 index 0000000..860563e --- /dev/null +++ b/Sln.Iot.Model/dao/RecordDnbInstant.cs @@ -0,0 +1,132 @@ +#region << 版 本 注 释 >> + +/*-------------------------------------------------------------------- +* 版权所有 (c) 2025 WenJY 保留所有权利。 +* CLR版本:4.0.30319.42000 +* 机器名称:Mr.Wen's MacBook Pro +* 命名空间:Sln.Iot.Model.dao +* 唯一标识:B1423370-1BD2-4199-AEDC-80C06296A9BC +* +* 创建者:WenJY +* 电子邮箱: +* 创建时间:2025-05-20 10:57:48 +* 版本:V1.0.0 +* 描述: +* +*-------------------------------------------------------------------- +* 修改人: +* 时间: +* 修改说明: +* +* 版本:V1.0.0 +*--------------------------------------------------------------------*/ + +#endregion << 版 本 注 释 >> + +using System; +using SqlSugar; + +namespace Sln.Iot.Model.dao +{ + /// + /// 电实时数据 + /// + [SplitTable(SplitType.Day)] + [SugarTable("record_dnb_instant_{year}{month}{day}"), TenantAttribute("tao_iot")] + public class RecordDnbInstant + { + /// + /// 编号 + /// + [SugarColumn(ColumnName="objid" ,IsPrimaryKey = true )] + public long objid { get; set; } + /// + /// 计量设备编号 + /// + [SugarColumn(ColumnName="monitor_id" )] + public string monitorId { get; set; } + + /// + /// A项电压 + /// + [SugarColumn(ColumnName="va" )] + public decimal? vA { get; set; } + + /// + /// B项电压 + /// + [SugarColumn(ColumnName="vb" )] + public decimal? vB { get; set; } + + /// + /// C项电压 + /// + [SugarColumn(ColumnName="vc" )] + public decimal? vC { get; set; } + + /// + /// A项电流 + /// + [SugarColumn(ColumnName="ia" )] + public decimal? iA { get; set; } + + /// + /// B项电流 + /// + [SugarColumn(ColumnName="ib" )] + public decimal? iB { get; set; } + + /// + /// C项电流 + /// + [SugarColumn(ColumnName="ic" )] + public decimal? iC { get; set; } + + /// + /// 功率因数 + /// + [SugarColumn(ColumnName="glys" )] + public decimal? powerFactor { get; set; } + + /// + /// 正向有功 + /// + [SugarColumn(ColumnName="zxyg" )] + public decimal? positiveActive { get; set; } + + /// + /// 有功功率 + /// + [SugarColumn(ColumnName="active_power" )] + public decimal? activePower { get; set; } + + /// + /// 无功功率 + /// + [SugarColumn(ColumnName="reactive_power" )] + public decimal? reactivePower { get; set; } + + /// + /// 采集方式 + /// 默认值: 0 + /// + [SugarColumn(ColumnName="collect_type" )] + public int collectType + { + get { return 1;} + } + + /// + /// 采集时间 + /// + [SugarColumn(ColumnName="collect_time" )] + public DateTime? collectTime { get; set; } + + /// + /// 记录时间 + /// + [SplitField] + [SugarColumn(ColumnName="record_time" )] + public DateTime? recordTime { get; set; } + } +} \ No newline at end of file diff --git a/Sln.Iot.Model/dao/RecordFluidInstant.cs b/Sln.Iot.Model/dao/RecordFluidInstant.cs new file mode 100644 index 0000000..0e08d9a --- /dev/null +++ b/Sln.Iot.Model/dao/RecordFluidInstant.cs @@ -0,0 +1,121 @@ +#region << 版 本 注 释 >> + +/*-------------------------------------------------------------------- +* 版权所有 (c) 2025 WenJY 保留所有权利。 +* CLR版本:4.0.30319.42000 +* 机器名称:Mr.Wen's MacBook Pro +* 命名空间:Sln.Iot.Model.dao +* 唯一标识:CB52EBBD-0D7F-498D-B2D0-B9926F06F5AF +* +* 创建者:WenJY +* 电子邮箱: +* 创建时间:2025-05-20 14:40:03 +* 版本:V1.0.0 +* 描述: +* +*-------------------------------------------------------------------- +* 修改人: +* 时间: +* 修改说明: +* +* 版本:V1.0.0 +*--------------------------------------------------------------------*/ + +#endregion << 版 本 注 释 >> + +using System; +using SqlSugar; + +namespace Sln.Iot.Model.dao +{ + /// + /// 流体实时数据 + /// + [SplitTable(SplitType.Day)] + [SugarTable("record_fluid_instant_{year}{month}{day}"), Tenant("tao_iot")] + public class RecordFluidInstant + { + /// + /// 自增标识 + /// + [SugarColumn(ColumnName="objid" ,IsPrimaryKey = true )] + public long objid { get; set; } + + /// + /// 计量设备编号 + /// + [SugarColumn(ColumnName="monitor_id" )] + public string monitorId { get; set; } + + /// + /// 温度值 + /// + [SugarColumn(ColumnName="temperature" )] + public decimal? temperature{get;set;} + + /// + /// 压力值 + /// + [SugarColumn(ColumnName="press" )] + public decimal? press{get;set;} + + /// + /// 频率值 + /// + [SugarColumn(ColumnName="frequency" )] + public decimal? frequency{get;set;} + + /// + /// 密度值 + /// + [SugarColumn(ColumnName="density" )] + public decimal? density{get;set;} + + /// + /// 瞬时热量 + /// + [SugarColumn(ColumnName="instant_heat" )] + public decimal? instantHeat { get; set; } + + /// + /// 累计热量 + /// + [SugarColumn(ColumnName="total_heat" )] + public decimal? totalHeat { get; set; } + + /// + /// 瞬时流量 + /// + [SugarColumn(ColumnName="instant_flow" )] + public decimal? instantFlow { get; set; } + + /// + /// 累计流量 + /// + [SugarColumn(ColumnName="total_flow" )] + public decimal? totalFlow { get; set; } + + /// + /// 采集方式 + /// 默认值: 0 + /// + [SugarColumn(ColumnName="collect_type" )] + public int collectType + { + get { return 1;} + } + + /// + /// 采集时间 + /// + [SugarColumn(ColumnName="collect_time" )] + public DateTime? collectTime { get; set; } + + /// + /// 记录时间 + /// + [SplitField] + [SugarColumn(ColumnName="record_time" )] + public DateTime? recordTime { get; set; } + } +} \ No newline at end of file diff --git a/Sln.Iot.Model/dao/RecordIotEnvInstant.cs b/Sln.Iot.Model/dao/RecordIotEnvInstant.cs new file mode 100644 index 0000000..8958735 --- /dev/null +++ b/Sln.Iot.Model/dao/RecordIotEnvInstant.cs @@ -0,0 +1,116 @@ +#region << 版 本 注 释 >> + +/*-------------------------------------------------------------------- +* 版权所有 (c) 2025 WenJY 保留所有权利。 +* CLR版本:4.0.30319.42000 +* 机器名称:Mr.Wen's MacBook Pro +* 命名空间:Sln.Iot.Model.dao +* 唯一标识:2946BBED-E772-4BC4-953F-F4B9834C27F6 +* +* 创建者:WenJY +* 电子邮箱: +* 创建时间:2025-04-11 15:20:56 +* 版本:V1.0.0 +* 描述: +* +*-------------------------------------------------------------------- +* 修改人: +* 时间: +* 修改说明: +* +* 版本:V1.0.0 +*--------------------------------------------------------------------*/ + +#endregion << 版 本 注 释 >> + +using System; +using SqlSugar; + +namespace Sln.Iot.Model.dao +{ + [SplitTable(SplitType.Day)] + [SugarTable("record_iotenv_instant_{year}{month}{day}"), TenantAttribute("tao_iot")] + public class RecordIotEnvInstant + { + /// + /// 编号 ,IsIdentity = true + /// + [SugarColumn(ColumnName="objid" ,IsPrimaryKey = true )] + public long objid { get; set; } + + /// + /// 计量设备编号 + /// + [SugarColumn(ColumnName="monitorId" )] + public string monitorId { get; set; } + + /// + /// 温度 + /// + [SugarColumn(ColumnName="temperature" )] + public decimal temperature { get; set; } + + /// + /// 湿度 + /// + [SugarColumn(ColumnName="humidity" )] + public decimal humidity { get; set; } + + /// + /// 照度 + /// + [SugarColumn(ColumnName="illuminance" )] + public decimal illuminance { get; set; } + + /// + /// 噪音 + /// + [SugarColumn(ColumnName="noise" )] + public decimal noise { get; set; } + + /// + /// 气体浓度 + /// + [SugarColumn(ColumnName="concentration" )] + public decimal concentration { get; set; } + + /// + /// 振动-速度 + /// + [SugarColumn(ColumnName = "vibration_speed")] + public decimal VibrationSpeed { get; set; } + + + /// + /// 振动-位移 + /// + [SugarColumn(ColumnName = "vibration_displacement")] + public decimal VibrationDisplacement { get; set; } + + /// + /// 振动-加速度 + /// + [SugarColumn(ColumnName = "vibration_acceleration")] + public decimal VibrationAcceleration { get; set; } + + /// + /// 振动-温度 + /// + [SugarColumn(ColumnName = "vibration_temp")] + public decimal VibrationTemp { get; set; } + + + /// + /// 采集时间 + /// + [SugarColumn(ColumnName="collectTime" )] + public DateTime? collectTime { get; set; } + + /// + /// 记录时间 + /// + [SplitField] + [SugarColumn(ColumnName="recodeTime" )] + public DateTime? recordTime { get; set; } + } +} \ No newline at end of file diff --git a/Sln.Iot.Model/dto/CommParams.cs b/Sln.Iot.Model/dto/CommParams.cs new file mode 100644 index 0000000..94a81a3 --- /dev/null +++ b/Sln.Iot.Model/dto/CommParams.cs @@ -0,0 +1,100 @@ +#region << 版 本 注 释 >> + +/*-------------------------------------------------------------------- +* 版权所有 (c) 2025 WenJY 保留所有权利。 +* CLR版本:4.0.30319.42000 +* 机器名称:Mr.Wen's MacBook Pro +* 命名空间:Sln.Iot.Model.dto +* 唯一标识:CA8D9816-589C-4E15-96FD-CA864799054D +* +* 创建者:WenJY +* 电子邮箱: +* 创建时间:2025-04-11 15:10:04 +* 版本:V1.0.0 +* 描述: +* +*-------------------------------------------------------------------- +* 修改人: +* 时间: +* 修改说明: +* +* 版本:V1.0.0 +*--------------------------------------------------------------------*/ + +#endregion << 版 本 注 释 >> + +namespace Sln.Iot.Model.dto +{ + public struct CommParams + { + /// + /// 温度 + /// + public const uint TTempreture = 0x8E50; + + /// + /// 湿度 + /// + public const uint Humidity = 0x8E52; + + + /// + /// 噪音 + /// + public const uint Noise = 0x8E53; + + /// + /// 振动-速度 + /// + public const uint VibrationSpeed = 0x8E54; + + /// + /// 振动-位移 + /// + public const uint VibrationDisplacement = 0x8E55; + + /// + /// 振动-加速度 + /// + public const uint VibrationAcceleration = 0x8E56; + + /// + /// 振动-温度 + /// + public const uint VibrationTemp = 0x8E57; + + /// + /// 采集时间 + /// + public const uint CJSJ = 0x8030; + + #region 电能数据结构体 + + public const uint AI = 0x8E21; + public const uint BI = 0x8E22; + public const uint CI = 0x8E23; + public const uint AU = 0x8E11; + public const uint BU = 0x8E12; + public const uint CU = 0x8E13; + public const uint GLYS = 0xB650; + public const uint ZXYGZ = 0x9010; + public const uint ZXWG = 0x9030; + public const uint YGGL = 0xB651; + public const uint WGGL = 0xB652; + + #endregion + + #region 流体数据结构体 + + public const uint Press = 0x9B00; + public const uint STemperature = 0x9B01; + public const uint Frequency = 0x9B02; + public const uint FluxInstantValue = 0x9B03; + public const uint FluxEyeableTotalValue = 0x9B05; + public const uint HeatInstantValue = 0x9B06; + public const uint HeatToftalValue = 0x9B07; + public const uint Density = 0x9B0E; + + #endregion + } +} \ No newline at end of file diff --git a/Sln.Iot.Model/dto/ResponsePack.cs b/Sln.Iot.Model/dto/ResponsePack.cs new file mode 100644 index 0000000..5cd6156 --- /dev/null +++ b/Sln.Iot.Model/dto/ResponsePack.cs @@ -0,0 +1,40 @@ +#region << 版 本 注 释 >> + +/*-------------------------------------------------------------------- +* 版权所有 (c) 2025 WenJY 保留所有权利。 +* CLR版本:4.0.30319.42000 +* 机器名称:Mr.Wen's MacBook Pro +* 命名空间:Sln.Iot.Model.dto +* 唯一标识:CE889E7E-08A9-4043-A157-32CB2773E568 +* +* 创建者:WenJY +* 电子邮箱: +* 创建时间:2025-04-11 15:08:47 +* 版本:V1.0.0 +* 描述: +* +*-------------------------------------------------------------------- +* 修改人: +* 时间: +* 修改说明: +* +* 版本:V1.0.0 +*--------------------------------------------------------------------*/ + +#endregion << 版 本 注 释 >> + +namespace Sln.Iot.Model.dto +{ + public class ResponsePack + { + public byte m_BeginChar = 0x68; //帧头 + public byte m_EnergyType; //能源类型 + public byte[] m_Meteraddr = new byte[2];//地址 + public byte[] m_Msta = new byte[2]; //命令序列号 + public byte m_StartFlag; //起始符 + public byte m_MessageType; //控制码 + public byte[] m_PackLen = new byte[2]; //数据长度 + public byte m_Verify; + public byte m_EndChar = 0x16; //尾盘 + } +} \ No newline at end of file diff --git a/Sln.Iot.Model/dto/TagInfo.cs b/Sln.Iot.Model/dto/TagInfo.cs new file mode 100644 index 0000000..93d12da --- /dev/null +++ b/Sln.Iot.Model/dto/TagInfo.cs @@ -0,0 +1,42 @@ +#region << 版 本 注 释 >> + +/*-------------------------------------------------------------------- +* 版权所有 (c) 2025 WenJY 保留所有权利。 +* CLR版本:4.0.30319.42000 +* 机器名称:Mr.Wen's MacBook Pro +* 命名空间:Sln.Iot.Model.dto +* 唯一标识:CF7EE865-8FD7-4B6E-BD69-B86E09886CB6 +* +* 创建者:WenJY +* 电子邮箱: +* 创建时间:2025-04-11 15:10:36 +* 版本:V1.0.0 +* 描述: +* +*-------------------------------------------------------------------- +* 修改人: +* 时间: +* 修改说明: +* +* 版本:V1.0.0 +*--------------------------------------------------------------------*/ + +#endregion << 版 本 注 释 >> + +using System; + +namespace Sln.Iot.Model.dto +{ + public class TagInfo + { + public byte[] PC = new byte[2]; + public int Count { get; set; } + public int RSSI{ get; set; } + public int Antana{ get; set; } + public byte[] EPC{ get; set; } + public byte[] Data{ get; set; } + public string PCstring = (string) null; + public string EPCstring = (string) null; + public DateTime Time{ get; set; } + } +} \ No newline at end of file diff --git a/Sln.Iot.Repository/Class1.cs b/Sln.Iot.Repository/Class1.cs new file mode 100644 index 0000000..e063971 --- /dev/null +++ b/Sln.Iot.Repository/Class1.cs @@ -0,0 +1,5 @@ +namespace Sln.Iot.Repository; + +public class Class1 +{ +} \ No newline at end of file diff --git a/Sln.Iot.Repository/Repository.cs b/Sln.Iot.Repository/Repository.cs new file mode 100644 index 0000000..80a6ea4 --- /dev/null +++ b/Sln.Iot.Repository/Repository.cs @@ -0,0 +1,44 @@ +#region << 版 本 注 释 >> + +/*-------------------------------------------------------------------- +* 版权所有 (c) 2025 WenJY 保留所有权利。 +* CLR版本:4.0.30319.42000 +* 机器名称:Mr.Wen's MacBook Pro +* 命名空间:Sln.Iot.Repository +* 唯一标识:130152CF-25A2-4CF6-BF55-FF62811E139D +* +* 创建者:WenJY +* 电子邮箱: +* 创建时间:2025-04-11 13:24:59 +* 版本:V1.0.0 +* 描述: +* +*-------------------------------------------------------------------- +* 修改人: +* 时间: +* 修改说明: +* +* 版本:V1.0.0 +*--------------------------------------------------------------------*/ + +#endregion << 版 本 注 释 >> + +using SqlSugar; + +namespace Sln.Iot.Repository +{ + public class Repository : SimpleClient where T : class, new() + { + public ITenant itenant = null;//多租户事务、GetConnection、IsAnyConnection等功能 + + public Repository(ISqlSugarClient db) + { + itenant = db.AsTenant();//用来处理事务 + base.Context = db.AsTenant().GetConnectionScopeWithAttr();//获取子Db + + //如果不想通过注入多个仓储 + //用到ChangeRepository或者Db.GetMyRepository需要看标题4写法 + } + + } +} \ No newline at end of file diff --git a/Sln.Iot.Repository/Sln.Iot.Repository.csproj b/Sln.Iot.Repository/Sln.Iot.Repository.csproj new file mode 100644 index 0000000..a10b9ab --- /dev/null +++ b/Sln.Iot.Repository/Sln.Iot.Repository.csproj @@ -0,0 +1,18 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + + diff --git a/Sln.Iot.Repository/SqlsugarSetup.cs b/Sln.Iot.Repository/SqlsugarSetup.cs new file mode 100644 index 0000000..c2b560f --- /dev/null +++ b/Sln.Iot.Repository/SqlsugarSetup.cs @@ -0,0 +1,73 @@ +#region << 版 本 注 释 >> + +/*-------------------------------------------------------------------- +* 版权所有 (c) 2025 WenJY 保留所有权利。 +* CLR版本:4.0.30319.42000 +* 机器名称:Mr.Wen's MacBook Pro +* 命名空间:Sln.Iot.Repository +* 唯一标识:EBCC8183-D907-4049-B036-0C5BA2284E22 +* +* 创建者:WenJY +* 电子邮箱: +* 创建时间:2025-04-11 13:39:23 +* 版本:V1.0.0 +* 描述: +* +*-------------------------------------------------------------------- +* 修改人: +* 时间: +* 修改说明: +* +* 版本:V1.0.0 +*--------------------------------------------------------------------*/ + +#endregion << 版 本 注 释 >> + +using System.Collections.Generic; +using Microsoft.Extensions.DependencyInjection; +using Sln.Iot.Config; +using SqlSugar; + +namespace Sln.Iot.Repository +{ + public static class SqlsugarSetup + { + /// + /// 注册SqlSugar + /// + /// + public static void AddSqlSugarSetup(this IServiceCollection services) + { + services.AddSingleton(x => + { + var appConfig = x.GetService(); + + var connectConfigList = new List(); + if (appConfig.sqlConfig != null) + { + foreach (var item in appConfig.sqlConfig) + { + if (item.isFlag) + { + var config = new ConnectionConfig() + { + ConfigId = item.configId, + DbType = (DbType)item.dbType, + ConnectionString = item.connStr, + InitKeyType = InitKeyType.Attribute, + IsAutoCloseConnection = true, + }; + connectConfigList.Add(config); + } + } + } + SqlSugarScope Db = new SqlSugarScope(connectConfigList, db => + { + db.Aop.OnLogExecuting = (sql, pars) => { }; + }); + + return Db; + }); + } + } +} \ No newline at end of file diff --git a/Sln.Iot.Repository/service/.DS_Store b/Sln.Iot.Repository/service/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..b71960d281cac3f8089ce41d5a813b259a8d37c5 GIT binary patch literal 6148 zcmeHKJxjzu5S=|EL{zxaa!W0*7GXVNVSSw-XgpCNApx~dV=wm)to$dIR{jqEf#92+ zCAZ5(OBI=c*_X^m9{X~a-6bM3y`N8r#zd6E8Alr!28{FUD>e#|Eui6NR9BPPMN_vc zl^Xb~3h=wjsHAhcrOWjF9qh-sx+#jXYD&cN#mVdA+xyF5nxB80Z?EIuhC?fOkEy1X zs+8C6#<`ex?_u%O^M>BKS@I6$2YA;pSFq9z-KEUi&tQ(SG55!5Kj)-k$v#|>^7H>p zO%+fDRDoYt0Bbf|zA31qDxeCe0=)wKeXwvw7qAobpAHQE2ms6w_J(V}#{pUn0A0XN z5D}P>R-m++aKtduj<^kVE?_4p?PNSNo)ef&IH8!Z!!p*FlW~GNssgG&s=%7uj(Pn* z`uhBzHtC%zpbGpe1x!}ViYcZP*4CBD@mg!)XK*&I>je82T==CJwY(JXz`Y@E`2y$y Tc7lk&bR*zp&_NaWQ3XB$LhEQ= literal 0 HcmV?d00001 diff --git a/Sln.Iot.Repository/service/IBaseMonitorInfoService.cs b/Sln.Iot.Repository/service/IBaseMonitorInfoService.cs new file mode 100644 index 0000000..8b292ed --- /dev/null +++ b/Sln.Iot.Repository/service/IBaseMonitorInfoService.cs @@ -0,0 +1,35 @@ +#region << 版 本 注 释 >> + +/*-------------------------------------------------------------------- +* 版权所有 (c) 2025 WenJY 保留所有权利。 +* CLR版本:4.0.30319.42000 +* 机器名称:Mr.Wen's MacBook Pro +* 命名空间:Sln.Iot.Repository.service +* 唯一标识:9B119436-8BE4-4693-9D28-F971874E03CB +* +* 创建者:WenJY +* 电子邮箱: +* 创建时间:2025-04-11 13:27:35 +* 版本:V1.0.0 +* 描述: +* +*-------------------------------------------------------------------- +* 修改人: +* 时间: +* 修改说明: +* +* 版本:V1.0.0 +*--------------------------------------------------------------------*/ + +#endregion << 版 本 注 释 >> + +using Sln.Iot.Model.dao; +using Sln.Iot.Repository.service.@base; + +namespace Sln.Iot.Repository.service +{ + public interface IBaseMonitorInfoService : IBaseService + { + + } +} \ No newline at end of file diff --git a/Sln.Iot.Repository/service/IRecordDnbInstantService.cs b/Sln.Iot.Repository/service/IRecordDnbInstantService.cs new file mode 100644 index 0000000..06932fd --- /dev/null +++ b/Sln.Iot.Repository/service/IRecordDnbInstantService.cs @@ -0,0 +1,42 @@ +#region << 版 本 注 释 >> + +/*-------------------------------------------------------------------- +* 版权所有 (c) 2025 WenJY 保留所有权利。 +* CLR版本:4.0.30319.42000 +* 机器名称:Mr.Wen's MacBook Pro +* 命名空间:Sln.Iot.Repository.service +* 唯一标识:D397E4B7-48E1-4BF3-8906-B11F96EE8B42 +* +* 创建者:WenJY +* 电子邮箱: +* 创建时间:2025-05-20 11:26:07 +* 版本:V1.0.0 +* 描述: +* +*-------------------------------------------------------------------- +* 修改人: +* 时间: +* 修改说明: +* +* 版本:V1.0.0 +*--------------------------------------------------------------------*/ + +#endregion << 版 本 注 释 >> + +using System.Collections.Generic; +using Sln.Iot.Model.dao; +using Sln.Iot.Repository.service.@base; + +namespace Sln.Iot.Repository.service +{ + public interface IRecordDnbInstantService:IBaseService + { + /// + /// 分表保存 + /// + /// + /// + /// + bool SplitInsert(List list,out List insertIds); + } +} \ No newline at end of file diff --git a/Sln.Iot.Repository/service/IRecordFluidInstantService.cs b/Sln.Iot.Repository/service/IRecordFluidInstantService.cs new file mode 100644 index 0000000..06ed4b0 --- /dev/null +++ b/Sln.Iot.Repository/service/IRecordFluidInstantService.cs @@ -0,0 +1,42 @@ +#region << 版 本 注 释 >> + +/*-------------------------------------------------------------------- +* 版权所有 (c) 2025 WenJY 保留所有权利。 +* CLR版本:4.0.30319.42000 +* 机器名称:Mr.Wen's MacBook Pro +* 命名空间:Sln.Iot.Repository.service +* 唯一标识:6403A242-FF9C-4942-B02B-8680BBD7ACBD +* +* 创建者:WenJY +* 电子邮箱: +* 创建时间:2025-05-20 14:46:25 +* 版本:V1.0.0 +* 描述: +* +*-------------------------------------------------------------------- +* 修改人: +* 时间: +* 修改说明: +* +* 版本:V1.0.0 +*--------------------------------------------------------------------*/ + +#endregion << 版 本 注 释 >> + +using System.Collections.Generic; +using Sln.Iot.Model.dao; +using Sln.Iot.Repository.service.@base; + +namespace Sln.Iot.Repository.service +{ + public interface IRecordFluidInstantService:IBaseService + { + /// + /// 分表保存 + /// + /// + /// + /// + bool SplitInsert(List list,out List insertIds); + } +} \ No newline at end of file diff --git a/Sln.Iot.Repository/service/IRecordIotEnvInstantService.cs b/Sln.Iot.Repository/service/IRecordIotEnvInstantService.cs new file mode 100644 index 0000000..f9f7cd2 --- /dev/null +++ b/Sln.Iot.Repository/service/IRecordIotEnvInstantService.cs @@ -0,0 +1,42 @@ +#region << 版 本 注 释 >> + +/*-------------------------------------------------------------------- +* 版权所有 (c) 2025 WenJY 保留所有权利。 +* CLR版本:4.0.30319.42000 +* 机器名称:Mr.Wen's MacBook Pro +* 命名空间:Sln.Iot.Repository.service +* 唯一标识:44931288-195F-4560-B9D8-9EA8A511FE8A +* +* 创建者:WenJY +* 电子邮箱: +* 创建时间:2025-04-11 15:44:29 +* 版本:V1.0.0 +* 描述: +* +*-------------------------------------------------------------------- +* 修改人: +* 时间: +* 修改说明: +* +* 版本:V1.0.0 +*--------------------------------------------------------------------*/ + +#endregion << 版 本 注 释 >> + +using System.Collections.Generic; +using Sln.Iot.Model.dao; +using Sln.Iot.Repository.service.@base; + +namespace Sln.Iot.Repository.service +{ + public interface IRecordIotEnvInstantService:IBaseService + { + /// + /// 分表保存 + /// + /// + /// + /// + bool SplitInsert(List list,out List insertIds); + } +} \ No newline at end of file diff --git a/Sln.Iot.Repository/service/Impl/BaseMonitorInfoServiceImpl.cs b/Sln.Iot.Repository/service/Impl/BaseMonitorInfoServiceImpl.cs new file mode 100644 index 0000000..33c5365 --- /dev/null +++ b/Sln.Iot.Repository/service/Impl/BaseMonitorInfoServiceImpl.cs @@ -0,0 +1,37 @@ +#region << 版 本 注 释 >> + +/*-------------------------------------------------------------------- +* 版权所有 (c) 2025 WenJY 保留所有权利。 +* CLR版本:4.0.30319.42000 +* 机器名称:Mr.Wen's MacBook Pro +* 命名空间:Sln.Iot.Repository.service.Impl +* 唯一标识:625A971B-5BA8-4E05-9AE1-DC6D8E3D6051 +* +* 创建者:WenJY +* 电子邮箱: +* 创建时间:2025-04-11 13:28:02 +* 版本:V1.0.0 +* 描述: +* +*-------------------------------------------------------------------- +* 修改人: +* 时间: +* 修改说明: +* +* 版本:V1.0.0 +*--------------------------------------------------------------------*/ + +#endregion << 版 本 注 释 >> + +using Sln.Iot.Model.dao; +using Sln.Iot.Repository.service.@base; + +namespace Sln.Iot.Repository.service.Impl +{ + public class BaseMonitorInfoServiceImpl : BaseServiceImpl, IBaseMonitorInfoService + { + public BaseMonitorInfoServiceImpl(Repository repository):base(repository) + { + } + } +} \ No newline at end of file diff --git a/Sln.Iot.Repository/service/Impl/RecordDnbInstantServiceImpl.cs b/Sln.Iot.Repository/service/Impl/RecordDnbInstantServiceImpl.cs new file mode 100644 index 0000000..e49b220 --- /dev/null +++ b/Sln.Iot.Repository/service/Impl/RecordDnbInstantServiceImpl.cs @@ -0,0 +1,73 @@ +#region << 版 本 注 释 >> + +/*-------------------------------------------------------------------- +* 版权所有 (c) 2025 WenJY 保留所有权利。 +* CLR版本:4.0.30319.42000 +* 机器名称:Mr.Wen's MacBook Pro +* 命名空间:Sln.Iot.Repository.service.Impl +* 唯一标识:EF060FA7-6D52-478C-84FF-F26EEC206685 +* +* 创建者:WenJY +* 电子邮箱: +* 创建时间:2025-05-20 11:26:53 +* 版本:V1.0.0 +* 描述: +* +*-------------------------------------------------------------------- +* 修改人: +* 时间: +* 修改说明: +* +* 版本:V1.0.0 +*--------------------------------------------------------------------*/ + +#endregion << 版 本 注 释 >> + +using System; +using System.Collections.Generic; +using Sln.Iot.Model.dao; +using Sln.Iot.Repository.service.@base; + +namespace Sln.Iot.Repository.service.Impl +{ + public class RecordDnbInstantServiceImpl: BaseServiceImpl, IRecordDnbInstantService + { + public RecordDnbInstantServiceImpl(Repository rep) : base(rep) + { + } + + /// + /// 分表保存 + /// + /// + /// + /// + /// + /// + public bool SplitInsert(List list,out List insertIds) + { + if (list == null) + { + throw new ArgumentNullException($"参数为空"); + } + try + { + + // _rep.AsTenant().BeginTran(); + + var sqlSugarClient = _rep.Context; + //加载指定分表策略 + //sqlSugarClient.CurrentConnectionConfig.ConfigureExternalServices.SplitTableService = new MonitorIdToSplitService(); + insertIds = sqlSugarClient.Insertable(list).SplitTable().ExecuteReturnSnowflakeIdList(); + + // _rep.AsTenant().CommitTran(); + return true; + } + catch (Exception ex) + { + // _rep.AsTenant().RollbackTran(); + throw new InvalidOperationException($"电能数据分表保存异常:{ex.Message}"); + } + } + } +} \ No newline at end of file diff --git a/Sln.Iot.Repository/service/Impl/RecordFluidInstantServiceImpl.cs b/Sln.Iot.Repository/service/Impl/RecordFluidInstantServiceImpl.cs new file mode 100644 index 0000000..e3bdcb8 --- /dev/null +++ b/Sln.Iot.Repository/service/Impl/RecordFluidInstantServiceImpl.cs @@ -0,0 +1,73 @@ +#region << 版 本 注 释 >> + +/*-------------------------------------------------------------------- +* 版权所有 (c) 2025 WenJY 保留所有权利。 +* CLR版本:4.0.30319.42000 +* 机器名称:Mr.Wen's MacBook Pro +* 命名空间:Sln.Iot.Repository.service.Impl +* 唯一标识:72B88660-5410-4016-9E12-B1E11841AB4B +* +* 创建者:WenJY +* 电子邮箱: +* 创建时间:2025-05-20 14:47:03 +* 版本:V1.0.0 +* 描述: +* +*-------------------------------------------------------------------- +* 修改人: +* 时间: +* 修改说明: +* +* 版本:V1.0.0 +*--------------------------------------------------------------------*/ + +#endregion << 版 本 注 释 >> + +using System; +using System.Collections.Generic; +using Sln.Iot.Model.dao; +using Sln.Iot.Repository.service.@base; + +namespace Sln.Iot.Repository.service.Impl +{ + public class RecordFluidInstantServiceImpl: BaseServiceImpl, IRecordFluidInstantService + { + public RecordFluidInstantServiceImpl(Repository rep) : base(rep) + { + } + + /// + /// 分表保存 + /// + /// + /// + /// + /// + /// + public bool SplitInsert(List list,out List insertIds) + { + if (list == null) + { + throw new ArgumentNullException($"参数为空"); + } + try + { + + // _rep.AsTenant().BeginTran(); + + var sqlSugarClient = _rep.Context; + //加载指定分表策略 + //sqlSugarClient.CurrentConnectionConfig.ConfigureExternalServices.SplitTableService = new MonitorIdToSplitService(); + insertIds = sqlSugarClient.Insertable(list).SplitTable().ExecuteReturnSnowflakeIdList(); + + // _rep.AsTenant().CommitTran(); + return true; + } + catch (Exception ex) + { + // _rep.AsTenant().RollbackTran(); + throw new InvalidOperationException($"流体数据分表保存异常:{ex.Message}"); + } + } + } +} \ No newline at end of file diff --git a/Sln.Iot.Repository/service/Impl/RecordIotEnvInstantServiceImpl.cs b/Sln.Iot.Repository/service/Impl/RecordIotEnvInstantServiceImpl.cs new file mode 100644 index 0000000..46a527c --- /dev/null +++ b/Sln.Iot.Repository/service/Impl/RecordIotEnvInstantServiceImpl.cs @@ -0,0 +1,74 @@ +#region << 版 本 注 释 >> + +/*-------------------------------------------------------------------- +* 版权所有 (c) 2025 WenJY 保留所有权利。 +* CLR版本:4.0.30319.42000 +* 机器名称:Mr.Wen's MacBook Pro +* 命名空间:Sln.Iot.Repository.service.Impl +* 唯一标识:C914F6F6-C7CC-4DDC-B061-C89A15419EF0 +* +* 创建者:WenJY +* 电子邮箱: +* 创建时间:2025-04-11 15:44:49 +* 版本:V1.0.0 +* 描述: +* +*-------------------------------------------------------------------- +* 修改人: +* 时间: +* 修改说明: +* +* 版本:V1.0.0 +*--------------------------------------------------------------------*/ + +#endregion << 版 本 注 释 >> + +using System; +using System.Collections.Generic; +using Sln.Iot.Model.dao; +using Sln.Iot.Repository.service.@base; +using Sln.Iot.Repository.service.split; + +namespace Sln.Iot.Repository.service.Impl +{ + public class RecordIotEnvInstantServiceImpl: BaseServiceImpl, IRecordIotEnvInstantService + { + public RecordIotEnvInstantServiceImpl(Repository rep) : base(rep) + { + } + + /// + /// 分表保存 + /// + /// + /// + /// + /// + /// + public bool SplitInsert(List list,out List insertIds) + { + if (list == null) + { + throw new ArgumentNullException($"参数为空"); + } + try + { + + // _rep.AsTenant().BeginTran(); + + var sqlSugarClient = _rep.Context; + //加载指定分表策略 + //sqlSugarClient.CurrentConnectionConfig.ConfigureExternalServices.SplitTableService = new MonitorIdToSplitService(); + insertIds = sqlSugarClient.Insertable(list).SplitTable().ExecuteReturnSnowflakeIdList(); + + // _rep.AsTenant().CommitTran(); + return true; + } + catch (Exception ex) + { + // _rep.AsTenant().RollbackTran(); + throw new InvalidOperationException($"物联网数据分表保存异常:{ex.Message}"); + } + } + } +} \ No newline at end of file diff --git a/Sln.Iot.Repository/service/base/BaseServiceImpl.cs b/Sln.Iot.Repository/service/base/BaseServiceImpl.cs new file mode 100644 index 0000000..2874397 --- /dev/null +++ b/Sln.Iot.Repository/service/base/BaseServiceImpl.cs @@ -0,0 +1,359 @@ +#region << 版 本 注 释 >> + +/*-------------------------------------------------------------------- +* 版权所有 (c) 2025 WenJY 保留所有权利。 +* CLR版本:4.0.30319.42000 +* 机器名称:Mr.Wen's MacBook Pro +* 命名空间:Sln.Iot.Repository.service.base +* 唯一标识:AAD164C5-C115-422B-B57B-E9669385D083 +* +* 创建者:WenJY +* 电子邮箱: +* 创建时间:2025-04-11 13:26:34 +* 版本:V1.0.0 +* 描述: +* +*-------------------------------------------------------------------- +* 修改人: +* 时间: +* 修改说明: +* +* 版本:V1.0.0 +*--------------------------------------------------------------------*/ + +#endregion << 版 本 注 释 >> + +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using SqlSugar; + +namespace Sln.Iot.Repository.service.@base +{ + public class BaseServiceImpl : IBaseService where T : class, new() + { + public readonly Repository _rep; + + public BaseServiceImpl(Repository rep) + { + _rep = rep; + } + + /// + /// 添加实体信息 + /// + /// + /// + /// + /// + public bool Insert(T model) + { + if (model == null) + { + throw new ArgumentNullException($"添加实体信息异常:实体参数为空"); + } + + try + { + return _rep.CopyNew().Insert(model); + } + catch (Exception ex) + { + throw new InvalidOperationException($"添加实体信息异常:{ex.Message}"); + } + + } + + /// + /// 批量添加实体集合 + /// + /// + /// + /// + /// + public bool Insert(List lisT) + { + if (lisT == null) + { + throw new ArgumentNullException($"批量添加实体集合异常:实体集合参数为空"); + } + try + { + + // _rep.AsTenant().BeginTran(); + var info = _rep.CopyNew().InsertRange(lisT); + // _rep.AsTenant().CommitTran(); + return true; + } + catch (Exception ex) + { + // _rep.AsTenant().RollbackTran(); + throw new InvalidOperationException($"批量添加实体集合异常:{ex.Message}"); + } + } + + /// + /// 根据id 删除信息 + /// + /// + /// + /// + public bool DeleteById(object id) + { + if (id == null) + { + throw new ArgumentNullException($"根据id删除信息异常:Id参数为空"); + } + try + { + return _rep.DeleteById(id); + } + catch (Exception ex) + { + throw new InvalidOperationException($"根据id删除信息异常:{ex.Message}"); + } + } + + /// + /// 根据实体删除信息 + /// + /// + /// + /// + /// + public bool Delete(T model) + { + if (model == null) + { + throw new ArgumentNullException($"根据实体删除信息异常:实体参数为空"); + } + try + { + return _rep.DeleteById(model); + } + catch (Exception ex) + { + throw new InvalidOperationException($"根据实体删除信息异常:{ex.Message}"); + } + } + + /// + /// 根据实体集合批量删除信息 + /// + /// + /// + /// + public bool Deletes(List entitys) + { + if (entitys == null) + { + throw new ArgumentNullException($"根据实体集合批量删除信息异常:实体集合参数为空"); + } + try + { + return _rep.Delete(entitys); + } + catch (Exception ex) + { + throw new InvalidOperationException($"根据实体集合批量删除信息异常:{ex.Message}"); + } + } + + /// + /// 根据实体更新信息 + /// + /// + /// + /// + public bool Update(T model) + { + if (model == null) + { + throw new ArgumentNullException($"根据实体更新信息异常:实体参数为空"); + } + try + { + return _rep.Update(model); + } + catch (Exception ex) + { + throw new InvalidOperationException($"根据实体更新信息异常:{ex.Message}"); + } + } + + /// + /// 批量更新实体集合信息 + /// + /// + /// + /// + public bool Update(List entitys) + { + if (entitys == null) + { + throw new ArgumentNullException($"批量更新实体集合信息异常:实体集合参数为空"); + } + + try + { + return _rep.UpdateRange(entitys); + } + catch (Exception ex) + { + throw new InvalidOperationException($"批量更新实体集合信息异常:{ex.Message}"); + } + } + + /// + /// 根据Where条件更新实体信息 + /// + /// + /// + /// + public bool Update(T entity, string strWhere) + { + if (entity == null) + { + throw new ArgumentNullException($"根据Where条件更新实体信息异常:实体参数为空"); + } + + if (string.IsNullOrEmpty(strWhere)) + { + throw new ArgumentNullException($"根据Where条件更新实体信息异常:Where参数为空"); + } + + try + { + return _rep.AsUpdateable(entity).Where(strWhere).ExecuteCommandHasChange(); + } + catch (Exception ex) + { + throw new InvalidOperationException($"根据Where条件更新实体信息异常:{ex.Message}"); + } + } + + /// + /// 根据实体更新指定列 + /// + /// + /// + /// + /// + /// + public bool Update(T entity, List lstColumns = null, List lstIgnoreColumns = null, string strWhere = "") + { + try + { + IUpdateable up = _rep.AsUpdateable(entity); + if (lstIgnoreColumns != null && lstIgnoreColumns.Count > 0) + { + up = up.IgnoreColumns(lstIgnoreColumns.ToArray()); + } + if (lstColumns != null && lstColumns.Count > 0) + { + up = up.UpdateColumns(lstColumns.ToArray()); + } + if (!string.IsNullOrEmpty(strWhere)) + { + up = up.Where(strWhere); + } + return up.ExecuteCommandHasChange(); + } + catch (Exception ex) + { + throw new InvalidOperationException($"根据实体更新指定列异常:{ex.Message}"); + } + } + + /// + /// 查询所有信息 + /// + /// + /// + public List Query() + { + try + { + return _rep.GetList(); + } + catch (Exception ex) + { + throw new InvalidOperationException($"查询所有信息异常:{ex.Message}"); + } + } + + /// + /// 根据Id查询实体 + /// + /// + /// + /// + public T Query(object objId) + { + if (objId == null) + { + throw new ArgumentNullException($"根据Id查询实体信息异常:Id参数为空"); + } + try + { + return _rep.GetById(objId); + } + catch (Exception ex) + { + throw new InvalidOperationException($"根据Id查询实体信息异常:{ex.Message}"); + } + } + + /// + /// 根据表达式查询 + /// + /// + /// + /// + public List Query(Expression> whereExpression) + { + if (whereExpression == null) + { + throw new ArgumentNullException($"根据表达式查询实体信息异常:表达式参数为空"); + } + try + { + return _rep.GetList(whereExpression); + } + catch (Exception ex) + { + throw new InvalidOperationException($"根据表达式查询实体信息异常:{ex.Message}"); + } + } + + /// + /// 根据表达式排序查询 + /// + /// + /// + /// + /// + /// + public List Query(Expression> whereExpression, Expression> orderByExpression, bool isAsc = true) + { + if (whereExpression == null) + { + throw new ArgumentNullException($"根据表达式排序查询信息异常:条件表达式参数为空"); + } + + if (orderByExpression == null) + { + throw new ArgumentNullException($"根据表达式排序查询信息异常:排序表达式参数为空"); + } + + try + { + return _rep.AsQueryable().OrderByIF(orderByExpression != null, orderByExpression, isAsc ? OrderByType.Asc : OrderByType.Desc).WhereIF(whereExpression != null, whereExpression).ToList(); + } + catch (Exception ex) + { + throw new InvalidOperationException($"根据表达式排序查询信息异常:{ex.Message}"); + } + } + } +} \ No newline at end of file diff --git a/Sln.Iot.Repository/service/base/IBaseService.cs b/Sln.Iot.Repository/service/base/IBaseService.cs new file mode 100644 index 0000000..ce2664a --- /dev/null +++ b/Sln.Iot.Repository/service/base/IBaseService.cs @@ -0,0 +1,130 @@ +#region << 版 本 注 释 >> + +/*-------------------------------------------------------------------- +* 版权所有 (c) 2025 WenJY 保留所有权利。 +* CLR版本:4.0.30319.42000 +* 机器名称:Mr.Wen's MacBook Pro +* 命名空间:Sln.Iot.Repository.service.base +* 唯一标识:4F2637EA-9206-45C6-92B8-E2CDBA5A1B22 +* +* 创建者:WenJY +* 电子邮箱: +* 创建时间:2025-04-11 13:26:02 +* 版本:V1.0.0 +* 描述: +* +*-------------------------------------------------------------------- +* 修改人: +* 时间: +* 修改说明: +* +* 版本:V1.0.0 +*--------------------------------------------------------------------*/ + +#endregion << 版 本 注 释 >> + +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace Sln.Iot.Repository.service.@base +{ + public interface IBaseService where T : class + { + /// + /// 添加实体信息 + /// + /// + /// + bool Insert(T model); + + /// + /// 批量添加实体集合 + /// + /// + /// + bool Insert(List lisT); + + /// + /// 根据id 删除信息 + /// + /// + /// + bool DeleteById(object id); + + /// + /// 根据实体删除信息 + /// + /// + /// + bool Delete(T model); + + /// + /// 根据实体集合批量删除信息 + /// + /// + /// + bool Deletes(List entitys); + + /// + /// 根据实体更新信息 + /// + /// + /// + bool Update(T model); + + /// + /// 批量更新实体集合信息 + /// + /// + /// + bool Update(List entitys); + + /// + /// 根据Where条件更新实体信息 + /// + /// + /// + /// + bool Update(T entity, string strWhere); + + /// + /// 根据实体更新指定列 + /// + /// + /// + /// + /// + /// + bool Update(T entity, List lstColumns = null, List lstIgnoreColumns = null, string strWhere = ""); + + /// + /// 查询所有信息 + /// + /// + List Query(); + + /// + /// 根据Id查询实体 + /// + /// + /// + T Query(object objId); + + /// + /// 根据表达式查询 + /// + /// + /// + List Query(Expression> whereExpression); + + /// + /// 根据表达式排序查询 + /// + /// 查询条件 + /// 排序条件 + /// 是否正序 + /// + List Query(Expression> whereExpression, Expression> orderByExpression, bool isAsc = true); + } +} \ No newline at end of file diff --git a/Sln.Iot.Repository/service/split/MonitorIdToSplitService.cs b/Sln.Iot.Repository/service/split/MonitorIdToSplitService.cs new file mode 100644 index 0000000..fbe1db9 --- /dev/null +++ b/Sln.Iot.Repository/service/split/MonitorIdToSplitService.cs @@ -0,0 +1,97 @@ +#region << 版 本 注 释 >> + +/*-------------------------------------------------------------------- +* 版权所有 (c) 2025 WenJY 保留所有权利。 +* CLR版本:4.0.30319.42000 +* 机器名称:Mr.Wen's MacBook Pro +* 命名空间:Sln.Iot.Repository.service.split +* 唯一标识:2D1DE17A-79B9-48D8-BB98-60E89BEC711A +* +* 创建者:WenJY +* 电子邮箱: +* 创建时间:2025-04-27 10:10:14 +* 版本:V1.0.0 +* 描述: +* +*-------------------------------------------------------------------- +* 修改人: +* 时间: +* 修改说明: +* +* 版本:V1.0.0 +*--------------------------------------------------------------------*/ + +#endregion << 版 本 注 释 >> + +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using SqlSugar; + +namespace Sln.Iot.Repository.service.split +{ + /// + /// 自定义分表策略:根据传感器 ID 进行分表 + /// + public class MonitorIdToSplitService:ISplitTableService + { + /// + /// 返回数据库中所有分表 + /// + /// + /// + /// + /// + public List GetAllTables(ISqlSugarClient db, EntityInfo EntityInfo, List tableInfos) + { + List result = new List(); + foreach (var item in tableInfos) + { + if (item.Name.Contains("record_iotenv_instant")) //区分标识如果不用正则符复杂一些,防止找错表 + { + SplitTableInfo data = new SplitTableInfo() + { + TableName = item.Name //要用item.name不要写错了 + }; + result.Add(data); + } + } + return result.OrderBy(it=>it.TableName).ToList();//打断点看一下有没有查出所有分表 + } + + /// + /// 获取分表字段的值 + /// + /// + /// + /// + /// + /// + public object GetFieldValue(ISqlSugarClient db, EntityInfo entityInfo, SplitType splitType, object entityValue) + { + var splitColumn = entityInfo.Columns.FirstOrDefault(it => it.PropertyInfo.GetCustomAttribute() != null); + var value = splitColumn.PropertyInfo.GetValue(entityValue, null); + return value; + } + /// + /// 默认表名 + /// + /// + /// + /// + public string GetTableName(ISqlSugarClient db, EntityInfo entityInfo) + { + return entityInfo.DbTableName; + } + + public string GetTableName(ISqlSugarClient db, EntityInfo entityInfo, SplitType type) + { + return entityInfo.DbTableName;//目前模式少不需要分类(自带的有 日、周、月、季、年等进行区分) + } + + public string GetTableName(ISqlSugarClient db, EntityInfo entityInfo, SplitType splitType, object fieldValue) + { + return entityInfo.DbTableName + "_"+fieldValue; //根据值按首字母 + } + } +} \ No newline at end of file diff --git a/Sln.Iot.Serilog/Class1.cs b/Sln.Iot.Serilog/Class1.cs new file mode 100644 index 0000000..4416261 --- /dev/null +++ b/Sln.Iot.Serilog/Class1.cs @@ -0,0 +1,5 @@ +namespace Sln.Iot.Serilog; + +public class Class1 +{ +} \ No newline at end of file diff --git a/Sln.Iot.Serilog/SerilogExtensions.cs b/Sln.Iot.Serilog/SerilogExtensions.cs new file mode 100644 index 0000000..48ca446 --- /dev/null +++ b/Sln.Iot.Serilog/SerilogExtensions.cs @@ -0,0 +1,65 @@ +#region << 版 本 注 释 >> + +/*-------------------------------------------------------------------- +* 版权所有 (c) 2025 WenJY 保留所有权利。 +* CLR版本:4.0.30319.42000 +* 机器名称:Mr.Wen's MacBook Pro +* 命名空间:Sln.Iot.Serilog +* 唯一标识:77C04261-6831-4763-A6E1-C6B2B66D4DD9 +* +* 创建者:WenJY +* 电子邮箱: +* 创建时间:2025-04-11 11:12:30 +* 版本:V1.0.0 +* 描述: +* +*-------------------------------------------------------------------- +* 修改人: +* 时间: +* 修改说明: +* +* 版本:V1.0.0 +*--------------------------------------------------------------------*/ + +#endregion << 版 本 注 释 >> + +using System; +using System.IO; +using Microsoft.Extensions.DependencyInjection; +using Serilog; +using Sln.Iot.Config; + +namespace Sln.Iot.Serilog +{ + public static class SerilogExtensions + { + public static void UseSerilogExtensions(this IServiceProvider service) + { + //启用Serilog中间件 + + + #region 通过配置文件读取日志存放位置 + var appConfig = service.GetService(); + var logPath = Path.Combine(appConfig.logPath, "Logs"); + #endregion + + Log.Logger = new LoggerConfiguration().MinimumLevel.Information().WriteTo.Console() + .WriteTo.Logger(lc => lc + .Filter.ByIncludingOnly(logEvent => 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)) + .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)) + .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)) + .CreateLogger(); + + + + } + } +} \ No newline at end of file diff --git a/Sln.Iot.Serilog/SerilogHelper.cs b/Sln.Iot.Serilog/SerilogHelper.cs new file mode 100644 index 0000000..7a00349 --- /dev/null +++ b/Sln.Iot.Serilog/SerilogHelper.cs @@ -0,0 +1,105 @@ +#region << 版 本 注 释 >> + +/*-------------------------------------------------------------------- +* 版权所有 (c) 2025 WenJY 保留所有权利。 +* CLR版本:4.0.30319.42000 +* 机器名称:Mr.Wen's MacBook Pro +* 命名空间:Sln.Iot.Serilog +* 唯一标识:15731D3E-0D48-41B6-B77B-A4CC592B4939 +* +* 创建者:WenJY +* 电子邮箱: +* 创建时间:2025-04-11 11:09:14 +* 版本:V1.0.0 +* 描述: +* +*-------------------------------------------------------------------- +* 修改人: +* 时间: +* 修改说明: +* +* 版本:V1.0.0 +*--------------------------------------------------------------------*/ + +#endregion << 版 本 注 释 >> + +using System; +using Serilog; + +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? Error_logger = Log.ForContext("Module", "Error"); + private readonly ILogger? Camera_logger = Log.ForContext("Module", "Camera"); + + /// + /// Info日志 + /// + /// + public void Info(string msg) + { + if (Info_logger != null) + { + this.Info_logger.Information(msg); + } + } + + /// + /// Plc日志 + /// + /// + public void Plc(string msg) + { + if (Plc_logger != null) + { + this.Plc_logger.Information(msg); + } + } + + /// + /// 相机日志 + /// + /// + public void Camera(string msg) + { + if (Camera_logger != null) + { + this.Camera_logger.Information(msg); + } + } + + /// + /// Error日志 + /// + /// + /// + public void Error(string msg, Exception ex = null) + { + if (!string.IsNullOrEmpty(msg) && ex == null) + { + this.Error_logger.Information("【附加信息】 : {0}
", new object[] { msg }); + } + else if (!string.IsNullOrEmpty(msg) && ex != null) + { + string errorMsg = BeautyErrorMsg(ex); + this.Error_logger.Information("【附加信息】 : {0}
{1}", new object[] { msg, errorMsg }); + } + else if (string.IsNullOrEmpty(msg) && ex != null) + { + string errorMsg = BeautyErrorMsg(ex); + this.Error_logger.Information(errorMsg); + } + } + + private string BeautyErrorMsg(Exception ex) + { + string errorMsg = string.Format("【异常类型】:{0}
【异常信息】:{1}
【堆栈调用】:{2}", new object[] { ex.GetType().Name, ex.Message, ex.StackTrace }); + errorMsg = errorMsg.Replace("\r\n", "
"); + errorMsg = errorMsg.Replace("位置", "位置"); + return errorMsg; + } + } +} \ No newline at end of file diff --git a/Sln.Iot.Serilog/Sln.Iot.Serilog.csproj b/Sln.Iot.Serilog/Sln.Iot.Serilog.csproj new file mode 100644 index 0000000..b540308 --- /dev/null +++ b/Sln.Iot.Serilog/Sln.Iot.Serilog.csproj @@ -0,0 +1,18 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + + diff --git a/Sln.Iot.Socket/Adapter/BufferRequestInfo.cs b/Sln.Iot.Socket/Adapter/BufferRequestInfo.cs new file mode 100644 index 0000000..2993e0b --- /dev/null +++ b/Sln.Iot.Socket/Adapter/BufferRequestInfo.cs @@ -0,0 +1,72 @@ +#region << 版 本 注 释 >> + +/*-------------------------------------------------------------------- +* 版权所有 (c) 2025 WenJY 保留所有权利。 +* CLR版本:4.0.30319.42000 +* 机器名称:Mr.Wen's MacBook Pro +* 命名空间:Sln.Iot.Socket.Adapter +* 唯一标识:8A1D4D97-E419-4A49-B921-7FF76B190F45 +* +* 创建者:WenJY +* 电子邮箱: +* 创建时间:2025-04-11 13:55:38 +* 版本:V1.0.0 +* 描述: +* +*-------------------------------------------------------------------- +* 修改人: +* 时间: +* 修改说明: +* +* 版本:V1.0.0 +*--------------------------------------------------------------------*/ + +#endregion << 版 本 注 释 >> + +using TouchSocket.Core; + +namespace Sln.Iot.Socket.Adapter +{ + public class BufferRequestInfo: IRequestInfo + { + /// + /// 帧头 0x68 1个字 + /// + public byte[] header { get; internal set; } + + /// + /// 采集设备编号,由采集器类型(1个字)、采集器地址(2个字)组成 + /// + public string ColletEquipCode { get; internal set; } + + /// + /// 自定义属性,DataType + /// + public byte DataType { get; internal set; } + + /// + /// Buffer长度 + /// + public int BufferLength { get; internal set; } + + /// + /// 内容体 + /// + public byte[] Body { get; internal set; } + + /// + /// 校验位 1个字:从帧头到数据累加CS校验 + /// + public byte CheckBit { get; internal set; } + + /// + /// 帧尾 0x16 1个字 + /// + public byte[] Tail { get; internal set; } + + /// + /// 原始Buffer + /// + public ByteBlock buffer { get; internal set; } + } +} \ No newline at end of file diff --git a/Sln.Iot.Socket/Adapter/CustomDataHandlingAdapter.cs b/Sln.Iot.Socket/Adapter/CustomDataHandlingAdapter.cs new file mode 100644 index 0000000..a08238c --- /dev/null +++ b/Sln.Iot.Socket/Adapter/CustomDataHandlingAdapter.cs @@ -0,0 +1,117 @@ +#region << 版 本 注 释 >> + +/*-------------------------------------------------------------------- +* 版权所有 (c) 2025 WenJY 保留所有权利。 +* CLR版本:4.0.30319.42000 +* 机器名称:Mr.Wen's MacBook Pro +* 命名空间:Sln.Iot.Socket.Adapter +* 唯一标识:50003A25-42CE-44A7-9940-FFDE3BD0A52A +* +* 创建者:WenJY +* 电子邮箱: +* 创建时间:2025-04-11 13:56:24 +* 版本:V1.0.0 +* 描述: +* +*-------------------------------------------------------------------- +* 修改人: +* 时间: +* 修改说明: +* +* 版本:V1.0.0 +*--------------------------------------------------------------------*/ + +#endregion << 版 本 注 释 >> + +using System; +using System.Text; +using TouchSocket.Core; + +namespace Sln.Iot.Socket.Adapter +{ + public class CustomDataHandlingAdapter:CustomDataHandlingAdapter + { + protected override FilterResult Filter(in ByteBlock byteBlock, bool beCached, ref BufferRequestInfo request, ref int tempCapacity) + { + CacheTimeoutEnable = true; + CacheTimeout = new TimeSpan(0, 0, 0, 0, 5000); + int pos = byteBlock.Pos; + try + { + if (byteBlock.CanReadLen < 5) + { + return FilterResult.Cache; + } + + byteBlock.Read(out byte[] header, 1); + + byteBlock.Read(out byte[] deviceType, 1); + + byteBlock.Read(out byte[] deviceId, 2); + + string DeviceType = Encoding.ASCII.GetString(deviceType); + string collectEquipCode = DeviceType + this.ConverToString(deviceId); + + byteBlock.Pos += 3; + + byteBlock.Read(out byte[] dataType, 1); + + byteBlock.Read(out byte[] lengthByte, 2); + + string hexString = BitConverter.ToString(lengthByte).Replace("-", ""); + int bodyLength = Convert.ToInt32(hexString, 16); + + if (bodyLength > byteBlock.CanReadLen) + { + byteBlock.Pos = pos; //body数据不足。回退游标 + return FilterResult.Cache; + } + else + { + byteBlock.Read(out byte[] body, bodyLength); + byteBlock.Read(out byte[] check, 1); + byteBlock.Read(out byte[] tail, 1); + + request = new BufferRequestInfo() + { + header = header, + ColletEquipCode = collectEquipCode, + DataType = dataType[0], + BufferLength = bodyLength, + Body = body, + CheckBit = check[0], + Tail = tail, + buffer = byteBlock + }; + + return FilterResult.Success; + } + }catch (Exception ex) + { + Logger.Error("FilterResult"+ex.Message); + byteBlock.Pos = pos; //body数据不足。回退游标 + return FilterResult.Cache; + } + + } + + private string ConverToString(byte[] data) + { + string str; + StringBuilder stb = new StringBuilder(); + for (int i = 0; i < data.Length; i++) + { + if ((int)data[i] > 15) + { + stb.Append(Convert.ToString(data[i], 16).ToUpper()); //添加字符串 + } + else //如果是小于0F需要加个零 + { + stb.Append("0" + Convert.ToString(data[i], 16).ToUpper()); + } + } + str = stb.ToString(); + return str; + } + } +} \ No newline at end of file diff --git a/Sln.Iot.Socket/Class1.cs b/Sln.Iot.Socket/Class1.cs new file mode 100644 index 0000000..3660e6e --- /dev/null +++ b/Sln.Iot.Socket/Class1.cs @@ -0,0 +1,5 @@ +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 new file mode 100644 index 0000000..0990bfc --- /dev/null +++ b/Sln.Iot.Socket/Sln.Iot.Socket.csproj @@ -0,0 +1,17 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + diff --git a/Sln.Iot.Socket/TcpServer.cs b/Sln.Iot.Socket/TcpServer.cs new file mode 100644 index 0000000..87e3e2a --- /dev/null +++ b/Sln.Iot.Socket/TcpServer.cs @@ -0,0 +1,127 @@ +#region << 版 本 注 释 >> + +/*-------------------------------------------------------------------- +* 版权所有 (c) 2025 WenJY 保留所有权利。 +* CLR版本:4.0.30319.42000 +* 机器名称:Mr.Wen's MacBook Pro +* 命名空间:Sln.Iot.Socket +* 唯一标识:6D821766-EAFA-4C51-A757-8786E77645AC +* +* 创建者:WenJY +* 电子邮箱: +* 创建时间:2025-04-11 13:51:11 +* 版本:V1.0.0 +* 描述: +* +*-------------------------------------------------------------------- +* 修改人: +* 时间: +* 修改说明: +* +* 版本:V1.0.0 +*--------------------------------------------------------------------*/ + +#endregion << 版 本 注 释 >> + +using System; +using Sln.Iot.Serilog; +using Sln.Iot.Socket.Adapter; +using TouchSocket.Core; +using TouchSocket.Sockets; + +namespace Sln.Iot.Socket +{ + public class TcpServer + { + private readonly SerilogHelper _logger; + private readonly TcpService _service; + + public TcpServer(SerilogHelper logger, TcpService service) + { + _logger = logger; + _service = service; + } + + /// + /// 接收客户端指令委托 + /// + public delegate void ReceivedClientBuffer(byte[] buffer); + public event ReceivedClientBuffer? ReceivedClientBufferEvent; + + public delegate void RefreshClientInfo(TcpService tcpService); + public event RefreshClientInfo? RefreshClientInfoEvent; + + [Obsolete("Obsolete")] + public delegate void ReceivedBufferRequestInfo(SocketClient client,BufferRequestInfo requestInfo); + public event ReceivedBufferRequestInfo ReceivedBufferRequestInfoEvent; + + public void Init(int serverPort) + { + try + { + _service.Connecting = (client, e) => { + _logger.Info($"客户端{client.IP}正在接入服务"); + return EasyTask.CompletedTask; + }; + _service.Connected = (client, e) => { + _logger.Info($"客户端{client.IP}接入服务成功"); + RefreshClientInfoEvent?.Invoke(_service); + return EasyTask.CompletedTask; + }; + _service.Disconnected = (client, e) => { + _logger.Info($"客户端{client.IP}断开连接"); + RefreshClientInfoEvent?.Invoke(_service); + return EasyTask.CompletedTask; + }; + _service.Received = (client, e) => + { + if (e.RequestInfo is BufferRequestInfo request) + { + 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; + }; + + _service.Setup(new TouchSocketConfig()//载入配置 + .SetListenIPHosts(new IPHost[] { new IPHost($"0.0.0.0:{serverPort}") }) + .SetTcpDataHandlingAdapter(() => new CustomDataHandlingAdapter()) + .ConfigureContainer(a =>//容器的配置顺序应该在最前面 + { + + a.AddConsoleLogger(); + }) + .ConfigurePlugins(a => + { + //自定义插件 + })); + _service.Start(); + _logger.Info($"TcpServer启动成功,监听端口:{serverPort}"); + } + catch (Exception ex) + { + //throw new InvalidOperationException($"TcpServer启动异常:{ex.Message}"); + _logger.Error($"TcpServer启动异常:{ex.Message}"); + } + + } + + /// + /// 向所有客户端发送心跳 + /// + public void SendHeartBeat() + { + var clients = _service.SocketClients.GetClients(); + foreach (var item in clients) + { + _service.Send(item.Id,"heartbeat"); + } + } + } +} \ No newline at end of file diff --git a/Sln.Iot.sln b/Sln.Iot.sln new file mode 100644 index 0000000..c7f584f --- /dev/null +++ b/Sln.Iot.sln @@ -0,0 +1,67 @@ + +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}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "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}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "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}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "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}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sln.Iot.Socket", "Sln.Iot.Socket\Sln.Iot.Socket.csproj", "{5B7C6367-7B41-48A6-9A71-2F191CE14000}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {2140AD68-D4CE-44EC-B9D3-20D18EB59F9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2140AD68-D4CE-44EC-B9D3-20D18EB59F9D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2140AD68-D4CE-44EC-B9D3-20D18EB59F9D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2140AD68-D4CE-44EC-B9D3-20D18EB59F9D}.Release|Any CPU.Build.0 = Release|Any CPU + {F59EB67D-66FD-43B9-B6CC-46BF25202C21}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F59EB67D-66FD-43B9-B6CC-46BF25202C21}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F59EB67D-66FD-43B9-B6CC-46BF25202C21}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F59EB67D-66FD-43B9-B6CC-46BF25202C21}.Release|Any CPU.Build.0 = Release|Any CPU + {2E10DED8-5F53-4ED9-892F-B0007B85806E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2E10DED8-5F53-4ED9-892F-B0007B85806E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2E10DED8-5F53-4ED9-892F-B0007B85806E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2E10DED8-5F53-4ED9-892F-B0007B85806E}.Release|Any CPU.Build.0 = Release|Any CPU + {899D8A81-D3E3-4599-8A8C-D60280A777F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {899D8A81-D3E3-4599-8A8C-D60280A777F3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {899D8A81-D3E3-4599-8A8C-D60280A777F3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {899D8A81-D3E3-4599-8A8C-D60280A777F3}.Release|Any CPU.Build.0 = Release|Any CPU + {503E7EAE-6323-4CE2-AAE4-C6A7CBDFC4B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {503E7EAE-6323-4CE2-AAE4-C6A7CBDFC4B2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {503E7EAE-6323-4CE2-AAE4-C6A7CBDFC4B2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {503E7EAE-6323-4CE2-AAE4-C6A7CBDFC4B2}.Release|Any CPU.Build.0 = Release|Any CPU + {DA193E49-8B4A-4C8D-B44E-844E22983DA8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DA193E49-8B4A-4C8D-B44E-844E22983DA8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DA193E49-8B4A-4C8D-B44E-844E22983DA8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DA193E49-8B4A-4C8D-B44E-844E22983DA8}.Release|Any CPU.Build.0 = Release|Any CPU + {A9CCC9F6-BE1C-4B73-AFBF-83D363D7F64F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A9CCC9F6-BE1C-4B73-AFBF-83D363D7F64F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A9CCC9F6-BE1C-4B73-AFBF-83D363D7F64F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A9CCC9F6-BE1C-4B73-AFBF-83D363D7F64F}.Release|Any CPU.Build.0 = Release|Any CPU + {5B7C6367-7B41-48A6-9A71-2F191CE14000}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {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 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {47F07B24-817F-4612-9BD3-D8AD5F4B31C3} + EndGlobalSection +EndGlobal diff --git a/Sln.Iot/Program.cs b/Sln.Iot/Program.cs new file mode 100644 index 0000000..c34cc9e --- /dev/null +++ b/Sln.Iot/Program.cs @@ -0,0 +1,108 @@ +using System.Reflection; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Sln.Iot.Business; +using Sln.Iot.Business.@base; +using Sln.Iot.Config; +using Sln.Iot.Repository; +using Sln.Iot.Serilog; +using Sln.Iot.Socket; +using TouchSocket.Sockets; + +namespace Sln.Iot +{ + internal class Program + { + public static IServiceProvider? ServiceProvider = null; + + static async Task Main(string[] args) + { + var services = new ServiceCollection(); + ConfigureServices(services); + ServiceProvider = services.BuildServiceProvider(); + ServiceProvider.UseSerilogExtensions(); + + var appConfig = ServiceProvider.GetService(); + var log = ServiceProvider.GetService(); + log.Info($"系统启动成功,日志存放位置:{appConfig.logPath}"); + + var _server = ServiceProvider.GetService(); + _server.Init(appConfig.listernPort); + + _server.ReceivedBufferRequestInfoEvent += (client, info) => + { + bool isRet = false; + BaseBusiness _business = null; + int bodyLength = 0; + switch (info.DataType) + { + case 0x08: //校时指令 + _business = ServiceProvider.GetService(); + break; + case 0x21: //登录指令 + _business = ServiceProvider.GetService(); + break; + case 0x24: //心跳指令 + _business = ServiceProvider.GetService(); + break; + case 0x83: //电能指令 + bodyLength = 70; + _business = ServiceProvider.GetService(); + break; + case 0x84: //水 + bodyLength = 58; + _business = ServiceProvider.GetService(); + break; + case 0x85: //物联网环境 + bodyLength = info.BufferLength; + _business = ServiceProvider.GetService(); + break; + default: + break; + } + + if (_business != null) + { + Parallel.Invoke( + () => _business.ResponseHandle(client, info.buffer), + () => _business.BufferAnalysis(client, info, bodyLength) + ); + } + }; + + await Task.Delay(-1); + } + + private static void ConfigureServices(IServiceCollection services) + { + services.AddSingleton(provider => + { + var configurationBuilder = new ConfigurationBuilder() + .SetBasePath(AppDomain.CurrentDomain.BaseDirectory) + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true); + IConfiguration configuration = configurationBuilder.Build(); + var ap = configuration.GetSection("AppConfig").Get(); + return ap; + }); + + Assembly[] assemblies = + { + Assembly.LoadFrom("Sln.Iot.Repository.dll"), + Assembly.LoadFrom("Sln.Iot.Socket.dll"), + Assembly.LoadFrom("Sln.Iot.Common.dll"), + Assembly.LoadFrom("Sln.Iot.Business.dll"), + }; + + services.Scan(scan => scan.FromAssemblies(assemblies) + .AddClasses() + .AsImplementedInterfaces() + .AsSelf() + .WithTransientLifetime()); + + services.AddSingleton(typeof(SerilogHelper)); + services.AddSingleton(typeof(TcpService)); + + services.AddSqlSugarSetup(); + } + } +} \ No newline at end of file diff --git a/Sln.Iot/Sln.Iot.csproj b/Sln.Iot/Sln.Iot.csproj new file mode 100644 index 0000000..ee5f903 --- /dev/null +++ b/Sln.Iot/Sln.Iot.csproj @@ -0,0 +1,32 @@ + + + + Exe + net6.0 + enable + enable + zh-Hans + + + + + + + + + + + + + + + + + + + + PreserveNewest + + + + diff --git a/Sln.Iot/appsettings.json b/Sln.Iot/appsettings.json new file mode 100644 index 0000000..fa27537 --- /dev/null +++ b/Sln.Iot/appsettings.json @@ -0,0 +1,19 @@ +{ + "AppConfig": { + "logPath": "\\\\Mac\\Home\\Public\\WorkSpace\\Mesnac\\项目资料\\IOT物联网数据采集\\日志信息", + "listernPort": 7001, + "virtualFlag": true, + "virtualValue": 9999999, + "electricTimeInterval": 1, //电力数据采集间隔,小于间隔数据不保存,单位:分钟 + "fluidTimeInterval": 1, //流体数据采集间隔,小于间隔数据不保存,单位:分钟 + "SqlConfig": [ + { + "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;" + } + ], + } +} diff --git a/Sln.Iot/readme.md b/Sln.Iot/readme.md new file mode 100644 index 0000000..823928f --- /dev/null +++ b/Sln.Iot/readme.md @@ -0,0 +1,210 @@ +**RKKC1604-N2型动力环境监控一体机.上行通讯协议** + +**1.终端主动发送登录指令(0x21/0xA1)** + +终端发起有效登录指令后,上位机软件才能对终端发送的数据进行解析和存库。 + +| 类别 | 数据内容 | 数据长度 | 备注 | +| --- | --- | --- | --- | +| 帧开始 | 0x68 | 1个字节 | | +| 采集器类型 | 0x45 | 1个字节 | 0x45-采集终端类型 | +| 采集器地址 | 0x0001~0x9999 | 2个字节 | 高位在前低位在后,BCD码形式 | +| 命令序列号 | | 2个字节 | 高位在前低位在后,命令帧的序列号 | +| 起始符 | 0x68 | 1个字节 | | +| 控制码 | 0x21 | 1个字节 | 登录指令标识码 | +| 数据长度 | 0x00 0x03 | 2个字节 | 固定为0x00,0x03 | +| 数据域 | 0x12 0x34 0x56 | 3个字节 | 固定为 0x12 0x34 0x56 | +| 校验码 | CS | 1个字节 | 从帧开始到数据域最后一位的数据累加CS校验 | +| 结束符 | 0x16 | 1个字节 | 1个字节 | + +举例: + +终端发送指令: + +68 45 01 02 00 BF 68 21 00 03 12 34 56 97 16 + +上位机返回指令: + +68 45 01 02 00 BF 68 A1 00 00 78 16 + +**2.上位机对采集终端设备校时指令(0x08/0x88)** + +为保证现场采集设备与服务器之间的时间同步,采集终端登录服务器成功后服务器需要对采集设备进行校时。在后续正常工作时,为纠正设备之间的时间偏差,建议每隔一段时间(比如8个小时)对现场终端进行一次校时。 + +| 类别 | 数据内容 | 数据长度 | 备注 | +| --- | --- | --- | --- | +| 帧开始 | 0x68 | 1个字节 | | +| 采集器类型 | 0x45 | 1个字节 | 0x45-采集终端类型 | +| 采集器地址 | 0x0001~0x9999 | 2个字节 | 高位在前低位在后,BCD码形式 | +| 命令序列号 | | 2个字节 | 高位在前低位在后,命令帧的序列号 | +| 起始符 | 0x68 | 1个字节 | | +| 控制码 | 0x08 | 1个字节 | 设置终端参数标识码 | +| 数据长度 | 0x00 0x0D | 2个字节 | 固定为0x00,0x0D | +| 数据域 | 0x00 | 1个字节 | 固定为0x00 | +| | 0x00 | 1个字节 | 固定为0x00 | +| | 0x31 0x32 0x33 | 3个字节 | 密码,固定为0x31 0x32 0x33 | +| | 0x80 0x30 | 2个字节 | 时间标识码,固定为0x80 0x30 | +| | 秒-分-时-日-月-年 | 6个字节 | 具体时间:秒-分-时-日-月-年 | +| 校验码 | CS | 1个字节 | 从帧开始到数据长度数据累加CS校验 | +| 结束符 | 0x16 | 1个字节 | 1个字节 | + +举例: + +上位机发送指令: + +68 45 01 02 40 00 68 08 00 0D 00 00 31 32 33 80 30 22 39 14 16 09 15 56 16 + +终端返回指令: + +68 45 01 02 40 00 68 88 00 04 00 80 30 00 94 16 + +**3.终端主动发送心跳指令(0x24/0xA4)** + +通过心跳指令判断采集设备与上位机直接的通讯连接状态,采集设备通过定时发送心跳指令,在有效时间内若得到上位机的响应,则判断出终端与上位机之间处于有效连接中,否则若在有效时间捏没有得到上位机的响应,则判断出终端没有连接上上位机,终端则发起登录上位机服务器的流程和指令。 + +| 类别 | 数据内容 | 数据长度 | 备注 | +| --- | --- | --- | --- | +| 帧开始 | 0x68 | 1个字节 | | +| 采集器类型 | 0x45 | 1个字节 | 0x45-采集终端类型 | +| 采集器地址 | 0x0001~0x9999 | 2个字节 | 高位在前低位在后,BCD码形式 | +| 命令序列号 | | 2个字节 | 高位在前低位在后,命令帧的序列号 | +| 起始符 | 0x68 | 1个字节 | | +| 控制码 | 0x24 | 1个字节 | 心跳指令标识码 | +| 数据长度 | | 2个字节 | 固定为0x00,0x00 | +| 校验码 | CS | 1个字节 | 从帧开始到数据长度数据累加CS校验 | +| 结束符 | 0x16 | 1个字节 | 1个字节 | + +举例: + +终端发送指令: + +68 45 00 01 00 01 68 24 00 00 3B 16 + +上位机返回指令: + +68 45 00 01 00 01 68 A4 00 00 BB 16 + +**4.终端主动上传实时数据指令** + +采集设备根据设定的时间间隔,定时对现场仪表进行数据采集,并将采集到的数据综合处理后主动上传到上位机平台。 + +| 类别 | 数据内容 | 数据长度 | 备注 | +| --- | --- | --- | --- | +| 帧开始 | 0x68 | 1个字节 | | +| 采集器类型 | 0x45 | 1个字节 | 0x45-采集终端类型 | +| 采集器地址 | 0x0001~0x9999 | 2个字节 | 高位在前低位在后,BCD码形式 | +| 命令序列号 | | 2个字节 | 高位在前低位在后,命令帧的序列号 | +| 起始符 | 0x68 | 1个字节 | | +| 控制码 | 0x83/0x84/0x85/0x86/0x87 | 1个字节 | 0x83-电力;0x84-压缩空气、水;0x85-温湿度、照度;0x86-开关量IO状态;0x87-蒸汽 | +| 数据长度 | N个仪表*1个仪表的长度 | 2个字节 | 1条仪表数据数据长度L +0x83:L=2(表序号)+8*6(8项数据项)+8(采集时间) = 58个字节 +0x84:L=2(表序号)+8*6(8项数据项)+8(采集时间) = 58个字节 +0x85: L=2(表序号)+4*6(8项数据项)+8(采集时间) = 34个字节 +0x86: L=2(表序号)+16(16路IO输入)+8(采集时间) = 26个字节,N=1 +0x87:L=2(表序号)+8*6(8项数据项)+8(采集时间) = 58个字节 | +| 数据域 | 0x00 | N*L个字节 | | +| 校验码 | CS | 1个字节 | 从帧开始到数据长度数据累加CS校验 | +| 结束符 | 0x16 | 1个字节 | 1个字节 | + +控制码: + +| 序号 | 终端主动发送数据控制码 | 上位机返回对应控制码 | 备注 | +| --- | --- | --- | --- | +| 1 | 0x83 | 0xB3 | | +| 2 | 0x84 | 0xB4 | | +| 3 | 0x85 | 0xB5 | | +| 3 | 0x85 | 0xB6 | | + +电力采集数据域举例: + +![](attachment:fecb539a-3dd8-4e4e-8761-046c06386784:image1.png) + +举例: + +终端发生命令帧: + +68 45 00 01 01 FF 68 83 00 3A 01 00 8E 11 61 4E 4B 3C 8E 12 61 4E 4B 3C 8E 13 61 4E 4B 3C 8E 21 61 4E 4B 3C 8E 22 61 4E 4B 3C 8E 23 61 4E 4B 3C B6 50 3F 7C AC 08 90 10 61 4E 4B 3C 80 30 50 40 16 28 04 20 F5 16 + +上位机返回: + +68 45 00 01 0A 3F 68 B3 00 00 12 16 + +**5.终端主动上传历史数据指令** + +在采集设备与上位机有效连接的期间内,采集设备对存储在内部存储区内的历史数据进行批量上传工作。 + +| 类别 | 数据内容 | 数据长度 | 备注 | +| --- | --- | --- | --- | +| 帧开始 | 0x68 | 1个字节 | | +| 采集器类型 | 0x45 | 1个字节 | 0x45-采集终端类型 | +| 采集器地址 | 0x0001~0x9999 | 2个字节 | 高位在前低位在后,BCD码形式 | +| 命令序列号 | | 2个字节 | 高位在前低位在后,命令帧的序列号 | +| 起始符 | 0x68 | 1个字节 | | +| 控制码 | 0x83/0x84/0x85/0x86/0x87 | 1个字节 | 0x83-电力;0x84-压缩空气、水;0x85-温湿度、照度;0x86-开关量IO状态;0x87-蒸汽 | +| 数据长度 | N个仪表*1个仪表的长度 | 2个字节 | 1条仪表数据数据长度L +0x93:L=2(表序号)+8*6(8项数据项)+8(采集时间) = 58个字节 +0x94:L=2(表序号)+8*6(8项数据项)+8(采集时间) = 58个字节 +0x95: L=2(表序号)+4*6(4项数据项)+8(采集时间) = 34个字节 +0x96: L=2(表序号)+16(16路IO输入)+8(采集时间) = 26个字节,N=1 +0x97:L=2(表序号)+8*6(8项数据项)+8(采集时间) = 58个字节 | +| 数据域 | 0x00 | N*L个字节 | | +| 校验码 | CS | 1个字节 | 从帧开始到数据长度数据累加CS校验 | +| 结束符 | 0x16 | 1个字节 | 1个字节 | + +控制码: + +| 序号 | 终端主动发送数据控制码 | 上位机返回对应控制码 | 备注 | +| --- | --- | --- | --- | +| 1 | 0x93 | 0xC3 | | +| 2 | 0x94 | 0xC4 | | +| 3 | 0x95 | 0xC5 | | +| 4 | 0x96 | 0xC6 | | +| 5 | 0x97 | 0xC7 | | + +电力采集数据域举例: + +![](attachment:fecb539a-3dd8-4e4e-8761-046c06386784:image1.png) + +举例: + +终端发生命令帧: + +68 45 00 01 01 FF 68 93 00 3A 01 00 8E 11 61 4E 4B 3C 8E 12 61 4E 4B 3C 8E 13 61 4E 4B 3C 8E 21 61 4E 4B 3C 8E 22 61 4E 4B 3C 8E 23 61 4E 4B 3C B6 50 3F 7C AC 08 90 10 61 4E 4B 3C 80 30 50 40 16 28 04 20 05 16 + +上位机返回: + +68 45 00 01 0A 3F 68 C3 00 00 22 16 + +# 附录:上传数据标识码(部分) + +| 标识符类型 | 说明 | 标识符类型 | 说明 | +| --- | --- | --- | --- | +| 0x8E11 | A相电压 | 0x9B00 | 仪表压力值 | +| 0x8E12 | B相电压 | 0x9B01 | 仪表温度值 | +| 0x8E13 | C相电压 | 0x9B02 | 仪表频率值 | +| 0x8E21 | A相电流 | 0x9B03 | 仪表瞬时流值 | +| 0x8E22 | B相电流 | 0x9B05 | 仪表累积流量值 | +| 0x8E23 | C相电流 | 0x9B06 | 仪表瞬时热量 | +| 0xB650 | 总功率因数 | 0x9B07 | 仪表累积热量值 | +| 0x9010 | 正向有功总电能 | 0x9B0E | 仪表密度值 | +| 0x8030 | 仪表数据采集时间 | | | + +| 标识符类型 | 说明 | 标识符类型 | 说明 | +| --- | --- | --- | --- | +| 0x8E50 | 温湿度.温度 | 0x8E51 | 照度.流明 | +| 0x8E52 | 温湿度.湿度 | 0x8E53 | 噪声.分贝 | +| 0x8E54 | 振动-速度 | 0x8E55 | 振动-位移 | +| 0x8E56 | 振动-加速度 | 0x8E57 | 振动-温度 | +| 0x8030 | 仪表数据采集时间 | | | + +指令样例: + +`温度指令:68 54 00 02 0E 7F 68 85 00 28 01 01 8E 50 70 A4 41 DD 8E 51 00 00 00 00 8E 52 00 00 00 00 8E 53 00 00 00 00 8E 54 00 00 00 00 80 30 06 47 04 03 09 24 25 16` + +`湿度指令:68 54 00 01 18 3F 68 85 00 28 01 01 8E 50 7A E1 41 FC 8E 51 00 00 00 00 8E 52 0A 3D 42 2E 8E 53 00 00 00 00 8E 54 00 00 00 00 80 30 34 47 04 03 09 24 39 16` + +`噪音指令:68 54 00 03 1B 3F 68 85 00 28 01 01 8E 50 00 00 00 00 8E 51 00 00 00 00 8E 52 00 00 00 00 8E 53 66 66 42 93 8E 54 00 00 00 00 80 30 36 53 09 14 03 25 AF 16` + +`电能指令:6845002104BF6883023011008E11199A43688E12333343678E13199A43688E21BA5E3FA98E2268733F718E23353F3FAEB651FF000000B65200000000B6504DD33F629010C6E14590803037520521052512008E11000043688E12199A43678E13199A43688E21000000008E22000000008E2300000000B651FF000000B65200000000B65000003F809010AF484605803043520521052513008E11E66643678E12199A43678E13199A43688E21353F3E5E8E2260423E658E239DB23E6FB651FF000000B65200000000B65016873F799010F33344BC803049520521052514008E11333343688E12199A43678E13199A43688E21000000008E22000000008E2300000000B651FF000000B65200000000B65000003F8090103266461C803054520521052515008E11000043688E12CCCD43668E13199A43688E21BA5E3F998E22AE143F978E2326E93FA1B651FF000000B65200000000B65081063F5590106F5C454B803000530521052516008E11000043688E12000043678E13199A43688E211EB83D858E2247AE3D618E23999A3D99B651FF000000B65200000000B650020C3F4B9010619A44F0803006530521052517008E11000043688E12E66643668E13333343688E21A1CB3E858E224FDF3E8D8E23FDF43E94B651FF000000B65200000000B65068733F5190101D1F44C7803011530521052518008E11199A43688E12CCCD43668E13000043688E21CED93EB78E223F7D3EB58E23CCCD3ECCB651FF000000B65200000000B6506C8B3F67901091EC44FD80301753052105257116` + +`流体指令:68450073197F688400E801009B00000000009B01000000009B02000000009B03000000009B0500003F809B06000000009B07000000009B0E00000000803019250521052502009B00000000009B01000000009B02000000009B03000000009B0500003F809B06000000009B07000000009B0E00000000803020250521052503009B00000000009B01000000009B02000000009B03000000009B05C00044219B06000000009B07000000009B0E00000000803021250521052504009B00000000009B01000000009B02000000009B03000000009B05000043BE9B06000000009B07000000009B0E0000000080302225052105254216` \ No newline at end of file diff --git a/Sln.Iot/sql.md b/Sln.Iot/sql.md new file mode 100644 index 0000000..eec7363 --- /dev/null +++ b/Sln.Iot/sql.md @@ -0,0 +1,144 @@ +# Sql File 数据结构 + +record_iotenv_instant:动力环境数据表 + +```sql +/* + Navicat Premium Data Transfer + + Source Server : 127.0.0.1_4000 + Source Server Type : MySQL + Source Server Version : 80011 (8.0.11-TiDB-v8.5.1) + Source Host : 127.0.0.1:4000 + Source Schema : tao_iot + + Target Server Type : MySQL + Target Server Version : 80011 (8.0.11-TiDB-v8.5.1) + File Encoding : 65001 + + Date: 20/05/2025 14:18:32 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for record_iotenv_instant +-- ---------------------------- +DROP TABLE IF EXISTS `record_iotenv_instant`; +CREATE TABLE `record_iotenv_instant` ( + `objid` bigint NOT NULL AUTO_INCREMENT COMMENT '主键标识', + `monitorId` varchar(50) DEFAULT NULL COMMENT '计量设备编号', + `temperature` decimal(18,2) DEFAULT NULL COMMENT '温度', + `humidity` decimal(18,2) DEFAULT NULL COMMENT '湿度', + `illuminance` decimal(18,2) DEFAULT NULL COMMENT '照度', + `noise` decimal(18,2) DEFAULT NULL COMMENT '噪声', + `concentration` decimal(18,2) DEFAULT NULL COMMENT '硫化氢浓度', + `vibration_speed` decimal(18,2) DEFAULT NULL COMMENT '振动-速度(mm/s)', + `vibration_displacement` decimal(18,2) DEFAULT NULL COMMENT '振动-位移(um)', + `vibration_acceleration` decimal(18,2) DEFAULT NULL COMMENT '振动-加速度(g)', + `vibration_temp` decimal(18,2) DEFAULT NULL COMMENT '振动-温度(℃)', + `collectTime` datetime DEFAULT NULL COMMENT '采集时间', + `recodeTime` datetime DEFAULT NULL COMMENT '记录时间', + PRIMARY KEY (`objid`) /*T![clustered_index] CLUSTERED */ +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin AUTO_INCREMENT=6687491 COMMENT='物联网数据'; + +SET FOREIGN_KEY_CHECKS = 1; + +``` + +record_dnb_instant:电能数据表 + +```sql +/* + Navicat Premium Data Transfer + + Source Server : 127.0.0.1_4000 + Source Server Type : MySQL + Source Server Version : 80011 (8.0.11-TiDB-v8.5.1) + Source Host : 127.0.0.1:4000 + Source Schema : tao_iot + + Target Server Type : MySQL + Target Server Version : 80011 (8.0.11-TiDB-v8.5.1) + File Encoding : 65001 + + Date: 20/05/2025 14:18:23 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for record_dnb_instant +-- ---------------------------- +DROP TABLE IF EXISTS `record_dnb_instant`; +CREATE TABLE `record_dnb_instant` ( + `objid` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', + `monitor_id` varchar(64) DEFAULT NULL COMMENT '计量设备编号', + `va` decimal(18,2) DEFAULT NULL COMMENT 'A项电压', + `vb` decimal(18,2) DEFAULT NULL COMMENT 'B项电压', + `vc` decimal(18,2) DEFAULT NULL COMMENT 'C项电压', + `ia` decimal(18,2) DEFAULT NULL COMMENT 'A项电流', + `ib` decimal(18,2) DEFAULT NULL COMMENT 'B项电流', + `ic` decimal(18,2) DEFAULT NULL COMMENT 'C项电流', + `glys` decimal(18,2) DEFAULT NULL COMMENT '功率因数', + `zxyg` decimal(18,2) DEFAULT NULL COMMENT '正向有功', + `active_power` decimal(18,2) DEFAULT NULL COMMENT '有功功率', + `reactive_power` decimal(18,2) DEFAULT NULL COMMENT '无功功率', + `collect_type` int DEFAULT '0' COMMENT '采集方式', + `collect_time` datetime DEFAULT NULL COMMENT '采集时间', + `record_time` datetime DEFAULT NULL COMMENT '记录时间', + PRIMARY KEY (`objid`) /*T![clustered_index] CLUSTERED */ +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='电实时数据'; + +SET FOREIGN_KEY_CHECKS = 1; + +``` + +record_fluid_instant:流体实时数据 + +```sql +/* + Navicat Premium Data Transfer + + Source Server : 127.0.0.1_4000 + Source Server Type : MySQL + Source Server Version : 80011 (8.0.11-TiDB-v8.5.1) + Source Host : 127.0.0.1:4000 + Source Schema : tao_iot + + Target Server Type : MySQL + Target Server Version : 80011 (8.0.11-TiDB-v8.5.1) + File Encoding : 65001 + + Date: 20/05/2025 14:31:46 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for record_fluid_instant +-- ---------------------------- +DROP TABLE IF EXISTS `record_fluid_instant`; +CREATE TABLE `record_fluid_instant` ( + `objid` bigint NOT NULL AUTO_INCREMENT COMMENT '自增标识', + `monitor_id` varchar(64) DEFAULT NULL COMMENT '计量设备编号', + `temperature` decimal(18,2) DEFAULT NULL COMMENT '温度值', + `press` decimal(18,2) DEFAULT NULL COMMENT '压力值', + `frequency` decimal(18,2) DEFAULT NULL COMMENT '频率值', + `density` decimal(18,2) DEFAULT NULL COMMENT '密度值', + `instant_heat` decimal(18,2) DEFAULT NULL COMMENT '瞬时热量', + `total_heat` decimal(18,2) DEFAULT NULL COMMENT '累计热量值', + `instant_flow` decimal(18,2) DEFAULT NULL COMMENT '瞬时流量', + `total_flow` decimal(18,2) DEFAULT NULL COMMENT '累计流量', + `collect_type` int DEFAULT '0' COMMENT '采集方式', + `collect_time` datetime DEFAULT NULL COMMENT '采集时间', + `record_time` datetime DEFAULT NULL COMMENT '记录时间', + PRIMARY KEY (`objid`) /*T![clustered_index] CLUSTERED */ +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='流体实时数据'; + +SET FOREIGN_KEY_CHECKS = 1; + +``` \ No newline at end of file