feat: 完成前后端旋转编码器的数字孪生
This commit is contained in:
		@@ -87,7 +87,8 @@ try
 | 
				
			|||||||
                    if (!string.IsNullOrEmpty(accessToken) && (
 | 
					                    if (!string.IsNullOrEmpty(accessToken) && (
 | 
				
			||||||
                            path.StartsWithSegments("/hubs/JtagHub") ||
 | 
					                            path.StartsWithSegments("/hubs/JtagHub") ||
 | 
				
			||||||
                            path.StartsWithSegments("/hubs/ProgressHub") ||
 | 
					                            path.StartsWithSegments("/hubs/ProgressHub") ||
 | 
				
			||||||
                            path.StartsWithSegments("/hubs/DigitalTubesHub")
 | 
					                            path.StartsWithSegments("/hubs/DigitalTubesHub") ||
 | 
				
			||||||
 | 
					                            path.StartsWithSegments("/hubs/RotaryEncoderHub")
 | 
				
			||||||
                        ))
 | 
					                        ))
 | 
				
			||||||
                    {
 | 
					                    {
 | 
				
			||||||
                        // Read the token out of the query string
 | 
					                        // Read the token out of the query string
 | 
				
			||||||
@@ -254,6 +255,7 @@ try
 | 
				
			|||||||
    app.MapHub<server.Hubs.JtagHub>("/hubs/JtagHub");
 | 
					    app.MapHub<server.Hubs.JtagHub>("/hubs/JtagHub");
 | 
				
			||||||
    app.MapHub<server.Hubs.ProgressHub>("/hubs/ProgressHub");
 | 
					    app.MapHub<server.Hubs.ProgressHub>("/hubs/ProgressHub");
 | 
				
			||||||
    app.MapHub<server.Hubs.DigitalTubesHub>("/hubs/DigitalTubesHub");
 | 
					    app.MapHub<server.Hubs.DigitalTubesHub>("/hubs/DigitalTubesHub");
 | 
				
			||||||
 | 
					    app.MapHub<server.Hubs.RotaryEncoderHub>("/hubs/RotaryEncoderHub");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Setup Program
 | 
					    // Setup Program
 | 
				
			||||||
    MsgBus.Init();
 | 
					    MsgBus.Init();
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										243
									
								
								server/src/Hubs/RotaryEncoderHub.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										243
									
								
								server/src/Hubs/RotaryEncoderHub.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,243 @@
 | 
				
			|||||||
 | 
					using Microsoft.AspNetCore.Authorization;
 | 
				
			||||||
 | 
					using System.Security.Claims;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.SignalR;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Cors;
 | 
				
			||||||
 | 
					using TypedSignalR.Client;
 | 
				
			||||||
 | 
					using DotNext;
 | 
				
			||||||
 | 
					using Peripherals.RotaryEncoderClient;
 | 
				
			||||||
 | 
					using System.Collections.Concurrent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#pragma warning disable 1998
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace server.Hubs;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[Hub]
 | 
				
			||||||
 | 
					public interface IRotaryEncoderHub
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    Task<bool> SetEnable(bool enable);
 | 
				
			||||||
 | 
					    Task<bool> RotateEncoderOnce(int num, RotaryEncoderDirection direction);
 | 
				
			||||||
 | 
					    Task<bool> EnableCycleRotateEncoder(int num, RotaryEncoderDirection direction, int freq);
 | 
				
			||||||
 | 
					    Task<bool> DisableCycleRotateEncoder();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[Receiver]
 | 
				
			||||||
 | 
					public interface IRotaryEncoderReceiver
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    Task OnReceiveRotate(int num, RotaryEncoderDirection direction);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class CycleTaskInfo
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public Task? CycleTask { get; set; }
 | 
				
			||||||
 | 
					    public RotaryEncoderCtrl EncoderClient { get; set; }
 | 
				
			||||||
 | 
					    public CancellationTokenSource CTS { get; set; } = new();
 | 
				
			||||||
 | 
					    public int Freq { get; set; }
 | 
				
			||||||
 | 
					    public int Num { get; set; }
 | 
				
			||||||
 | 
					    public RotaryEncoderDirection Direction { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public CycleTaskInfo(
 | 
				
			||||||
 | 
					        RotaryEncoderCtrl client,
 | 
				
			||||||
 | 
					        int num, int freq,
 | 
				
			||||||
 | 
					        RotaryEncoderDirection direction)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        EncoderClient = client;
 | 
				
			||||||
 | 
					        Num = num;
 | 
				
			||||||
 | 
					        Direction = direction;
 | 
				
			||||||
 | 
					        Freq = freq;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[Authorize]
 | 
				
			||||||
 | 
					[EnableCors("SignalR")]
 | 
				
			||||||
 | 
					public class RotaryEncoderHub : Hub<IRotaryEncoderReceiver>, IRotaryEncoderHub
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
 | 
				
			||||||
 | 
					    private readonly IHubContext<RotaryEncoderHub, IRotaryEncoderReceiver> _hubContext;
 | 
				
			||||||
 | 
					    private readonly Database.UserManager _userManager = new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private ConcurrentDictionary<(string, string), CycleTaskInfo> _cycleTasks = new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public RotaryEncoderHub(IHubContext<RotaryEncoderHub, IRotaryEncoderReceiver> hubContext)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        _hubContext = hubContext;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private Optional<Database.Board> TryGetBoard()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        var userName = Context.User?.FindFirstValue(ClaimTypes.Name);
 | 
				
			||||||
 | 
					        if (string.IsNullOrEmpty(userName))
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            logger.Error("User name is null or empty");
 | 
				
			||||||
 | 
					            return null;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var userRet = _userManager.GetUserByName(userName);
 | 
				
			||||||
 | 
					        if (!userRet.IsSuccessful || !userRet.Value.HasValue)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            logger.Error($"User '{userName}' not found");
 | 
				
			||||||
 | 
					            return null;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        var user = userRet.Value.Value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var boardRet = _userManager.GetBoardByID(user.BoardID);
 | 
				
			||||||
 | 
					        if (!boardRet.IsSuccessful || !boardRet.Value.HasValue)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            logger.Error($"Board not found");
 | 
				
			||||||
 | 
					            return null;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return boardRet.Value.Value;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public async Task<bool> SetEnable(bool enable)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        try
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var board = TryGetBoard().OrThrow(() => new Exception("Board not found"));
 | 
				
			||||||
 | 
					            var encoderCtrl = new RotaryEncoderCtrl(board.IpAddr, board.Port, 0);
 | 
				
			||||||
 | 
					            var result = await encoderCtrl.SetEnable(enable);
 | 
				
			||||||
 | 
					            if (!result.IsSuccessful)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                logger.Error(result.Error, "SetEnable failed");
 | 
				
			||||||
 | 
					                return false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return result.Value;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        catch (Exception ex)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            logger.Error(ex, "Failed to set enable");
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public async Task<bool> RotateEncoderOnce(int num, RotaryEncoderDirection direction)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        try
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (num <= 0 || num > 4)
 | 
				
			||||||
 | 
					                throw new ArgumentException($"RotaryEncoder num should be 1~3, instead of {num}");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var board = TryGetBoard().OrThrow(() => new Exception("Board not found"));
 | 
				
			||||||
 | 
					            var encoderCtrl = new RotaryEncoderCtrl(board.IpAddr, board.Port, 0);
 | 
				
			||||||
 | 
					            var result = await encoderCtrl.RotateEncoderOnce(num, direction);
 | 
				
			||||||
 | 
					            if (!result.IsSuccessful)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                logger.Error(result.Error, $"RotateEncoderOnce({num}, {direction}) failed");
 | 
				
			||||||
 | 
					                return false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return result.Value;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        catch (Exception ex)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            logger.Error(ex, "Failed to rotate encoder once");
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public async Task<bool> EnableCycleRotateEncoder(int num, RotaryEncoderDirection direction, int freq)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        try
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (num <= 0 || num > 4) throw new ArgumentException(
 | 
				
			||||||
 | 
					                $"RotaryEncoder num should be 1~3, instead of {num}");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (freq <= 0 || freq > 1000) throw new ArgumentException(
 | 
				
			||||||
 | 
					                $"Frequency should be between 1 and 1000, instead of {freq}");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var board = TryGetBoard().OrThrow(() => new Exception("Board not found"));
 | 
				
			||||||
 | 
					            var key = (board.ID.ToString(), Context.ConnectionId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (_cycleTasks.TryGetValue(key, out var existing))
 | 
				
			||||||
 | 
					                await DisableCycleRotateEncoder();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var cts = new CancellationTokenSource();
 | 
				
			||||||
 | 
					            var encoderCtrl = new RotaryEncoderCtrl(board.IpAddr, board.Port, 0);
 | 
				
			||||||
 | 
					            var cycleTaskInfo = new CycleTaskInfo(encoderCtrl, num, freq, direction);
 | 
				
			||||||
 | 
					            cycleTaskInfo.CycleTask = CycleRotate(cycleTaskInfo, Context.ConnectionId, board.ID.ToString());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            _cycleTasks[key] = cycleTaskInfo;
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        catch (Exception ex)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            logger.Error(ex, "Failed to enable cycle rotate encoder");
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public async Task<bool> DisableCycleRotateEncoder()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        try
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var board = TryGetBoard().OrThrow(() => new Exception("Board not found"));
 | 
				
			||||||
 | 
					            var key = (board.ID.ToString(), Context.ConnectionId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (_cycleTasks.TryRemove(key, out var taskInfo))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                taskInfo.CTS.Cancel();
 | 
				
			||||||
 | 
					                if (taskInfo.CycleTask != null)
 | 
				
			||||||
 | 
					                    await taskInfo.CycleTask;
 | 
				
			||||||
 | 
					                taskInfo.CTS.Dispose();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        catch (Exception ex)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            logger.Error(ex, "Failed to disable cycle rotate encoder");
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private Task CycleRotate(CycleTaskInfo taskInfo, string clientId, string boardId)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        var ctrl = taskInfo.EncoderClient;
 | 
				
			||||||
 | 
					        var token = taskInfo.CTS.Token;
 | 
				
			||||||
 | 
					        return Task.Run(async () =>
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var cntError = 0;
 | 
				
			||||||
 | 
					            while (!token.IsCancellationRequested)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                var ret = await ctrl.RotateEncoderOnce(taskInfo.Num, taskInfo.Direction);
 | 
				
			||||||
 | 
					                if (!ret.IsSuccessful)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    logger.Error(
 | 
				
			||||||
 | 
					                        $"Failed to rotate encoder {taskInfo.Num} on board {boardId}: {ret.Error}");
 | 
				
			||||||
 | 
					                    cntError++;
 | 
				
			||||||
 | 
					                    if (cntError >= 3)
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        logger.Error(
 | 
				
			||||||
 | 
					                            $"Too many errors occurred while rotating encoder {taskInfo.Num} on board {boardId}");
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (!ret.Value)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    logger.Warn(
 | 
				
			||||||
 | 
					                        $"Encoder {taskInfo.Num} on board {boardId} is not responding");
 | 
				
			||||||
 | 
					                    continue;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                await _hubContext.Clients
 | 
				
			||||||
 | 
					                    .Client(clientId)
 | 
				
			||||||
 | 
					                    .OnReceiveRotate(taskInfo.Num, taskInfo.Direction);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                await Task.Delay(1000 / taskInfo.Freq, token);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }, token)
 | 
				
			||||||
 | 
					        .ContinueWith((task) =>
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (task.IsFaulted)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                logger.Error($"Rotary encoder cycle operation failed: {task.Exception}");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else if (task.IsCanceled)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                logger.Info($"Rotary encoder cycle operation cancelled for board {boardId}");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                logger.Info($"Rotary encoder cycle completed for board {boardId}");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										69
									
								
								server/src/Peripherals/RotaryEncoderClient.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								server/src/Peripherals/RotaryEncoderClient.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,69 @@
 | 
				
			|||||||
 | 
					using System.Net;
 | 
				
			||||||
 | 
					using DotNext;
 | 
				
			||||||
 | 
					using Tapper;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Peripherals.RotaryEncoderClient;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class RotaryEncoderCtrlAddr
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public const UInt32 BASE = 0xB0_00_00_20;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public const UInt32 ENABLE = BASE;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[TranspilationSource]
 | 
				
			||||||
 | 
					public enum RotaryEncoderDirection : uint
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    CounterClockwise = 0,
 | 
				
			||||||
 | 
					    Clockwise = 1,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class RotaryEncoderCtrl
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    readonly int timeout = 500;
 | 
				
			||||||
 | 
					    readonly int taskID;
 | 
				
			||||||
 | 
					    readonly int port;
 | 
				
			||||||
 | 
					    readonly string address;
 | 
				
			||||||
 | 
					    private IPEndPoint ep;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public RotaryEncoderCtrl(string address, int port, int taskID, int timeout = 500)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if (timeout < 0)
 | 
				
			||||||
 | 
					            throw new ArgumentException("Timeout couldn't be negative", nameof(timeout));
 | 
				
			||||||
 | 
					        this.address = address;
 | 
				
			||||||
 | 
					        this.port = port;
 | 
				
			||||||
 | 
					        this.ep = new IPEndPoint(IPAddress.Parse(address), port);
 | 
				
			||||||
 | 
					        this.taskID = taskID;
 | 
				
			||||||
 | 
					        this.timeout = timeout;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public async ValueTask<Result<bool>> SetEnable(bool enable)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if (MsgBus.IsRunning)
 | 
				
			||||||
 | 
					            MsgBus.UDPServer.ClearUDPData(this.address, this.taskID);
 | 
				
			||||||
 | 
					        else return new(new Exception("Message Bus not work!"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var ret = await UDPClientPool.WriteAddr(
 | 
				
			||||||
 | 
					            this.ep, this.taskID, RotaryEncoderCtrlAddr.ENABLE, enable ? 0x1U : 0x0U, this.timeout);
 | 
				
			||||||
 | 
					        if (!ret.IsSuccessful) return new(ret.Error);
 | 
				
			||||||
 | 
					        return ret.Value;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public async ValueTask<Result<bool>> RotateEncoderOnce(int num, RotaryEncoderDirection direction)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if (MsgBus.IsRunning)
 | 
				
			||||||
 | 
					            MsgBus.UDPServer.ClearUDPData(this.address, this.taskID);
 | 
				
			||||||
 | 
					        else return new(new Exception("Message Bus not work!"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var ret = await UDPClientPool.WriteAddr(
 | 
				
			||||||
 | 
					            this.ep, this.taskID, RotaryEncoderCtrlAddr.BASE + (UInt32)num, (UInt32)direction, this.timeout);
 | 
				
			||||||
 | 
					        if (!ret.IsSuccessful)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            logger.Error($"Set Rotary Encoder {num} {direction.ToString()} failed: {ret.Error}");
 | 
				
			||||||
 | 
					            return new(ret.Error);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return ret.Value;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,4 +1,3 @@
 | 
				
			|||||||
using System.Collections;
 | 
					 | 
				
			||||||
using System.Net;
 | 
					using System.Net;
 | 
				
			||||||
using DotNext;
 | 
					using DotNext;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -11,9 +10,6 @@ class SwitchCtrlAddr
 | 
				
			|||||||
    public const UInt32 ENABLE = BASE;
 | 
					    public const UInt32 ENABLE = BASE;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// <summary>
 | 
					 | 
				
			||||||
/// 矩阵键盘外设类,用于控制和管理矩阵键盘的功能。
 | 
					 | 
				
			||||||
/// </summary>
 | 
					 | 
				
			||||||
public class SwitchCtrl
 | 
					public class SwitchCtrl
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
 | 
					    private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,258 +1,301 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <div class="ec11-container" :style="{
 | 
					  <div
 | 
				
			||||||
    width: width + 'px',
 | 
					    class="inline-block select-none"
 | 
				
			||||||
    height: height + 'px',
 | 
					    :style="{
 | 
				
			||||||
    position: 'relative',
 | 
					      width: width + 'px',
 | 
				
			||||||
  }">
 | 
					      height: height + 'px',
 | 
				
			||||||
    <svg xmlns="http://www.w3.org/2000/svg" :width="width" :height="height" viewBox="0 0 100 100"
 | 
					      position: 'relative',
 | 
				
			||||||
      class="ec11-encoder">
 | 
					    }"
 | 
				
			||||||
      <defs>
 | 
					  >
 | 
				
			||||||
        <!-- 发光效果滤镜 -->
 | 
					    <svg
 | 
				
			||||||
        <filter id="glow" x="-50%" y="-50%" width="200%" height="200%">
 | 
					      xmlns="http://www.w3.org/2000/svg"
 | 
				
			||||||
          <feFlood result="flood" flood-color="#00ff88" flood-opacity="1"></feFlood>
 | 
					      :width="width"
 | 
				
			||||||
          <feComposite in="flood" result="mask" in2="SourceGraphic" operator="in"></feComposite>
 | 
					      :height="height"
 | 
				
			||||||
          <feMorphology in="mask" result="dilated" operator="dilate" radius="1"></feMorphology>
 | 
					      viewBox="0 0 100 100"
 | 
				
			||||||
          <feGaussianBlur in="dilated" stdDeviation="2" result="blur1" />
 | 
					      class="ec11-encoder"
 | 
				
			||||||
          <feGaussianBlur in="dilated" stdDeviation="4" result="blur2" />
 | 
					    >
 | 
				
			||||||
          <feGaussianBlur in="dilated" stdDeviation="8" result="blur3" />
 | 
					      <defs>
 | 
				
			||||||
          <feMerge>
 | 
					        <!-- 发光效果滤镜 -->
 | 
				
			||||||
            <feMergeNode in="blur3" />
 | 
					        <filter id="glow" x="-50%" y="-50%" width="200%" height="200%">
 | 
				
			||||||
            <feMergeNode in="blur2" />
 | 
					          <feFlood
 | 
				
			||||||
            <feMergeNode in="blur1" />
 | 
					            result="flood"
 | 
				
			||||||
            <feMergeNode in="SourceGraphic" />
 | 
					            flood-color="#00ff88"
 | 
				
			||||||
          </feMerge>
 | 
					            flood-opacity="1"
 | 
				
			||||||
        </filter>
 | 
					          ></feFlood>
 | 
				
			||||||
        
 | 
					          <feComposite
 | 
				
			||||||
        <!-- 编码器主体渐变 -->
 | 
					            in="flood"
 | 
				
			||||||
        <radialGradient id="encoderGradient" cx="50%" cy="30%">
 | 
					            result="mask"
 | 
				
			||||||
          <stop offset="0%" stop-color="#666666" />
 | 
					            in2="SourceGraphic"
 | 
				
			||||||
          <stop offset="70%" stop-color="#333333" />
 | 
					            operator="in"
 | 
				
			||||||
          <stop offset="100%" stop-color="#1a1a1a" />
 | 
					          ></feComposite>
 | 
				
			||||||
        </radialGradient>
 | 
					          <feMorphology
 | 
				
			||||||
        
 | 
					            in="mask"
 | 
				
			||||||
        <!-- 旋钮渐变 -->
 | 
					            result="dilated"
 | 
				
			||||||
        <radialGradient id="knobGradient" cx="30%" cy="30%">
 | 
					            operator="dilate"
 | 
				
			||||||
          <stop offset="0%" stop-color="#555555" />
 | 
					            radius="1"
 | 
				
			||||||
          <stop offset="70%" stop-color="#222222" />
 | 
					          ></feMorphology>
 | 
				
			||||||
          <stop offset="100%" stop-color="#111111" />
 | 
					          <feGaussianBlur in="dilated" stdDeviation="2" result="blur1" />
 | 
				
			||||||
        </radialGradient>
 | 
					          <feGaussianBlur in="dilated" stdDeviation="4" result="blur2" />
 | 
				
			||||||
        
 | 
					          <feGaussianBlur in="dilated" stdDeviation="8" result="blur3" />
 | 
				
			||||||
        <!-- 按下状态渐变 -->
 | 
					          <feMerge>
 | 
				
			||||||
        <radialGradient id="knobPressedGradient" cx="50%" cy="50%">
 | 
					            <feMergeNode in="blur3" />
 | 
				
			||||||
          <stop offset="0%" stop-color="#333333" />
 | 
					            <feMergeNode in="blur2" />
 | 
				
			||||||
          <stop offset="70%" stop-color="#555555" />
 | 
					            <feMergeNode in="blur1" />
 | 
				
			||||||
          <stop offset="100%" stop-color="#888888" />
 | 
					            <feMergeNode in="SourceGraphic" />
 | 
				
			||||||
        </radialGradient>
 | 
					          </feMerge>
 | 
				
			||||||
      </defs>
 | 
					        </filter>
 | 
				
			||||||
      
 | 
					
 | 
				
			||||||
      <!-- 编码器底座 -->
 | 
					        <!-- 编码器主体渐变 -->
 | 
				
			||||||
      <rect x="10" y="30" width="80" height="60" rx="8" ry="8" 
 | 
					        <radialGradient id="encoderGradient" cx="50%" cy="30%">
 | 
				
			||||||
            fill="#2a2a2a" stroke="#444444" stroke-width="1"/>
 | 
					          <stop offset="0%" stop-color="#666666" />
 | 
				
			||||||
      
 | 
					          <stop offset="70%" stop-color="#333333" />
 | 
				
			||||||
      <!-- 编码器主体外壳 -->
 | 
					          <stop offset="100%" stop-color="#1a1a1a" />
 | 
				
			||||||
      <circle cx="50" cy="60" r="32" fill="url(#encoderGradient)" stroke="#555555" stroke-width="1"/>
 | 
					        </radialGradient>
 | 
				
			||||||
      
 | 
					
 | 
				
			||||||
      <!-- 编码器接线端子 -->
 | 
					        <!-- 旋钮渐变 -->
 | 
				
			||||||
      <rect x="5" y="75" width="4" height="8" fill="#c9c9c9" rx="1"/>
 | 
					        <radialGradient id="knobGradient" cx="30%" cy="30%">
 | 
				
			||||||
      <rect x="15" y="85" width="4" height="8" fill="#c9c9c9" rx="1"/>
 | 
					          <stop offset="0%" stop-color="#555555" />
 | 
				
			||||||
      <rect x="25" y="85" width="4" height="8" fill="#c9c9c9" rx="1"/>
 | 
					          <stop offset="70%" stop-color="#222222" />
 | 
				
			||||||
      <rect x="81" y="85" width="4" height="8" fill="#c9c9c9" rx="1"/>
 | 
					          <stop offset="100%" stop-color="#111111" />
 | 
				
			||||||
      <rect x="91" y="75" width="4" height="8" fill="#c9c9c9" rx="1"/>
 | 
					        </radialGradient>
 | 
				
			||||||
      
 | 
					
 | 
				
			||||||
      <!-- 旋钮 -->
 | 
					        <!-- 按下状态渐变 -->
 | 
				
			||||||
      <circle cx="50" cy="60" r="22" 
 | 
					        <radialGradient id="knobPressedGradient" cx="50%" cy="50%">
 | 
				
			||||||
              :fill="isPressed ? 'url(#knobPressedGradient)' : 'url(#knobGradient)'" 
 | 
					          <stop offset="0%" stop-color="#333333" />
 | 
				
			||||||
              stroke="#666666" stroke-width="1"
 | 
					          <stop offset="70%" stop-color="#555555" />
 | 
				
			||||||
              :transform="`rotate(${rotation/2} 50 60)`"
 | 
					          <stop offset="100%" stop-color="#888888" />
 | 
				
			||||||
              class="interactive"
 | 
					        </radialGradient>
 | 
				
			||||||
              @mousedown="handleMouseDown"
 | 
					      </defs>
 | 
				
			||||||
              @mouseup="handlePress(false)"
 | 
					
 | 
				
			||||||
              @mouseleave="handlePress(false)"/>
 | 
					      <!-- 编码器底座 -->
 | 
				
			||||||
      
 | 
					      <rect
 | 
				
			||||||
      <!-- 旋钮指示器 -->
 | 
					        x="10"
 | 
				
			||||||
      <line x1="50" y1="42" x2="50" y2="48" 
 | 
					        y="30"
 | 
				
			||||||
            stroke="#ffffff" stroke-width="2" stroke-linecap="round"
 | 
					        width="80"
 | 
				
			||||||
            :transform="`rotate(${rotation} 50 60)`"/>
 | 
					        height="60"
 | 
				
			||||||
      
 | 
					        rx="8"
 | 
				
			||||||
      <!-- 旋钮上的纹理刻度 -->
 | 
					        ry="8"
 | 
				
			||||||
      <g :transform="`rotate(${rotation} 50 60)`">
 | 
					        fill="#2a2a2a"
 | 
				
			||||||
        <circle cx="50" cy="60" r="18" fill="none" stroke="#777777" stroke-width="0.5"/>
 | 
					        stroke="#444444"
 | 
				
			||||||
        <!-- 刻度线 -->
 | 
					        stroke-width="1"
 | 
				
			||||||
        <g v-for="i in 16" :key="i">
 | 
					      />
 | 
				
			||||||
          <line :x1="50 + 16 * Math.cos((i-1) * Math.PI / 8)" 
 | 
					
 | 
				
			||||||
                :y1="60 + 16 * Math.sin((i-1) * Math.PI / 8)"
 | 
					      <!-- 编码器主体外壳 -->
 | 
				
			||||||
                :x2="50 + 18 * Math.cos((i-1) * Math.PI / 8)" 
 | 
					      <circle
 | 
				
			||||||
                :y2="60 + 18 * Math.sin((i-1) * Math.PI / 8)"
 | 
					        cx="50"
 | 
				
			||||||
                stroke="#999999" stroke-width="0.5"/>
 | 
					        cy="60"
 | 
				
			||||||
        </g>
 | 
					        r="32"
 | 
				
			||||||
      </g>
 | 
					        fill="url(#encoderGradient)"
 | 
				
			||||||
      
 | 
					        stroke="#555555"
 | 
				
			||||||
      <!-- 编码器编号标签 -->
 | 
					        stroke-width="1"
 | 
				
			||||||
      <text x="50" y="15" text-anchor="middle" font-family="Arial" font-size="10" 
 | 
					      />
 | 
				
			||||||
            fill="#cccccc" font-weight="bold">
 | 
					
 | 
				
			||||||
        EC11-{{ encoderNumber }}
 | 
					      <!-- 编码器接线端子 -->
 | 
				
			||||||
      </text>
 | 
					      <rect x="5" y="75" width="4" height="8" fill="#c9c9c9" rx="1" />
 | 
				
			||||||
      
 | 
					      <rect x="15" y="85" width="4" height="8" fill="#c9c9c9" rx="1" />
 | 
				
			||||||
      <!-- 状态指示器 -->
 | 
					      <rect x="25" y="85" width="4" height="8" fill="#c9c9c9" rx="1" />
 | 
				
			||||||
      <circle cx="85" cy="20" r="3" :fill="isPressed ? '#ff4444' : '#444444'" 
 | 
					      <rect x="81" y="85" width="4" height="8" fill="#c9c9c9" rx="1" />
 | 
				
			||||||
              :filter="isPressed ? 'url(#glow)' : ''" 
 | 
					      <rect x="91" y="75" width="4" height="8" fill="#c9c9c9" rx="1" />
 | 
				
			||||||
              stroke="#666666" stroke-width="0.5"/>
 | 
					
 | 
				
			||||||
    </svg>
 | 
					      <!-- 旋钮 -->
 | 
				
			||||||
  </div>
 | 
					      <circle
 | 
				
			||||||
</template>
 | 
					        cx="50"
 | 
				
			||||||
 | 
					        cy="60"
 | 
				
			||||||
<script lang="ts" setup>
 | 
					        r="22"
 | 
				
			||||||
import { ref, computed } from 'vue';
 | 
					        :fill="isPressed ? 'url(#knobPressedGradient)' : 'url(#knobGradient)'"
 | 
				
			||||||
 | 
					        stroke="#666666"
 | 
				
			||||||
interface Props {
 | 
					        stroke-width="1"
 | 
				
			||||||
  size?: number;
 | 
					        :transform="`rotate(${rotationStep * 7.5} 50 60)`"
 | 
				
			||||||
  encoderNumber?: number;
 | 
					        class="interactive"
 | 
				
			||||||
}
 | 
					        @mousedown="handleMouseDown"
 | 
				
			||||||
 | 
					        @mouseup="handlePress(false)"
 | 
				
			||||||
const props = withDefaults(defineProps<Props>(), {
 | 
					        @mouseleave="handlePress(false)"
 | 
				
			||||||
  size: 1,
 | 
					      />
 | 
				
			||||||
  encoderNumber: 1
 | 
					
 | 
				
			||||||
});
 | 
					      <!-- 旋钮指示器 -->
 | 
				
			||||||
 | 
					      <line
 | 
				
			||||||
// 组件状态
 | 
					        x1="50"
 | 
				
			||||||
const isPressed = ref(false);
 | 
					        y1="42"
 | 
				
			||||||
const rotation = ref(0);
 | 
					        x2="50"
 | 
				
			||||||
 | 
					        y2="48"
 | 
				
			||||||
// 拖动状态
 | 
					        stroke="#ffffff"
 | 
				
			||||||
const isDragging = ref(false);
 | 
					        stroke-width="2"
 | 
				
			||||||
const dragStartX = ref(0);
 | 
					        stroke-linecap="round"
 | 
				
			||||||
const lastTriggerX = ref(0);
 | 
					        :transform="`rotate(${rotationStep * 15} 50 60)`"
 | 
				
			||||||
const dragThreshold = 20; // 每20像素触发一次旋转
 | 
					      />
 | 
				
			||||||
const hasRotated = ref(false); // 标记是否已经发生了旋转
 | 
					
 | 
				
			||||||
 | 
					      <!-- 旋钮上的纹理刻度 -->
 | 
				
			||||||
// 计算宽高
 | 
					      <g :transform="`rotate(${rotationStep * 15} 50 60)`">
 | 
				
			||||||
const width = computed(() => 100 * props.size);
 | 
					        <circle
 | 
				
			||||||
const height = computed(() => 100 * props.size);
 | 
					          cx="50"
 | 
				
			||||||
 | 
					          cy="60"
 | 
				
			||||||
// 定义发出的事件
 | 
					          r="18"
 | 
				
			||||||
const emit = defineEmits(['press', 'release', 'rotate-left', 'rotate-right']);
 | 
					          fill="none"
 | 
				
			||||||
 | 
					          stroke="#777777"
 | 
				
			||||||
// 鼠标按下处理
 | 
					          stroke-width="0.5"
 | 
				
			||||||
function handleMouseDown(event: MouseEvent) {
 | 
					        />
 | 
				
			||||||
  isDragging.value = true;
 | 
					        <!-- 刻度线 -->
 | 
				
			||||||
  dragStartX.value = event.clientX;
 | 
					        <g v-for="i in 16" :key="i">
 | 
				
			||||||
  lastTriggerX.value = event.clientX;
 | 
					          <line
 | 
				
			||||||
  hasRotated.value = false; // 重置旋转标记
 | 
					            :x1="50 + 16 * Math.cos(((i - 1) * Math.PI) / 8)"
 | 
				
			||||||
  
 | 
					            :y1="60 + 16 * Math.sin(((i - 1) * Math.PI) / 8)"
 | 
				
			||||||
  // 添加全局鼠标事件监听
 | 
					            :x2="50 + 18 * Math.cos(((i - 1) * Math.PI) / 8)"
 | 
				
			||||||
  document.addEventListener('mousemove', handleMouseMove);
 | 
					            :y2="60 + 18 * Math.sin(((i - 1) * Math.PI) / 8)"
 | 
				
			||||||
  document.addEventListener('mouseup', handleMouseUp);
 | 
					            stroke="#999999"
 | 
				
			||||||
}
 | 
					            stroke-width="0.5"
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
// 鼠标移动处理
 | 
					        </g>
 | 
				
			||||||
function handleMouseMove(event: MouseEvent) {
 | 
					      </g>
 | 
				
			||||||
  if (!isDragging.value) return;
 | 
					
 | 
				
			||||||
  
 | 
					      <!-- 编码器编号标签 -->
 | 
				
			||||||
  const currentX = event.clientX;
 | 
					      <text
 | 
				
			||||||
  const deltaX = currentX - lastTriggerX.value;
 | 
					        x="50"
 | 
				
			||||||
  
 | 
					        y="15"
 | 
				
			||||||
  // 检查是否达到触发阈值
 | 
					        text-anchor="middle"
 | 
				
			||||||
  if (Math.abs(deltaX) >= dragThreshold) {
 | 
					        font-family="Arial"
 | 
				
			||||||
    hasRotated.value = true; // 标记已经发生旋转
 | 
					        font-size="10"
 | 
				
			||||||
    
 | 
					        fill="#cccccc"
 | 
				
			||||||
    if (deltaX > 0) {
 | 
					        font-weight="bold"
 | 
				
			||||||
      // 右拖动 - 右旋转
 | 
					      >
 | 
				
			||||||
      rotation.value += 15;
 | 
					        EC11-{{ encoderNumber }}
 | 
				
			||||||
      emit('rotate-right', { 
 | 
					      </text>
 | 
				
			||||||
        encoderNumber: props.encoderNumber
 | 
					
 | 
				
			||||||
      });
 | 
					      <!-- 状态指示器 -->
 | 
				
			||||||
    } else {
 | 
					      <circle
 | 
				
			||||||
      // 左拖动 - 左旋转
 | 
					        cx="85"
 | 
				
			||||||
      rotation.value -= 15;
 | 
					        cy="20"
 | 
				
			||||||
      emit('rotate-left', { 
 | 
					        r="3"
 | 
				
			||||||
        encoderNumber: props.encoderNumber
 | 
					        :fill="isPressed ? '#ff4444' : '#444444'"
 | 
				
			||||||
      });
 | 
					        :filter="isPressed ? 'url(#glow)' : ''"
 | 
				
			||||||
    }
 | 
					        stroke="#666666"
 | 
				
			||||||
    
 | 
					        stroke-width="0.5"
 | 
				
			||||||
    // 更新最后触发位置
 | 
					      />
 | 
				
			||||||
    lastTriggerX.value = currentX;
 | 
					    </svg>
 | 
				
			||||||
    
 | 
					  </div>
 | 
				
			||||||
    // 保持角度在0-360度范围内
 | 
					</template>
 | 
				
			||||||
    rotation.value = rotation.value % 720;
 | 
					
 | 
				
			||||||
    if (rotation.value < 0) {
 | 
					<script lang="ts" setup>
 | 
				
			||||||
      rotation.value += 720;
 | 
					import { useRotaryEncoder } from "@/stores/Peripherals/RotaryEncoder";
 | 
				
			||||||
    }
 | 
					import { RotaryEncoderDirection } from "@/utils/signalR/Peripherals.RotaryEncoderClient";
 | 
				
			||||||
  }
 | 
					import { watch } from "vue";
 | 
				
			||||||
}
 | 
					import { watchEffect } from "vue";
 | 
				
			||||||
 | 
					import { ref, computed } from "vue";
 | 
				
			||||||
// 鼠标松开处理
 | 
					
 | 
				
			||||||
function handleMouseUp() {
 | 
					const rotataryEncoderStore = useRotaryEncoder();
 | 
				
			||||||
  isDragging.value = false;
 | 
					
 | 
				
			||||||
  
 | 
					interface Props {
 | 
				
			||||||
  // 只有在没有发生旋转的情况下才识别为按压事件
 | 
					  size?: number;
 | 
				
			||||||
  if (!hasRotated.value) {
 | 
					  enableDigitalTwin?: boolean;
 | 
				
			||||||
    // 触发按压和释放事件(模拟快速按压)
 | 
					  encoderNumber?: number;
 | 
				
			||||||
    handlePress(true);
 | 
					}
 | 
				
			||||||
    // 使用setTimeout来模拟按压和释放的时序
 | 
					
 | 
				
			||||||
    setTimeout(() => {
 | 
					const props = withDefaults(defineProps<Props>(), {
 | 
				
			||||||
      handlePress(false);
 | 
					  size: 1,
 | 
				
			||||||
    }, 100);
 | 
					  enableDigitalTwin: false,
 | 
				
			||||||
  }
 | 
					  encoderNumber: 1,
 | 
				
			||||||
  
 | 
					});
 | 
				
			||||||
  // 移除全局事件监听
 | 
					
 | 
				
			||||||
  document.removeEventListener('mousemove', handleMouseMove);
 | 
					// 组件状态
 | 
				
			||||||
  document.removeEventListener('mouseup', handleMouseUp);
 | 
					const isPressed = ref(false);
 | 
				
			||||||
}
 | 
					const rotationStep = ref(0); // 步进计数,1步=15度
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 按压处理
 | 
					// 拖动状态对象,增加 hasRotated 标记
 | 
				
			||||||
function handlePress(pressed: boolean) {
 | 
					const drag = ref<{
 | 
				
			||||||
  if (pressed !== isPressed.value) {
 | 
					  active: boolean;
 | 
				
			||||||
    isPressed.value = pressed;
 | 
					  startX: number;
 | 
				
			||||||
    if (pressed) {
 | 
					  hasRotated: boolean;
 | 
				
			||||||
      emit('press', { encoderNumber: props.encoderNumber });
 | 
					} | null>(null);
 | 
				
			||||||
    } else {
 | 
					
 | 
				
			||||||
      emit('release', { encoderNumber: props.encoderNumber });
 | 
					const dragThreshold = 20; // 每20像素触发一次旋转
 | 
				
			||||||
    }
 | 
					
 | 
				
			||||||
  }
 | 
					// 计算宽高
 | 
				
			||||||
}
 | 
					const width = computed(() => 100 * props.size);
 | 
				
			||||||
 | 
					const height = computed(() => 100 * props.size);
 | 
				
			||||||
// 暴露组件方法
 | 
					
 | 
				
			||||||
defineExpose({
 | 
					// 鼠标按下处理
 | 
				
			||||||
  press: () => handlePress(true),
 | 
					function handleMouseDown(event: MouseEvent) {
 | 
				
			||||||
  release: () => handlePress(false),
 | 
					  drag.value = { active: true, startX: event.clientX, hasRotated: false };
 | 
				
			||||||
  rotateLeft: () => {
 | 
					  window.addEventListener("mousemove", handleMouseMove);
 | 
				
			||||||
    rotation.value -= 15;
 | 
					  window.addEventListener("mouseup", handleMouseUp);
 | 
				
			||||||
    emit('rotate-left', { 
 | 
					}
 | 
				
			||||||
      encoderNumber: props.encoderNumber
 | 
					
 | 
				
			||||||
    });
 | 
					// 鼠标移动处理
 | 
				
			||||||
  },
 | 
					function handleMouseMove(event: MouseEvent) {
 | 
				
			||||||
  rotateRight: () => {
 | 
					  if (!drag.value?.active) return;
 | 
				
			||||||
    rotation.value += 15;
 | 
					  const dx = event.clientX - drag.value.startX;
 | 
				
			||||||
    emit('rotate-right', { 
 | 
					  if (Math.abs(dx) >= dragThreshold) {
 | 
				
			||||||
      encoderNumber: props.encoderNumber
 | 
					    rotationStep.value += dx > 0 ? 1 : -1;
 | 
				
			||||||
    });
 | 
					    drag.value.startX = event.clientX;
 | 
				
			||||||
  },
 | 
					    drag.value.hasRotated = true;
 | 
				
			||||||
  isPressed: () => isPressed.value
 | 
					  }
 | 
				
			||||||
});
 | 
					}
 | 
				
			||||||
</script>
 | 
					
 | 
				
			||||||
 | 
					// 鼠标松开处理
 | 
				
			||||||
<script lang="ts">
 | 
					function handleMouseUp() {
 | 
				
			||||||
// 添加一个静态方法来获取默认props
 | 
					  if (drag.value && drag.value.active) {
 | 
				
			||||||
export function getDefaultProps() {
 | 
					    // 仅在未发生旋转时才触发按压
 | 
				
			||||||
  return {
 | 
					    if (!drag.value.hasRotated) {
 | 
				
			||||||
    size: 1,
 | 
					      isPressed.value = true;
 | 
				
			||||||
    encoderNumber: 1
 | 
					      setTimeout(() => {
 | 
				
			||||||
  };
 | 
					        isPressed.value = false;
 | 
				
			||||||
}
 | 
					      }, 100);
 | 
				
			||||||
</script>
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
<style scoped lang="postcss">
 | 
					  drag.value = null;
 | 
				
			||||||
.ec11-container {
 | 
					  window.removeEventListener("mousemove", handleMouseMove);
 | 
				
			||||||
  display: inline-block;
 | 
					  window.removeEventListener("mouseup", handleMouseUp);
 | 
				
			||||||
  user-select: none;
 | 
					}
 | 
				
			||||||
}
 | 
					
 | 
				
			||||||
 | 
					// 按压处理(用于鼠标离开和mouseup)
 | 
				
			||||||
.ec11-encoder {
 | 
					function handlePress(pressed: boolean) {
 | 
				
			||||||
  display: block;
 | 
					  isPressed.value = pressed;
 | 
				
			||||||
  overflow: visible;
 | 
					}
 | 
				
			||||||
}
 | 
					
 | 
				
			||||||
 | 
					watchEffect(() => {
 | 
				
			||||||
.interactive {
 | 
					  rotataryEncoderStore.setEnable(props.enableDigitalTwin);
 | 
				
			||||||
  cursor: pointer;
 | 
					});
 | 
				
			||||||
}
 | 
					
 | 
				
			||||||
</style>
 | 
					watch(
 | 
				
			||||||
 | 
					  () => rotationStep.value,
 | 
				
			||||||
 | 
					  (newStep, oldStep) => {
 | 
				
			||||||
 | 
					    if (newStep > oldStep) {
 | 
				
			||||||
 | 
					      rotataryEncoderStore.rotateOnce(
 | 
				
			||||||
 | 
					        props.encoderNumber,
 | 
				
			||||||
 | 
					        RotaryEncoderDirection.Clockwise,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    } else if (newStep < oldStep) {
 | 
				
			||||||
 | 
					      rotataryEncoderStore.rotateOnce(
 | 
				
			||||||
 | 
					        props.encoderNumber,
 | 
				
			||||||
 | 
					        RotaryEncoderDirection.CounterClockwise,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts">
 | 
				
			||||||
 | 
					// 添加一个静态方法来获取默认props
 | 
				
			||||||
 | 
					export function getDefaultProps() {
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    size: 1,
 | 
				
			||||||
 | 
					    enableDigitalTwin: false,
 | 
				
			||||||
 | 
					    encoderNumber: 1,
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style scoped lang="postcss">
 | 
				
			||||||
 | 
					.ec11-container {
 | 
				
			||||||
 | 
					  display: inline-block;
 | 
				
			||||||
 | 
					  user-select: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.ec11-encoder {
 | 
				
			||||||
 | 
					  display: block;
 | 
				
			||||||
 | 
					  overflow: visible;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.interactive {
 | 
				
			||||||
 | 
					  cursor: pointer;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										92
									
								
								src/stores/Peripherals/RotaryEncoder.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								src/stores/Peripherals/RotaryEncoder.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,92 @@
 | 
				
			|||||||
 | 
					import { AuthManager } from "@/utils/AuthManager";
 | 
				
			||||||
 | 
					import type { RotaryEncoderDirection } from "@/utils/signalR/Peripherals.RotaryEncoderClient";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  getHubProxyFactory,
 | 
				
			||||||
 | 
					  getReceiverRegister,
 | 
				
			||||||
 | 
					} from "@/utils/signalR/TypedSignalR.Client";
 | 
				
			||||||
 | 
					import type {
 | 
				
			||||||
 | 
					  IRotaryEncoderHub,
 | 
				
			||||||
 | 
					  IRotaryEncoderReceiver,
 | 
				
			||||||
 | 
					} from "@/utils/signalR/TypedSignalR.Client/server.Hubs";
 | 
				
			||||||
 | 
					import { HubConnectionState, type HubConnection } from "@microsoft/signalr";
 | 
				
			||||||
 | 
					import { isUndefined } from "mathjs";
 | 
				
			||||||
 | 
					import { defineStore } from "pinia";
 | 
				
			||||||
 | 
					import { onMounted, onUnmounted, ref, shallowRef } from "vue";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const useRotaryEncoder = defineStore("RotaryEncoder", () => {
 | 
				
			||||||
 | 
					  const rotaryEncoderHub = shallowRef<{
 | 
				
			||||||
 | 
					    connection: HubConnection;
 | 
				
			||||||
 | 
					    proxy: IRotaryEncoderHub;
 | 
				
			||||||
 | 
					  } | null>(null);
 | 
				
			||||||
 | 
					  const rotaryEncoderReceiver: IRotaryEncoderReceiver = {
 | 
				
			||||||
 | 
					    onReceiveRotate: async (data) => {},
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  onMounted(() => {
 | 
				
			||||||
 | 
					    initHub();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  onUnmounted(() => {
 | 
				
			||||||
 | 
					    clearHub();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function initHub() {
 | 
				
			||||||
 | 
					    if (rotaryEncoderHub.value) return;
 | 
				
			||||||
 | 
					    const connection = AuthManager.createHubConnection("RotaryEncoderHub");
 | 
				
			||||||
 | 
					    const proxy =
 | 
				
			||||||
 | 
					      getHubProxyFactory("IRotaryEncoderHub").createHubProxy(connection);
 | 
				
			||||||
 | 
					    getReceiverRegister("IRotaryEncoderReceiver").register(
 | 
				
			||||||
 | 
					      connection,
 | 
				
			||||||
 | 
					      rotaryEncoderReceiver,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    connection.start();
 | 
				
			||||||
 | 
					    rotaryEncoderHub.value = { connection, proxy };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function clearHub() {
 | 
				
			||||||
 | 
					    if (!rotaryEncoderHub.value) return;
 | 
				
			||||||
 | 
					    rotaryEncoderHub.value.connection.stop();
 | 
				
			||||||
 | 
					    rotaryEncoderHub.value = null;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function reinitializeHub() {
 | 
				
			||||||
 | 
					    clearHub();
 | 
				
			||||||
 | 
					    initHub();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function getHubProxy() {
 | 
				
			||||||
 | 
					    if (!rotaryEncoderHub.value) throw new Error("Hub not initialized");
 | 
				
			||||||
 | 
					    return rotaryEncoderHub.value.proxy;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async function setEnable(enabled: boolean) {
 | 
				
			||||||
 | 
					    const proxy = getHubProxy();
 | 
				
			||||||
 | 
					    return await proxy.setEnable(enabled);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async function rotateOnce(num: number, direction: RotaryEncoderDirection) {
 | 
				
			||||||
 | 
					    const proxy = getHubProxy();
 | 
				
			||||||
 | 
					    return await proxy.rotateEncoderOnce(num, direction);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async function enableCycleRotate(
 | 
				
			||||||
 | 
					    num: number,
 | 
				
			||||||
 | 
					    direction: RotaryEncoderDirection,
 | 
				
			||||||
 | 
					    freq: number,
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    const proxy = getHubProxy();
 | 
				
			||||||
 | 
					    return await proxy.enableCycleRotateEncoder(num, direction, freq);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async function disableCycleRotate() {
 | 
				
			||||||
 | 
					    const proxy = getHubProxy();
 | 
				
			||||||
 | 
					    return await proxy.disableCycleRotateEncoder();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    setEnable,
 | 
				
			||||||
 | 
					    rotateOnce,
 | 
				
			||||||
 | 
					    enableCycleRotate,
 | 
				
			||||||
 | 
					    disableCycleRotate,
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
@@ -45,7 +45,7 @@ export class AuthManager {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  // SignalR连接 - 简单明了
 | 
					  // SignalR连接 - 简单明了
 | 
				
			||||||
  static createHubConnection(
 | 
					  static createHubConnection(
 | 
				
			||||||
    hubPath: "ProgressHub" | "JtagHub" | "DigitalTubesHub",
 | 
					    hubPath: "ProgressHub" | "JtagHub" | "DigitalTubesHub" | "RotaryEncoderHub",
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
    return new HubConnectionBuilder()
 | 
					    return new HubConnectionBuilder()
 | 
				
			||||||
      .withUrl(`http://127.0.0.1:5000/hubs/${hubPath}`, {
 | 
					      .withUrl(`http://127.0.0.1:5000/hubs/${hubPath}`, {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										10
									
								
								src/utils/signalR/Peripherals.RotaryEncoderClient.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/utils/signalR/Peripherals.RotaryEncoderClient.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					/* THIS (.ts) FILE IS GENERATED BY Tapper */
 | 
				
			||||||
 | 
					/* eslint-disable */
 | 
				
			||||||
 | 
					/* tslint:disable */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Transpiled from Peripherals.RotaryEncoderClient.RotaryEncoderDirection */
 | 
				
			||||||
 | 
					export enum RotaryEncoderDirection {
 | 
				
			||||||
 | 
					    CounterClockwise = 0,
 | 
				
			||||||
 | 
					    Clockwise = 1,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -3,8 +3,9 @@
 | 
				
			|||||||
/* tslint:disable */
 | 
					/* tslint:disable */
 | 
				
			||||||
// @ts-nocheck
 | 
					// @ts-nocheck
 | 
				
			||||||
import type { HubConnection, IStreamResult, Subject } from '@microsoft/signalr';
 | 
					import type { HubConnection, IStreamResult, Subject } from '@microsoft/signalr';
 | 
				
			||||||
import type { IDigitalTubesHub, IJtagHub, IProgressHub, IDigitalTubesReceiver, IJtagReceiver, IProgressReceiver } from './server.Hubs';
 | 
					import type { IDigitalTubesHub, IJtagHub, IProgressHub, IRotaryEncoderHub, IDigitalTubesReceiver, IJtagReceiver, IProgressReceiver, IRotaryEncoderReceiver } from './server.Hubs';
 | 
				
			||||||
import type { DigitalTubeTaskStatus, ProgressInfo } from '../server.Hubs';
 | 
					import type { DigitalTubeTaskStatus, ProgressInfo } from '../server.Hubs';
 | 
				
			||||||
 | 
					import type { RotaryEncoderDirection } from '../Peripherals.RotaryEncoderClient';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// components
 | 
					// components
 | 
				
			||||||
@@ -46,6 +47,7 @@ export type HubProxyFactoryProvider = {
 | 
				
			|||||||
    (hubType: "IDigitalTubesHub"): HubProxyFactory<IDigitalTubesHub>;
 | 
					    (hubType: "IDigitalTubesHub"): HubProxyFactory<IDigitalTubesHub>;
 | 
				
			||||||
    (hubType: "IJtagHub"): HubProxyFactory<IJtagHub>;
 | 
					    (hubType: "IJtagHub"): HubProxyFactory<IJtagHub>;
 | 
				
			||||||
    (hubType: "IProgressHub"): HubProxyFactory<IProgressHub>;
 | 
					    (hubType: "IProgressHub"): HubProxyFactory<IProgressHub>;
 | 
				
			||||||
 | 
					    (hubType: "IRotaryEncoderHub"): HubProxyFactory<IRotaryEncoderHub>;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const getHubProxyFactory = ((hubType: string) => {
 | 
					export const getHubProxyFactory = ((hubType: string) => {
 | 
				
			||||||
@@ -58,12 +60,16 @@ export const getHubProxyFactory = ((hubType: string) => {
 | 
				
			|||||||
    if(hubType === "IProgressHub") {
 | 
					    if(hubType === "IProgressHub") {
 | 
				
			||||||
        return IProgressHub_HubProxyFactory.Instance;
 | 
					        return IProgressHub_HubProxyFactory.Instance;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    if(hubType === "IRotaryEncoderHub") {
 | 
				
			||||||
 | 
					        return IRotaryEncoderHub_HubProxyFactory.Instance;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}) as HubProxyFactoryProvider;
 | 
					}) as HubProxyFactoryProvider;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type ReceiverRegisterProvider = {
 | 
					export type ReceiverRegisterProvider = {
 | 
				
			||||||
    (receiverType: "IDigitalTubesReceiver"): ReceiverRegister<IDigitalTubesReceiver>;
 | 
					    (receiverType: "IDigitalTubesReceiver"): ReceiverRegister<IDigitalTubesReceiver>;
 | 
				
			||||||
    (receiverType: "IJtagReceiver"): ReceiverRegister<IJtagReceiver>;
 | 
					    (receiverType: "IJtagReceiver"): ReceiverRegister<IJtagReceiver>;
 | 
				
			||||||
    (receiverType: "IProgressReceiver"): ReceiverRegister<IProgressReceiver>;
 | 
					    (receiverType: "IProgressReceiver"): ReceiverRegister<IProgressReceiver>;
 | 
				
			||||||
 | 
					    (receiverType: "IRotaryEncoderReceiver"): ReceiverRegister<IRotaryEncoderReceiver>;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const getReceiverRegister = ((receiverType: string) => {
 | 
					export const getReceiverRegister = ((receiverType: string) => {
 | 
				
			||||||
@@ -76,6 +82,9 @@ export const getReceiverRegister = ((receiverType: string) => {
 | 
				
			|||||||
    if(receiverType === "IProgressReceiver") {
 | 
					    if(receiverType === "IProgressReceiver") {
 | 
				
			||||||
        return IProgressReceiver_Binder.Instance;
 | 
					        return IProgressReceiver_Binder.Instance;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    if(receiverType === "IRotaryEncoderReceiver") {
 | 
				
			||||||
 | 
					        return IRotaryEncoderReceiver_Binder.Instance;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}) as ReceiverRegisterProvider;
 | 
					}) as ReceiverRegisterProvider;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// HubProxy
 | 
					// HubProxy
 | 
				
			||||||
@@ -171,6 +180,39 @@ class IProgressHub_HubProxy implements IProgressHub {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class IRotaryEncoderHub_HubProxyFactory implements HubProxyFactory<IRotaryEncoderHub> {
 | 
				
			||||||
 | 
					    public static Instance = new IRotaryEncoderHub_HubProxyFactory();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private constructor() {
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public readonly createHubProxy = (connection: HubConnection): IRotaryEncoderHub => {
 | 
				
			||||||
 | 
					        return new IRotaryEncoderHub_HubProxy(connection);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class IRotaryEncoderHub_HubProxy implements IRotaryEncoderHub {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public constructor(private connection: HubConnection) {
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public readonly setEnable = async (enable: boolean): Promise<boolean> => {
 | 
				
			||||||
 | 
					        return await this.connection.invoke("SetEnable", enable);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public readonly rotateEncoderOnce = async (num: number, direction: RotaryEncoderDirection): Promise<boolean> => {
 | 
				
			||||||
 | 
					        return await this.connection.invoke("RotateEncoderOnce", num, direction);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public readonly enableCycleRotateEncoder = async (num: number, direction: RotaryEncoderDirection, freq: number): Promise<boolean> => {
 | 
				
			||||||
 | 
					        return await this.connection.invoke("EnableCycleRotateEncoder", num, direction, freq);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public readonly disableCycleRotateEncoder = async (): Promise<boolean> => {
 | 
				
			||||||
 | 
					        return await this.connection.invoke("DisableCycleRotateEncoder");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Receiver
 | 
					// Receiver
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -237,3 +279,24 @@ class IProgressReceiver_Binder implements ReceiverRegister<IProgressReceiver> {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class IRotaryEncoderReceiver_Binder implements ReceiverRegister<IRotaryEncoderReceiver> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static Instance = new IRotaryEncoderReceiver_Binder();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private constructor() {
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public readonly register = (connection: HubConnection, receiver: IRotaryEncoderReceiver): Disposable => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const __onReceiveRotate = (...args: [number, RotaryEncoderDirection]) => receiver.onReceiveRotate(...args);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        connection.on("OnReceiveRotate", __onReceiveRotate);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const methodList: ReceiverMethod[] = [
 | 
				
			||||||
 | 
					            { methodName: "OnReceiveRotate", method: __onReceiveRotate }
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return new ReceiverMethodSubscription(connection, methodList);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,6 +4,7 @@
 | 
				
			|||||||
// @ts-nocheck
 | 
					// @ts-nocheck
 | 
				
			||||||
import type { IStreamResult, Subject } from '@microsoft/signalr';
 | 
					import type { IStreamResult, Subject } from '@microsoft/signalr';
 | 
				
			||||||
import type { DigitalTubeTaskStatus, ProgressInfo } from '../server.Hubs';
 | 
					import type { DigitalTubeTaskStatus, ProgressInfo } from '../server.Hubs';
 | 
				
			||||||
 | 
					import type { RotaryEncoderDirection } from '../Peripherals.RotaryEncoderClient';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type IDigitalTubesHub = {
 | 
					export type IDigitalTubesHub = {
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@@ -60,6 +61,31 @@ export type IProgressHub = {
 | 
				
			|||||||
    getProgress(taskId: string): Promise<ProgressInfo>;
 | 
					    getProgress(taskId: string): Promise<ProgressInfo>;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type IRotaryEncoderHub = {
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    * @param enable Transpiled from bool
 | 
				
			||||||
 | 
					    * @returns Transpiled from System.Threading.Tasks.Task<bool>
 | 
				
			||||||
 | 
					    */
 | 
				
			||||||
 | 
					    setEnable(enable: boolean): Promise<boolean>;
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    * @param num Transpiled from int
 | 
				
			||||||
 | 
					    * @param direction Transpiled from Peripherals.RotaryEncoderClient.RotaryEncoderDirection
 | 
				
			||||||
 | 
					    * @returns Transpiled from System.Threading.Tasks.Task<bool>
 | 
				
			||||||
 | 
					    */
 | 
				
			||||||
 | 
					    rotateEncoderOnce(num: number, direction: RotaryEncoderDirection): Promise<boolean>;
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    * @param num Transpiled from int
 | 
				
			||||||
 | 
					    * @param direction Transpiled from Peripherals.RotaryEncoderClient.RotaryEncoderDirection
 | 
				
			||||||
 | 
					    * @param freq Transpiled from int
 | 
				
			||||||
 | 
					    * @returns Transpiled from System.Threading.Tasks.Task<bool>
 | 
				
			||||||
 | 
					    */
 | 
				
			||||||
 | 
					    enableCycleRotateEncoder(num: number, direction: RotaryEncoderDirection, freq: number): Promise<boolean>;
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    * @returns Transpiled from System.Threading.Tasks.Task<bool>
 | 
				
			||||||
 | 
					    */
 | 
				
			||||||
 | 
					    disableCycleRotateEncoder(): Promise<boolean>;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type IDigitalTubesReceiver = {
 | 
					export type IDigitalTubesReceiver = {
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
    * @param data Transpiled from byte[]
 | 
					    * @param data Transpiled from byte[]
 | 
				
			||||||
@@ -84,3 +110,12 @@ export type IProgressReceiver = {
 | 
				
			|||||||
    onReceiveProgress(message: ProgressInfo): Promise<void>;
 | 
					    onReceiveProgress(message: ProgressInfo): Promise<void>;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type IRotaryEncoderReceiver = {
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					    * @param num Transpiled from int
 | 
				
			||||||
 | 
					    * @param direction Transpiled from Peripherals.RotaryEncoderClient.RotaryEncoderDirection
 | 
				
			||||||
 | 
					    * @returns Transpiled from System.Threading.Tasks.Task
 | 
				
			||||||
 | 
					    */
 | 
				
			||||||
 | 
					    onReceiveRotate(num: number, direction: RotaryEncoderDirection): Promise<void>;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user