114 lines
3.3 KiB
C#
114 lines
3.3 KiB
C#
|
|
using System;
|
|||
|
|
using System.Collections.Generic;
|
|||
|
|
using System.Linq;
|
|||
|
|
using System.Threading;
|
|||
|
|
using System.Threading.Tasks;
|
|||
|
|
using System.Windows.Forms;
|
|||
|
|
|
|||
|
|
public sealed class ManualControlSender : IDisposable
|
|||
|
|
{
|
|||
|
|
private readonly UdpReliableChannel _udp;
|
|||
|
|
private readonly byte _device; // ex: (byte)T_Code.Mov ou T_Code.Dir
|
|||
|
|
private readonly HashSet<Keys> _pressed = new();
|
|||
|
|
private readonly object _lock = new();
|
|||
|
|
|
|||
|
|
private CancellationTokenSource? _cts;
|
|||
|
|
private Task? _loop;
|
|||
|
|
|
|||
|
|
// Ajuste fino
|
|||
|
|
public int RepeatIntervalMs { get; set; } = 250; // 4 Hz
|
|||
|
|
public int BurstDownCount { get; set; } = 6;
|
|||
|
|
public int BurstDownIntervalMs { get; set; } = 25;
|
|||
|
|
public int BurstStopCount { get; set; } = 10;
|
|||
|
|
public int BurstStopIntervalMs { get; set; } = 25;
|
|||
|
|
|
|||
|
|
// protocolo: type 0x01 = KeyCmd
|
|||
|
|
private const byte TYPE_KEY_CMD = 0x01;
|
|||
|
|
|
|||
|
|
public ManualControlSender(UdpReliableChannel udp, byte device)
|
|||
|
|
{
|
|||
|
|
_udp = udp;
|
|||
|
|
_device = device;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void Start()
|
|||
|
|
{
|
|||
|
|
_cts = new CancellationTokenSource();
|
|||
|
|
_loop = Task.Run(() => Loop(_cts.Token));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void KeyDown(Keys key)
|
|||
|
|
{
|
|||
|
|
if (!IsManualKey(key)) return;
|
|||
|
|
|
|||
|
|
lock (_lock) _pressed.Add(key);
|
|||
|
|
|
|||
|
|
// burst imediato pra resposta rápida
|
|||
|
|
_ = SendKeyBurstAsync(key, BurstDownCount, BurstDownIntervalMs);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void KeyUp(Keys key)
|
|||
|
|
{
|
|||
|
|
if (!IsManualKey(key)) return;
|
|||
|
|
|
|||
|
|
lock (_lock) _pressed.Remove(key);
|
|||
|
|
|
|||
|
|
// sempre que soltar algo, manda STOP (Esc) burst
|
|||
|
|
_ = SendKeyBurstAsync(Keys.Escape, BurstStopCount, BurstStopIntervalMs);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private async Task Loop(CancellationToken ct)
|
|||
|
|
{
|
|||
|
|
while (!ct.IsCancellationRequested)
|
|||
|
|
{
|
|||
|
|
Keys? active = null;
|
|||
|
|
lock (_lock)
|
|||
|
|
{
|
|||
|
|
if (_pressed.Count > 0)
|
|||
|
|
active = ChooseActiveKey(_pressed);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (active.HasValue)
|
|||
|
|
{
|
|||
|
|
// reenviar leve enquanto segurando
|
|||
|
|
await SendKeyOnceAsync(active.Value);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try { await Task.Delay(RepeatIntervalMs, ct); }
|
|||
|
|
catch (TaskCanceledException) { }
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private static bool IsManualKey(Keys k) =>
|
|||
|
|
k == Keys.Up || k == Keys.Down || k == Keys.Left || k == Keys.Right ||
|
|||
|
|
k == Keys.Escape;
|
|||
|
|
|
|||
|
|
// Prioridade: se tiver Up/Down e Left/Right junto, você pode escolher uma regra
|
|||
|
|
private static Keys ChooseActiveKey(IEnumerable<Keys> keys)
|
|||
|
|
{
|
|||
|
|
// Exemplo: prioriza ESC, depois Up/Down, depois Left/Right
|
|||
|
|
if (keys.Contains(Keys.Escape)) return Keys.Escape;
|
|||
|
|
if (keys.Contains(Keys.Up)) return Keys.Up;
|
|||
|
|
if (keys.Contains(Keys.Down)) return Keys.Down;
|
|||
|
|
if (keys.Contains(Keys.Left)) return Keys.Left;
|
|||
|
|
if (keys.Contains(Keys.Right)) return Keys.Right;
|
|||
|
|
return Keys.Escape;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private Task SendKeyBurstAsync(Keys key, int count, int intervalMs)
|
|||
|
|
{
|
|||
|
|
var payload = UdpKeyPayload.Build(_device, key);
|
|||
|
|
return _udp.SendBurstAsync(TYPE_KEY_CMD, payload, count, intervalMs, requestAck: false);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private Task SendKeyOnceAsync(Keys key)
|
|||
|
|
{
|
|||
|
|
var payload = UdpKeyPayload.Build(_device, key);
|
|||
|
|
return _udp.SendAsync(TYPE_KEY_CMD, payload, requestAck: false);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void Dispose()
|
|||
|
|
{
|
|||
|
|
try { _cts?.Cancel(); } catch { }
|
|||
|
|
}
|
|||
|
|
}
|