ESP8266 WiFi物联网智能插座—上位机软件实现

news2024/11/23 20:51:32

1、软件架构

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

// 日志库-Log4net
// 通信库-SuperSocket
// WPF组件库-HandyControl
// 插件库-G2Cy.Plugins.NETCore.WPF

2、开发环境

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

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

3、程序设计

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

4、程序功能

4.1、服务配置

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

主要代码如下:

<DockPanel>
    <TextBlock Text="v2023.09.27.0001" DockPanel.Dock="Bottom" HorizontalAlignment="Center" Padding="{DynamicResource DefaultControlPadding}" FontSize="{DynamicResource MainFontSize}" Foreground="{DynamicResource SecondaryTextBrush}"></TextBlock>
    <Border DockPanel.Dock="Top" Height="{DynamicResource HeaderHeight}" Padding="38,0,0,0" Background="Transparent" BorderThickness="0,0,0,1" BorderBrush="{DynamicResource BackgroundBrush}">
        <StackPanel Orientation="Horizontal">
            <Path Data="{DynamicResource LogoGeometry}" Fill="{DynamicResource InfoBrush}" Width="30" Height="30"></Path>
            <TextBlock Text="调试终端" FontWeight="Normal" Foreground="{DynamicResource TextIconBrush}" FontSize="{DynamicResource HeadFontSize}" VerticalAlignment="Center" Margin="10,6,0,0"></TextBlock>
        </StackPanel>
    </Border>

    <StackPanel Margin="12,18,18,0">
        <TextBlock Text="服务配置" LineHeight="33.6" FontSize="{DynamicResource TitleFontSize}" Foreground="{DynamicResource PrimaryTextBrush}"></TextBlock>
        <hc:ComboBox x:Name="cmbx_type" SelectedIndex="{Binding Proto,Mode=OneWay}" hc:TitleElement.Title="协议类型" Padding="{DynamicResource TextboxPadding}"  BorderThickness="1" Background="{DynamicResource BackgroundBrush}" BorderBrush="{DynamicResource BorderBrush}">
            <ComboBoxItem Content="TCP"></ComboBoxItem>
            <ComboBoxItem Content="UDP"></ComboBoxItem>
        </hc:ComboBox>
        <hc:ComboBox x:Name="cmbx_ip" SelectedIndex="0" hc:TitleElement.Title="IP地址" SelectedValue="{Binding IP,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Margin="0,10,0,0" Padding="{DynamicResource TextboxPadding}" BorderThickness="1" Background="{DynamicResource BackgroundBrush}" BorderBrush="{DynamicResource BorderBrush}">
        </hc:ComboBox>
        <TextBlock Text="默认选择localhost" FontSize="{DynamicResource MainFontSize}" Margin="0,7,0,0" Foreground="{DynamicResource SecondaryTextBrush}"></TextBlock>
        <UniformGrid Rows="1">
            <hc:TextBox x:Name="txt_port" hc:TitleElement.Title="监听端口" Text="{Binding Port,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Width="68.8" Margin="0,10,0,0" Padding="{DynamicResource TextboxPadding}" BorderThickness="1" Background="{DynamicResource BackgroundBrush}" BorderBrush="{DynamicResource BorderBrush}"></hc:TextBox>
            <ContentControl x:Name="socket_status" Content="停止" Foreground="{DynamicResource PrimaryTextBrush}"
                            hc:TitleElement.Title="服务状态" 
                            hc:TitleElement.TitlePlacement="Top" 
                            Width="Auto" Margin="10,10,0,0" HorizontalContentAlignment="Left" Padding="{DynamicResource TextboxPadding}" Template="{StaticResource ContentTopTemplate}">
            </ContentControl>
        </UniformGrid>
        <TextBlock Text="建议端口1000~65535间" FontSize="{DynamicResource MainFontSize}" Margin="0,7,0,0" Foreground="{DynamicResource SecondaryTextBrush}"></TextBlock>
        <UniformGrid Rows="1" Margin="0,12,0,0">
            <Button x:Name="btn_start" Click="btn_start_Click" Padding="{DynamicResource ButtonPadding}" hc:BackgroundSwitchElement.MouseHoverBackground="{DynamicResource InfoBrush}"  hc:BackgroundSwitchElement.MouseDownBackground="{DynamicResource InfoBrush}" HorizontalAlignment="Left" VerticalAlignment="Center" Style="{DynamicResource ButtonCustom}" hc:BorderElement.CornerRadius="{DynamicResource MainCornerRadius}" Content="开启" Background="{DynamicResource InfoBrush}"></Button>
            <Button x:Name="btn_stop" IsEnabled="False" Click="btn_stop_Click"  Padding="{DynamicResource ButtonPadding}" HorizontalAlignment="Right" VerticalAlignment="Center"   Style="{DynamicResource ButtonCustom}" hc:BorderElement.CornerRadius="4" Content="停止" BorderThickness="1" BorderBrush="{DynamicResource BorderBrush}" Background="{DynamicResource DarkPrimaryBrush}"></Button>
        </UniformGrid>
    </StackPanel>
</DockPanel>

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

/// <summary>
/// 服务配置实体
/// </summary>
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<string> sSIds;

    public int Port
    {
        get {
            return port;
        }
        set { port = value; RaisePropertyChanged(); }
    }

    /// <summary>
    /// WIFI列表
    /// </summary>
    public List<string> SSIds { get => sSIds; set { sSIds = value; RaisePropertyChanged(); } }

    private string ssid;
    /// <summary>
    /// 选中ssid
    /// </summary>
    public string Ssid
    {
        get { return ssid; }
        set { ssid = value;  RaisePropertyChanged(); }
    }

    /// <summary>
    /// 选中模式
    /// </summary>
    private int selectedMode;
    /// <summary>
    /// 选中模式
    /// </summary>
    public int SelectedMode
    {
        get { return selectedMode; }
        set { selectedMode = value;  RaisePropertyChanged(); }
    }

    private bool modeIsEnabled;
    /// <summary>
    /// 是否启用模式切换
    /// </summary>
    public bool ModeIsEnabled
    {
        get { return modeIsEnabled; }
        set { modeIsEnabled = value;RaisePropertyChanged(); }
    }

    private bool configEnabled;
    /// <summary>
    /// 是否启用配置下发
    /// </summary>
    public bool ConfigEnabled
    {
        get { return configEnabled; }
        set { configEnabled = value; RaisePropertyChanged(); }
    }

    private bool resetEnabled;
    /// <summary>
    /// 是否启用配置重置
    /// </summary>
    public bool ResetEnabled
    {
        get { return resetEnabled; }
        set { resetEnabled = value; RaisePropertyChanged(); }
    }
}

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

try
{
    SuperSocketHostBuilder<RequestModel> socketHostBuilder = CreateSocketServerBuilder<RequestModel, RequestModelPipelineFilter>();
    // socket服务配置
    socketHostBuilder.ConfigureSuperSocket(config =>
    {
        config.ClearIdleSessionInterval = 60;
        config.DefaultTextEncoding = Encoding.UTF8;
        config.IdleSessionTimeOut = 150;
        config.Name = "deviceserver";
    });
    server = socketHostBuilder
        .UsePackageDecoder<MyPackageDecoder>()
        .UsePackageHandler(async (s, p) =>
        {
            // 更新页面数据
            IServiceProvider serviceProvider = Program.DefaultHost.Services;
            var Vm = serviceProvider.GetRequiredService<DeviceListVM>();

            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<AppSession>()
        .UseClearIdleSession()
        .UseHostedService<CustomSocketService>()
        .UseInProcSessionContainer()
        .BuildAsServer();

    await server.StartAsync();
    UpdateEnabled(true);
}
catch (Exception ex)
{
    _logger.LogError(ex.Message, ex);
}

4.2、节点数据

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

public class RequestModelPipelineFilter : FixedHeaderPipelineFilter<RequestModel>
{
    /// <summary>
    /// 是否发现头部
    /// </summary>
    private bool _foundHeader;
    /// <summary>
    /// 头部长度
    /// </summary>
    private readonly int _headerSize;
    /// <summary>
    /// 目标数据包总长度
    /// </summary>
    private int _totalSize;
    /// <summary>
    /// 校验长度
    /// </summary>
    private int _verifySize;
    // 设置对应从接收缓冲区中获取的头部字节长度
    public RequestModelPipelineFilter()
        : base(3)
    {
        _verifySize = 1;
        _headerSize = 3;
    }
    /// <summary>
    /// 从Header头部获取内容长度
    /// </summary>
    /// <param name="buffer">数据字节包</param>
    /// <returns>内容长度</returns>
    protected override int GetBodyLengthFromHeader(ref ReadOnlySequence<byte> buffer)
    {
        var reader = new SequenceReader<byte>(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;
    }

    /// <summary>
    /// 过滤执行函数
    /// </summary>
    /// <param name="reader">数据字节包</param>
    /// <returns>数据包实例</returns>
    public override RequestModel Filter(ref SequenceReader<byte> reader)
    {
        if (!_foundHeader)
        {
            if (reader.Length < _headerSize)
            {
                return null;
            }

            ReadOnlySequence<byte> 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<byte> 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];
    /// <summary>
    /// 设备节点类型
    /// </summary>
    public DeviceNodeType NodeType { get; set; }
    /// <summary>
    /// 功能码
    /// </summary>
    public FeatureType  FeatureType { get; set; }
    public Msg_Type Msg_Type { get; set; }

    public byte[] GetBytes(Encoding encoding)
    {
        return this.ToJson().ToBytes(encoding);
    }

    /// <summary>
    /// 构建包数据
    /// </summary>
    /// <param name="buffer">原始字节组</param>
    public static RequestModel CreatedModel(ReadOnlySequence<byte> buffer)
    {
        RequestModel requestModel = new RequestModel();
        // 填充数据
        var reader = new SequenceReader<byte>(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<byte> 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展示部分

设备列表:

主要代码:

<DockPanel>
    <Border BorderBrush="{DynamicResource BorderBrush}" BorderThickness="0,0,1,0">
        <DockPanel Width="192" Background="{DynamicResource SecondaryBackgroundBrush}">
            <Border DockPanel.Dock="Top" Padding="0,12,0,12" BorderBrush="{DynamicResource BorderBrush}" BorderThickness="0,0,0,1">
                <TextBlock Text="设备列表" FontSize="{DynamicResource TitleFontSize}" Foreground="{DynamicResource PrimaryTextBrush}"
                           VerticalAlignment="Bottom" HorizontalAlignment="Center"></TextBlock>
            </Border>
            <ListBox x:Name="lbx_devices" ItemsSource="{Binding AppSessions}"  FontSize="{DynamicResource SecondFontSize}" 
                     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}">
                <ListBox.ItemContainerStyle>
                        <Style TargetType="ListBoxItem">
                            <Setter Property="IsSelected" Value="{Binding IsSeleted,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
                            <Setter Property="hc:IconElement.Geometry" Value="{Binding DeviceType,Converter={StaticResource StringGeometryConvert}}"/>
                            <Setter Property="Template">
                                <Setter.Value>
                                    <ControlTemplate TargetType="ListBoxItem">
                                        <hc:SimplePanel Height="60">
                                            <Border x:Name="Bg"></Border>
                                            <Border x:Name="selectline" BorderThickness="0,0,6,0" CornerRadius="0,3,3,0" BorderBrush="{DynamicResource InfoBrush}" Height="{TemplateBinding Height}" HorizontalAlignment="Left"></Border>
                                            <hc:SimplePanel  Margin="40,0,0,0">
                                                <Path x:Name="icon" Data="{Binding Path=(hc:IconElement.Geometry),RelativeSource={RelativeSource Mode=TemplatedParent}}" 
                                                  Width="{DynamicResource LargeFontSize}"
                                                  Height="{DynamicResource LargeFontSize}"
                                                  Fill="{DynamicResource InfoBrush}" HorizontalAlignment="Left"></Path>
                                                <StackPanel HorizontalAlignment="Left" Orientation="Vertical" Margin="36,0,0,0" VerticalAlignment="Center">
                                                    <TextBlock x:Name="maintxt" FontSize="{DynamicResource MainFontSize}" Foreground="{DynamicResource PrimaryTextBrush}" Text="{Binding DeviceId, StringFormat=0x\{0:X4\}}"></TextBlock>
                                                    <ContentPresenter x:Name="content"
                                                          HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Content="{Binding DeviceName}"></ContentPresenter>
                                                </StackPanel>
                                            </hc:SimplePanel>
                                        </hc:SimplePanel>
                                        <ControlTemplate.Triggers>
                                            <Trigger Property="IsSelected" Value="True">
                                                <Setter TargetName="selectline" Property="Visibility" Value="Visible"/>
                                                <Setter Property="Background" TargetName="Bg" Value="{DynamicResource BackgroundBrush}"/>
                                                <Setter Property="Fill" TargetName="icon" Value="{DynamicResource InfoBrush}"/>
                                                <Setter Property="Opacity" TargetName="content" Value="1"/>
                                                <Setter Property="Opacity" TargetName="maintxt" Value="1"/>
                                                <Setter Property="Opacity" TargetName="icon" Value="1"/>
                                            </Trigger>
                                            <Trigger Property="IsSelected" Value="False">
                                                <Setter TargetName="selectline" Property="Visibility" Value="Collapsed"/>
                                                <Setter Property="Background" TargetName="Bg" Value="Transparent"/>
                                                <Setter Property="Opacity" TargetName="content" Value=".5"/>
                                                <Setter Property="Fill" TargetName="icon" Value="{DynamicResource PrimaryTextBrush}"/>
                                                <Setter Property="Opacity" TargetName="content" Value=".5"/>
                                                <Setter Property="Opacity" TargetName="maintxt" Value="0.5"/>
                                                <Setter Property="Opacity" TargetName="icon" Value=".5"/>
                                            </Trigger>
                                        </ControlTemplate.Triggers>
                                    </ControlTemplate>
                                </Setter.Value>
                            </Setter>
                        </Style>
                    </ListBox.ItemContainerStyle>
            </ListBox>
        </DockPanel>
    </Border>
    <Border Padding="0,5.8,0,0" Background="{DynamicResource SecondaryBackgroundBrush}">
        <ContentControl DataContext="{Binding ElementName=lbx_devices,Path=SelectedItem}" Name="DeviceContent"></ContentControl>
    </Border>
</DockPanel>

内容部分:

主要代码:

<TabControl x:Class="G2CyHome.WpfOutlet.Views.OutletFeature"
             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">
    <TabItem Height="{DynamicResource TabHeadSize}" hc:TitleElement.Background="{DynamicResource InfoBrush}" Background="Transparent" Padding="8" BorderBrush="{DynamicResource BorderBrush}">
        <TabItem.Header>
            <DockPanel>
                <Path Data="{DynamicResource FeatureGeometry}" Margin="0,0,8,0" Height="{DynamicResource TabLogoSize}" Width="{DynamicResource TabLogoSize}" Fill="{DynamicResource PrimaryTextBrush}"></Path>
                <TextBlock Text="节点数据" Margin="0,0,8,0" Foreground="{DynamicResource PrimaryTextBrush}" FontSize="{DynamicResource TitleFontSize}" VerticalAlignment="Center"></TextBlock>
            </DockPanel>
        </TabItem.Header>
        <DockPanel>
            <hc:UniformSpacingPanel Margin="33,24,33,0" VerticalSpacing="64" Orientation="Vertical">
                <StackPanel DataContext="{Binding DeviceData}">
                    <TextBlock Text="基本信息" Foreground="{DynamicResource SecondaryTextBrush}" FontSize="{DynamicResource TitleFontSize}"></TextBlock>
                    <hc:Divider Margin="0,7" LineStrokeThickness="1" LineStrokeDashArray="2, 2" Orientation="Horizontal" MaxWidth="2000"/>
                    <hc:Row Gutter="24" Margin="0,10">
                        <hc:Col Span="8">
                            <hc:TextBox hc:TitleElement.Title="节点ID" Style="{DynamicResource TitleTextBoxStyle}" Text="{Binding Path=Device_Id, StringFormat=0x\{0:X4\}, Mode=OneWay}"></hc:TextBox>
                        </hc:Col>
                        <hc:Col Span="8">
                            <hc:TextBox hc:TitleElement.Title="软件版本" Style="{DynamicResource TitleTextBoxStyle}" Text="{Binding Software_Version,Mode=OneWay,UpdateSourceTrigger=PropertyChanged}"></hc:TextBox>
                        </hc:Col>
                        <hc:Col Span="8">
                            <hc:TextBox hc:TitleElement.Title="硬件版本" Style="{DynamicResource TitleTextBoxStyle}" Text="{Binding Hardware_Version,Mode=OneWay,UpdateSourceTrigger=PropertyChanged}"></hc:TextBox>
                        </hc:Col>
                    </hc:Row>
                    <hc:Row Gutter="24" Margin="0,10">
                        <hc:Col Span="8">
                            <hc:TextBox hc:TitleElement.Title="节点IP" Style="{DynamicResource TitleTextBoxStyle}" Text="{Binding ClientIP,Mode=OneWay,UpdateSourceTrigger=PropertyChanged}"></hc:TextBox>
                        </hc:Col>
                        <hc:Col Span="8">
                            <hc:TextBox hc:TitleElement.Title="出厂时间" Style="{DynamicResource TitleTextBoxStyle}" Text="{Binding Release_Time,Mode=OneWay,UpdateSourceTrigger=PropertyChanged}"></hc:TextBox>
                        </hc:Col>
                        <hc:Col Span="8">
                            <hc:TextBox hc:TitleElement.Title="负载时间" Style="{DynamicResource TitleTextBoxStyle}" Text="{Binding Run_Time,Mode=OneWay,UpdateSourceTrigger=PropertyChanged}"></hc:TextBox>
                        </hc:Col>
                    </hc:Row>
                </StackPanel>
                <StackPanel DataContext="{Binding DeviceData}">
                    <TextBlock Text="周期时间" Foreground="{DynamicResource SecondaryTextBrush}" FontSize="{DynamicResource TitleFontSize}"></TextBlock>
                    <hc:Divider Margin="0,7" LineStrokeThickness="1" LineStrokeDashArray="2, 2" Orientation="Horizontal" MaxWidth="2000"/>
                    <hc:UniformSpacingPanel Spacing="42" Orientation="Horizontal">
                        <hc:TextBox hc:TitleElement.Title="数据上传周期(秒)"  Width="250" Style="{DynamicResource TitleTextBoxStyle}" Text="{Binding Upload_Cycle,Mode=OneWay,UpdateSourceTrigger=PropertyChanged}"></hc:TextBox>
                        <hc:TextBox hc:TitleElement.Title="数据采样周期(毫秒)" Width="250"  Style="{DynamicResource TitleTextBoxStyle}" Text="{Binding Sample_Cycle,Mode=OneWay,UpdateSourceTrigger=PropertyChanged}"></hc:TextBox>
                    </hc:UniformSpacingPanel>
                </StackPanel>
                <StackPanel DataContext="{Binding DeviceData}">
                    <TextBlock Text="电参数据" Foreground="{DynamicResource SecondaryTextBrush}" FontSize="{DynamicResource TitleFontSize}"></TextBlock>
                    <hc:Divider Margin="0,7" LineStrokeThickness="1" LineStrokeDashArray="2, 2" Orientation="Horizontal" MaxWidth="2000"/>
                    <hc:UniformSpacingPanel ItemWidth="160" Orientation="Horizontal" Margin="0,10,0,0">
                        <DockPanel>
                            <TextBlock Text="V" VerticalAlignment="Center" DockPanel.Dock="Right" Margin="5,0,15,0"></TextBlock>
                            <hc:TextBox hc:TitleElement.Title="电压" Style="{DynamicResource TitleTextBoxStyle}" Text="{Binding Voltage, Mode=OneWay, StringFormat=\{0:F\}, UpdateSourceTrigger=PropertyChanged}"></hc:TextBox>
                        </DockPanel>
                        <DockPanel>
                            <TextBlock Text="mA" VerticalAlignment="Center" DockPanel.Dock="Right" Margin="5,0,15,0"></TextBlock>
                            <hc:TextBox hc:TitleElement.Title="电流" Style="{DynamicResource TitleTextBoxStyle}" Text="{Binding Current,Mode=OneWay, StringFormat=\{0:F\},UpdateSourceTrigger=PropertyChanged}"></hc:TextBox>
                        </DockPanel>
                        <DockPanel>
                            <TextBlock Text="Kw/h" VerticalAlignment="Center" DockPanel.Dock="Right" Margin="5,0,15,0"></TextBlock>
                            <hc:TextBox hc:TitleElement.Title="功率" Style="{DynamicResource TitleTextBoxStyle}" Text="{Binding Power,Mode=OneWay, StringFormat=\{0:F\},UpdateSourceTrigger=PropertyChanged}"></hc:TextBox>
                        </DockPanel>
                        <DockPanel>
                            <TextBlock Text="Kw" VerticalAlignment="Center" DockPanel.Dock="Right" Margin="5,0,15,0"></TextBlock>
                            <hc:TextBox hc:TitleElement.Title="电量" Style="{DynamicResource TitleTextBoxStyle}" Text="{Binding Electricity,Mode=OneWay, StringFormat=\{0:F\},UpdateSourceTrigger=PropertyChanged}"></hc:TextBox>
                        </DockPanel>
                    </hc:UniformSpacingPanel>
                </StackPanel>
            </hc:UniformSpacingPanel>
        </DockPanel>
    </TabItem>
    <!--数据控制-->
    <!--数据调试-->
</TabControl>

4.3、节点控制

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

/// <summary>
/// 功能类型
/// </summary>
public enum FeatureType:byte
{
    /// <summary>
    /// 0x01 设备数据上传功能码
    /// </summary>
    [Description("数据上传")]
    Upload = 0x01,
    /// <summary>
    /// 0x02 下发响应功能码
    /// </summary>
    [Description("下发响应")]
    Callback = 0x02,
    /// <summary>
    /// 0x05 节点控制功能码
    /// </summary>
    [Description("节点控制")]
    Push = 0x05,
    /// <summary>
    /// 0x04 节点配置功能码
    /// </summary>
    [Description("节点配置")]
     Config= 0x04,
    /// <summary>
    /// 0x03 模式切换功能码
    /// </summary>
    [Description("模式切换")]
    ModeCfg = 0x03,
    /// <summary>
    /// 0x06 升级响应功能码
    /// </summary>
    [Description("升级响应")]
    UpdateAck = 0x06,
}

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

/// <summary>
/// 设备响应回发接口
/// </summary>
public interface IDeviceCallback<T>
{
    /// <summary>
    /// 客户端IP
    /// </summary>
    [JsonPropertyName("clientip")]
    public string ClientIP { get; set; }
    /// <summary>
    /// 设备标识
    /// </summary>
    [JsonPropertyName("device_id")]
    public int Device_Id { get; set; }
    /// <summary>
    /// 软件版本号
    /// </summary>
    [JsonPropertyName("software_version")]
    public string Software_Version { get; set; }
    /// <summary>
    /// 硬件版本号
    /// </summary>
    [JsonPropertyName("hardware_version")]
    public string Hardware_Version { get; set; }
    /// <summary>
    ///  状态码:0 响应成功,1 响应失败
    /// </summary>
    public bool State_Code { get; set; }
    // 字节转对象
    T BytesToObject(byte[] bytes);
}

设备回发基类:

// 设备回发基类
public class DeviceCallback : VMBase, IDeviceCallback<DeviceCallback> {
	public virtual DeviceCallback BytesToObject(byte[] bytes)
	{
	    return null;
	}
}

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

/// <summary>
/// 智能插座响应类
/// </summary>
public class OutletCallback:DeviceCallback
{
    public override DeviceCallback BytesToObject(byte[] bytes)
    {
        SequenceReader<byte> read = new SequenceReader<byte>(new ReadOnlySequence<byte>(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展示部分

主要代码:

<TabControl x:Class="G2CyHome.WpfOutlet.Views.OutletFeature"
             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">
    <!--节点数据-->
    <TabItem Height="{DynamicResource TabHeadSize}" Background="Transparent" hc:TitleElement.Background="{DynamicResource InfoBrush}" Padding="8" BorderBrush="{DynamicResource BorderBrush}">
        <TabItem.Header>
            <DockPanel>
                <Path Data="{DynamicResource ModeGeometry}" Margin="0,0,8,0" Width="{DynamicResource TabLogoSize}" Height="{DynamicResource TabLogoSize}" Fill="{DynamicResource PrimaryTextBrush}"></Path>
                <TextBlock Text="节点控制" Margin="0,0,8,0" Foreground="{DynamicResource PrimaryTextBrush}" FontSize="{DynamicResource TitleFontSize}" VerticalAlignment="Center"></TextBlock>
            </DockPanel>
        </TabItem.Header>
        <DockPanel>
            <hc:UniformSpacingPanel Margin="33,24,33,0" VerticalSpacing="24" Orientation="Vertical">
                <StackPanel>
                    <DockPanel>
                        <ContentControl DockPanel.Dock="Right" hc:TitleElement.Title="" VerticalAlignment="Center">
                            <ContentControl.Template>
                                <ControlTemplate TargetType="ContentControl">
                                    <StackPanel Orientation="Horizontal">
                                        <TextBlock FontSize="{DynamicResource SecondFontSize}" VerticalAlignment="Center" Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},Path=(hc:TitleElement.Title)}" Foreground="{DynamicResource PrimaryTextBrush}" ></TextBlock>
                                        <Path x:Name="Icon" VerticalAlignment="Center" Data="{DynamicResource ModeGeometry}" Height="{DynamicResource MainFontSize}" Margin="8,0,8,0" Width="{DynamicResource MainFontSize}" Fill="{DynamicResource SuccessBrush}">
                                        </Path>
                                        <TextBlock FontSize="{DynamicResource SecondFontSize}" VerticalAlignment="Center" Text="运行模式" Foreground="{DynamicResource PrimaryTextBrush}" ></TextBlock>
                                    </StackPanel>
                                </ControlTemplate>
                            </ContentControl.Template>
                        </ContentControl>
                        <TextBlock Text="继电器控制" Foreground="{DynamicResource SecondaryTextBrush}" FontSize="{DynamicResource TitleFontSize}" VerticalAlignment="Center"></TextBlock>
                    </DockPanel>
                    <hc:Divider Margin="0, 7" LineStrokeThickness="1" LineStrokeDashArray="2, 2" Orientation="Horizontal" MaxWidth="2000"/>
                    <hc:UniformSpacingPanel HorizontalSpacing="80" Orientation="Horizontal" DataContext="{Binding DeviceData}">
                        <StackPanel Orientation="Horizontal">
                            <ContentControl x:Name="content_status" Content="{Binding Relay_Statestr,UpdateSourceTrigger=PropertyChanged,Mode=OneWay}"  VerticalAlignment="Center" Margin="0,10,0,10" hc:TitleElement.Title="继电器状态">
                                <ContentControl.Template>
                                    <ControlTemplate TargetType="ContentControl">
                                        <StackPanel Orientation="Horizontal">
                                            <TextBlock FontSize="{DynamicResource MainFontSize}" VerticalAlignment="Center" Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},Path=(hc:TitleElement.Title)}" Foreground="{DynamicResource PrimaryTextBrush}" ></TextBlock>
                                            <Path x:Name="Icon" VerticalAlignment="Center" HorizontalAlignment="Center" Data="{DynamicResource SwitchGeometry}" Height="{DynamicResource MainFontSize}" Margin="8,0,8,0" Width="{DynamicResource MainFontSize}" Fill="{DynamicResource BorderBrush}">
                                            </Path>
                                            <TextBlock x:Name="txt_status" FontSize="{DynamicResource MainFontSize}" VerticalAlignment="Center" HorizontalAlignment="Center" Text="关闭" Foreground="{DynamicResource PrimaryTextBrush}" ></TextBlock>
                                        </StackPanel>
                                        <ControlTemplate.Triggers>
                                            <Trigger Property="Content" Value="1">
                                                <Setter Property="Fill" TargetName="Icon" Value="{DynamicResource InfoBrush}"/>
                                                <Setter Property="Opacity" Value="1" TargetName="txt_status"/>
                                                <Setter Property="Text" Value="开启" TargetName="txt_status"/>
                                            </Trigger>
                                            <Trigger Property="Content" Value="0">
                                                <Setter Property="Fill" TargetName="Icon" Value="{DynamicResource BorderBrush}"/>
                                                <Setter Property="Opacity" Value=".5" TargetName="txt_status"/>
                                                <Setter Property="Text" Value="关闭" TargetName="txt_status"/>
                                            </Trigger>
                                        </ControlTemplate.Triggers>
                                    </ControlTemplate>
                                </ContentControl.Template>
                            </ContentControl>
                            <ToggleButton x:Name="check_switch" BorderThickness="0" Width="40" IsEnabled="{Binding IsSwitch,Mode=OneWay}" IsChecked="{Binding Relay_State,Mode=OneWay,Converter={StaticResource Int2BooleanConverter}}" Cursor="Hand" Height="40" Margin="5" HorizontalAlignment="Center" Style="{StaticResource ToggleButtonFlip}">
                                <hc:StatusSwitchElement.CheckedElement>
                                    <Border Background="{DynamicResource InfoBrush}">
                                        <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Text="" Foreground="{DynamicResource TextIconBrush}"/>
                                    </Border>
                                </hc:StatusSwitchElement.CheckedElement>
                                <Border Background="{DynamicResource BackgroundBrush}">
                                    <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Text="" Foreground="{DynamicResource TextIconBrush}"/>
                                </Border>
                            </ToggleButton>
                        </StackPanel>
                    </hc:UniformSpacingPanel>
                </StackPanel>
                <StackPanel>
                    <DockPanel DataContext="{Binding DeviceCfg}">
                        <ContentControl DockPanel.Dock="Right"  Content="{Binding Relay_State,UpdateSourceTrigger=PropertyChanged,Mode=OneWay}"  VerticalAlignment="Center" Margin="10,10,0,10" hc:TitleElement.Title="">
                            <ContentControl.Template>
                                <ControlTemplate TargetType="ContentControl">
                                    <StackPanel Orientation="Horizontal">
                                        <TextBlock FontSize="{DynamicResource SecondFontSize}" VerticalAlignment="Center" Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},Path=(hc:TitleElement.Title)}" Foreground="{DynamicResource PrimaryTextBrush}" ></TextBlock>
                                        <Path x:Name="Icon" VerticalAlignment="Center" Data="{DynamicResource ModeGeometry}" Height="{DynamicResource MainFontSize}" Margin="8,0,8,0" Width="{DynamicResource MainFontSize}" Fill="{DynamicResource WarningBrush}">
                                        </Path>
                                        <TextBlock FontSize="{DynamicResource SecondFontSize}" VerticalAlignment="Center" Text="配置模式" Foreground="{DynamicResource PrimaryTextBrush}" ></TextBlock>
                                    </StackPanel>
                                </ControlTemplate>
                            </ContentControl.Template>
                        </ContentControl>
                        <TextBlock Text="配置项下发" Foreground="{DynamicResource SecondaryTextBrush}" FontSize="20" VerticalAlignment="Center"></TextBlock>
                    </DockPanel>
                    <hc:Divider Margin="0,7" LineStrokeThickness="1" LineStrokeDashArray="2, 2" Orientation="Horizontal" MaxWidth="2000"/>
                    <hc:Row Gutter="24" Margin="0,10" DataContext="{Binding ServerCfg}">
                        <hc:Col Span="8">
                            <hc:TextBox Style="{DynamicResource TitleTextBoxBorderStyle}" Text="{Binding IP,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" hc:TitleElement.Title="服务地址" hc:TitleElement.TitlePlacement="Left" Background="{DynamicResource BackgroundBrush}"></hc:TextBox>
                        </hc:Col>
                        <hc:Col Span="8">
                            <hc:TextBox VerticalContentAlignment="Center" Style="{DynamicResource TitleTextBoxBorderStyle}" Text="{Binding Port,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" hc:TitleElement.Title="服务端口" Background="{DynamicResource BackgroundBrush}"></hc:TextBox>
                        </hc:Col>
                        <hc:Col Span="8">
                            <hc:TextBox Text="{Binding Ssid}" VerticalContentAlignment="Center" VerticalAlignment="Center" FontSize="{DynamicResource MainFontSize}" hc:TitleElement.VerticalAlignment="Center" Background="{DynamicResource BackgroundBrush}"  hc:TitleElement.Title="服务WIFI" hc:TitleElement.TitlePlacement="Left"></hc:TextBox>
                        </hc:Col>
                    </hc:Row>
                    <hc:Row Gutter="24" Margin="0,10" DataContext="{Binding DeviceCfg}">
                        <hc:Col Span="8">
                            <hc:TextBox hc:TitleElement.Title="出厂时间" Style="{DynamicResource TitleTextBoxBorderStyle}" Text="{Binding Release_Time,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Background="{DynamicResource BackgroundBrush}"></hc:TextBox>
                        </hc:Col>
                        <hc:Col Span="8">
                            <hc:TextBox hc:TitleElement.Title="软件版本" Style="{DynamicResource TitleTextBoxBorderStyle}" Text="{Binding Software_Version,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Background="{DynamicResource BackgroundBrush}"></hc:TextBox>
                        </hc:Col>
                        <hc:Col Span="8">
                            <hc:TextBox hc:TitleElement.Title="硬件版本" Style="{DynamicResource TitleTextBoxBorderStyle}" Text="{Binding Hardware_Version,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Background="{DynamicResource BackgroundBrush}"></hc:TextBox>
                        </hc:Col>
                    </hc:Row>
                    <hc:Row Gutter="24" Margin="0,10" DataContext="{Binding DeviceCfg}">
                        <hc:Col Span="8">
                            <hc:TextBox hc:TitleElement.Title="设备新Id" Style="{DynamicResource TitleTextBoxBorderStyle}" Text="{Binding Device_NewId,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Background="{DynamicResource BackgroundBrush}"  ></hc:TextBox>
                        </hc:Col>
                        <hc:Col Span="8">
                            <!--<hc:TextBox Name="txt_nodetype" hc:TitleElement.Title="设备类型" Style="{DynamicResource TitleTextBoxBorderStyle}" Text="{Binding NodeType,Mode=OneWay,UpdateSourceTrigger=PropertyChanged}" Background="{DynamicResource BackgroundBrush}"  ></hc:TextBox>-->
                        </hc:Col>
                        <hc:Col Span="8">
                        </hc:Col>
                    </hc:Row>
                    <hc:Row Gutter="24" Margin="0,10" DataContext="{Binding DeviceCfg}">
                        <hc:Col Span="8">
                            <hc:TextBox hc:TitleElement.Title="上传周期(秒)" Style="{DynamicResource TitleTextBoxBorderStyle}" Text="{Binding Upload_Cycle,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Background="{DynamicResource BackgroundBrush}"></hc:TextBox>
                        </hc:Col>
                        <hc:Col Span="8">
                            <hc:TextBox hc:TitleElement.Title="采样周期(毫秒)"  Style="{DynamicResource TitleTextBoxBorderStyle}" Text="{Binding Sample_Cycle,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Background="{DynamicResource BackgroundBrush}" ></hc:TextBox>
                        </hc:Col>
                        <hc:Col Span="8"></hc:Col>
                    </hc:Row>
                    <hc:Row Gutter="24" Margin="0,10" DataContext="{Binding ServerCfg}">
                        <hc:Col Span="8">
                            <Button FontSize="{DynamicResource MainFontSize}" Content="配置节点" IsEnabled="{Binding ConfigEnabled,Mode=OneWay,UpdateSourceTrigger=PropertyChanged}"  Padding="8" hc:BackgroundSwitchElement.MouseHoverBackground="{DynamicResource InfoBrush}"  hc:BackgroundSwitchElement.MouseDownBackground="{DynamicResource InfoBrush}" HorizontalAlignment="Left" VerticalAlignment="Center" Style="{DynamicResource ButtonCustom}" hc:BorderElement.CornerRadius="4" Margin="0,0,0,0"  Background="{DynamicResource InfoBrush}" Click="Button_Click_2"></Button>
                        </hc:Col>
                        <hc:Col Span="8">
                            <Button FontSize="{DynamicResource MainFontSize}" Content="升级节点"  Padding="8" hc:BackgroundSwitchElement.MouseHoverBackground="{DynamicResource InfoBrush}"  hc:BackgroundSwitchElement.MouseDownBackground="{DynamicResource InfoBrush}" HorizontalAlignment="Left" VerticalAlignment="Center" Style="{DynamicResource ButtonCustom}" hc:BorderElement.CornerRadius="4" Margin="0,0,0,0"  Background="{DynamicResource InfoBrush}" Click="Button_Click_1"></Button>
                        </hc:Col>
                        <hc:Col Span="8">
                            <Button FontSize="{DynamicResource MainFontSize}" Content="重启节点"  Padding="8" hc:BackgroundSwitchElement.MouseHoverBackground="{DynamicResource InfoBrush}"  hc:BackgroundSwitchElement.MouseDownBackground="{DynamicResource InfoBrush}" HorizontalAlignment="Left" VerticalAlignment="Center" Style="{DynamicResource ButtonCustom}" hc:BorderElement.CornerRadius="4" Margin="0,0,0,0"  Background="{DynamicResource InfoBrush}" Click="Button_Click_3"></Button>
                        </hc:Col>
                        <hc:Col Span="8">
                            <Button FontSize="{DynamicResource MainFontSize}" Content="配置重置" IsEnabled="{Binding ResetEnabled,Mode=OneWay,UpdateSourceTrigger=PropertyChanged}"  Padding="8" hc:BackgroundSwitchElement.MouseHoverBackground="{DynamicResource InfoBrush}"  hc:BackgroundSwitchElement.MouseDownBackground="{DynamicResource InfoBrush}" HorizontalAlignment="Left" VerticalAlignment="Center" Style="{DynamicResource ButtonCustom}" hc:BorderElement.CornerRadius="4" Margin="0,0,0,0"  Background="{DynamicResource InfoBrush}" Name="btn_reset" Click="btn_reset_Click"></Button>
                        </hc:Col>
                    </hc:Row>
                </StackPanel>
            </hc:UniformSpacingPanel>
        </DockPanel>
    </TabItem>
    <!--节点调试-->
</TabControl>

5、程序功能特点

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

本地日志封装如下:

/// <summary>
/// log4net日志记录
/// </summary>
public class Log4NetLogger : ILogger
{
    private readonly ILog _log;

    /// <summary>
    /// 初始化一个<see cref="Log4NetLogger"/>类型的新实例
    /// </summary>
    public Log4NetLogger(string loggerRepository, string name)
    {
        _log = LogManager.GetLogger(loggerRepository, name);
    }

    /// <summary>Writes a log entry.</summary>
    /// <param name="logLevel">日志级别,将按这个级别写不同的日志</param>
    /// <param name="eventId">事件编号.</param>
    /// <param name="state">The entry to be written. Can be also an object.</param>
    /// <param name="exception">The exception related to this entry.</param>
    /// <param name="formatter">Function to create a <c>string</c> message of the <paramref name="state" /> and <paramref name="exception" />.</param>
    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> 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;
            }
        }
    }

    /// <summary>
    /// Checks if the given <paramref name="logLevel" /> is enabled.
    /// </summary>
    /// <param name="logLevel">level to be checked.</param>
    /// <returns><c>true</c> if enabled.</returns>
    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);
        }
    }

    /// <summary>Begins a logical operation scope.</summary>
    /// <param name="state">The identifier for the scope.</param>
    /// <returns>An IDisposable that ends the logical operation scope on dispose.</returns>
    public IDisposable BeginScope<TState>(TState state)
    {
        return null;
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1559776.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Mock.js的基本使用

mock顾名思义&#xff0c;就是模拟的意思&#xff0c;它模拟什么呢&#xff1f;假设我们在开发的过程中&#xff0c;我们需要使用到接口&#xff0c;但是后端接口并没有完善&#xff0c;那么我们就可以使用到mock.js&#xff0c;它可以随机生成数据&#xff0c;拦截AJAX请求&am…

壁纸小程序Vue3(分类页面和用户页面基础布局)

1.配置tabBar pages.json "tabBar": {"color": "#9799a5","selectedColor": "#28B389","list": [{"text": "推荐","pagePath": "pages/index/index","iconPath&quo…

网络安全-内网渗透2

一、MIC 将我们上次未描述完的MIC在这里详细解释一下 咱们所抓的第二个包会给返回一个服务端的challenge 之后服务器回包的第三个包会回复一个client challenge 所以咱们客户端和服务端现在分别有两个challenge&#xff0c;相当于客户端和服务端互相交换了一下challenge 因此…

《深度学习入门之PyTorch》书籍阅读笔记

《深度学习入门之PyTorch》书籍阅读笔记 ISBN 978-7-121-32620-2 多层全连接神经网络 Pytorch基础 Tensor张量 是一个多维矩阵&#xff0c;pytorch的tensor可以和numpy的ndarray相互转换&#xff0c;但numpy的ndarray只能在CPU上运行。不同数据类型的Tensor&#xff0c;t…

nginx的https与动态负载均衡

nginx的https 证书可以根据你的域名和服务器服务商去进行签发 , 比如 : 阿里云 腾讯云 百度云 华为云等 这里使用的是腾讯云 : 下载证书 : 选择 nginx: 下载之后传递到服务器上。 下面开始配置nginx的https: 1. 解压下载的证书包 cd /etc/ssl unzip xxcc.dwa_nginx.zip mv…

JMeter基础用法和测试WebSocket请求

目录 JMeter websocket插件安装测试接口的编写添加测试线程组创建取样器创建WebSocket连接创建循环控制器创建WebSocket request-response Sampler创建固定定时器 正则匹配上一个请求的数据做为当前请求参数正则编写使用匹配值 CSV文件读取参数添加汇总报告和结果树 JMeter web…

zookeeper如何管理客户端与服务端之间的链接?(zookeeper sessions)

zookeeper客户端与服务端之间的链接用zookeeper session表示。 zookeeper session有三个状态&#xff1a; CONNECTING, ASSOCIATING, CONNECTED, CONNECTEDREADONLY, CLOSED, AUTH_FAILED, NOT_CONNECTED&#xff08;start时的状态&#xff09; 1、CONNECTING 。 表明客户…

vulhub中Apache Solr 远程命令执行漏洞复现(CVE-2017-12629)

Apache Solr 是一个开源的搜索服务器。Solr 使用 Java 语言开发&#xff0c;主要基于 HTTP 和 Apache Lucene 实现。原理大致是文档通过Http利用XML加到一个搜索集合中。查询该集合也是通过 http收到一个XML/JSON响应来实现。此次7.1.0之前版本总共爆出两个漏洞&#xff1a;[XM…

金三银四面试题(九):JVM常见面试题(3)

今天我们继续探讨常见的JVM面试题。这些问题不比之前的问题庞大&#xff0c;多用于面试中JVM部分的热身运动&#xff0c;开胃菜&#xff0c;但是大家已经要认真准备。 你能保证GC 执行吗&#xff1f; 不能&#xff0c;虽然你可以调用System.gc() 或者Runtime.gc()&#xff0c…

vue3+threejs新手从零开发卡牌游戏(二十四):添加p2战斗逻辑

用代码模拟p2战斗逻辑&#xff0c;按流程进行步骤拆分&#xff1a; 1.p2抽卡 2.p2召唤怪兽上场 3.p2战斗 其中战斗部分分为几种情况&#xff1a; 情况一&#xff1a;p2场上卡牌由大到小进行排序&#xff0c;按序轮询可以攻击的卡牌&#xff0c;然后攻击p1场上卡牌由大到小…

代码随想录阅读笔记-二叉树【完全二叉树节点个数】

题目 给出一个完全二叉树&#xff0c;求出该树的节点个数。 示例 1&#xff1a; 输入&#xff1a;root [1,2,3,4,5,6]输出&#xff1a;6 示例 2&#xff1a; 输入&#xff1a;root []输出&#xff1a;0 示例 3&#xff1a; 输入&#xff1a;root [1]输出&#xff1a;1 提示&…

玩转Django分页器

一、Pagination 分页器编程步骤 View, 导入django.core.paginator.Paginator类&#xff0c;创建Paginator 对象时&#xff0c;输入qs对象&#xff0c;以及每页显示条数。 接收 URL, 从请求参数中读取page数值 &#xff0c;通过 paginator.page(page_num) 返回请求页的page_obj…

pyinstaller打包多线程pyqt5程序后,报错,反复弹窗等问题

报错1&#xff1a; Traceback (most recent call last): File “MPL.py”, line 502, in File “Lib\site-packages\PyInstaller\hooks\rthooks\pyi_rth_multiprocessing.py”, line 45, in _freeze_support ValueError: not enough values to unpack (expected 2, got 1) 报…

绘制空心环形

1.通过几个div拼接绘制空心环形进度条。 通过 -webkit-mask: radial-gradient(transparent 150px, #fff 150px);绘制空心圆 html:<body><div class"circle"><div class"circle-left"></div><div class"circle-left-mask&…

从0开始搭建基于VUE的前端项目(二) 安装和配置element-ui组件库

版本和地址 ElementUI 2.15.14 (https://element.eleme.io/)按需引入的插件 babel-plugin-component(1.1.1) https://github.com/ElementUI/babel-plugin-component 安装 npm install element-ui完整引入(不建议) 这种方式最后打包的源文件很大&#xff0c;造成网络资源的浪…

LeetCode-56. 合并区间【数组 排序】

LeetCode-56. 合并区间【数组 排序】 题目描述&#xff1a;解题思路一&#xff1a;排序&#xff1f;怎么排&#xff1f;当然是排各个区间的左边界&#xff0c;然后判断下一个边界的左边界与结果数组里面的右边界是否重叠。解题思路二&#xff1a;优化解题思路三&#xff1a;0 题…

Vitepress部署到GitHub Pages,工作流

效果&#xff1a; 第一步&#xff1a; 部署 VitePress 站点 | VitePress 执行 npm run docs:build&#xff0c;npm run docs:preview&#xff0c;生成dist文件 第二步&#xff1a; 手动创建.gitignore文件&#xff1a; node_modules .DS_Store dist-ssr cache .cache .temp *…

[html]基础知识点汇总

前言 经过一阵子学习后&#xff0c;把知识点全部提炼了出来&#xff0c;自我感觉比较全和简洁&#xff0c;希望能够帮到大家。 本机实验环境 火狐浏览器&#xff0c;vscode&#xff0c;windows11&#xff0c;程序运行插件&#xff1a;live server html介绍 html--前端语言…

深入PostgreSQL中的pg_global表空间

pg_global表空间的位置 在PG当中&#xff0c;一个实例(cluster)初始化完以后&#xff0c;你会看到有下边两个与表空间相关的目录生成&#xff1a; $PGDATA/base $PGDATA/global 我们再用元命令\db以及相关视图看看相应的表空间信息&#xff1a; postgres# \db …

synchronized 关键字 - 监视器锁 monitor lock

目录 一、1 synchronized 的特性 1、互斥 2、可重入 二、synchronized 使用示例 1、修饰代码块: 明确指定锁哪个对象. 2、直接修饰普通⽅法: 锁的 SynchronizedDemo 对象 3、修饰静态方法: 锁的 SynchronizedDemo 类的对象 我们重点要理解&#xff0c;synchronized 锁…