Files
2026-07-13 13:13:17 +08:00

145 lines
4.7 KiB
C#

#nullable enable
using System.Diagnostics;
using System.Threading;
using T3.Core.IO;
using T3.IoServices;
using T3.Core.Utils;
namespace Lib.io.dmx;
[Guid("7A610996-913A-443B-921A-12E6B05F4110")]
internal sealed class DmxOutput : Instance<DmxOutput>, IStatusProvider, ICustomDropdownHolder, IDisposable
{
private readonly byte[] _dmxFrameBuffer = new byte[513];
private readonly Stopwatch _stopwatch = new();
[Input(Guid = "0E2F7F23-99A1-428A-9343-263B29831BC3")]
public readonly InputSlot<bool> Connect = new();
[Input(Guid = "59381A83-3736-4767-948A-18A81180630C")]
public readonly InputSlot<List<int>> DmxUniverse = new();
[Output(Guid = "A0D0B722-1959-4240-812A-1845C3A5A096")]
public readonly Slot<bool> IsConnected = new();
[Input(Guid = "C1F747F5-3634-4142-A16D-346743A13728")]
public readonly InputSlot<int> MaxFps = new(40);
[Input(Guid = "2845601E-2374-4581-8843-169289290176")]
public readonly InputSlot<string> PortName = new();
[Output(Guid = "D2C7B38A-8514-4521-884F-66D605485458")]
public readonly Slot<Command> Result = new();
private bool _lastConnectState;
private string? _lastErrorMessage = "Disconnected";
private string? _lastPortName;
private long _nextFrameTimeTicks;
private IStatusProvider.StatusLevel _statusLevel = IStatusProvider.StatusLevel.Notice;
public DmxOutput()
{
Result.UpdateAction = Update;
IsConnected.UpdateAction = Update;
}
string ICustomDropdownHolder.GetValueForInput(Guid id)
{
if (PortName.Value == null)
return string.Empty;
return id == PortName.Id
? PortName.Value
: string.Empty;
}
IEnumerable<string> ICustomDropdownHolder.GetOptionsForInput(Guid id)
{
return id == PortName.Id ? SerialConnectionManager.GetAvailableSerialPortsWithDescriptions() : Empty<string>();
}
void ICustomDropdownHolder.HandleResultForInput(Guid id, string? s, bool i)
{
if (string.IsNullOrEmpty(s) || !i || id != PortName.Id) return;
PortName.SetTypedInputValue(SerialConnectionManager.GetPortNameFromDeviceDescription(s));
}
public void Dispose()
{
SerialConnectionManager.Unregister(this, _lastPortName);
}
public IStatusProvider.StatusLevel GetStatusLevel()
{
return _statusLevel;
}
public string? GetStatusMessage()
{
return _lastErrorMessage;
}
private void Update(EvaluationContext context)
{
var shouldConnect = Connect.GetValue(context);
var portName = PortName.GetValue(context) ?? string.Empty;
var settingsChanged = portName != _lastPortName || shouldConnect != _lastConnectState;
if (settingsChanged)
{
SerialConnectionManager.Unregister(this, _lastPortName);
if (shouldConnect && !string.IsNullOrEmpty(portName))
try
{
SerialConnectionManager.Register(this, portName, 250000, null, PortModes.DMX);
SetStatus($"Connected to {portName}", IStatusProvider.StatusLevel.Success);
}
catch (Exception e)
{
SetStatus($"Failed to connect: {e.Message}", IStatusProvider.StatusLevel.Error);
}
else
SetStatus("Disconnected", IStatusProvider.StatusLevel.Notice);
_lastPortName = portName;
_lastConnectState = shouldConnect;
}
var isConnected = SerialConnectionManager.IsPortOpen(portName);
IsConnected.Value = isConnected;
if (!isConnected) return;
var maxFps = MaxFps.GetValue(context);
if (maxFps > 0)
{
if (!_stopwatch.IsRunning) _stopwatch.Start();
while (_stopwatch.ElapsedTicks < _nextFrameTimeTicks)
if (_nextFrameTimeTicks - _stopwatch.ElapsedTicks > Stopwatch.Frequency / 1000) Thread.Sleep(1);
else Thread.SpinWait(100);
_nextFrameTimeTicks += Stopwatch.Frequency / maxFps;
}
if (DmxUniverse.DirtyFlag.IsDirty)
{
var universeData = DmxUniverse.GetValue(context);
Array.Clear(_dmxFrameBuffer, 1, 512);
if (universeData != null)
{
var count = Math.Min(universeData.Count, 512);
for (var i = 0; i < count; i++)
_dmxFrameBuffer[i + 1] = (byte)universeData[i].Clamp(0, 255);
}
}
SerialConnectionManager.SendDmxFrame(portName, _dmxFrameBuffer);
}
public void SetStatus(string m, IStatusProvider.StatusLevel l)
{
_lastErrorMessage = m;
_statusLevel = l;
}
}