首页 > 其他分享 >一款使用NET+MQTT+Arduino开发的智能浇花工具

一款使用NET+MQTT+Arduino开发的智能浇花工具

时间:2024-12-19 18:08:44浏览次数:6  
标签:Arduino color NET Wi MQTT Fi Serial message

  最近闲来无事,对硬件控制产生了兴趣。看到家里的盆栽,我突然萌生了制作一个自动浇水工具的想法。通过在淘宝搜索并查找相关资料,我了解了需要的硬件和通信协议。接下来,我们先看看需要做哪些准备工作(如安装 Arduino、.NET、EMQX 工具等,请自行搜索并完成安装)。

准备工作

硬件清单(淘宝可购买):

  1. 湿度传感器

  2. 湿度传感器继电器

  3. WiFi 控制继电器(我用的是 ESP32。注意,这两种继电器是否能合并为一个?目前没有找到这样的硬件,只能用2个继电器了)

  4. 抽水器

  5. 电池(可选,也可以直接接家用电路,但要注意安全)

软件清单

  1. .NET 6

  2. EMQX MQTT

  3. 服务器(Linux + Docker)

硬件和软件模块功能简介

1. 继电器的作用

  继电器是一种电磁开关设备,能够在低电压或低电流的控制信号下,控制高电压或大电流的电路开关。主要功能包括:

  • 电路隔离:保护低电压控制电路免受高电压或高电流的影响。

  • 信号放大:将小电流控制信号转化为高功率输出。

  • 多路控制:通过一个控制信号切换多个电路。

  • 自动化控制:与传感器和控制器协作,实现自动化。

  • 远程控制:通过 WiFi、蓝牙等信号远程控制。

2. 湿度传感器的作用

  湿度传感器用于检测和测量环境湿度,并将湿度值转换为电信号,供继电器和控制系统读取。它是实现自动化浇水的核心输入模块。

3. EMQX 的作用

  EMQX 是一种开源的 MQTT 消息服务器,基于 Erlang/OTP 构建,专为高并发、低延迟和高可靠性设计。它的核心作用是实现物联网(IoT)设备间的消息通信,支持通过 MQTT 协议连接大量设备并在它们之间传递消息。

实现思路

  其余不做一一描述了,我们的目标是制作一个网页或手机 App,通过操作前端界面向服务器发送信号,服务器将信号转发给继电器,从而实现远程控制硬件。

实操步骤

第一步:电路连接

  我们需要先把电路按照以下图片连接起来,期间接线时注意短路的,通电后测试每块硬件是否能正常亮灯(本人不太熟悉硬件,所以硬件接线过程中会有接错的情况,一般接错了灯是不会亮的);

第二步:服务端配置与代码编写

  1. 配置 EMQX

    • 在服务器上安装并配置 EMQX(推荐 Linux + Docker 环境,简单高效)。

    • 配置完成后,通过网页管理界面验证是否成功运行(注意mqtt的消息端口和管理地址端口是分开的)。成功界面如下图所示:

    •  

  2. 调用 EMQX 工具的代码

    • 请使用MQTTnet的包,然后以下是调用 EMQX 的示例代码:

    • public class MqttBackgroundService : BackgroundService
      {
          private readonly MqttService _mqttService;
          private readonly ILogger<MqttBackgroundService> _logger;
          private readonly IConfiguration _configuration;
      
          public MqttBackgroundService(MqttService mqttService, ILogger<MqttBackgroundService> logger, IConfiguration configuration)
          {
              _mqttService = mqttService;
              _logger = logger;
              _configuration = configuration;
          }
      
          protected override async Task ExecuteAsync(CancellationToken stoppingToken)
          {
              try
              {
                  // 启动 MQTT 客户端并连接
                  await _mqttService.ConnectAsync();
                  // 读取配置文件中的 MqttClients 配置
                  var mqttClientsConfig = _configuration.GetSection("MqttClients").Get<List<MqttClientConfig>>();
                  foreach (var clientConfig in mqttClientsConfig)
                  {
                      await _mqttService.SubscribeAsync(clientConfig.Topic);
                  }
                      // 保持连接,直到应用程序停止
                  while (!stoppingToken.IsCancellationRequested)
                  {
                      await Task.Delay(1000); // 可执行心跳检测或其他后台任务
                  }
              }
              catch (Exception ex)
              {
                  _logger.LogError($"Error in MQTT connection: {ex.Message}");
              }
          }
      }
       public class MqttService
       {
           private IMqttClient _mqttClient;
           private string _brokerAddress = ""; // 设置EMQX服务器地址
           private int _brokerPort = 1883; // 默认MQTT端口
           private readonly SemaphoreSlim _mqttLock = new SemaphoreSlim(1, 1); // 线程锁
      
           public MqttService()
           {
               var mqttFactory = new MqttFactory();
               _mqttClient = mqttFactory.CreateMqttClient();
           }
      
           // 启动时连接
           public async Task ConnectAsync()
           {
               await _mqttLock.WaitAsync();
               try
               {
                   if (!_mqttClient.IsConnected)
                   {
                       var options = new MqttClientOptionsBuilder()
                           .WithTcpServer(_brokerAddress, _brokerPort)
                           .WithCredentials("", "")  // 添加用户名和密码
                           .WithCleanSession()
                           .Build();
      
                       _mqttClient.ConnectedAsync += ConnectedHandler;
                       _mqttClient.DisconnectedAsync += DisconnectedHandler;
                       _mqttClient.ApplicationMessageReceivedAsync += ApplicationMessageReceivedHandler;
      
                       await _mqttClient.ConnectAsync(options);
                       Console.WriteLine("MQTT Connected.");
                   }
               }
               finally
               {
                   _mqttLock.Release();
               }
           }
      
           // 连接事件处理
           private async Task ConnectedHandler(MqttClientConnectedEventArgs e)
           {
               Console.WriteLine("Connected to MQTT broker.");
               // 连接成功后订阅主题
               //await SubscribeAsync("test/topic");
               //_mqttLock.Release();
           }
      
           // 断开连接事件处理
           private Task DisconnectedHandler(MqttClientDisconnectedEventArgs e)
           {
               Console.WriteLine("Disconnected from MQTT broker.");
               return Task.CompletedTask;
           }
      
           // 接收到消息事件处理
           private async Task ApplicationMessageReceivedHandler(MqttApplicationMessageReceivedEventArgs e)
           {
               var payload = e.ApplicationMessage.PayloadSegment.ToArray();
               // 获取消息的 topic
               var topic = e.ApplicationMessage.Topic;
               var msg = Encoding.UTF8.GetString(payload);
               Console.WriteLine($"MessageReceive: {msg}");
      
               //await Task.CompletedTask.Wait();
           }
      
           // 订阅主题
           public async Task SubscribeAsync(string topic)
           {
               await _mqttLock.WaitAsync();
               try
               {
                   await _mqttClient.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic(topic).Build());
                   Console.WriteLine($"Subscribed to topic: {topic}");
               }
               finally
               {
                   _mqttLock.Release();
               }
           }
      
           // 发布消息
           public async Task PublishAsync(string topic, string message)
           {
               await _mqttLock.WaitAsync();
               try
               {
                   var mqttMessage = new MqttApplicationMessageBuilder()
                   .WithTopic(topic)
                   .WithPayload(message)
                   .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtMostOnce)  // 改为 QoS 0
                   .Build();
                   if (!_mqttClient.IsConnected)
                   {
                       Console.WriteLine("MQTT client is not connected.");
                       await ConnectAsync();  // 尝试重新连接
                   }
                   await _mqttClient.PublishAsync(mqttMessage);
                   Console.WriteLine($"Message sent: {message}");
               }
               finally
               {
                   _mqttLock.Release();
               }
           }
      
           // 断开连接
           public async Task DisconnectAsync()
           {
               await _mqttLock.WaitAsync();
               try
               {
                   await _mqttClient.DisconnectAsync();
                   Console.WriteLine("Disconnected from MQTT broker.");
               }
               finally
               {
                   _mqttLock.Release();
               }
           }
       }

       

  1. 编写 API

    • 实现继电器开关的接口。

    • 可加入定时控制功能(通过调度任务定时开关继电器)。

    • [Route("api/[controller]")]
      [ApiController]
      public class MqttController : ControllerBase
      {
          private readonly MqttService _mqttService;
          private readonly FileService _fileService;
          public MqttController(MqttService mqttService, FileService fileService)
          {
              _mqttService = mqttService;
              _fileService = fileService;
          }
          [HttpPost("sendMessage")]
          public async Task<IActionResult> SendMessage(MqttMessageRequest request)
          {
              try
              {
      
                  await _fileService.SaveFileContentAsync(request.Message);
                  // 2. 发布消息到主题
                  await _mqttService.PublishAsync(request.Topic, JsonSerializer.Serialize( request.Message));
                  Console.WriteLine($"Message sent to topic {request.Topic}: {JsonSerializer.Serialize(request.Message)}");
      
                  return Ok(request.Message);
              }
              catch (Exception ex)
              {
                  return StatusCode(500, $"Failed to send message: {ex.Message}");
              }
          }
          // 根据 ClientId 获取文件内容
          [HttpGet("GetFile/{clientId}")]
          public async Task<IActionResult> GetFileContent(string clientId)
          {
              try
              {
                  var model = await _fileService.GetFileContentAsync(clientId);
                  return Ok(model); // 返回对应的文件内容
              }
              catch (FileNotFoundException ex)
              {
                  return NotFound(ex.Message); // 如果找不到文件,返回 404
              }
              catch (Exception ex)
              {
                  return StatusCode(500, ex.Message); // 其他异常返回 500
              }
          }
      }
      <!DOCTYPE html>
      <html lang="zh">
      <head>
          <meta charset="UTF-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>MQTT 控制开关</title>
          <style>
              body {
                  font-family: Arial, sans-serif;
                  background-color: #f4f7fa;
                  margin: 0;
                  padding: 0;
              }
      
              .container {
                  width: 100%;
                  max-width: 600px;
                  margin: 50px auto;
                  padding: 20px;
                  background-color: #fff;
                  border-radius: 8px;
                  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
              }
      
              h1 {
                  text-align: center;
                  color: #333;
              }
      
              .form-group {
                  margin-bottom: 20px;
              }
      
                  .form-group label {
                      display: block;
                      font-size: 16px;
                      color: #555;
                      margin-bottom: 8px;
                  }
      
                  .form-group input[type="time"] {
                      width: 100%;
                      padding: 10px;
                      font-size: 14px;
                      border: 1px solid #ddd;
                      border-radius: 4px;
                      background-color: #f9f9f9;
                      box-sizing: border-box; /* 保证padding包含在宽度内 */
                  }
      
              /* 自定义开关按钮样式 */
              .switch {
                  position: relative;
                  display: inline-block;
                  width: 60px;
                  height: 34px;
              }
      
                  .switch input {
                      opacity: 0;
                      width: 0;
                      height: 0;
                  }
      
              .slider {
                  position: absolute;
                  cursor: pointer;
                  top: 0;
                  left: 0;
                  right: 0;
                  bottom: 0;
                  background-color: #ccc;
                  transition: .4s;
                  border-radius: 34px;
              }
      
                  .slider:before {
                      position: absolute;
                      content: "";
                      height: 26px;
                      width: 26px;
                      border-radius: 50%;
                      left: 4px;
                      bottom: 4px;
                      background-color: white;
                      transition: .4s;
                  }
      
              input:checked + .slider {
                  background-color: #4CAF50;
              }
      
                  input:checked + .slider:before {
                      transform: translateX(26px);
                  }
      
              button {
                  width: 100%;
                  padding: 12px;
                  font-size: 16px;
                  color: #fff;
                  background-color: #007bff;
                  border: none;
                  border-radius: 4px;
                  cursor: pointer;
                  transition: background-color 0.3s;
              }
      
                  button:hover {
                      background-color: #0056b3;
                  }
      
              /* 弹框样式 */
              .modal {
                  display: none;
                  position: fixed;
                  z-index: 1;
                  left: 0;
                  top: 0;
                  width: 100%;
                  height: 100%;
                  background-color: rgba(0, 0, 0, 0.5);
                  justify-content: center;
                  align-items: center;
              }
      
              .modal-content {
                  background-color: #fff;
                  padding: 20px;
                  border-radius: 8px;
                  width: 80%;
                  max-width: 400px;
                  text-align: center;
              }
      
              .modal .message {
                  font-size: 18px;
                  color: #28a745;
              }
      
              .modal .error-message {
                  color: #dc3545;
              }
      
              .modal-close {
                  margin-top: 20px;
                  padding: 10px 20px;
                  background-color: #007bff;
                  color: #fff;
                  border: none;
                  border-radius: 4px;
                  cursor: pointer;
              }
      
                  .modal-close:hover {
                      background-color: #0056b3;
                  }
          </style>
          <script>
              // 函数:当开关按钮被点击时执行POST请求
              function toggleSwitch() {
                  var topic = "Client3";
                  var message = {
                      ClientId: "Client3",
                      DailyScheduledTime: document.getElementById('scheduledTime').value,
                      CurrentSwitch: document.getElementById('switch').checked
                  };
      
                  var data = {
                      Topic: topic,
                      Message: message
                  };
      
                  // 使用fetch发送POST请求
                  fetch('/api/Mqtt/sendMessage', {
                      method: 'POST',
                      headers: {
                          'Content-Type': 'application/json'
                      },
                      body: JSON.stringify(data)
                  })
                      .then(response => response.json())
                      .then(data => {
                          console.log('成功:', data);
                          showModal('操作成功!', 'message');
                      })
                      .catch((error) => {
                          console.error('错误:', error);
                          showModal('操作失败,请重试!', 'error-message');
                      });
              }
      
              // 弹框显示函数
              function showModal(message, messageType) {
                  const modal = document.getElementById('responseModal');
                  const modalMessage = document.getElementById('modalMessage');
                  const modalContent = document.getElementById('modalContent');
      
                  modalMessage.textContent = message;
                  modalContent.classList.remove('message', 'error-message');
                  modalContent.classList.add(messageType);
      
                  modal.style.display = 'flex'; // 显示弹框
              }
      
              // 关闭弹框
              function closeModal() {
                  const modal = document.getElementById('responseModal');
                  modal.style.display = 'none';
              }
          </script>
      </head>
      <body>
      
          <div class="container">
              <h1>MQTT 控制开关</h1>
      
              <div class="form-group">
                  <label for="scheduledTime">每天定时: </label>
                  <input type="time" id="scheduledTime" required />
              </div>
      
              <div class="form-group">
                  <label for="switch">当前开关状态: </label>
                  <!-- 自定义开关按钮 -->
                  <label class="switch">
                      <input type="checkbox" id="switch" />
                      <span class="slider"></span>
                  </label>
              </div>
      
              <button onclick="toggleSwitch()">提交</button>
          </div>
      
          <!-- 弹框 -->
          <div id="responseModal" class="modal">
              <div class="modal-content" id="modalContent">
                  <div id="modalMessage" class="message">操作成功!</div>
                  <button class="modal-close" onclick="closeModal()">关闭</button>
              </div>
          </div>
      
      </body>
      </html>

       

第三步:Arduino 嵌入式代码编写

  过程本人踩过的坑:1.要了解继电器的GPIO用是哪个,不然你无法控制;2.Wifi控制的情况下,由于继电器是有一个虚拟WiFi的,你需要链接到继电器的虚拟WiFi后,才能配置自家的WiFi;当然你也可以连接蓝牙来配置;3.需要把设置好的WiFi账号密码记录到继电器否则每次启动都要配置WiFi(代码写入即可);

  以下是我这次的代码可做参考:

#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <HTTPClient.h>
#include <Preferences.h>  // Include Preferences library

#define RELAY_PIN 4   // Relay control pin
#define LED_ALWAYS_ON 3  // LED pin for other devices like lights

const char* ssid = "ESP32_Config";       // Wi-Fi hotspot name for initial setup
const char* password = "123456";       // Wi-Fi password for the hotspot

String targetSSID = "";
String targetPassword = "";

const char* mqttServer = ""; // MQTT server address
const int mqttPort = ;                // MQTT port
const char* mqttUser = "";           // MQTT username
const char* mqttPassword = "";  // MQTT password

WiFiClient espClient;
PubSubClient client(espClient);           // MQTT client
AsyncWebServer server(80);                // Web server object listening on port 80

const int relayPin = 5;  // Relay control pin (modify as necessary)
// HTTP request URL
String apiUrl = "";//API地址

// Preferences instance for storing Wi-Fi credentials
Preferences preferences;

// MQTT callback function
void mqttCallback(char* topic, byte* payload, unsigned int length) {
  String message = "";
  for (int i = 0; i < length; i++) {
    message += (char)payload[i];
  }

  // Create a JSON document to parse the incoming message
  StaticJsonDocument<200> doc;
  DeserializationError error = deserializeJson(doc, message);

  if (error) {
    Serial.println("Failed to parse JSON");
    return;
  }

  String clientId = doc["ClientId"].as<String>();  // Extract clientId from JSON
  bool currentSwitch = doc["CurrentSwitch"].as<bool>();  // Extract currentSwitch value

  if (clientId == "Client3") {
    digitalWrite(RELAY_PIN, currentSwitch ? HIGH : LOW);  // Control relay based on currentSwitch
  }
}

// Wi-Fi connection function
bool connectWiFi() {
  Serial.println("Connecting to Wi-Fi...");
  WiFi.begin(targetSSID.c_str(), targetPassword.c_str());
  int attempts = 0;
  while (WiFi.status() != WL_CONNECTED && attempts < 20) {
    delay(500);
    Serial.print(".");
    attempts++;
  }

  if (WiFi.status() == WL_CONNECTED) {
    Serial.println("\nWi-Fi connected successfully!");
    Serial.print("IP Address: ");
    Serial.println(WiFi.localIP());
    digitalWrite(LED_ALWAYS_ON, HIGH);  // Turn on LED
    return true;
  } else {
    Serial.println("\nWi-Fi connection failed!");
    return false;
  }
}

// MQTT connection function
bool connectMQTT() {
  while (!client.connected()) {
    Serial.print("Connecting to MQTT...");
    if (client.connect("ESP32Client", mqttUser, mqttPassword)) {
      Serial.println("Connected to MQTT Broker.");
      return true;
    } else {
      Serial.print(".");
      delay(1000);  // Retry if connection fails
    }
  }
  return false;
}

// Function to get data from API and subscribe to MQTT topics
void getDataFromAPI() {
  HTTPClient http;
  http.begin(apiUrl);  // Specify URL
  int httpCode = http.GET();  // Send GET request

  if (httpCode > 0) {
    String payload = http.getString();
    Serial.println("Received Data:");
    Serial.println(payload);

    // Parse the JSON response from the API
    DynamicJsonDocument doc(1024);
    deserializeJson(doc, payload);

    String clientId = doc["clientId"];
    client.subscribe(clientId.c_str());  // Subscribe to the topic
  } else {
    Serial.println("Failed to get HTTP data");
  }

  http.end();  // End HTTP request
}

void setup() {
  Serial.begin(115200);
  pinMode(RELAY_PIN, OUTPUT);
  digitalWrite(RELAY_PIN, LOW);  // Initialize relay to LOW

  pinMode(LED_ALWAYS_ON, OUTPUT);
  digitalWrite(LED_ALWAYS_ON, LOW);  // Initialize LED to LOW

  // Initialize Preferences to read and write Wi-Fi credentials
  preferences.begin("wifi_config", false);  // Open preferences in read-write mode

  // Read saved Wi-Fi credentials from Preferences
  targetSSID = preferences.getString("SSID", "");
  targetPassword = preferences.getString("PASSWORD", "");

  // If the credentials are saved, try to connect to Wi-Fi
  if (targetSSID != "" && targetPassword != "") {
    if (connectWiFi()) {
      // Once connected to Wi-Fi, connect to the MQTT broker
      client.setServer(mqttServer, mqttPort);
      client.setCallback(mqttCallback);

      if (connectMQTT()) {
        getDataFromAPI();  // Fetch data from the API if MQTT connection is successful
      } else {
        Serial.println("Failed to connect to MQTT Broker.");
      }
    }
  } else {
    Serial.println("No saved Wi-Fi credentials. Keeping the device in AP mode.");
    // Set up the device in AP mode for Wi-Fi configuration
    WiFi.softAP(ssid, password);
    Serial.println("Wi-Fi hotspot started.");
    Serial.print("IP Address: ");
    Serial.println(WiFi.softAPIP());
  }

  // Web server to handle Wi-Fi credentials saving
  server.on("/save", HTTP_POST, [](AsyncWebServerRequest *request) {
    targetSSID = request->arg("SSID");
    targetPassword = request->arg("PASSWORD");

    // Save Wi-Fi credentials in Preferences
    preferences.putString("SSID", targetSSID);
    preferences.putString("PASSWORD", targetPassword);

    String response = "{\"status\":\"success\",\"message\":\"Wi-Fi configuration saved!\"}";
    request->send(200, "application/json", response);

    // After saving the credentials, connect to Wi-Fi
    if (connectWiFi()) {
      
      // Connect to MQTT broker
      client.setServer(mqttServer, mqttPort);
      client.setCallback(mqttCallback);
      if (connectMQTT()) {
        getDataFromAPI();  // Fetch data from API
      }
    }
  });

  // Start the web server
  server.begin();
}

void loop() {
  // If Wi-Fi connection is lost, attempt to reconnect
  if (WiFi.status() != WL_CONNECTED) {
    Serial.println("Wi-Fi connection lost, reconnecting...");
    connectWiFi();
  }

  // Ensure the MQTT client loop is running
  client.loop();
}

 

最终效果展示

  通过网页或手机 App 操作,可以实现:

  1. 远程控制继电器开关。

  2. 根据湿度传感器数据,自动开启或关闭抽水器。

  整个项目非常简单,适合入门硬件控制的朋友。DIY 成本约 20 元,而购买成品价格至少 50 元。

经验总结

  通过这个项目,我从对硬件一无所知逐渐熟悉了硬件控制,深刻感受到今天硬件发展的迅速。希望本文能对有一定软件基础并想尝试硬件开发的朋友有所帮助!

 

标签:Arduino,color,NET,Wi,MQTT,Fi,Serial,message
From: https://www.cnblogs.com/weirun/p/18617489

相关文章

  • .NET 8.0在linux中搭建consul+ocelot+nginx转发全流程,以及docker容器化
    闲来无事自己在电脑上搭了一套docker容器化加上服务发现反向代理的套餐,在这儿把流程写个大概,后面玩儿的别踩坑了。源码地址:https://github.com/Asomnus/MyProject1.git一、环境工具1.开发:net8.0SDK、vs2022(我用的这个,支持8.0都行)、mysql、redis等等(根据业务自己选)2.虚拟机相......
  • 【k8s集群应用】Kubernetes二进制部署实例(master02+负载均衡)+Dashboard
    文章目录配置指南在`master02`节点上部署Kubernetes组件配置负载均衡器(Nginx+Keepalived)在`lb01`和`lb02`节点上操作修改Node节点上的配置文件并使用VIP在`master01`节点上测试Pod的创建和访问DashboardDashboard介绍部署Dashboard上传`recommended.......
  • 基于QT+MQTT的实时视频监控
    MQTT(MessageQueuingTelemetryTransport)是一种基于发布/订阅模式的轻量级通信协议,广泛应用于物联网领域。它允许设备通过极少的代码和有限的带宽实现实时可靠的消息服务。MQTT协议的核心在于其三部分组成的控制报文:固定报头、可变报头和有效载荷。mqtt中的一些名词解释:https:......
  • C#使用log4net和sqlite数据库记录日志
    1安装包两个包:log4netSystem.Data.SQLite第二个包也可以使用Microsoft.Data.Sqlite,查到的资料显示如果环境使用的是.NETCore或.NET5+,则建议使用Microsoft.Data.Sqlite。但是我并没有测试第二个包,可能使用上有区别。2下载Sqlite如果本地没有sqlite环境的话,需要先下......
  • 计算机毕设项目分享:281bfi3e+基于ASP.NET的图书借阅系统(毕设源码+论文+PPT)
    基于ASP.NET的图书借阅系统摘 要随着科学技术水平的逐年发展,构建一个高效、便捷的图书借阅系统。解决传统图书馆借阅过程中存在的问题,如人工查询繁琐、借阅效率低等。系统具有良好的用户界面和操作体验,方便用户快速找到所需图书并进行借阅操作。系统有助于图书馆管理者了......
  • 云消息队列 MQTT 版
    云消息队列MQTT版是基于MQTT协议的云端消息队列服务,通常用于物联网(IoT)应用场景中。MQTT(MessageQueuingTelemetryTransport)是一种轻量级、基于发布/订阅模式的消息传输协议,专为低带宽、不稳定的网络和设备资源受限的环境设计。什么是MQTT协议?MQTT是一种简单的、开......
  • UT 覆盖率 报告 dotnet-coverage
    安装dotnet-coverage和dotnet-reportgeneratordotnettoolinstall-gdotnet-coveragedotnettoolinstall-gdotnet-reportgenerator-globaltool运行测试,输出XML格式:dotnet-coveragecollect-fxml-ocoverage.xmldotnettest<solution/project>例如:在测试......
  • 让.NET应用支持Http/3,QUIC协议
    1.必备条件1.1.NET应用开启httpsPrograme.cs中配置了https支持,varbuilder=WebApplication.CreateBuilder(args);builder.WebHost.ConfigureKestrel((context,options)=>{options.ListenAnyIP(5001,listenOptions=>{listenOptions.Protocols=H......
  • 一款基于 .NET 开发的多功能流媒体管理控制平台
    前言今天大姚给大家分享一个基于.NET开发且开源(MITLicense)的多功能流媒体管理控制平台:AKStream。项目介绍AKStream是一个基于.NET开发且开源(MITLicense)的、功能全面的流媒体管理控制平台,集成了GB28181、RTSP、RTMP、HTTP等设备的推拉流控制、PTZ控制、音视频文件录制管......
  • C#/.NET/.NET Core技术前沿周刊 | 第 17 期(2024年12.09-12.15)
    前言C#/.NET/.NETCore技术前沿周刊,你的每周技术指南针!记录、追踪C#/.NET/.NETCore领域、生态的每周最新、最实用、最有价值的技术文章、社区动态、优质项目和学习资源等。让你时刻站在技术前沿,助力技术成长与视野拓宽。欢迎投稿、推荐或自荐优质文章、项目、学习资源等......