智能家居
relativesource(智能家居WPF上位机+ModbusRTU)

一、效果展示

二、 VS2022

界面设计

引用NModbus4

三、代码展示

ViewModels、MainWindowViewModel.cs

using CommunityToolkit.Mvvm.ComponentModel;using CommunityToolkit.Mvvm.Input;using Modbus.Device;using System;using System.Collections.Generic;using System.Collections.ObjectModel;using System.IO;using System.IO.Ports;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Windows.documents;using System.Windows.Media;using System.Windows.Media.Imaging;using Wpf.Ui.Controls;namespace WpfHomeModbusDemo.ViewModels{    public partial class MainWindowViewModel: ObservableObject    {        public MainWindowViewModel()        {            //获取串口列表            portNameList = new ObservableCollection<string>(SerialPort.GetPortNames());            //初始化波特率 数据位 校验位 停止位 列表            BaudRateList = new ObservableCollection<int> { 4800, 9600, 19200, 38400, 57600, 115200 };            DataBitsList = new ObservableCollection<int> { 7, 8 };            ParityList = new ObservableCollection<Parity>(Enum.GetValues(typeof(Parity)).Cast<Parity>());            StopBitsList = new ObservableCollection<StopBits>(Enum.GetValues(typeof(StopBits)).Cast<StopBits>());            //设置串口默认值            SelectedBaudRate = 9600;            SelectedDataBits = 8;            SelectedParity = Parity.None;            SelectedStopBits = StopBits.One;            //设置默认媒体图片            MediaImage = new BitmapImage(new Uri("pack://application:,,,/Images/image.jpg"));        }        //串口列表        [ObservableProperty]        private ObservableCollection<string> portNameList;        //波特率列表        [ObservableProperty]        private ObservableCollection<int> baudRateList;        //数据位列表        [ObservableProperty]        private ObservableCollection<int> dataBitsList;        [ObservableProperty]        private ObservableCollection<Parity> parityList;        [ObservableProperty]        private ObservableCollection<StopBits> stopBitsList;        //串口属性        [ObservableProperty]        private string selectedPort;        [ObservableProperty]        private int selectedBaudRate;        [ObservableProperty]        private int selectedDataBits;        [ObservableProperty]        private Parity selectedParity;        [ObservableProperty]        private StopBits selectedStopBits;        [ObservableProperty]        private bool isConnected;        private SerialPort serialPort;        private IModbusSerialMaster master;        //连接串口        [RelayCommand]        private void Connect()        {            if (IsConnected == true)            {                Open();                StartReading();            }            else            {                Close();            }        }        //打开连接        private bool Open()         {            try            {                serialPort = new SerialPort();                serialPort.PortName = SelectedPort;                serialPort.BaudRate = SelectedBaudRate;                serialPort.DataBits = SelectedDataBits;                serialPort.Parity = SelectedParity;                serialPort.StopBits = SelectedStopBits;                serialPort.Open();                master = ModbusSerialMaster.CreateRtu(serialPort);                master.Transport.ReadTimeout = 2000; //读超时                master.Transport.Retries = 3;  //重试次数                var uiMessageBox = new Wpf.Ui.Controls.MessageBox                {                    Title = "提示",                    Content = "串口打开成功",                };                uiMessageBox.ShowDialogAsync(true);                return true;            }            catch (UnauthorizedAccessException)            {                var uiMessageBox = new Wpf.Ui.Controls.MessageBox                {                    Title = "错误",                    Content = "无法访问串口,可能串口被其他程序占用",                };                uiMessageBox.ShowDialogAsync(false);                return false;            }            catch (IOException ex)            {                var uiMessageBox = new Wpf.Ui.Controls.MessageBox                {                    Title = "错误",                    Content = "串口连接错误: " + ex.Message,                };                uiMessageBox.ShowDialogAsync(false);                return false;            }            catch (Exception ex)            {                var uiMessageBox = new Wpf.Ui.Controls.MessageBox                {                    Title = "错误",                    Content = "打开串口时发生未知错误: " + ex.Message                };                uiMessageBox.ShowDialogAsync(false);                return false;            }        }        //关闭连接        private void Close()        {            try            {                if (serialPort.IsOpen)                {                    serialPort.Close();                    var uiMessageBox = new Wpf.Ui.Controls.MessageBox                    {                        Title = "提示",                        Content = "串口已关闭!"                    };                    uiMessageBox.ShowDialogAsync(false);                }            }            catch (Exception ex)            {                var uiMessageBox = new Wpf.Ui.Controls.MessageBox                {                    Title = "错误",                    Content = "关闭串口时发生错误: " + ex.Message                };                uiMessageBox.ShowDialogAsync(false);            }        }        //检查连接        private void CheckConnection()        {            if (master == null || !IsConnected)            {                throw new InvalidOperationException("串口未打开或已断开,请检查连接!");            }        }        [ObservableProperty]        private bool mainLightIsChecked;        [ObservableProperty]        private bool deskLampIsChecked;        [ObservableProperty]        private bool tvIsChecked;        [ObservableProperty]        private bool pS5IsChecked;        [ObservableProperty]        private ImageSource mediaImage;        //主厅灯光数值        [ObservableProperty]        private string mainLight;        //数据读取        private async void StartReading()        {            while (true)            {                // 如果没有连接,则停止循环                if (!IsConnected)                {                    break;                }                Read(); // 执行读取数据                await Task.Delay(1000); // 每隔1秒读取一次            }        }        private void Read()        {            if (master == null)            {                //var uiMessageBox = new Wpf.Ui.Controls.MessageBox                //{                //    Title = "提示",                //    Content = "串口未打开或已断开,请检查连接!",                //};                //uiMessageBox.ShowDialogAsync(false);                return;            }            //客厅部分            byte slaveAddress = 1;      //从站地址            ushort startAddress = 0;    //起始寄存器地址            ushort numberOfPoints = 4;  //读取寄存器数量            // 读取从站信息            CheckConnection();            // 01 输入状态            try            {                bool[] LivingRoom = master.ReadCoils(slaveAddress, startAddress, numberOfPoints);                //主厅灯                MainLightIsChecked = LivingRoom[0];                //氛围灯                DeskLampIsChecked = LivingRoom[1];                //电视                TvIsChecked = LivingRoom[2];                //PS5                PS5IsChecked = LivingRoom[3];            }            catch (Modbus.SlaveException ex)            {                HandleModbusException(ex);            }            // 04 输入寄存器            try            {                //04                ushort[] register = master.ReadHoldingRegisters(slaveAddress, startAddress, numberOfPoints);                MainLight = register[0]!.ToString();            }            catch (Modbus.SlaveException ex)            {                HandleModbusException(ex);            }        }        // 处理 Modbus 错误        private void HandleModbusException(Modbus.SlaveException ex)        {            var uiMessageBox = new Wpf.Ui.Controls.MessageBox            {                Title = "提示",                Content = "未知错误",            };            switch (ex.SlaveExceptionCode)            {                case 1:                    uiMessageBox.Content = "异常代码 1: 非法功能码!";                    uiMessageBox.ShowDialogAsync();                    break;                case 2:                    uiMessageBox.Content = "异常代码 2: 非法数据地址!";                    uiMessageBox.ShowDialogAsync();                    break;                case 3:                    uiMessageBox.Content = "异常代码 3: 非法数据值!";                    uiMessageBox.ShowDialogAsync();                    break;                case 4:                    uiMessageBox.Content = "异常代码 4: 从设备故障!";                    uiMessageBox.ShowDialogAsync();                    break;                case 5:                    uiMessageBox.Content = "异常代码 5: 硬件故障!";                    uiMessageBox.ShowDialogAsync();                    break;                case 6:                    uiMessageBox.Content = "异常代码 6: 从设备忙!";                    uiMessageBox.ShowDialogAsync();                    break;                case 7:                    uiMessageBox.Content = "异常代码 7: 内存错误!";                    uiMessageBox.ShowDialogAsync();                    break;                default:                    uiMessageBox.Content = "未知异常代码: " + ex.SlaveExceptionCode;                    uiMessageBox.ShowDialogAsync();                    break;            }            Close();        }        [RelayCommand]        private void WriteLighting()         {            //写入数据            if (master == null)            {                var uiMessageBox = new Wpf.Ui.Controls.MessageBox                {                    Title = "提示",                    Content = "串口未打开或已断开,请检查连接!",                };                uiMessageBox.ShowDialogAsync(false);                return;            }            byte slaveAddress = 1;            ushort coilAddress = 0;            //可能事件与IsChecked冲突,这里暂时不需要取反            //bool value = !MainLightIsChecked;            bool value = MainLightIsChecked;            master.WriteSingleCoil(slaveAddress, coilAddress, value);            ushort registerAddress = 0;            ushort valueToWrite = 0;            try            {                master.WriteSingleRegister(slaveAddress, registerAddress, valueToWrite);            }            catch (Modbus.SlaveException ex)            {                HandleModbusException(ex);            }            // 可选:写完后读回来验证            //ushort[] readBack = master.ReadHoldingRegisters(slaveAddress, registerAddress, 1);            //MainLight = readBack[0].ToString();        }    }}

MainWindow.xaml

<Window    x:Class="WpfHomeModbusDemo.MainWindow"    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"    xmlns:controls="clr-namespace:WpfHomeModbusDemo.Controls"    xmlns:converters="clr-namespace:WpfHomeModbusDemo.Converters"    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"    xmlns:local="clr-namespace:WpfHomeModbusDemo"    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"    xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"    xmlns:viewmodels="clr-namespace:WpfHomeModbusDemo.ViewModels"    Title=""    Width="1200"    Height="750"    d:DataContext="{d:DesignInstance viewmodels:MainWindowViewModel}"    mc:Ignorable="d">    <Window.Resources>        <converters:ProgressRingVisibilityConverter x:Key="ProgressRingVisibilityConverter" />    </Window.Resources>    <Grid>        <Grid.ColumnDefinitions>            <ColumnDefinition Width="0.25*" />            <ColumnDefinition />        </Grid.ColumnDefinitions>        <Grid Grid.Column="0" Background="#111517">            <DockPanel                MinWidth="150"                Margin="25"                HorizontalAlignment="Center"                LastChildFill="False">                <StackPanel DockPanel.Dock="Top">                    <StackPanel Orientation="Vertical">                        <TextBlock Foreground="White" Text="PortName" />                        <ComboBox                            Margin="0,10,0,0"                            ItemsSource="{Binding PortNameList}"                            SelectedItem="{Binding SelectedPort, Mode=TwoWay}" />                    </StackPanel>                    <StackPanel Margin="0,20,0,0" Orientation="Vertical">                        <TextBlock Foreground="White" Text="BaudRate" />                        <ComboBox                            Margin="0,10,0,0"                            ItemsSource="{Binding BaudRateList}"                            SelectedItem="{Binding SelectedBaudRate, Mode=TwoWay}" />                    </StackPanel>                    <StackPanel Margin="0,20,0,0" Orientation="Vertical">                        <TextBlock Foreground="White" Text="DataBits" />                        <ComboBox                            Margin="0,10,0,0"                            ItemsSource="{Binding DataBitsList}"                            SelectedItem="{Binding SelectedDataBits, Mode=TwoWay}" />                    </StackPanel>                    <StackPanel Margin="0,20,0,0" Orientation="Vertical">                        <TextBlock Foreground="White" Text="Parity" />                        <ComboBox                            Margin="0,10,0,0"                            ItemsSource="{Binding ParityList}"                            SelectedItem="{Binding SelectedParity, Mode=TwoWay}" />                    </StackPanel>                    <StackPanel Margin="0,20,0,0" Orientation="Vertical">                        <TextBlock Foreground="White" Text="StopBits" />                        <ComboBox                            Margin="0,10,0,0"                            ItemsSource="{Binding StopBitsList}"                            SelectedItem="{Binding SelectedStopBits, Mode=TwoWay}" />                    </StackPanel>                    <ui:ToggleSwitch                        x:Name="SerialToggle"                        Margin="0,50,0,0"                        Command="{Binding ConnectCommand}"                        IsChecked="{Binding IsConnected, Mode=TwoWay}"                        OffContent="关"                        onContent="开" />                </StackPanel>            </DockPanel>        </Grid>        <Grid Grid.Column="1" Background="#151a1b">            <UniformGrid                Margin="30"                Columns="3"                Rows="2">                <DockPanel Margin="0,0,10,10">                    <TextBlock                        DockPanel.Dock="Top"                        FontFamily="微软雅黑"                        FontSize="24"                        FontWeight="Bold"                        Foreground="#bcbebf"                        Text="客厅" />                    <UniformGrid                        Margin="0,10,0,0"                        Columns="2"                        Rows="2">                        <controls:DeviceToggleButton                            Margin="0,0,5,5"                            DeviceName="主厅"                            DeviceType="灯光"                            Devicevalue="{Binding MainLight}"                            Icon="{DynamicResource Lighting}"                            Command="{Binding WriteLightingCommand}"                            IsChecked="{Binding MainLightIsChecked}">                            <ui:ProgressRing                                Width="32"                                Height="32"                                Foreground="#b6b6b6"                                Progress="{Binding Devicevalue, RelativeSource={RelativeSource AncestorType=controls:DeviceToggleButton}}"                                Visibility="{Binding Devicevalue, RelativeSource={RelativeSource AncestorType=controls:DeviceToggleButton}, Converter={StaticResource ProgressRingVisibilityConverter}}" />                        </controls:DeviceToggleButton>                        <controls:DeviceToggleButton                            Margin="5,0,0,5"                            DeviceName="氛围"                            DeviceType="灯光"                            Icon="{DynamicResource DeskLamp}"                            IsChecked="{Binding DeskLampIsChecked}" />                        <controls:DeviceToggleButton                            Margin="0,5,5,0"                            DeviceName="电视"                            DeviceType="媒体"                            Icon="{DynamicResource Tv}"                            IsChecked="{Binding TvIsChecked}" />                        <controls:DeviceToggleButton                            Margin="5,5,0,0"                            DeviceName="游戏机"                            DeviceType="媒体"                            Icon="{DynamicResource PS5}"                            IsChecked="{Binding PS5IsChecked}" />                    </UniformGrid>                </DockPanel>                <DockPanel Margin="10,0,10,10">                    <TextBlock                        DockPanel.Dock="Top"                        FontFamily="微软雅黑"                        FontSize="24"                        FontWeight="Bold"                        Foreground="#bcbebf"                        Text="工作室" />                    <UniformGrid                        Margin="0,10,0,0"                        Columns="2"                        Rows="2">                        <controls:DeviceToggleButton                            Margin="0,0,5,5"                            DeviceName="工作"                            DeviceType="灯光"                            Icon="{DynamicResource Lighting}" />                        <controls:DeviceToggleButton                            Margin="5,0,0,5"                            DeviceName="射灯"                            DeviceType="灯光"                            Icon="{DynamicResource Spotlight}" />                        <controls:DeviceToggleButton                            Margin="0,5,5,0"                            DeviceName="电脑"                            DeviceType="媒体"                            Icon="{DynamicResource Computer}" />                        <controls:DeviceToggleButton                            Margin="5,5,0,0"                            DeviceName="音响"                            DeviceType="媒体"                            Icon="{DynamicResource Speaker}" />                    </UniformGrid>                </DockPanel>                <DockPanel Margin="10,0,0,10">                    <TextBlock                        DockPanel.Dock="Top"                        FontFamily="微软雅黑"                        FontSize="24"                        FontWeight="Bold"                        Foreground="#bcbebf"                        Text="卧室" />                    <UniformGrid                        Margin="0,10,0,0"                        Columns="2"                        Rows="2">                        <controls:DeviceToggleButton                            Margin="0,0,5,5"                            DeviceName="床头灯(左)"                            DeviceType="灯光"                            Icon="{DynamicResource BedsideLamp}" />                        <controls:DeviceToggleButton                            Margin="5,0,0,5"                            DeviceName="床头灯(右)"                            DeviceType="灯光"                            Icon="{DynamicResource BedsideLamp}" />                        <controls:DeviceToggleButton                            Margin="0,5,5,0"                            DeviceName="风扇"                            DeviceType="家居"                            Icon="{DynamicResource Fan}" />                        <controls:DeviceToggleButton                            Margin="5,5,0,0"                            DeviceName="电视"                            DeviceType="媒体"                            Icon="{DynamicResource Tv}" />                    </UniformGrid>                </DockPanel>                <DockPanel Margin="0,10,10,0">                    <TextBlock                        DockPanel.Dock="Top"                        FontFamily="微软雅黑"                        FontSize="24"                        FontWeight="Bold"                        Foreground="#bcbebf"                        Text="媒体" />                    <Border                        Margin="0,10,0,0"                        Background="Transparent"                        ClipToBounds="True"                        CornerRadius="10"                        DockPanel.Dock="Bottom">                        <Image Source="{Binding MediaImage}"  Stretch="UniformToFill" />                    </Border>                </DockPanel>                <DockPanel Margin="10,10,10,0">                    <TextBlock                        DockPanel.Dock="Top"                        FontFamily="微软雅黑"                        FontSize="24"                        FontWeight="Bold"                        Foreground="#bcbebf"                        Text="灯光" />                    <UniformGrid                        Margin="0,10,0,0"                        Columns="2"                        Rows="2">                        <controls:DeviceToggleButton                            Margin="0,0,5,5"                            DeviceName="卫生间"                            DeviceType="灯光"                            Icon="{DynamicResource Bathroom}" />                        <controls:DeviceToggleButton                            Margin="5,0,0,5"                            DeviceName="厨房"                            DeviceType="灯光"                            Icon="{DynamicResource Kitchen}" />                        <controls:DeviceToggleButton                            Margin="0,5,5,0"                            DeviceName="衣帽间"                            DeviceType="灯光"                            Icon="{DynamicResource WalkInCloset}" />                        <controls:DeviceToggleButton                            Margin="5,5,0,0"                            DeviceName="大厅"                            DeviceType="灯光"                            Icon="{DynamicResource Spotlight}" />                    </UniformGrid>                </DockPanel>                <DockPanel Margin="10,10,0,0">                    <TextBlock                        DockPanel.Dock="Top"                        FontFamily="微软雅黑"                        FontSize="24"                        FontWeight="Bold"                        Foreground="#bcbebf"                        Text="家" />                    <UniformGrid                        Margin="0,10,0,0"                        Columns="2"                        Rows="2">                        <controls:DeviceToggleButton                            Margin="0,0,5,5"                            DeviceName="温度计"                            DeviceType="家居"                            Icon="{DynamicResource Thermometer}" />                        <controls:DeviceToggleButton                            Margin="5,0,0,5"                            DeviceName="插座能耗"                            DeviceType="家居"                            Icon="{DynamicResource EnergyConsumption}" />                        <controls:DeviceToggleButton                            Margin="0,5,5,0"                            DeviceName="空调"                            DeviceType="家居"                            Icon="{DynamicResource AirConditioner}" />                        <controls:DeviceToggleButton                            Margin="5,5,0,0"                            DeviceName="Yao"                            DeviceType="成员"                            Icon="{DynamicResource Man}" />                    </UniformGrid>                </DockPanel>            </UniformGrid>        </Grid>    </Grid></Window>

MainWindow.xaml.cs

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Windows;using System.Windows.Controls;using System.Windows.Data;using System.Windows.documents;using System.Windows.Input;using System.Windows.Media;using System.Windows.Media.Imaging;using System.Windows.Navigation;using System.Windows.Shapes;using WpfHomeModbusDemo.ViewModels;namespace WpfHomeModbusDemo{    /// <summary>    /// MainWindow.xaml 的交互逻辑    /// </summary>    public partial class MainWindow : Window    {        public MainWindow()        {            InitializeComponent();            this.DataContext = new MainWindowViewModel();        }    }}

学员招募

C#上位机编程一对一教学+就位指导,有兴趣的私信。


顶一下()     踩一下()

热门推荐

发表评论
0评