跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C#AI算法

.NET 8 跨平台自动化开发实战:从设备监控到路径规划

.NET 8 跨平台在工业自动化中展现出高性价比,支持 Windows 工控机与树莓派等嵌入式设备统一开发。通过 MQTT 等协议实现上下位机一体化架构,涵盖温湿度监控、设备控制及 RRT* 路径规划等核心场景。文章提供了完整的 Worker Service 与 WinForms 代码示例,并针对实时性、资源占用、断网续传及动态避障等工业现场常见问题给出了具体优化方案,助力提升开发效率与维护便利性。

Kubernet发布于 2026/3/27更新于 2026/8/1838 浏览

.NET 8 跨平台自动化开发实战

基于真实中小型自动化项目经验(实验室温湿度监控、智能家居、小型产线测试台等),本文分享一套全部使用 .NET 8(跨平台)的解决方案。代码同时适用于 Windows 工控机/上位机和树莓派/工业迷你PC/Jetson Nano 等下位机运行环境。

用 C# 做下位机,正好能弥补传统方案的短板。尤其是.NET Core(现在的.NET 8)跨平台之后,C#不仅能跑在Windows上,还能流畅运行在树莓派、工业迷你PC等嵌入式设备上,这让'同一套语言写上下位机'成为可能。

一句话总结:在中小规模、非极端苛刻实时性的场景里,C#上下位机一体化开发是当前性价比最高、最容易维护的方案。

典型场景与技术选型对比

场景类型传统方案C#上下位机一体化方案优势适用性评分
实验室温湿度监控PLC + 组态王 / LabVIEWC#统一开发,成本低,易集成数据库/云端,扩展性强★★★★★
小型产线测试台STM32 + 上位机C#上下位机共享代码逻辑,调试效率翻倍,维护成本大幅降低★★★★★
智能家居/小型设备控制ESP32 + App/小程序C#跨端(桌面+嵌入式+移动端MAUI),生态统一★★★★☆
高实时运动控制西门子1200 + C#上位机下位机仍建议PLC,上位机C#,不适合C#做下位机★★☆☆☆

结论:当实时性要求 < 10ms、需要极高抗干扰时,优先PLC/单片机;当实时性 50–500ms 可接受、需要快速迭代、数据分析、网络通信时,C#上下位机一体化是最佳选择。

上下位机一体化架构设计

[上位机(PC/工控机).NET 8 WinForms / MAUI]
├── UI层(实时曲线、参数设置、报警看板)
├── 业务层(数据分析、报表、云端同步)
└── 通信层(MQTT / TCP Socket / gRPC)
↑↓(同一协议,双向通信)
[下位机(树莓派 / 迷你PC / Jetson).NET 8 Console / Worker Service]
├── 采集层(串口 / I2C / GPIO / ADC)
├── 执行层(继电器 / PWM / DAC / 步进电机)
└── 通信层(MQTT / TCP Socket / gRPC)
↑↓
[物理层]
├── 传感器(DHT22、SHT30、PT100、压力变送器)
└── 执行器(继电器、电磁阀、步进电机、伺服)

核心通信协议选择

中小项目推荐排序如下:

  1. MQTT(首选):轻量、发布订阅、断网续传、跨端天然支持。
  2. TCP Socket:自定义协议,延迟最低,适合高频小包。
  3. gRPC:结构化、高性能、支持流式通信(未来趋势)。
  4. Modbus TCP:兼容老设备,但效率较低。

实战代码实现(温湿度监控 + 设备控制)

1. 下位机核心代码(Console + MQTT)

下位机项目:Worker Service(.NET 8)

这里我们使用 System.Device.Gpio 和 Iot.Device.Dht 库来驱动硬件。注意初始化时的引脚编号和 GPIO 控制器配置。

using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using MQTTnet;
using MQTTnet.Client;
using System.Device.Gpio;
using System.Device.I2c;
using Iot.Device.Dht;

public class Worker : BackgroundService
{
    private readonly ILogger<Worker> _logger;
    private IMqttClient _mqttClient;
    private Dht22 _dht22;

    public Worker(ILogger<Worker> logger)
    {
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        // 初始化 DHT22(GPIO 4)
        _dht22 = new Dht22(4, PinNumberingScheme.Board);

        // MQTT 客户端
        var factory = new MqttFactory();
        _mqttClient = factory.CreateMqttClient();
        var options = new MqttClientOptionsBuilder()
            .WithTcpServer("192.168.1.100", 1883) // 上位机 IP
            .WithClientId("RPi-Downlink-" + Guid.NewGuid().ToString("N").Substring(0, 8))
            .Build();

        await _mqttClient.ConnectAsync(options, stoppingToken);

        // 订阅上位机控制指令
        await _mqttClient.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("device/control/#").Build());
        
        _mqttClient.ApplicationMessageReceivedAsync += async e =>
        {
            string topic = e.ApplicationMessage.Topic;
            string payload = Encoding.UTF8.GetString(e.ApplicationMessage.PayloadSegment);
            
            if (topic == "device/control/relay1")
            {
                bool state = payload == "ON";
                // 假设继电器接 GPIO 17
                using var controller = new GpioController();
                controller.OpenPin(17, PinMode.Output);
                controller.Write(17, state ? PinValue.High : PinValue.Low);
                _logger.LogInformation("继电器 1 {State}", state ? "开" : "关");
            }
        };

        // 定时采集 & 上报
        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                var reading = _dht22.Read();
                if (reading.IsValid)
                {
                    var json = System.Text.Json.JsonSerializer.Serialize(new
                    {
                        temperature = reading.Temperature.DegreesCelsius,
                        humidity = reading.Humidity.Percent,
                        timestamp = DateTime.UtcNow
                    });
                    var msg = new MqttApplicationMessageBuilder()
                        .WithTopic("device/sensor/dht22")
                        .WithPayload(json)
                        .Build();
                    await _mqttClient.PublishAsync(msg, stoppingToken);
                }
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "传感器读取异常");
            }
            await Task.Delay(2000, stoppingToken);
        }
    }
}

2. 上位机接收与控制界面

WinForms 示例(实时曲线 + 控制按钮)

上位机负责数据展示和指令下发。这里使用了 ZedGraph 绘制实时曲线,并通过 MQTT 订阅传感器数据。

public partial class FormMain : Form
{
    private IMqttClient _mqttClient;
    private RollingPointPairList curveTemp = new RollingPointPairList(3600);
    private RollingPointPairList curveHumid = new RollingPointPairList(3600);

    public FormMain()
    {
        InitializeComponent();
        SetupZedGraph();

        // MQTT 连接
        var factory = new MqttFactory();
        _mqttClient = factory.CreateMqttClient();
        var options = new MqttClientOptionsBuilder()
            .WithTcpServer("localhost", 1883) // 或树莓派 IP
            .WithClientId("PC-Uplink")
            .Build();
        _mqttClient.ConnectAsync(options);

        _mqttClient.ApplicationMessageReceivedAsync += e =>
        {
            if (e.ApplicationMessage.Topic == "device/sensor/dht22")
            {
                var json = Encoding.UTF8.GetString(e.ApplicationMessage.PayloadSegment);
                var data = System.Text.Json.JsonSerializer.Deserialize<SensorData>(json);
                this.Invoke((MethodInvoker)(() =>
                {
                    curveTemp.Add(DateTime.Now.ToOADate(), data.temperature);
                    curveHumid.Add(DateTime.Now.ToOADate(), data.humidity);
                    zgc.Invalidate();
                    lblTemp.Text = $"温度:{data.temperature:F1} °C";
                    lblHumid.Text = $"湿度:{data.humidity:F1} %";
                }));
            }
            return Task.CompletedTask;
        };

        // 订阅
        _mqttClient.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("device/sensor/#").Build());
    }

    private void btnRelayOn_Click(object sender, EventArgs e)
    {
        var msg = new MqttApplicationMessageBuilder()
            .WithTopic("device/control/relay1")
            .WithPayload("ON")
            .Build();
        _mqttClient.PublishAsync(msg);
    }

    private void btnRelayOff_Click(object sender, EventArgs e)
    {
        var msg = new MqttApplicationMessageBuilder()
            .WithTopic("device/control/relay1")
            .WithPayload("OFF")
            .Build();
        _mqttClient.PublishAsync(msg);
    }

    private void SetupZedGraph()
    {
        var pane = zgc.GraphPane;
        pane.AddCurve("温度", curveTemp, Color.Red, SymbolType.None);
        pane.AddCurve("湿度", curveHumid, Color.Blue, SymbolType.None);
    }
}

public class SensorData
{
    public double temperature { get; set; }
    public double humidity { get; set; }
}

进阶案例:RRT* 路径规划算法

除了数据采集,自动化项目中常涉及 AGV、机械臂的路径规划。下面是一个基于 .NET 8 的 RRT* 算法实现版本,包含详细注释、异常处理及动态障碍支持。

1. 核心类定义

using System;
using System.Collections.Generic;
using System.Linq;

// 点结构(支持浮点坐标)
public readonly struct Point2D(double x, double y)
{
    public double X { get; } = x;
    public double Y { get; } = y;

    public double DistanceTo(Point2D other) => Math.Sqrt(Math.Pow(X - other.X, 2) + Math.Pow(Y - other.Y, 2));

    public override string ToString() => $"({X:F2}, {Y:F2})";
}

// RRT* 节点
public class RRTNode
{
    public Point2D Position { get; }
    public RRTNode Parent { get; set; }
    public double Cost { get; set; } // 从起点到该节点的累计成本
    public List<RRTNode> Children { get; } = new(); // 用于 rewire 时快速查找子节点

    public RRTNode(Point2D pos, RRTNode parent = null, double cost = 0)
    {
        Position = pos;
        Parent = parent;
        Cost = cost;
        if (parent != null) parent.Children.Add(this);
    }

    // 计算到目标的启发式距离(可换成欧氏、曼哈顿等)
    public double HeuristicTo(Point2D goal) => Position.DistanceTo(goal);
}

// 障碍物(简单矩形表示)
public class Obstacle
{
    public double MinX { get; }
    public double MinY { get; }
    public double MaxX { get; }
    public double MaxY { get; }

    public Obstacle(double minX, double minY, double maxX, double maxY)
    {
        MinX = minX; MinY = minY; MaxX = maxX; MaxY = maxY;
    }

    public bool Contains(Point2D p) => p.X >= MinX && p.X <= MaxX && p.Y >= MinY && p.Y <= MaxY;

    public bool IntersectsLine(Point2D a, Point2D b)
    {
        // 线段与矩形相交检测(简化版,实际项目可使用更精确算法)
        return !(b.X < MinX || a.X > MaxX || b.Y < MinY || a.Y > MaxY);
    }
}

2. RRT* 主规划类

public class RRTStarPlanner
{
    private readonly Point2D _start;
    private readonly Point2D _goal;
    private readonly double _maxStep;      // 单次扩展最大步长
    private readonly double _nearRadius;   // 附近节点搜索半径
    private readonly int _maxIterations;   // 最大迭代次数
    private readonly double _goalSampleRate; // 朝目标采样概率
    private readonly double _goalTolerance; // 到达目标的容差半径
    private readonly List<Obstacle> _obstacles; // 静态障碍物
    private readonly List<RRTNode> _nodes = new();
    private readonly Random _rand = new();

    public RRTStarPlanner(Point2D start, Point2D goal, double maxStep = 30.0, double nearRadius = 50.0, int maxIterations = 5000, double goalSampleRate = 0.1, double goalTolerance = 10.0, List<Obstacle> obstacles = null)
    {
        _start = start;
        _goal = goal;
        _maxStep = maxStep;
        _nearRadius = nearRadius;
        _maxIterations = maxIterations;
        _goalSampleRate = goalSampleRate;
        _goalTolerance = goalTolerance;
        _obstacles = obstacles ?? new List<Obstacle>();

        // 初始化根节点
        _nodes.Add(new RRTNode(start, null, 0));
    }

    /// <summary>
    /// 执行 RRT* 规划,返回路径点列表(从起点到终点)
    /// </summary>
    /// <returns>成功返回路径点列表,失败返回 null</returns>
    public List<Point2D> Plan()
    {
        for (int i = 0; i < _maxIterations; i++)
        {
            Point2D randPoint = Sample();
            RRTNode nearest = Nearest(randPoint);
            Point2D newPos = Steer(nearest.Position, randPoint);

            if (!IsCollision(nearest.Position, newPos))
            {
                var nearNodes = Near(newPos);
                var parent = ChooseParent(nearest, nearNodes, newPos);
                var newNode = new RRTNode(newPos, parent, parent.Cost + parent.Position.DistanceTo(newPos));
                _nodes.Add(newNode);

                // RRT* 核心:rewire 附近节点
                Rewire(nearNodes, newNode);

                // 检查是否到达目标
                if (newPos.DistanceTo(_goal) <= _goalTolerance)
                {
                    return ReconstructPath(newNode);
                }
            }
        }

        // 找不到路径,返回最近的节点路径(渐进最优特性)
        var closest = _nodes.OrderBy(n => n.Position.DistanceTo(_goal)).First();
        return ReconstructPath(closest);
    }

    private Point2D Sample()
    {
        // 以一定概率直接采样目标点(加快收敛)
        if (_rand.NextDouble() < _goalSampleRate) return _goal;

        // 随机采样(地图范围可根据实际场景调整)
        double x = _rand.NextDouble() * 1000;
        double y = _rand.NextDouble() * 1000;
        return new Point2D(x, y);
    }

    private RRTNode Nearest(Point2D point) => _nodes.OrderBy(n => n.Position.DistanceTo(point)).First();

    private Point2D Steer(Point2D from, Point2D to)
    {
        double dist = from.DistanceTo(to);
        if (dist <= _maxStep) return to;
        double angle = Math.Atan2(to.Y - from.Y, to.X - from.X);
        return new Point2D(from.X + _maxStep * Math.Cos(angle), from.Y + _maxStep * Math.Sin(angle));
    }

    private List<RRTNode> Near(Point2D point) => _nodes.Where(n => n.Position.DistanceTo(point) < _nearRadius).ToList();

    private RRTNode ChooseParent(RRTNode nearest, List<RRTNode> nearNodes, Point2D newPos)
    {
        RRTNode best = nearest;
        double bestCost = nearest.Cost + nearest.Position.DistanceTo(newPos);
        foreach (var near in nearNodes)
        {
            double cost = near.Cost + near.Position.DistanceTo(newPos);
            if (cost < bestCost && !IsCollision(near.Position, newPos))
            {
                bestCost = cost;
                best = near;
            }
        }
        return best;
    }

    private void Rewire(List<RRTNode> nearNodes, RRTNode newNode)
    {
        foreach (var near in nearNodes)
        {
            double newCost = newNode.Cost + newNode.Position.DistanceTo(near.Position);
            if (newCost < near.Cost && !IsCollision(newNode.Position, near.Position))
            {
                // 断开旧父节点连接
                near.Parent?.Children.Remove(near);
                near.Parent = newNode;
                near.Cost = newCost;
                newNode.Children.Add(near);
            }
        }
    }

    private bool IsCollision(Point2D a, Point2D b)
    {
        foreach (var obs in _obstacles)
        {
            // 简单线段 - 矩形相交检测(实际可使用更精确算法)
            if (LineIntersectsRect(a, b, obs)) return true;
        }
        return false;
    }

    private bool LineIntersectsRect(Point2D a, Point2D b, Obstacle rect)
    {
        // 实现略(可使用标准线段 - 矩形相交算法,如 Liang-Barsky 或 Cohen-Sutherland)
        // 这里简化返回 false,实际项目中必须完整实现
        return false;
    }

    private List<Point2D> ReconstructPath(RRTNode node)
    {
        var path = new List<Point2D>();
        while (node != null)
        {
            path.Add(node.Position);
            node = node.Parent;
        }
        path.Reverse();
        return path;
    }
}

使用示例

// 地图范围 1000×1000,障碍物示例
var obstacles = new List<Obstacle>
{
    new Obstacle(200, 200, 300, 500), // 矩形障碍
    new Obstacle(600, 400, 800, 550)
};

var planner = new RRTStarPlanner(
    start: new Point2D(50, 50),
    goal: new Point2D(950, 950),
    maxStep: 25.0,       // 步长(可调,越大越快但路径越粗糙)
    nearRadius: 60.0,    // 附近搜索半径(影响 rewire 效果)
    maxIterations: 8000, // 迭代次数(越大越优,但耗时增加)
    goalSampleRate: 0.15,// 偏向目标采样概率
    goalTolerance: 15.0, // 到达目标的容差半径
    obstacles: obstacles
);

// 规划路径
var path = planner.Plan();
if (path != null)
{
    Console.WriteLine($"找到路径,共 {path.Count} 个点");
    foreach (var p in path) Console.WriteLine(p);
}
else
{
    Console.WriteLine("未找到路径");
}

工业现场踩坑经验与优化建议

  1. 实时性坑:MQTT 默认 QoS 0,容易丢包 → 改用 QoS 1(至少送达一次)。
  2. 下位机资源坑:树莓派 CPU 占用高 → 采集间隔调到 2–5s,推理任务放上位机。
  3. 断网续传:下位机本地缓存(SQLite),重连后批量上报。
  4. 跨端统一:用 .NET MAUI 做上位机 + 移动端监控,代码复用率 95%。
  5. 部署:下位机用 .NET 8 Worker Service + systemd(Linux)开机自启。
  6. RRT 优化*:初始规划用较少迭代(1000 次)快速出路径,后台持续优化(每秒再跑 2000 次迭代)。规划超时控制:超过 300ms 返回当前最优路径。
  7. 动态障碍集成:每帧从 YOLOv8 + DeepSORT 获取动态障碍位置,更新 _obstacles 列表(临时添加圆形/矩形禁区)。当路径被阻挡时触发局部重规划(只重新规划前半段)。
  8. 路径平滑:RRT* 路径通常锯齿状,可后处理使用贝塞尔曲线或样条插值(例如 MathNet.Numerics 库)。
  9. 部署建议:.NET 8 AOT 单文件发布(体积 <100MB),工控机可用 Windows IoT Enterprise / 麒麟 ARM64,地图数据 SQLite 持久化 + Redis 缓存动态障碍。

目录

  1. .NET 8 跨平台自动化开发实战
  2. 典型场景与技术选型对比
  3. 上下位机一体化架构设计
  4. 核心通信协议选择
  5. 实战代码实现(温湿度监控 + 设备控制)
  6. 1. 下位机核心代码(Console + MQTT)
  7. 2. 上位机接收与控制界面
  8. 进阶案例:RRT* 路径规划算法
  9. 1. 核心类定义
  10. 2. RRT* 主规划类
  11. 使用示例
  12. 工业现场踩坑经验与优化建议
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • Transformer 架构详解与大模型应用实战指南
  • LangGraph4J 构建 Java 多智能体工作流指南
  • Redis Cluster 哈希槽分片机制与 Linux 集群搭建实操
  • Java 21 虚拟线程日志优化与百万级并发追踪最佳实践
  • C++ 手写 Web 服务器:基于 JSON 与 HTTP 协议的实现
  • 10 分钟搭建专属 AI Agent 全流程实操指南
  • Java 分治算法实战:快速与归并排序
  • AR 健身教练:形随心动 - 基于 Rokid CXR-M SDK 的实践落地
  • C++ 二叉搜索树详解:增删查改与 key/value 场景实现
  • Rust 语言的前世今生与核心技术解析
  • Ubuntu 本地部署 OpenClaw:接入 Ollama 推理与飞书通道
  • Dify AI 智能体部署与使用指南
  • Dev-C++ 安装、配置与快捷键使用指南
  • ChatGPT 降低 AIGC 率指令实战:从原理到最佳实践
  • 理解 IDE 调用大模型的 Session 机制与管理
  • 用 LLama-Factory 微调个性化 AI 角色
  • 微信小程序云开发起步:模板选择与环境创建指南
  • DOM Testing Library 异步测试实战:waitFor 与 waitForElementToBeRemoved
  • AstrBot + NapCat 一键部署智能 QQ 机器人及 cpolar 公网访问配置
  • 当前互联网行情下,程序员职业前景分析与发展建议

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • RSA密钥对生成器

    生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online

  • Mermaid 预览与可视化编辑

    基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online

  • 随机西班牙地址生成器

    随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online