实训台接线:串口要选对。接线做成数据(BoardState.wiring),默认按教材布局图 COM2 ADAM-4017+、COM3 ADAM-4150、COM4 ZigBee 协调器、COM5 LED 屏,实验可在 meta.json 的 wiring 里改:项目 7 的书上代码在一个串口上打开两个模块,这三个实验把 4150 并到 COM2 的 485 总线上。选错串口时返回值照反编译的真机版 dll(4017 全 null、4150 全 false,4150 的 OnOff 和 LED 推送返回成功但设备不动作,ZigBee 返回 false);串口没打开时照教材仿真版。Open / Close / 没打开就读写报告给服务器,实训台最上方用红字提示("程序在 COM2 上打开 ADAM-4150,它接在 COM3""ADAM-4150 的串口没有打开,程序却在读写它"),评分不通过时也附上。只读接线展示:鼠标移到实训台设备上,标题栏写出线路,对应通道、模块、串口高亮;串口服务器按接线列出设备,程序打开的口亮绿点;程序里串口下拉框的灰字按当前接线。打开实验时浏览器发 experiment 消息换接线,老师看学生提交时按作业的实验接线(提交接口返回 sampleId)。"4017"统一写成 ADAM-4017+
上课前逐个检查 23 个实验后的修改:补上三处评分漏洞(项目 3 任务 1、项目 4 任务 3 的关闭和项目 7 任务 2 的熄灯,程序什么都不做也能通过);评分说明的空格;任务说明按实测补充(项目 2 任务 1–3、项目 3 任务 2、项目 4 任务 2、项目 7 任务 1–3、项目 8 任务 5)。使用说明、设计方案同步,设计方案记下以后可能扩展"物联网设备安装与调试"课 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
163e553de4
commit
46e0f40cce
35 files changed
+637
-98
No files matched your search
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using DeviceCtrlLibrary.Model;
|
||||
using Sim.Host;
|
||||
@@ -7,7 +8,8 @@ namespace DeviceCtrlLibrary;
|
||||
|
||||
/// <summary>
|
||||
/// 教材的设备控制入口:DeviceCtrlHelper.ledHelper.SendLedString(...) 等。
|
||||
/// 返回值约定与教材仿真版 dll 保持一致;不同之处是数值来自虚拟实训台,而不是随机数。
|
||||
/// 返回值约定与教材仿真版 dll 保持一致;不同之处是数值来自虚拟实训台,而不是随机数,
|
||||
/// 并且和真机版一样,设备要在它接的那个串口上打开才有反应(接线见 TrainingBoard)。
|
||||
/// </summary>
|
||||
public class DeviceCtrlHelper
|
||||
{
|
||||
@@ -20,6 +22,38 @@ public class DeviceCtrlHelper
|
||||
public static IPCameraHelper ipCameraHelper = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 告诉实训台程序在哪个串口上打开、关闭了设备,以及串口没打开时还在读写设备(网页上据此提示)。
|
||||
/// 没变就不发:书上有在定时器里反复 Open 的写法;没打开时的读写每次打开之后最多报一次
|
||||
/// </summary>
|
||||
internal static class PortReport
|
||||
{
|
||||
private static readonly HashSet<string> Unopened = new();
|
||||
|
||||
public static string? Open(string device, string? current, string? portName)
|
||||
{
|
||||
lock (Unopened) Unopened.Remove(device);
|
||||
if (current != portName) SimHost.SendDevice(device, "open", portName ?? "");
|
||||
return portName;
|
||||
}
|
||||
|
||||
public static string? Close(string device, string? current)
|
||||
{
|
||||
if (current is not null) SimHost.SendDevice(device, "close", null);
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>串口没打开(portName 为空)时返回 true,并报告一次</summary>
|
||||
public static bool NotOpen(string device, string? portName)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(portName)) return false;
|
||||
bool first;
|
||||
lock (Unopened) first = Unopened.Add(device);
|
||||
if (first) SimHost.SendDevice(device, "unopened", null);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public class SerialPortHelper
|
||||
{
|
||||
private string? _portName;
|
||||
@@ -48,18 +82,21 @@ public class LEDHelper
|
||||
private const string SendFailed = "Error:发送失败";
|
||||
private string? _portName;
|
||||
|
||||
public void OpenLedCom(string portName) => _portName = portName;
|
||||
public void OpenLedCom(string portName) => _portName = PortReport.Open("led", _portName, portName);
|
||||
|
||||
/// <summary>成功时原样返回发送的文字,并显示到实训台的 LED 屏上;串口未打开时返回 "Error:发送失败"</summary>
|
||||
/// <summary>
|
||||
/// 成功时原样返回发送的文字,并显示到实训台的 LED 屏上;串口未打开时返回 "Error:发送失败"。
|
||||
/// 打开的串口上没接 LED 屏时和真机一样也原样返回(真机只管发出去、不等回应),只是屏上不显示
|
||||
/// </summary>
|
||||
public string SendLedString(string sendString)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_portName)) return SendFailed;
|
||||
SimHost.SendDevice("led", "text", sendString);
|
||||
if (PortReport.NotOpen("led", _portName)) return SendFailed;
|
||||
if (TrainingBoard.Reaches("led", _portName)) SimHost.SendDevice("led", "text", sendString);
|
||||
return sendString;
|
||||
}
|
||||
|
||||
/// <summary>教材 dll 里没有这个方法,但书上项目 2 任务 3 调用了它;保留空方法让书上代码能编译</summary>
|
||||
public void Close() => _portName = null;
|
||||
/// <summary>教材 dll 里没有这个方法,但书上项目 2 任务 3 调用了它;保留它让书上代码能编译</summary>
|
||||
public void Close() => _portName = PortReport.Close("led", _portName);
|
||||
}
|
||||
|
||||
public class ADAM4017Helper
|
||||
@@ -70,19 +107,19 @@ public class ADAM4017Helper
|
||||
public void Open(string PortName, int Temperature_Port = 0, int Humidity_Port = 1, int Light_Port = 2,
|
||||
int AirPressure_Port = 3, int AirQuality_Port = 4, int CO2_Port = 5, int Wind_Port = 6)
|
||||
{
|
||||
_portName = PortName;
|
||||
_portName = PortReport.Open("adam4017", _portName, PortName);
|
||||
_channels = new[] { Temperature_Port, Humidity_Port, Light_Port, AirPressure_Port, AirQuality_Port, CO2_Port, Wind_Port };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取各通道数值。数值一律是整数字符串(教材项目 7 用 Convert.ToInt16 转换,带小数会出错);
|
||||
/// 串口未打开时所有属性为 null,与教材仿真版一致。
|
||||
/// 串口未打开时所有属性为 null,与教材仿真版一致;打开的串口上没接 4017+ 时收不到回应,和真机版一样也全是 null。
|
||||
/// </summary>
|
||||
public ADAM4017ResModel SetData()
|
||||
{
|
||||
LegacyThreads.CheckPoint();
|
||||
var model = new ADAM4017ResModel();
|
||||
if (string.IsNullOrWhiteSpace(_portName)) return model;
|
||||
if (PortReport.NotOpen("adam4017", _portName) || !TrainingBoard.Reaches("adam4017", _portName)) return model;
|
||||
model.temperatureValue = Read(0);
|
||||
model.humidityValue = Read(1);
|
||||
model.lightValue = Read(2);
|
||||
@@ -95,7 +132,7 @@ public class ADAM4017Helper
|
||||
|
||||
private string Read(int quantity) => TrainingBoard.ReadAnalog(_channels[quantity]).ToString();
|
||||
|
||||
public void Close() => _portName = null;
|
||||
public void Close() => _portName = PortReport.Close("adam4017", _portName);
|
||||
}
|
||||
|
||||
public class ADAM4150Helper
|
||||
@@ -105,16 +142,16 @@ public class ADAM4150Helper
|
||||
|
||||
public void Open(string PortName, int BodyInfrared_Port = 0, int Fire_Port = 1, int Smoke_Port = 2, int Infrared_Port = 3)
|
||||
{
|
||||
_portName = PortName;
|
||||
_portName = PortReport.Open("adam4150", _portName, PortName);
|
||||
_channels = new[] { BodyInfrared_Port, Fire_Port, Smoke_Port, Infrared_Port };
|
||||
}
|
||||
|
||||
/// <summary>读取人体红外、火焰、烟雾、红外对射;串口未打开时全部为 false</summary>
|
||||
/// <summary>读取人体红外、火焰、烟雾、红外对射;串口未打开、或打开的串口上没接 4150(收不到回应)时全部为 false</summary>
|
||||
public ADAM4150ResModel SetData()
|
||||
{
|
||||
LegacyThreads.CheckPoint();
|
||||
var model = new ADAM4150ResModel();
|
||||
if (string.IsNullOrWhiteSpace(_portName)) return model;
|
||||
if (PortReport.NotOpen("adam4150", _portName) || !TrainingBoard.Reaches("adam4150", _portName)) return model;
|
||||
model.bodyInfraredValue = Read(0);
|
||||
model.fireValue = Read(1);
|
||||
model.smokeValue = Read(2);
|
||||
@@ -126,31 +163,35 @@ public class ADAM4150Helper
|
||||
|
||||
/// <summary>
|
||||
/// 控制继电器 DO0–DO4(DO0 报警灯、DO1 照明灯、DO2 路灯、DO3/DO4 插座)。
|
||||
/// 和真实设备一样,串口没有打开时控制无效、返回 false(教材仿真版不检查,这里更贴近实物)。
|
||||
/// 和真实设备一样,串口没有打开时控制无效、返回 false(教材仿真版不检查,这里更贴近实物);
|
||||
/// 真机版只管发出命令、不等回应,所以打开的串口上没接 4150 时也返回 true,只是继电器不动作。
|
||||
/// </summary>
|
||||
public bool OnOff(int DO, bool IsOpen)
|
||||
{
|
||||
if (DO < 0 || DO > 4 || string.IsNullOrWhiteSpace(_portName)) return false;
|
||||
SimHost.SendDevice("adam4150", "do", new[] { DO, IsOpen ? 1 : 0 });
|
||||
if (DO < 0 || DO > 4 || PortReport.NotOpen("adam4150", _portName)) return false;
|
||||
if (TrainingBoard.Reaches("adam4150", _portName)) SimHost.SendDevice("adam4150", "do", new[] { DO, IsOpen ? 1 : 0 });
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Close() => _portName = null;
|
||||
public void Close() => _portName = PortReport.Close("adam4150", _portName);
|
||||
}
|
||||
|
||||
public class ZigBeeHelper
|
||||
{
|
||||
private string? _portName;
|
||||
|
||||
public void Open(string PortName) => _portName = PortName;
|
||||
public void Open(string PortName) => _portName = PortReport.Open("zigbee", _portName, PortName);
|
||||
|
||||
/// <summary>控制 ZigBee 继电器节点 0、1(教材用节点 1 控制路灯);串口没有打开时控制无效、返回 false</summary>
|
||||
/// <summary>
|
||||
/// 控制 ZigBee 继电器节点 0、1(教材用节点 1 控制路灯);串口没有打开时控制无效、返回 false。
|
||||
/// 真机版要等协调器回应,打开的串口上没接协调器时收不到回应,也返回 false
|
||||
/// </summary>
|
||||
public bool OnOff(int RelayNum, bool IsOpen)
|
||||
{
|
||||
if (RelayNum < 0 || RelayNum > 1 || string.IsNullOrWhiteSpace(_portName)) return false;
|
||||
if (RelayNum < 0 || RelayNum > 1 || PortReport.NotOpen("zigbee", _portName) || !TrainingBoard.Reaches("zigbee", _portName)) return false;
|
||||
SimHost.SendDevice("zigbee", "relay", new[] { RelayNum, IsOpen ? 1 : 0 });
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Close() => _portName = null;
|
||||
public void Close() => _portName = PortReport.Close("zigbee", _portName);
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Nodes;
|
||||
|
||||
namespace DeviceCtrlLibrary;
|
||||
@@ -15,6 +16,14 @@ public static class TrainingBoard
|
||||
private static int[] _analog = { 25, 60, 500, 101, 60, 600, 3, 0 };
|
||||
// ADAM-4150 DI0–DI6(人体红外、火焰、烟雾、红外对射、预留)
|
||||
private static bool[] _digital = new bool[7];
|
||||
// 接线:设备 → 串口(教材布局图的接法,随实验而定,见 shared/protocol.ts 的 DEFAULT_WIRING)
|
||||
private static readonly Dictionary<string, string> Wiring = new()
|
||||
{
|
||||
["adam4017"] = "COM2",
|
||||
["adam4150"] = "COM3",
|
||||
["zigbee"] = "COM4",
|
||||
["led"] = "COM5",
|
||||
};
|
||||
|
||||
static TrainingBoard()
|
||||
{
|
||||
@@ -22,7 +31,7 @@ public static class TrainingBoard
|
||||
if (!string.IsNullOrEmpty(initial) && JsonNode.Parse(initial) is JsonObject json) Update(json);
|
||||
}
|
||||
|
||||
/// <summary>平台内部使用:应用服务器推送的 {"analog":[…],"digital":[…],"clock":…}(clock 不带时表示不变)</summary>
|
||||
/// <summary>平台内部使用:应用服务器推送的 {"analog":[…],"digital":[…],"clock":…,"wiring":{…}}(clock、wiring 不带时表示不变)</summary>
|
||||
public static void Update(JsonObject json)
|
||||
{
|
||||
if (json.ContainsKey("clock")) Sim.Host.SimClock.Update(json["clock"]);
|
||||
@@ -32,9 +41,22 @@ public static class TrainingBoard
|
||||
for (var i = 0; i < analog.Count && i < _analog.Length; i++) _analog[i] = (int?)analog[i] ?? 0;
|
||||
if (json["digital"] is JsonArray digital)
|
||||
for (var i = 0; i < digital.Count && i < _digital.Length; i++) _digital[i] = (bool?)digital[i] ?? false;
|
||||
if (json["wiring"] is JsonObject wiring)
|
||||
foreach (var (device, port) in wiring)
|
||||
if (Wiring.ContainsKey(device) && port is not null) Wiring[device] = (string)port!;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 程序打开的串口上有没有接这台设备。和真机一样,串口打开错了不报错,只是设备收不到命令、也不回应。
|
||||
/// Windows 的串口名不分大小写
|
||||
/// </summary>
|
||||
internal static bool Reaches(string device, string? portName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(portName)) return false;
|
||||
lock (Lock) return Wiring.TryGetValue(device, out var port) && string.Equals(port, portName.Trim(), StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
internal static int ReadAnalog(int channel)
|
||||
{
|
||||
lock (Lock) return channel >= 0 && channel < _analog.Length ? _analog[channel] : 0;
|
||||
|
||||
Reference in new issue
Block a user