feat: 完成前后端旋转编码器的数字孪生
This commit is contained in:
parent
cbf85165b7
commit
4a55143b8e
|
@ -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();
|
||||||
|
|
|
@ -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}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,17 +1,39 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="ec11-container" :style="{
|
<div
|
||||||
|
class="inline-block select-none"
|
||||||
|
:style="{
|
||||||
width: width + 'px',
|
width: width + 'px',
|
||||||
height: height + 'px',
|
height: height + 'px',
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
}">
|
}"
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" :width="width" :height="height" viewBox="0 0 100 100"
|
>
|
||||||
class="ec11-encoder">
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
:width="width"
|
||||||
|
:height="height"
|
||||||
|
viewBox="0 0 100 100"
|
||||||
|
class="ec11-encoder"
|
||||||
|
>
|
||||||
<defs>
|
<defs>
|
||||||
<!-- 发光效果滤镜 -->
|
<!-- 发光效果滤镜 -->
|
||||||
<filter id="glow" x="-50%" y="-50%" width="200%" height="200%">
|
<filter id="glow" x="-50%" y="-50%" width="200%" height="200%">
|
||||||
<feFlood result="flood" flood-color="#00ff88" flood-opacity="1"></feFlood>
|
<feFlood
|
||||||
<feComposite in="flood" result="mask" in2="SourceGraphic" operator="in"></feComposite>
|
result="flood"
|
||||||
<feMorphology in="mask" result="dilated" operator="dilate" radius="1"></feMorphology>
|
flood-color="#00ff88"
|
||||||
|
flood-opacity="1"
|
||||||
|
></feFlood>
|
||||||
|
<feComposite
|
||||||
|
in="flood"
|
||||||
|
result="mask"
|
||||||
|
in2="SourceGraphic"
|
||||||
|
operator="in"
|
||||||
|
></feComposite>
|
||||||
|
<feMorphology
|
||||||
|
in="mask"
|
||||||
|
result="dilated"
|
||||||
|
operator="dilate"
|
||||||
|
radius="1"
|
||||||
|
></feMorphology>
|
||||||
<feGaussianBlur in="dilated" stdDeviation="2" result="blur1" />
|
<feGaussianBlur in="dilated" stdDeviation="2" result="blur1" />
|
||||||
<feGaussianBlur in="dilated" stdDeviation="4" result="blur2" />
|
<feGaussianBlur in="dilated" stdDeviation="4" result="blur2" />
|
||||||
<feGaussianBlur in="dilated" stdDeviation="8" result="blur3" />
|
<feGaussianBlur in="dilated" stdDeviation="8" result="blur3" />
|
||||||
|
@ -46,11 +68,27 @@
|
||||||
</defs>
|
</defs>
|
||||||
|
|
||||||
<!-- 编码器底座 -->
|
<!-- 编码器底座 -->
|
||||||
<rect x="10" y="30" width="80" height="60" rx="8" ry="8"
|
<rect
|
||||||
fill="#2a2a2a" stroke="#444444" stroke-width="1"/>
|
x="10"
|
||||||
|
y="30"
|
||||||
|
width="80"
|
||||||
|
height="60"
|
||||||
|
rx="8"
|
||||||
|
ry="8"
|
||||||
|
fill="#2a2a2a"
|
||||||
|
stroke="#444444"
|
||||||
|
stroke-width="1"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- 编码器主体外壳 -->
|
<!-- 编码器主体外壳 -->
|
||||||
<circle cx="50" cy="60" r="32" fill="url(#encoderGradient)" stroke="#555555" stroke-width="1"/>
|
<circle
|
||||||
|
cx="50"
|
||||||
|
cy="60"
|
||||||
|
r="32"
|
||||||
|
fill="url(#encoderGradient)"
|
||||||
|
stroke="#555555"
|
||||||
|
stroke-width="1"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- 编码器接线端子 -->
|
<!-- 编码器接线端子 -->
|
||||||
<rect x="5" y="75" width="4" height="8" fill="#c9c9c9" rx="1" />
|
<rect x="5" y="75" width="4" height="8" fill="#c9c9c9" rx="1" />
|
||||||
|
@ -60,175 +98,179 @@
|
||||||
<rect x="91" y="75" width="4" height="8" fill="#c9c9c9" rx="1" />
|
<rect x="91" y="75" width="4" height="8" fill="#c9c9c9" rx="1" />
|
||||||
|
|
||||||
<!-- 旋钮 -->
|
<!-- 旋钮 -->
|
||||||
<circle cx="50" cy="60" r="22"
|
<circle
|
||||||
|
cx="50"
|
||||||
|
cy="60"
|
||||||
|
r="22"
|
||||||
:fill="isPressed ? 'url(#knobPressedGradient)' : 'url(#knobGradient)'"
|
:fill="isPressed ? 'url(#knobPressedGradient)' : 'url(#knobGradient)'"
|
||||||
stroke="#666666" stroke-width="1"
|
stroke="#666666"
|
||||||
:transform="`rotate(${rotation/2} 50 60)`"
|
stroke-width="1"
|
||||||
|
:transform="`rotate(${rotationStep * 7.5} 50 60)`"
|
||||||
class="interactive"
|
class="interactive"
|
||||||
@mousedown="handleMouseDown"
|
@mousedown="handleMouseDown"
|
||||||
@mouseup="handlePress(false)"
|
@mouseup="handlePress(false)"
|
||||||
@mouseleave="handlePress(false)"/>
|
@mouseleave="handlePress(false)"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- 旋钮指示器 -->
|
<!-- 旋钮指示器 -->
|
||||||
<line x1="50" y1="42" x2="50" y2="48"
|
<line
|
||||||
stroke="#ffffff" stroke-width="2" stroke-linecap="round"
|
x1="50"
|
||||||
:transform="`rotate(${rotation} 50 60)`"/>
|
y1="42"
|
||||||
|
x2="50"
|
||||||
|
y2="48"
|
||||||
|
stroke="#ffffff"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
:transform="`rotate(${rotationStep * 15} 50 60)`"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- 旋钮上的纹理刻度 -->
|
<!-- 旋钮上的纹理刻度 -->
|
||||||
<g :transform="`rotate(${rotation} 50 60)`">
|
<g :transform="`rotate(${rotationStep * 15} 50 60)`">
|
||||||
<circle cx="50" cy="60" r="18" fill="none" stroke="#777777" stroke-width="0.5"/>
|
<circle
|
||||||
|
cx="50"
|
||||||
|
cy="60"
|
||||||
|
r="18"
|
||||||
|
fill="none"
|
||||||
|
stroke="#777777"
|
||||||
|
stroke-width="0.5"
|
||||||
|
/>
|
||||||
<!-- 刻度线 -->
|
<!-- 刻度线 -->
|
||||||
<g v-for="i in 16" :key="i">
|
<g v-for="i in 16" :key="i">
|
||||||
<line :x1="50 + 16 * Math.cos((i-1) * Math.PI / 8)"
|
<line
|
||||||
:y1="60 + 16 * Math.sin((i-1) * Math.PI / 8)"
|
:x1="50 + 16 * Math.cos(((i - 1) * Math.PI) / 8)"
|
||||||
:x2="50 + 18 * Math.cos((i-1) * Math.PI / 8)"
|
:y1="60 + 16 * Math.sin(((i - 1) * Math.PI) / 8)"
|
||||||
:y2="60 + 18 * Math.sin((i-1) * Math.PI / 8)"
|
:x2="50 + 18 * Math.cos(((i - 1) * Math.PI) / 8)"
|
||||||
stroke="#999999" stroke-width="0.5"/>
|
:y2="60 + 18 * Math.sin(((i - 1) * Math.PI) / 8)"
|
||||||
|
stroke="#999999"
|
||||||
|
stroke-width="0.5"
|
||||||
|
/>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<!-- 编码器编号标签 -->
|
<!-- 编码器编号标签 -->
|
||||||
<text x="50" y="15" text-anchor="middle" font-family="Arial" font-size="10"
|
<text
|
||||||
fill="#cccccc" font-weight="bold">
|
x="50"
|
||||||
|
y="15"
|
||||||
|
text-anchor="middle"
|
||||||
|
font-family="Arial"
|
||||||
|
font-size="10"
|
||||||
|
fill="#cccccc"
|
||||||
|
font-weight="bold"
|
||||||
|
>
|
||||||
EC11-{{ encoderNumber }}
|
EC11-{{ encoderNumber }}
|
||||||
</text>
|
</text>
|
||||||
|
|
||||||
<!-- 状态指示器 -->
|
<!-- 状态指示器 -->
|
||||||
<circle cx="85" cy="20" r="3" :fill="isPressed ? '#ff4444' : '#444444'"
|
<circle
|
||||||
|
cx="85"
|
||||||
|
cy="20"
|
||||||
|
r="3"
|
||||||
|
:fill="isPressed ? '#ff4444' : '#444444'"
|
||||||
:filter="isPressed ? 'url(#glow)' : ''"
|
:filter="isPressed ? 'url(#glow)' : ''"
|
||||||
stroke="#666666" stroke-width="0.5"/>
|
stroke="#666666"
|
||||||
|
stroke-width="0.5"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed } from 'vue';
|
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";
|
||||||
|
|
||||||
|
const rotataryEncoderStore = useRotaryEncoder();
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
size?: number;
|
size?: number;
|
||||||
|
enableDigitalTwin?: boolean;
|
||||||
encoderNumber?: number;
|
encoderNumber?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
size: 1,
|
size: 1,
|
||||||
encoderNumber: 1
|
enableDigitalTwin: false,
|
||||||
|
encoderNumber: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 组件状态
|
// 组件状态
|
||||||
const isPressed = ref(false);
|
const isPressed = ref(false);
|
||||||
const rotation = ref(0);
|
const rotationStep = ref(0); // 步进计数,1步=15度
|
||||||
|
|
||||||
|
// 拖动状态对象,增加 hasRotated 标记
|
||||||
|
const drag = ref<{
|
||||||
|
active: boolean;
|
||||||
|
startX: number;
|
||||||
|
hasRotated: boolean;
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
// 拖动状态
|
|
||||||
const isDragging = ref(false);
|
|
||||||
const dragStartX = ref(0);
|
|
||||||
const lastTriggerX = ref(0);
|
|
||||||
const dragThreshold = 20; // 每20像素触发一次旋转
|
const dragThreshold = 20; // 每20像素触发一次旋转
|
||||||
const hasRotated = ref(false); // 标记是否已经发生了旋转
|
|
||||||
|
|
||||||
// 计算宽高
|
// 计算宽高
|
||||||
const width = computed(() => 100 * props.size);
|
const width = computed(() => 100 * props.size);
|
||||||
const height = computed(() => 100 * props.size);
|
const height = computed(() => 100 * props.size);
|
||||||
|
|
||||||
// 定义发出的事件
|
|
||||||
const emit = defineEmits(['press', 'release', 'rotate-left', 'rotate-right']);
|
|
||||||
|
|
||||||
// 鼠标按下处理
|
// 鼠标按下处理
|
||||||
function handleMouseDown(event: MouseEvent) {
|
function handleMouseDown(event: MouseEvent) {
|
||||||
isDragging.value = true;
|
drag.value = { active: true, startX: event.clientX, hasRotated: false };
|
||||||
dragStartX.value = event.clientX;
|
window.addEventListener("mousemove", handleMouseMove);
|
||||||
lastTriggerX.value = event.clientX;
|
window.addEventListener("mouseup", handleMouseUp);
|
||||||
hasRotated.value = false; // 重置旋转标记
|
|
||||||
|
|
||||||
// 添加全局鼠标事件监听
|
|
||||||
document.addEventListener('mousemove', handleMouseMove);
|
|
||||||
document.addEventListener('mouseup', handleMouseUp);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 鼠标移动处理
|
// 鼠标移动处理
|
||||||
function handleMouseMove(event: MouseEvent) {
|
function handleMouseMove(event: MouseEvent) {
|
||||||
if (!isDragging.value) return;
|
if (!drag.value?.active) return;
|
||||||
|
const dx = event.clientX - drag.value.startX;
|
||||||
const currentX = event.clientX;
|
if (Math.abs(dx) >= dragThreshold) {
|
||||||
const deltaX = currentX - lastTriggerX.value;
|
rotationStep.value += dx > 0 ? 1 : -1;
|
||||||
|
drag.value.startX = event.clientX;
|
||||||
// 检查是否达到触发阈值
|
drag.value.hasRotated = true;
|
||||||
if (Math.abs(deltaX) >= dragThreshold) {
|
|
||||||
hasRotated.value = true; // 标记已经发生旋转
|
|
||||||
|
|
||||||
if (deltaX > 0) {
|
|
||||||
// 右拖动 - 右旋转
|
|
||||||
rotation.value += 15;
|
|
||||||
emit('rotate-right', {
|
|
||||||
encoderNumber: props.encoderNumber
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// 左拖动 - 左旋转
|
|
||||||
rotation.value -= 15;
|
|
||||||
emit('rotate-left', {
|
|
||||||
encoderNumber: props.encoderNumber
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新最后触发位置
|
|
||||||
lastTriggerX.value = currentX;
|
|
||||||
|
|
||||||
// 保持角度在0-360度范围内
|
|
||||||
rotation.value = rotation.value % 720;
|
|
||||||
if (rotation.value < 0) {
|
|
||||||
rotation.value += 720;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 鼠标松开处理
|
// 鼠标松开处理
|
||||||
function handleMouseUp() {
|
function handleMouseUp() {
|
||||||
isDragging.value = false;
|
if (drag.value && drag.value.active) {
|
||||||
|
// 仅在未发生旋转时才触发按压
|
||||||
// 只有在没有发生旋转的情况下才识别为按压事件
|
if (!drag.value.hasRotated) {
|
||||||
if (!hasRotated.value) {
|
isPressed.value = true;
|
||||||
// 触发按压和释放事件(模拟快速按压)
|
|
||||||
handlePress(true);
|
|
||||||
// 使用setTimeout来模拟按压和释放的时序
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
handlePress(false);
|
isPressed.value = false;
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// 移除全局事件监听
|
drag.value = null;
|
||||||
document.removeEventListener('mousemove', handleMouseMove);
|
window.removeEventListener("mousemove", handleMouseMove);
|
||||||
document.removeEventListener('mouseup', handleMouseUp);
|
window.removeEventListener("mouseup", handleMouseUp);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 按压处理
|
// 按压处理(用于鼠标离开和mouseup)
|
||||||
function handlePress(pressed: boolean) {
|
function handlePress(pressed: boolean) {
|
||||||
if (pressed !== isPressed.value) {
|
|
||||||
isPressed.value = pressed;
|
isPressed.value = pressed;
|
||||||
if (pressed) {
|
|
||||||
emit('press', { encoderNumber: props.encoderNumber });
|
|
||||||
} else {
|
|
||||||
emit('release', { encoderNumber: props.encoderNumber });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 暴露组件方法
|
watchEffect(() => {
|
||||||
defineExpose({
|
rotataryEncoderStore.setEnable(props.enableDigitalTwin);
|
||||||
press: () => handlePress(true),
|
|
||||||
release: () => handlePress(false),
|
|
||||||
rotateLeft: () => {
|
|
||||||
rotation.value -= 15;
|
|
||||||
emit('rotate-left', {
|
|
||||||
encoderNumber: props.encoderNumber
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => rotationStep.value,
|
||||||
|
(newStep, oldStep) => {
|
||||||
|
if (newStep > oldStep) {
|
||||||
|
rotataryEncoderStore.rotateOnce(
|
||||||
|
props.encoderNumber,
|
||||||
|
RotaryEncoderDirection.Clockwise,
|
||||||
|
);
|
||||||
|
} else if (newStep < oldStep) {
|
||||||
|
rotataryEncoderStore.rotateOnce(
|
||||||
|
props.encoderNumber,
|
||||||
|
RotaryEncoderDirection.CounterClockwise,
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
rotateRight: () => {
|
);
|
||||||
rotation.value += 15;
|
|
||||||
emit('rotate-right', {
|
|
||||||
encoderNumber: props.encoderNumber
|
|
||||||
});
|
|
||||||
},
|
|
||||||
isPressed: () => isPressed.value
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
@ -236,7 +278,8 @@ defineExpose({
|
||||||
export function getDefaultProps() {
|
export function getDefaultProps() {
|
||||||
return {
|
return {
|
||||||
size: 1,
|
size: 1,
|
||||||
encoderNumber: 1
|
enableDigitalTwin: false,
|
||||||
|
encoderNumber: 1,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -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}`, {
|
||||||
|
|
|
@ -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>;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue