Files
xuyueandClaude Opus 5.5 46e0f40cce
CI/CD / test (push) Canceled after 0s
CI/CD / deploy (push) Canceled after 0s
实训台接线:串口要选对。接线做成数据(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>
2026-10-28 17:40:57 +08:00

198 lines
8.0 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using System.Linq;
using DeviceCtrlLibrary.Model;
using Sim.Host;
namespace DeviceCtrlLibrary;
/// <summary>
/// 教材的设备控制入口:DeviceCtrlHelper.ledHelper.SendLedString(...) 等。
/// 返回值约定与教材仿真版 dll 保持一致;不同之处是数值来自虚拟实训台,而不是随机数,
/// 并且和真机版一样,设备要在它接的那个串口上打开才有反应(接线见 TrainingBoard)。
/// </summary>
public class DeviceCtrlHelper
{
// 教材 dll 里这些是公开静态字段(不是属性),这里保持一致
public static SerialPortHelper serialPortHelper = new();
public static LEDHelper ledHelper = new();
public static ADAM4017Helper adam4017Helper = new();
public static ADAM4150Helper adam4150Helper = new();
public static ZigBeeHelper zigBeeHelper = new();
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;
private int _baudRate;
public void Open(string portName, int BaudRate)
{
_portName = portName;
_baudRate = BaudRate;
}
/// <summary>返回一串十六进制字节(大写、空格分隔、末尾带空格),未打开时返回 "Error"</summary>
public string GetStringData()
{
if (string.IsNullOrWhiteSpace(_portName) || _baudRate == 0) return "Error";
var bytes = new byte[27];
Random.Shared.NextBytes(bytes);
return string.Concat(bytes.Select(b => $"{b:X2} "));
}
public void Close() => _portName = null;
}
public class LEDHelper
{
private const string SendFailed = "Error:发送失败";
private string? _portName;
public void OpenLedCom(string portName) => _portName = PortReport.Open("led", _portName, portName);
/// <summary>
/// 成功时原样返回发送的文字,并显示到实训台的 LED 屏上;串口未打开时返回 "Error:发送失败"。
/// 打开的串口上没接 LED 屏时和真机一样也原样返回(真机只管发出去、不等回应),只是屏上不显示
/// </summary>
public string SendLedString(string 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 = PortReport.Close("led", _portName);
}
public class ADAM4017Helper
{
private string? _portName;
private int[] _channels = { 0, 1, 2, 3, 4, 5, 6 };
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 = 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,与教材仿真版一致;打开的串口上没接 4017+ 时收不到回应,和真机版一样也全是 null。
/// </summary>
public ADAM4017ResModel SetData()
{
LegacyThreads.CheckPoint();
var model = new ADAM4017ResModel();
if (PortReport.NotOpen("adam4017", _portName) || !TrainingBoard.Reaches("adam4017", _portName)) return model;
model.temperatureValue = Read(0);
model.humidityValue = Read(1);
model.lightValue = Read(2);
model.airPressureValue = Read(3);
model.airQualityValue = Read(4);
model.CO2Value = Read(5);
model.windValue = Read(6);
return model;
}
private string Read(int quantity) => TrainingBoard.ReadAnalog(_channels[quantity]).ToString();
public void Close() => _portName = PortReport.Close("adam4017", _portName);
}
public class ADAM4150Helper
{
private string? _portName;
private int[] _channels = { 0, 1, 2, 3 };
public void Open(string PortName, int BodyInfrared_Port = 0, int Fire_Port = 1, int Smoke_Port = 2, int Infrared_Port = 3)
{
_portName = PortReport.Open("adam4150", _portName, PortName);
_channels = new[] { BodyInfrared_Port, Fire_Port, Smoke_Port, Infrared_Port };
}
/// <summary>读取人体红外、火焰、烟雾、红外对射;串口未打开、或打开的串口上没接 4150(收不到回应)时全部为 false</summary>
public ADAM4150ResModel SetData()
{
LegacyThreads.CheckPoint();
var model = new ADAM4150ResModel();
if (PortReport.NotOpen("adam4150", _portName) || !TrainingBoard.Reaches("adam4150", _portName)) return model;
model.bodyInfraredValue = Read(0);
model.fireValue = Read(1);
model.smokeValue = Read(2);
model.infraredValue = Read(3);
return model;
}
private bool Read(int sensor) => TrainingBoard.ReadDigital(_channels[sensor]);
/// <summary>
/// 控制继电器 DO0–DO4(DO0 报警灯、DO1 照明灯、DO2 路灯、DO3/DO4 插座)。
/// 和真实设备一样,串口没有打开时控制无效、返回 false(教材仿真版不检查,这里更贴近实物);
/// 真机版只管发出命令、不等回应,所以打开的串口上没接 4150 时也返回 true,只是继电器不动作。
/// </summary>
public bool OnOff(int DO, bool IsOpen)
{
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 = PortReport.Close("adam4150", _portName);
}
public class ZigBeeHelper
{
private string? _portName;
public void Open(string PortName) => _portName = PortReport.Open("zigbee", _portName, PortName);
/// <summary>
/// 控制 ZigBee 继电器节点 0、1(教材用节点 1 控制路灯);串口没有打开时控制无效、返回 false。
/// 真机版要等协调器回应,打开的串口上没接协调器时收不到回应,也返回 false
/// </summary>
public bool OnOff(int RelayNum, bool IsOpen)
{
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 = PortReport.Close("zigbee", _portName);
}