SSD 降溫大作戰 EP3 - C# 查詢 ESP32 藍牙 COM Port
0 |
搞定用 ESP32 控制 PWM 風扇轉速後,下一步是建立 PC 與 ESP32 間的傳輸通道,讓 Windows 端程式能傳送溫度資料給 ESP32,依據溫度目標決定風扇轉速高低,將溫度控制在指定範圍。
起初的構想是讓 ESP32 連上 WiFi,跑一個小網站提供目標溫度設定、即時溫度轉速監看 UI,以及傳入目前溫度的 WebAPI。後來想想,要用 WiFi 就得設定基地台 SSID 及密碼,佔用一個 IP,主機的無線網路被限制住不能切換(例如:某些特殊測試需要暫時改連手機上線)... 有管理成本及運用限制,ESP32 支援藍牙,而且風扇絕對會放在主機旁邊,100% 在藍牙傳輸範圍內,為什麼不用藍牙傳輸呢?
之前都是玩 WiFi,第一次嘗試透過藍牙收送資料,現有程式庫已把複雜的部分都包好了,只需要 #include <BluetoothSerial.h>
引用程式庫、BluetoothSerial BT;
宣告物件,BT.begin("ESP32-Display");
註冊名稱,Windows 端找到它完成配對,Windows 便會多出兩個 COM Port:
其中標註 'ESP32SPP' 的可以對 ESP32 傳送及接收資料。不用寫程式,用 Putty 等支援 COM Port 傳輸的軟體就能測試。我寫了一個小測試,接收藍牙 COM Port 傳來的字元顯示在 OLED 上,按 Enter 時透過藍牙 COM Port 將整句傳回:
#include <Arduino.h>
#include <BluetoothSerial.h>
#include <Wire.h>
#include <Adafruit_I2CDevice.h>
#include <Adafruit_SSD1306.h>
#include "bitmap.h"
#define SCREEN_ADDRESS 0x3C
Adafruit_SSD1306 display(128, 64, &Wire, -1);
BluetoothSerial BT;
void setup()
{
Serial.begin(115200);
while (!Serial)
{
}
Wire.setPins(26, 27); // for ESPro Matrix board
// SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS))
{
Serial.println(F("SSD1306 allocation failed"));
for (;;)
; // Don't proceed, loop forever
}
// Clear the buffer
display.clearDisplay();
display.drawBitmap(32, 0, guineapigBitmap, 64, 64, WHITE);
display.display();
delay(1000);
display.setTextColor(WHITE, BLACK);
display.setCursor(0, 56);
display.ttyPrintln();
display.ttyPrintln("Bluetooth Test");
display.display();
BT.begin("ESP32-Display"); //宣告 Bluetooch 物件
}
String inputString = "";
void loop()
{
if (BT.available())
{
char c = BT.read(); // 從藍牙讀取 Windows 傳來的字元
if (c == '\r') // enter
{
// 換行時整行輸出在 OLED
display.ttyPrintln();
display.display();
// 並將整句內容從藍牙傳回去
BT.println("\r\nEcho=" + inputString);
inputString = "";
}
else
{
// 將接收到的字元顯示在 OLED 上,提供打字即時回饋
inputString += c;
BT.print(c);
display.ttyPrint(String(c));
display.display();
}
}
}
實際操作會像這樣:
所以只需在記錄 SSD、CPU 溫度時,透過 SerialPort 類別 傳送溫度數字(或溫控規則寫在 C# 端,直接設定轉速)給 ESP32 控制風量,智慧型輔助散熱系統就完成了。
其實 ESP32 是用哪個 COM Port,人工查好改設定就好了。但我硬是自找麻煩,花了點時間,試著實現「用藍牙裝置名稱找到 COM Port」。如此設定檔只需提供裝置名稱,由程式自動找到對映的 COM Port。主要參考兩篇 Stackoverflow 討論(連結放在註解處),復習了 WMI 查詢技巧、還用上 ValueTuple 跟 yield return,我寫成以下工具類別:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
internal class BluetoothComPortScanner
{
public static IEnumerable<(string ComPort, string DeviceName, string? Desc, bool Output)> GetBluetoothComPorts()
{
//REF: https://stackoverflow.com/a/64443911/288936
var results = new ManagementObjectSearcher(@"
SELECT PNPClass, PNPDeviceID, Name, HardwareID
FROM Win32_PnPEntity
WHERE
(Name LIKE '%COM%' AND PNPDeviceID LIKE '%BTHENUM%' AND PNPClass = 'Ports') OR
(PNPClass = 'Bluetooth' AND PNPDeviceID LIKE '%BTHENUM\\DEV%')").Get();
var comPorts = new List<ManagementObject>();
var devices = new List<ManagementObject>();
foreach (ManagementObject mo in results)
{
if (mo["PNPClass"].ToString() == "Bluetooth")
{
devices.Add(mo);
}
else if (mo["PNPClass"].ToString() == "Ports")
{
comPorts.Add(mo);
}
}
string extractComName(string text)
{
var m = Regex.Match(text, "[(](?<p>COM[0-9]+)[)]");
if (m.Success) return m.Groups["p"].Value;
return text;
}
foreach (ManagementObject device in devices)
{
var m = Regex.Match(device["PNPDeviceID"]!.ToString()!, "_(?<id>[0-9A-F]+)$");
if (!m.Success) continue;
var devId = m.Groups["id"].Value;
var devName = device["Name"].ToString()!;
foreach (ManagementObject comPort in comPorts)
{
var comPortPNPDeviceID = comPort["PNPDeviceID"]!.ToString()!;
if (comPortPNPDeviceID.Contains(devId))
{
yield return (
extractComName(comPort["Name"].ToString()!),
devName,
GetBusReportedDeviceDesc(comPort["PNPDeviceID"].ToString()!),
true);
var hwIdPrefix = ((string[])comPort["HardwareID"]).First().Split('_').First();
var inComPort = comPorts.Except(new[] { comPort })
.FirstOrDefault(o => ((string[])o["HardwareID"])[0].StartsWith(hwIdPrefix));
if (inComPort != null)
yield return (
extractComName(inComPort["Name"].ToString()!),
devName,
string.Empty,
false);
}
}
}
}
static string? GetBusReportedDeviceDesc(string devId)
{
//REF https://stackoverflow.com/a/69363717/288936
foreach (var mo in new ManagementObjectSearcher(null!, "SELECT * FROM Win32_PnPEntity").Get().OfType<ManagementObject>())
{
if (mo["PNPDeviceID"] as string != devId) continue;
var args = new object[] { new string[] { "DEVPKEY_Device_BusReportedDeviceDesc" }, null! };
mo.InvokeMethod("GetDeviceProperties", args);
var mbos = (ManagementBaseObject[])args[1];
if (mbos.Length > 0)
{
var data = mbos[0].Properties.OfType<PropertyData>().FirstOrDefault(p => p.Name == "Data");
if (data != null)
{
return data.Value as string;
}
}
}
return null;
}
}
得到 Windows 藍牙設定 UI 相同的結果,成功!
Example of how to use C# to enumerate Bluetooth COM ports.
Comments
Be the first to post a comment