using System; using System.Drawing; using System.IO; using System.Threading; using Sim.Host; namespace DeviceCtrlLibrary; /// 摄像头每拍到一帧画面时触发(教材 dll 里的委托名就是 CameraImage) public delegate void CameraImage(ImageReadEvenArgs e); public class ImageEventArgs : EventArgs { } /// 摄像头画面事件的参数;类名拼写与教材 dll 一致(EvenArgs) public class ImageReadEvenArgs { public ImageEventArgs ImageEventArg { get; set; } public Image img { get; set; } } /// /// 网络摄像机。画面是自绘的东围墙场景(Camera/scene.svg 切出的帧):Pan 方法每次转动一格, /// 实训台的红外对射被遮挡时画面里出现翻墙的人。和教材仿真版一样,ImageRead 在后台线程上定时触发。 /// public class IPCameraHelper { // 与 Camera/build-frames.ts 一致:左右 7 档、上下 3 档,开始时在正中 private const int StepsX = 7; private const int StepsY = 3; private const int FrameIntervalMs = 250; private static readonly string FramesDir = Path.Combine(Path.GetDirectoryName(typeof(IPCameraHelper).Assembly.Location)!, "Camera", "frames"); private static readonly Image?[] Frames = new Image?[StepsX * StepsY * 2]; private readonly object _lock = new(); private string? _ip; private Timer? _timer; private int _x = StepsX / 2; private int _y = StepsY / 2; private int _ticking; private bool _errorReported; public event CameraImage ImageRead; /// 连接摄像机。任何非空的 IP 都能连上(教材用 admin、空密码) public void Open(string IpAdderss, string Uid, string Pwd) => _ip = IpAdderss; /// 开始传送画面;没有 Open 时不传 public void Start() { if (string.IsNullOrWhiteSpace(_ip)) return; lock (_lock) { _timer ??= new Timer(_ => Tick()); _timer.Change(0, FrameIntervalMs); } SimHost.SendDevice("camera", "on", true); } public void Stop() { lock (_lock) _timer?.Change(Timeout.Infinite, Timeout.Infinite); SimHost.SendDevice("camera", "on", false); } public void Close() { Stop(); _ip = null; } public void PanLeft() => Pan(-1, 0); public void PanRight() => Pan(1, 0); public void PanUp() => Pan(0, -1); public void PanDown() => Pan(0, 1); /// 云台转动一格,到头后不再转;没有 Open 时无效 private void Pan(int dx, int dy) { if (string.IsNullOrWhiteSpace(_ip)) return; int x, y; lock (_lock) { _x = Math.Clamp(_x + dx, 0, StepsX - 1); _y = Math.Clamp(_y + dy, 0, StepsY - 1); (x, y) = (_x, _y); } SimHost.SendDevice("camera", "pan", new[] { x, y }); } private void Tick() { // 上一帧的处理方法还没执行完时跳过这一帧 if (Interlocked.Exchange(ref _ticking, 1) == 1) return; try { var handler = ImageRead; if (handler == null) return; int x, y; lock (_lock) (x, y) = (_x, _y); handler(new ImageReadEvenArgs { ImageEventArg = new ImageEventArgs(), img = Frame(x, y, TrainingBoard.ReadDigital(3)) }); } catch (Exception ex) { // 教材仿真版用 System.Timers.Timer,处理方法里的异常不会让程序退出;这里报告一次,之后照常传画面 if (!_errorReported) { _errorReported = true; SimHost.ReportException(ex); } } finally { Volatile.Write(ref _ticking, 0); } } /// 同一位置的画面复用同一个 Image 对象:赋给 PictureBox 的是同一张图时不用重新发送 private static Image Frame(int x, int y, bool intruder) { var index = (y * StepsX + x) * 2 + (intruder ? 1 : 0); lock (Frames) { return Frames[index] ??= Image.FromStream(new MemoryStream(File.ReadAllBytes(Path.Combine(FramesDir, $"{x}-{y}-{(intruder ? 1 : 0)}.jpg")))); } } }