1、软件架构

上位机主要作为下位机数据上传服务端以及节点调试的控制端,可以等效认为是专属版本调试工具。针对智能插座协议,对于下位机进行可视化监测和管理。 软件技术架构如下,主要为针对 Windows 的PC 端应用程序,采用WPF以及C# 实现功能开发,其中包含MVVM架构。

// 日志库-Log4net

// 通信库-SuperSocket

// WPF组件库-HandyControl

// 插件库-G2Cy.Plugins.NETCore.WPF

项目PCB、原理图和代码下载:https://download.csdn.net/download/m0_38106923/89089246

2、开发环境

主要在Windows10操作系统中,使用Visual Studio 2022 进行开发,项目源码结构如下:

G2CyHome.Models : 包含UI部分通用的一些依赖类,例如工具,协议枚举、命令控制类等。G2CyHome.Wpf : 包含主程序相关窗体和类。G2CyHome.WpfOutlet : 主要包含插座UI组件相关类。

3、程序设计

上位机测试程序主要功能如下,其中主要包括:服务配置、节点数据以及节点控制。

4、程序功能

4.1、服务配置

服务配置,主要在当前同局域网下,启动Socket 服务,对应端口和IP 与同局域网下位机形成通信,基础代码逻辑如下,包括UI、ViewModel以及服务。 1)UI部分 主要代码如下:

hc:TitleElement.Title="服务状态"

hc:TitleElement.TitlePlacement="Top"

Width="Auto" Margin="10,10,0,0" HorizontalContentAlignment="Left" Padding="{DynamicResource TextboxPadding}" Template="{StaticResource ContentTopTemplate}">

2)ViewModel部分,主要代码在ServerCfgVM中。

///

/// 服务配置实体

///

public class ServerCfgVM : VMBase

{

public ServerCfgVM()

{

Port = 6886;

Proto = 0;

SSIds = WiFiUtils.GetWiFiSSID().ToList();

SelectedMode = 0;// 默认为运行模式

ModeIsEnabled = true;// 默认为启用状态

ResetEnabled = true;// 默认为启用状态

}

private string iP;

// 协议格式

private int proto;

public int Proto

{

get { return proto; }

set { proto = value; RaisePropertyChanged(); }

}

// IP地址

public string IP {

get {

return iP;

} set { iP = value; RaisePropertyChanged(); } }

// 端口号

private int port;

private List sSIds;

public int Port

{

get {

return port;

}

set { port = value; RaisePropertyChanged(); }

}

///

/// WIFI列表

///

public List SSIds { get => sSIds; set { sSIds = value; RaisePropertyChanged(); } }

private string ssid;

///

/// 选中ssid

///

public string Ssid

{

get { return ssid; }

set { ssid = value; RaisePropertyChanged(); }

}

///

/// 选中模式

///

private int selectedMode;

///

/// 选中模式

///

public int SelectedMode

{

get { return selectedMode; }

set { selectedMode = value; RaisePropertyChanged(); }

}

private bool modeIsEnabled;

///

/// 是否启用模式切换

///

public bool ModeIsEnabled

{

get { return modeIsEnabled; }

set { modeIsEnabled = value;RaisePropertyChanged(); }

}

private bool configEnabled;

///

/// 是否启用配置下发

///

public bool ConfigEnabled

{

get { return configEnabled; }

set { configEnabled = value; RaisePropertyChanged(); }

}

private bool resetEnabled;

///

/// 是否启用配置重置

///

public bool ResetEnabled

{

get { return resetEnabled; }

set { resetEnabled = value; RaisePropertyChanged(); }

}

}

3)服务部分 服务主要为Socket 服务端,配置项用于对服务进行监听和关闭服务管理。

try

{

SuperSocketHostBuilder socketHostBuilder = CreateSocketServerBuilder();

// socket服务配置

socketHostBuilder.ConfigureSuperSocket(config =>

{

config.ClearIdleSessionInterval = 60;

config.DefaultTextEncoding = Encoding.UTF8;

config.IdleSessionTimeOut = 150;

config.Name = "deviceserver";

});

server = socketHostBuilder

.UsePackageDecoder()

.UsePackageHandler(async (s, p) =>

{

// 更新页面数据

IServiceProvider serviceProvider = Program.DefaultHost.Services;

var Vm = serviceProvider.GetRequiredService();

try

{

DeviceVm deviceSession = Vm.AppSessions.FirstOrDefault(x => x.SessionID == s.SessionID);

if (deviceSession != null)

{

// 判定设备类型

switch (p.NodeType)

{

// 智能插座

case DeviceNodeType.Outlet:

// 执行插座处理逻辑

break;

case DeviceNodeType.None:

default:

break;

}

}

}

catch (Exception ex)

{

_logger.LogError(ex.Message, ex);

}

})

.UseSession()

.UseClearIdleSession()

.UseHostedService()

.UseInProcSessionContainer()

.BuildAsServer();

await server.StartAsync();

UpdateEnabled(true);

}

catch (Exception ex)

{

_logger.LogError(ex.Message, ex);

}

4.2、节点数据

在保证数据服务监听已经启动的情况下,采集来自目标选中节点的传输数据。 1)节点协议解析 数据字节包解析类RequestModelPipelineFilter:

public class RequestModelPipelineFilter : FixedHeaderPipelineFilter

{

///

/// 是否发现头部

///

private bool _foundHeader;

///

/// 头部长度

///

private readonly int _headerSize;

///

/// 目标数据包总长度

///

private int _totalSize;

///

/// 校验长度

///

private int _verifySize;

// 设置对应从接收缓冲区中获取的头部字节长度

public RequestModelPipelineFilter()

: base(3)

{

_verifySize = 1;

_headerSize = 3;

}

///

/// 从Header头部获取内容长度

///

/// 数据字节包

/// 内容长度

protected override int GetBodyLengthFromHeader(ref ReadOnlySequence buffer)

{

var reader = new SequenceReader(buffer);

reader.Advance(buffer.Length - 1);

//reader.TryRead(out byte length);

byte[] bytes = reader.Sequence.Slice(1, 2).ToArray();

//Array.Reverse(bytes);

int length = BitConverter.ToInt16(bytes, 0);

return length;

}

///

/// 过滤执行函数

///

/// 数据字节包

/// 数据包实例

public override RequestModel Filter(ref SequenceReader reader)

{

if (!_foundHeader)

{

if (reader.Length < _headerSize)

{

return null;

}

ReadOnlySequence buffer = reader.Sequence.Slice(0, _headerSize);

int bodyLengthFromHeader = GetBodyLengthFromHeader(ref buffer);

if (bodyLengthFromHeader < 0)

{

throw new ProtocolException("Failed to get body length from the package header.");

}

if (bodyLengthFromHeader == 0)

{

try

{

return DecodePackage(ref buffer);

}

finally

{

reader.Advance(_headerSize);

// 重置是否找到头部

_foundHeader = false;

}

}

_foundHeader = true;

// 总长度

_totalSize = bodyLengthFromHeader;

}

int totalSize = _totalSize;

// 判定当前实际数据包长度是否小于目标数据包总长度

if (reader.Length < totalSize)

{

return null;

}

ReadOnlySequence buffer2 = reader.Sequence.Slice(0, totalSize);

try

{

return DecodePackage(ref buffer2);

}

finally

{

reader.Advance(totalSize);

// 重置是否找到头部

_foundHeader = false;

}

}

}

数据接收类RequestModel:

public class RequestModel

{

public byte[] Data { get; set; }=new byte[0];

///

/// 设备节点类型

///

public DeviceNodeType NodeType { get; set; }

///

/// 功能码

///

public FeatureType FeatureType { get; set; }

public Msg_Type Msg_Type { get; set; }

public byte[] GetBytes(Encoding encoding)

{

return this.ToJson().ToBytes(encoding);

}

///

/// 构建包数据

///

/// 原始字节组

public static RequestModel CreatedModel(ReadOnlySequence buffer)

{

RequestModel requestModel = new RequestModel();

// 填充数据

var reader = new SequenceReader(buffer);

// 获取产品类型

reader.TryRead(out byte devicetype);

// 高位(设备类型)

byte heigth = (byte)(devicetype & 0xf0);

requestModel.NodeType = (DeviceNodeType)Enum.ToObject(typeof(DeviceNodeType), heig

// 低位(功能码)

byte lower = (byte)(devicetype & 0x0f);

requestModel.FeatureType = (FeatureType)Enum.ToObject(typeof(FeatureType), lower);

int lentype = (int)buffer.Length-3;//包含校验位-crc校验(2)

// 跳过总长度(2)

reader.Advance(2);

try

{

//string datastr = reader.ReadString(Encoding.UTF8, lentype);

byte[] datas = reader.Sequence.Slice(reader.Position,lentype).ToArray();

requestModel.Data = datas;

}

catch (System.Text.Json.JsonException ex)

{

// 异常处理

return new RequestModel();

}

return requestModel;

}

public static bool VerifyCRC(ReadOnlySequence buffer)

{

byte[] nobytes = buffer.Slice(0,buffer.Length-2).ToArray();

byte[] flagbytes = buffer.Slice(buffer.Length-2).ToArray();

byte[] crcs = CRCUtils.Crc18(nobytes, 0, nobytes.Length);

for (int i = 0; i < 2; i++)

{

if (flagbytes[i] != crcs[i])

{

return false;

}

}

return true;

}

}

public enum Msg_Type

{

// 节点数据上报

Upload,

// 控制回发

Call

}

2)UI展示部分 设备列表: 主要代码:

VerticalAlignment="Bottom" HorizontalAlignment="Center">

Foreground="{DynamicResource SecondaryTextBrush}"

SelectedItem="{Binding SeletectedSession,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"

Style="{DynamicResource ListBoxCustom}"

SelectionChanged="ListBox_SelectionChanged" Background="Transparent"

BorderThickness="0" d:ItemsSource="{d:SampleData ItemCount=5}">

内容部分: 主要代码:

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

xmlns:hc="https://handyorg.github.io/handycontrol"

xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

Style="{DynamicResource TabControlCapsuleSolid}"

Background="Transparent"

BorderThickness="0,1,0,0"

mc:Ignorable="d"

d:DesignHeight="450" d:DesignWidth="800"

DataContextChanged="TabControl_DataContextChanged"

SelectionChanged="TabControl_SelectionChanged">

4.3、节点控制

1)协议下发 涉及到的下发协议主要包含:控制继电器开关、节点配置、模式切换、控制状态回发。 主要通过数据包协议类型FeatureType进行区分判定:

///

/// 功能类型

///

public enum FeatureType:byte

{

///

/// 0x01 设备数据上传功能码

///

[Description("数据上传")]

Upload = 0x01,

///

/// 0x02 下发响应功能码

///

[Description("下发响应")]

Callback = 0x02,

///

/// 0x05 节点控制功能码

///

[Description("节点控制")]

Push = 0x05,

///

/// 0x04 节点配置功能码

///

[Description("节点配置")]

Config= 0x04,

///

/// 0x03 模式切换功能码

///

[Description("模式切换")]

ModeCfg = 0x03,

///

/// 0x06 升级响应功能码

///

[Description("升级响应")]

UpdateAck = 0x06,

}

功能类型通过不同类型接口进行下发指令区分: 例如 控制回发类接口,实现IDeviceCallback

///

/// 设备响应回发接口

///

public interface IDeviceCallback

{

///

/// 客户端IP

///

[JsonPropertyName("clientip")]

public string ClientIP { get; set; }

///

/// 设备标识

///

[JsonPropertyName("device_id")]

public int Device_Id { get; set; }

///

/// 软件版本号

///

[JsonPropertyName("software_version")]

public string Software_Version { get; set; }

///

/// 硬件版本号

///

[JsonPropertyName("hardware_version")]

public string Hardware_Version { get; set; }

///

/// 状态码:0 响应成功,1 响应失败

///

public bool State_Code { get; set; }

// 字节转对象

T BytesToObject(byte[] bytes);

}

设备回发基类:

// 设备回发基类

public class DeviceCallback : VMBase, IDeviceCallback {

public virtual DeviceCallback BytesToObject(byte[] bytes)

{

return null;

}

}

插座控制回发类实现内容如下:

///

/// 智能插座响应类

///

public class OutletCallback:DeviceCallback

{

public override DeviceCallback BytesToObject(byte[] bytes)

{

SequenceReader read = new SequenceReader(new ReadOnlySequence(bytes));

// 设备id

byte[] deviceid = new byte[2];

read.TryCopyTo(deviceid);

read.Advance(deviceid.Length);

Device_Id = BitConverter.ToUInt16(deviceid);

// 软件版本

byte[] software = new byte[15];

read.TryCopyTo(software);

read.Advance(software.Length);

Software_Version = software.GetStrFromBytes(15);

// 硬件版本

byte[] hardware = new byte[15];

read.TryCopyTo(hardware);

Hardware_Version = hardware.GetStrFromBytes(15);

read.Advance(hardware.Length);

// 状态码

read.TryRead(out byte state);

// 高位(回发功能码) 高位转低位

byte heigth = (byte)((state & 0xf0)>>4);

FeatureCallType = (FeatureType)Enum.ToObject(typeof(FeatureType), heigth);

// 低位(状态码)

byte lower = (byte)(state & 0x0f);

State_Code = lower == 0x00;

return this;

}

}

2)UI展示部分 主要代码:

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

xmlns:hc="https://handyorg.github.io/handycontrol"

xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

Style="{DynamicResource TabControlCapsuleSolid}"

Background="Transparent"

BorderThickness="0,1,0,0"

mc:Ignorable="d"

d:DesignHeight="450" d:DesignWidth="800"

DataContextChanged="TabControl_DataContextChanged"

SelectionChanged="TabControl_SelectionChanged">

5、程序功能特点

程序目前设计采用插件方式加载,数据协议提供数据调试和程序本地日志查看。 本地日志封装如下:

///

/// log4net日志记录

///

public class Log4NetLogger : ILogger

{

private readonly ILog _log;

///

/// 初始化一个类型的新实例

///

public Log4NetLogger(string loggerRepository, string name)

{

_log = LogManager.GetLogger(loggerRepository, name);

}

///

Writes a log entry.

/// 日志级别,将按这个级别写不同的日志

/// 事件编号.

/// The entry to be written. Can be also an object.

/// The exception related to this entry.

/// Function to create a string message of the and .

public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter)

{

if (!IsEnabled(logLevel))

{

return;

}

string message = null;

if (formatter != null)

{

message = formatter(state, exception);

}

if (!string.IsNullOrEmpty(message) || exception != null)

{

switch (logLevel)

{

case LogLevel.Trace:

case LogLevel.Debug:

_log.Debug(message);

break;

case LogLevel.Information:

_log.Info(message);

break;

case LogLevel.Warning:

_log.Warn(message);

break;

case LogLevel.Error:

_log.Error(message, exception);

break;

case LogLevel.Critical:

_log.Fatal(message, exception);

break;

case LogLevel.None:

break;

default:

_log.Warn($"遇到未知的日志级别 {logLevel}, 使用Info级别写入日志。");

_log.Info(message, exception);

break;

}

}

}

///

/// Checks if the given is enabled.

///

/// level to be checked.

/// true if enabled.

public bool IsEnabled(LogLevel logLevel)

{

switch (logLevel)

{

case LogLevel.Trace:

case LogLevel.Debug:

return _log.IsDebugEnabled;

case LogLevel.Information:

return _log.IsInfoEnabled;

case LogLevel.Warning:

return _log.IsWarnEnabled;

case LogLevel.Error:

return _log.IsErrorEnabled;

case LogLevel.Critical:

return _log.IsFatalEnabled;

case LogLevel.None:

return false;

default:

throw new ArgumentOutOfRangeException(nameof(logLevel), logLevel, null);

}

}

///

Begins a logical operation scope.

/// The identifier for the scope.

/// An IDisposable that ends the logical operation scope on dispose.

public IDisposable BeginScope(TState state)

{

return null;

}

项目PCB、原理图和代码下载:https://download.csdn.net/download/m0_38106923/89089246

精彩文章

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。