using System;
using System.Collections.Generic;
using System.Linq;
using DeviceCtrlLibrary.Model;
using Sim.Host;
namespace DeviceCtrlLibrary;
///
/// 教材的设备控制入口:DeviceCtrlHelper.ledHelper.SendLedString(...) 等。
/// 返回值约定与教材仿真版 dll 保持一致;不同之处是数值来自虚拟实训台,而不是随机数,
/// 并且和真机版一样,设备要在它接的那个串口上打开才有反应(接线见 TrainingBoard)。
///
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();
}
///
/// 告诉实训台程序在哪个串口上打开、关闭了设备,以及串口没打开时还在读写设备(网页上据此提示)。
/// 没变就不发:书上有在定时器里反复 Open 的写法;没打开时的读写每次打开之后最多报一次
///
internal static class PortReport
{
private static readonly HashSet 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;
}
/// 串口没打开(portName 为空)时返回 true,并报告一次
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;
}
/// 返回一串十六进制字节(大写、空格分隔、末尾带空格),未打开时返回 "Error"
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);
///
/// 成功时原样返回发送的文字,并显示到实训台的 LED 屏上;串口未打开时返回 "Error:发送失败"。
/// 打开的串口上没接 LED 屏时和真机一样也原样返回(真机只管发出去、不等回应),只是屏上不显示
///
public string SendLedString(string sendString)
{
if (PortReport.NotOpen("led", _portName)) return SendFailed;
if (TrainingBoard.Reaches("led", _portName)) SimHost.SendDevice("led", "text", sendString);
return sendString;
}
/// 教材 dll 里没有这个方法,但书上项目 2 任务 3 调用了它;保留它让书上代码能编译
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 };
}
///
/// 读取各通道数值。数值一律是整数字符串(教材项目 7 用 Convert.ToInt16 转换,带小数会出错);
/// 串口未打开时所有属性为 null,与教材仿真版一致;打开的串口上没接 4017+ 时收不到回应,和真机版一样也全是 null。
///
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 };
}
/// 读取人体红外、火焰、烟雾、红外对射;串口未打开、或打开的串口上没接 4150(收不到回应)时全部为 false
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]);
///
/// 控制继电器 DO0–DO4(DO0 报警灯、DO1 照明灯、DO2 路灯、DO3/DO4 插座)。
/// 和真实设备一样,串口没有打开时控制无效、返回 false(教材仿真版不检查,这里更贴近实物);
/// 真机版只管发出命令、不等回应,所以打开的串口上没接 4150 时也返回 true,只是继电器不动作。
///
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);
///
/// 控制 ZigBee 继电器节点 0、1(教材用节点 1 控制路灯);串口没有打开时控制无效、返回 false。
/// 真机版要等协调器回应,打开的串口上没接协调器时收不到回应,也返回 false
///
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);
}