mirror of
https://gitee.com/dashuaibran/jyker
synced 2025-09-26 18:59:11 +08:00
自制FOC 版本
This commit is contained in:
parent
11cb84a40b
commit
0bc17570d3
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
HMIcode/SmallProject/.vs/SmallProject/v17/.futdcache.v2
Normal file
BIN
HMIcode/SmallProject/.vs/SmallProject/v17/.futdcache.v2
Normal file
Binary file not shown.
55
HMIcode/SmallProject/OpenBLive/Client/BApiClient.cs
Normal file
55
HMIcode/SmallProject/OpenBLive/Client/BApiClient.cs
Normal file
@ -0,0 +1,55 @@
|
||||
using Newtonsoft.Json;
|
||||
using OpenBLive.Client.Data;
|
||||
using OpenBLive.Runtime;
|
||||
|
||||
namespace OpenBLive.Client
|
||||
{
|
||||
public class BApiClient : IBApiClient
|
||||
{
|
||||
/// <summary>
|
||||
/// 开启互动玩法
|
||||
/// </summary>
|
||||
/// <param name="code"></param>
|
||||
/// <param name="appId"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<AppStartInfo> StartInteractivePlay(string code, string appId)
|
||||
{
|
||||
var respStr = await BApi.StartInteractivePlay(code, appId);
|
||||
return JsonConvert.DeserializeObject<AppStartInfo>(respStr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 关闭应用
|
||||
/// </summary>
|
||||
/// <param name="appId">应用Id</param>
|
||||
/// <param name="gameId">开启应用 返回的gameId</param>
|
||||
/// <returns></returns>
|
||||
public async Task<EmptyInfo> EndInteractivePlay(string appId, string gameId)
|
||||
{
|
||||
var respStr = await BApi.EndInteractivePlay(appId, gameId);
|
||||
return JsonConvert.DeserializeObject<EmptyInfo>(respStr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量应用心跳
|
||||
/// </summary>
|
||||
/// <param name="gameIds">开启应用 返回的gameId</param>
|
||||
/// <returns></returns>
|
||||
public async Task<EmptyInfo> HeartBeatInteractivePlay(string gameId)
|
||||
{
|
||||
var respStr = await BApi.HeartBeatInteractivePlay(gameId);
|
||||
return JsonConvert.DeserializeObject<EmptyInfo>(respStr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量应用心跳
|
||||
/// </summary>
|
||||
/// <param name="gameIds">开启应用 返回的gameId</param>
|
||||
/// <returns></returns>
|
||||
public async Task<EmptyInfo> BatchHeartBeatInteractivePlay(string[] gameIds)
|
||||
{
|
||||
var respStr = await BApi.BatchHeartBeatInteractivePlay(gameIds);
|
||||
return JsonConvert.DeserializeObject<EmptyInfo>(respStr);
|
||||
}
|
||||
}
|
||||
}
|
110
HMIcode/SmallProject/OpenBLive/Client/Data/AppStartInfo.cs
Normal file
110
HMIcode/SmallProject/OpenBLive/Client/Data/AppStartInfo.cs
Normal file
@ -0,0 +1,110 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace OpenBLive.Client.Data
|
||||
{
|
||||
public class AppStartInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// 请求相应 非0为异常case 业务处理
|
||||
/// </summary>
|
||||
[JsonProperty("code")]
|
||||
public int Code;
|
||||
/// <summary>
|
||||
/// 异常case提示文案
|
||||
/// </summary>
|
||||
[JsonProperty("message")]
|
||||
public string Message;
|
||||
/// <summary>
|
||||
///响应体
|
||||
/// </summary>
|
||||
[JsonProperty("data")]
|
||||
public AppStartData Data { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取GameId
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string GetGameId() => Data?.GameInfo?.GameId;
|
||||
/// <summary>
|
||||
/// 获取长链地址
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IList<string> GetWssLink() => Data?.WebsocketInfo?.WssLink;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取长链地址
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string GetAuthBody() => Data?.WebsocketInfo?.AuthBody;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public class AppStartData
|
||||
{
|
||||
/// <summary>
|
||||
/// 场次信息
|
||||
/// </summary>
|
||||
[JsonProperty("game_info")]
|
||||
public AppStartGameInfo GameInfo;
|
||||
/// <summary>
|
||||
/// 长连信息
|
||||
/// </summary>
|
||||
[JsonProperty("websocket_info")]
|
||||
public AppStartWebsocketInfo WebsocketInfo;
|
||||
/// <summary>
|
||||
/// 主播信息
|
||||
/// </summary>
|
||||
[JsonProperty("anchor_info")]
|
||||
public AppStartAnchorInfo AnchorInfo;
|
||||
}
|
||||
|
||||
public class AppStartGameInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// 场次id,心跳key(心跳保持20s-60s)调用一次,超过60s无心跳自动关闭,长连停止推送消息
|
||||
/// </summary>
|
||||
[JsonProperty("game_id")]
|
||||
public string GameId;
|
||||
}
|
||||
public class AppStartWebsocketInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// 长连使用的请求json体 第三方无需关注内容,建立长连时使用即可
|
||||
/// </summary>
|
||||
[JsonProperty("auth_body")]
|
||||
public string AuthBody;
|
||||
/// <summary>
|
||||
/// wss 长连地址
|
||||
/// </summary>
|
||||
[JsonProperty("wss_link")]
|
||||
public List<string> WssLink;
|
||||
}
|
||||
|
||||
|
||||
public class AppStartAnchorInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// 主播房间号
|
||||
/// </summary>
|
||||
[JsonProperty("room_id")]
|
||||
public long RoomId;
|
||||
/// <summary>
|
||||
/// 主播昵称
|
||||
/// </summary>
|
||||
[JsonProperty("uname")]
|
||||
public string UName;
|
||||
/// <summary>
|
||||
/// 主播头像
|
||||
/// </summary>
|
||||
[JsonProperty("uface")]
|
||||
public string UFace;
|
||||
/// <summary>
|
||||
/// 主播Uid
|
||||
/// </summary>
|
||||
[JsonProperty("uid")]
|
||||
public string Uid;
|
||||
}
|
||||
}
|
19
HMIcode/SmallProject/OpenBLive/Client/Data/EmptyInfo.cs
Normal file
19
HMIcode/SmallProject/OpenBLive/Client/Data/EmptyInfo.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace OpenBLive.Client.Data
|
||||
{
|
||||
public class EmptyInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// 请求相应 非0为异常case 业务处理
|
||||
/// </summary>
|
||||
[JsonProperty("code")]
|
||||
public int Code;
|
||||
/// <summary>
|
||||
/// 异常case提示文案
|
||||
/// </summary>
|
||||
[JsonProperty("message")]
|
||||
public string Message;
|
||||
|
||||
}
|
||||
}
|
43
HMIcode/SmallProject/OpenBLive/Client/IBApiClient.cs
Normal file
43
HMIcode/SmallProject/OpenBLive/Client/IBApiClient.cs
Normal file
@ -0,0 +1,43 @@
|
||||
using OpenBLive.Client.Data;
|
||||
|
||||
namespace OpenBLive.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// BapiClient 相关文档 https://open-live.bilibili.com/document/doc&tool/auth.html
|
||||
/// </summary>
|
||||
public interface IBApiClient
|
||||
{
|
||||
/// <summary>
|
||||
/// 开启应用
|
||||
/// </summary>
|
||||
/// <param name="code">
|
||||
/// 身份码
|
||||
/// 获取地址 :https://link.bilibili.com/p/center/index#/my-room/start-live
|
||||
/// </param>
|
||||
/// <param name="appId">
|
||||
/// AppId
|
||||
/// 申请的应用Id
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
Task<AppStartInfo> StartInteractivePlay(string code, string appId);
|
||||
/// <summary>
|
||||
/// 关闭应用
|
||||
/// </summary>
|
||||
/// <param name="appId">应用Id</param>
|
||||
/// <param name="gameId">开启玩法 返回的gameId</param>
|
||||
/// <returns></returns>
|
||||
Task<EmptyInfo> EndInteractivePlay(string appId, string gameId);
|
||||
/// <summary>
|
||||
/// 应用心跳
|
||||
/// </summary>
|
||||
/// <param name="gameId">开启玩法 返回的gameId</param>
|
||||
/// <returns></returns>
|
||||
Task<EmptyInfo> HeartBeatInteractivePlay(string gameId);
|
||||
/// <summary>
|
||||
/// 批量应用心跳
|
||||
/// </summary>
|
||||
/// <param name="gameIds">开启玩法 返回的gameId</param>
|
||||
/// <returns></returns>
|
||||
Task<EmptyInfo> BatchHeartBeatInteractivePlay(string[] gameIds);
|
||||
}
|
||||
}
|
23
HMIcode/SmallProject/OpenBLive/OpenBLive.csproj
Normal file
23
HMIcode/SmallProject/OpenBLive/OpenBLive.csproj
Normal file
@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<NoWarn>1701;1702;CS8618;CS8625;8603;8600;8601</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<NoWarn>1701;1702;CS8618;CS8625;8603;8600;8601</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="Websocket.Client" Version="4.4.43" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
159
HMIcode/SmallProject/OpenBLive/Runtime/BApi.cs
Normal file
159
HMIcode/SmallProject/OpenBLive/Runtime/BApi.cs
Normal file
@ -0,0 +1,159 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OpenBLive.Runtime.Data;
|
||||
using OpenBLive.Runtime.Utilities;
|
||||
using Logger = OpenBLive.Runtime.Utilities.Logger;
|
||||
#if NET5_0_OR_GREATER
|
||||
using System.Net;
|
||||
#elif UNITY_2020_3_OR_NEWER
|
||||
using UnityEngine.Networking;
|
||||
#endif
|
||||
|
||||
namespace OpenBLive.Runtime
|
||||
{
|
||||
/// <summary>
|
||||
/// 各类b站api
|
||||
/// </summary>
|
||||
public static class BApi
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否为测试环境的api
|
||||
/// </summary>
|
||||
public static bool isTestEnv;
|
||||
|
||||
/// <summary>
|
||||
/// 开放平台域名
|
||||
/// </summary>
|
||||
private static string OpenLiveDomain =>
|
||||
isTestEnv ? "http://test-live-open.biliapi.net" : "https://live-open.biliapi.com";
|
||||
|
||||
/// <summary>
|
||||
/// 应用开启
|
||||
/// </summary>
|
||||
private const string k_InteractivePlayStart = "/v2/app/start";
|
||||
|
||||
/// <summary>
|
||||
/// 应用关闭
|
||||
/// </summary>
|
||||
private const string k_InteractivePlayEnd = "/v2/app/end";
|
||||
|
||||
/// <summary>
|
||||
/// 应用心跳
|
||||
/// </summary>
|
||||
private const string k_InteractivePlayHeartBeat = "/v2/app/heartbeat";
|
||||
|
||||
/// <summary>
|
||||
/// 应用批量心跳
|
||||
/// </summary>
|
||||
private const string k_InteractivePlayBatchHeartBeat = "/v2/app/batchHeartbeat";
|
||||
|
||||
|
||||
private const string k_Post = "POST";
|
||||
|
||||
|
||||
|
||||
public static async Task<string> StartInteractivePlay(string code, string appId)
|
||||
{
|
||||
var postUrl = OpenLiveDomain + k_InteractivePlayStart;
|
||||
var param = $"{{\"code\":\"{code}\",\"app_id\":{appId}}}";
|
||||
|
||||
var result = await RequestWebUTF8(postUrl, k_Post, param);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static async Task<string> EndInteractivePlay(string appId, string gameId)
|
||||
{
|
||||
var postUrl = OpenLiveDomain + k_InteractivePlayEnd;
|
||||
var param = $"{{\"app_id\":{appId},\"game_id\":\"{gameId}\"}}";
|
||||
|
||||
var result = await RequestWebUTF8(postUrl, k_Post, param);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static async Task<string> HeartBeatInteractivePlay(string gameId)
|
||||
{
|
||||
var postUrl = OpenLiveDomain + k_InteractivePlayHeartBeat;
|
||||
string param = "";
|
||||
if (gameId != null)
|
||||
{
|
||||
param = $"{{\"game_id\":\"{gameId}\"}}";
|
||||
|
||||
}
|
||||
|
||||
var result = await RequestWebUTF8(postUrl, k_Post, param);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static async Task<string> BatchHeartBeatInteractivePlay(string[] gameIds)
|
||||
{
|
||||
var postUrl = OpenLiveDomain + k_InteractivePlayBatchHeartBeat;
|
||||
GameIds games = new GameIds()
|
||||
{
|
||||
gameIds = gameIds
|
||||
};
|
||||
var param = JsonConvert.SerializeObject(games);
|
||||
var result = await RequestWebUTF8(postUrl, k_Post, param);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static async Task<string> RequestWebUTF8(string url, string method, string param,
|
||||
string cookie = null)
|
||||
{
|
||||
#if NET5_0_OR_GREATER
|
||||
string result = "";
|
||||
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
|
||||
req.Method = method;
|
||||
|
||||
if (param != null)
|
||||
{
|
||||
SignUtility.SetReqHeader(req, param, cookie);
|
||||
}
|
||||
|
||||
HttpWebResponse httpResponse = (HttpWebResponse)(await req.GetResponseAsync());
|
||||
Stream stream = httpResponse.GetResponseStream();
|
||||
|
||||
if (stream != null)
|
||||
{
|
||||
using StreamReader reader = new StreamReader(stream, Encoding.UTF8);
|
||||
result = await reader.ReadToEndAsync();
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
#elif UNITY_2020_3_OR_NEWER
|
||||
UnityWebRequest webRequest = new UnityWebRequest(url);
|
||||
webRequest.method = method;
|
||||
if (param != null)
|
||||
{
|
||||
SignUtility.SetReqHeader(webRequest, param, cookie);
|
||||
}
|
||||
|
||||
webRequest.downloadHandler = new DownloadHandlerBuffer();
|
||||
webRequest.disposeUploadHandlerOnDispose = true;
|
||||
webRequest.disposeDownloadHandlerOnDispose = true;
|
||||
await webRequest.SendWebRequest();
|
||||
var text = webRequest.downloadHandler.text;
|
||||
|
||||
webRequest.Dispose();
|
||||
return text;
|
||||
#endif
|
||||
}
|
||||
#if UNITY_2020_3_OR_NEWER
|
||||
private static TaskAwaiter GetAwaiter(this UnityEngine.AsyncOperation asyncOp)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<object>();
|
||||
asyncOp.completed += _ => { tcs.SetResult(null); };
|
||||
return ((Task) tcs.Task).GetAwaiter();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
233
HMIcode/SmallProject/OpenBLive/Runtime/BLiveClient.cs
Normal file
233
HMIcode/SmallProject/OpenBLive/Runtime/BLiveClient.cs
Normal file
@ -0,0 +1,233 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OpenBLive.Runtime.Data;
|
||||
using OpenBLive.Runtime.Utilities;
|
||||
|
||||
namespace OpenBLive.Runtime
|
||||
{
|
||||
public delegate void ReceiveDanmakuEvent(Dm dm);
|
||||
|
||||
public delegate void ReceiveGiftEvent(SendGift sendGift);
|
||||
|
||||
public delegate void ReceiveGuardBuyEvent(Guard guard);
|
||||
|
||||
public delegate void ReceiveSuperChatEvent(SuperChat e);
|
||||
|
||||
public delegate void ReceiveSuperChatDelEvent(SuperChatDel e);
|
||||
|
||||
public delegate void ReceiveLikeEvent(Like like);
|
||||
|
||||
public delegate void ReceiveEnterEvent(Enter enter);
|
||||
|
||||
public delegate void ReceiveLiveStartEvent(LiveStart liveStart);
|
||||
|
||||
public delegate void ReceiveLiveEndEvent(LiveEnd liveEnd);
|
||||
|
||||
public delegate void ReceiveRawNotice(string raw, JObject jObject);
|
||||
|
||||
public abstract class BLiveClient : IDisposable
|
||||
{
|
||||
private Timer m_Timer;
|
||||
protected string token;
|
||||
|
||||
/// <summary>
|
||||
/// 弹幕数据
|
||||
/// </summary>
|
||||
public event ReceiveDanmakuEvent OnDanmaku;
|
||||
|
||||
/// <summary>
|
||||
/// 赠送礼物
|
||||
/// </summary>
|
||||
public event ReceiveGiftEvent OnGift;
|
||||
|
||||
public event ReceiveGuardBuyEvent OnGuardBuy;
|
||||
|
||||
/// <summary>
|
||||
/// SC赠送
|
||||
/// </summary>
|
||||
public event ReceiveSuperChatEvent OnSuperChat;
|
||||
|
||||
/// <summary>
|
||||
/// SC删除
|
||||
/// </summary>
|
||||
public event ReceiveSuperChatDelEvent OnSuperChatDel;
|
||||
|
||||
/// <summary>
|
||||
/// 点赞信息
|
||||
/// </summary>
|
||||
public event ReceiveLikeEvent OnLike;
|
||||
|
||||
/// <summary>
|
||||
/// 进入房间
|
||||
/// </summary>
|
||||
public event ReceiveEnterEvent OnEnter;
|
||||
|
||||
/// <summary>
|
||||
/// 开始直播
|
||||
/// </summary>
|
||||
public event ReceiveLiveStartEvent OnLiveStart;
|
||||
|
||||
/// <summary>
|
||||
/// 停止直播
|
||||
/// </summary>
|
||||
public event ReceiveLiveEndEvent OnLiveEnd;
|
||||
|
||||
/// <summary>
|
||||
/// 原始数据包
|
||||
/// </summary>
|
||||
public event ReceiveRawNotice ReceiveNotice;
|
||||
|
||||
/// <summary>
|
||||
/// 更新人气
|
||||
/// </summary>
|
||||
public event EventHandler<int> UpdatePopularity;
|
||||
|
||||
public event EventHandler Open;
|
||||
public abstract void Connect();
|
||||
/// <summary>
|
||||
/// 带有重连
|
||||
/// </summary>
|
||||
public abstract void Connect(TimeSpan timeout);
|
||||
public abstract void Disconnect();
|
||||
public abstract void Dispose();
|
||||
public abstract void Send(byte[] packet);
|
||||
public abstract Task SendAsync(byte[] packet);
|
||||
public abstract void Send(Packet packet);
|
||||
protected abstract Task SendAsync(Packet packet);
|
||||
|
||||
protected virtual void OnOpen()
|
||||
{
|
||||
SendAsync(Packet.Authority(token));
|
||||
|
||||
m_Timer?.Dispose();
|
||||
m_Timer = new Timer((e) => (
|
||||
(BLiveClient)e)?.SendAsync(Packet.HeartBeat()), this, 0, 30 * 1000);
|
||||
}
|
||||
#if UNITY_2021_2_OR_NEWER || NET5_0_OR_GREATER
|
||||
protected void ProcessPacket(ReadOnlySpan<byte> bytes) =>
|
||||
ProcessPacketAsync(new Packet(bytes));
|
||||
#else
|
||||
protected void ProcessPacket(byte[] bytes) =>
|
||||
ProcessPacketAsync(new Packet(bytes));
|
||||
#endif
|
||||
|
||||
|
||||
private void ProcessPacketAsync(Packet packet)
|
||||
{
|
||||
var header = packet.Header;
|
||||
switch (header.ProtocolVersion)
|
||||
{
|
||||
case ProtocolVersion.UnCompressed:
|
||||
case ProtocolVersion.HeartBeat:
|
||||
break;
|
||||
case ProtocolVersion.Zlib:
|
||||
//no Zlib compress in OpenBLive wss
|
||||
//await foreach (var packet1 in ZlibDeCompressAsync(packet.PacketBody))
|
||||
//ProcessPacketAsync(packet1);
|
||||
return;
|
||||
case ProtocolVersion.Brotli:
|
||||
//no Brotli compress in OpenBLive wss
|
||||
//await foreach (var packet1 in BrotliDecompressAsync(packet.PacketBody))
|
||||
//ProcessPacketAsync(packet1);
|
||||
return;
|
||||
default:
|
||||
throw new NotSupportedException(
|
||||
"New bilibili danmaku protocol appears, please contact the author if you see this Exception.");
|
||||
}
|
||||
|
||||
switch (header.Operation)
|
||||
{
|
||||
case Operation.AuthorityResponse:
|
||||
Open?.Invoke(this, null);
|
||||
break;
|
||||
case Operation.HeartBeatResponse:
|
||||
Array.Reverse(packet.PacketBody);
|
||||
|
||||
#if UNITY_2021_2_OR_NEWER || NET5_0_OR_GREATER
|
||||
var popularity = BitConverter.ToInt32(packet.PacketBody);
|
||||
#else
|
||||
var popularity = BitConverter.ToInt32(packet.PacketBody,0);
|
||||
#endif
|
||||
|
||||
UpdatePopularity?.Invoke(this, popularity);
|
||||
break;
|
||||
case Operation.ServerNotify:
|
||||
try
|
||||
{
|
||||
ProcessNotice(Encoding.UTF8.GetString(packet.PacketBody));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
break;
|
||||
// HeartBeat packet request, only send by client
|
||||
case Operation.HeartBeat:
|
||||
// This operation key only used for sending authority packet by client
|
||||
case Operation.Authority:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void ProcessNotice(string rawMessage)
|
||||
{
|
||||
var json = JObject.Parse(rawMessage);
|
||||
ReceiveNotice?.Invoke(rawMessage, json);
|
||||
var data = json["data"]?.ToString();
|
||||
if (String.IsNullOrWhiteSpace(data))
|
||||
return;
|
||||
Logger.Log($"收到长连发来数据:\n{json?.ToString()}");
|
||||
try
|
||||
{
|
||||
switch (json["cmd"]?.ToString())
|
||||
{
|
||||
case "LIVE_OPEN_PLATFORM_DM":
|
||||
var dm = JsonConvert.DeserializeObject<Dm>(data);
|
||||
OnDanmaku?.Invoke(dm);
|
||||
break;
|
||||
case "LIVE_OPEN_PLATFORM_SUPER_CHAT":
|
||||
var superChat = JsonConvert.DeserializeObject<SuperChat>(data);
|
||||
OnSuperChat?.Invoke(superChat);
|
||||
break;
|
||||
case "LIVE_OPEN_PLATFORM_SUPER_CHAT_DEL":
|
||||
var superChatDel = JsonConvert.DeserializeObject<SuperChatDel>(data);
|
||||
OnSuperChatDel?.Invoke(superChatDel);
|
||||
break;
|
||||
case "LIVE_OPEN_PLATFORM_SEND_GIFT":
|
||||
var gift = JsonConvert.DeserializeObject<SendGift>(data);
|
||||
OnGift?.Invoke(gift);
|
||||
break;
|
||||
case "LIVE_OPEN_PLATFORM_GUARD":
|
||||
var guard = JsonConvert.DeserializeObject<Guard>(data);
|
||||
OnGuardBuy?.Invoke(guard);
|
||||
break;
|
||||
case "LIVE_OPEN_PLATFORM_LIKE":
|
||||
var like = JsonConvert.DeserializeObject<Like>(data);
|
||||
OnLike?.Invoke(like);
|
||||
break;
|
||||
case "LIVE_OPEN_PLATFORM_LIVE_ROOM_ENTER":
|
||||
var enter = JsonConvert.DeserializeObject<Enter>(data);
|
||||
OnEnter?.Invoke(enter);
|
||||
break;
|
||||
case "LIVE_OPEN_PLATFORM_LIVE_START":
|
||||
var live_start = JsonConvert.DeserializeObject<LiveStart>(data);
|
||||
OnLiveStart?.Invoke(live_start);
|
||||
break;
|
||||
case "LIVE_OPEN_PLATFORM_LIVE_END":
|
||||
var live_end = JsonConvert.DeserializeObject<LiveEnd>(data);
|
||||
OnLiveEnd?.Invoke(live_end);
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Utilities.Logger.LogError("json数据解析异常 rawMessage: " + rawMessage + e.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
32
HMIcode/SmallProject/OpenBLive/Runtime/Data/AnchorInfo.cs
Normal file
32
HMIcode/SmallProject/OpenBLive/Runtime/Data/AnchorInfo.cs
Normal file
@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace OpenBLive.Runtime.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// 礼物数据中的主播数据 https://open-live.bilibili.com/document/f9ce25be-312e-1f4a-85fd-fef21f1637f8
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public struct AnchorInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// 主播UID(即将废弃)
|
||||
/// </summary>
|
||||
[JsonProperty("uid")] public long uid;
|
||||
|
||||
/// <summary>
|
||||
/// 主播open_id
|
||||
/// </summary>
|
||||
[JsonProperty("open_id")] public string open_id;
|
||||
|
||||
/// <summary>
|
||||
/// 收礼主播昵称
|
||||
/// </summary>
|
||||
[JsonProperty("uname")] public string userName;
|
||||
|
||||
/// <summary>
|
||||
/// 收礼主播头像
|
||||
/// </summary>
|
||||
[JsonProperty("uface")] public string userFace;
|
||||
}
|
||||
}
|
30
HMIcode/SmallProject/OpenBLive/Runtime/Data/BToken.cs
Normal file
30
HMIcode/SmallProject/OpenBLive/Runtime/Data/BToken.cs
Normal file
@ -0,0 +1,30 @@
|
||||
namespace OpenBLive.Runtime.Data
|
||||
{
|
||||
public struct BToken
|
||||
{
|
||||
/// <summary>
|
||||
/// -1=non oauthKey
|
||||
/// -2=oauthKey not marching
|
||||
/// -4=未扫码
|
||||
/// -5=已扫码
|
||||
/// 0=登录成功
|
||||
/// </summary>
|
||||
public int code;
|
||||
/// <summary>
|
||||
/// 登录用户的uid
|
||||
/// </summary>
|
||||
public long dedeUserID;
|
||||
/// <summary>
|
||||
/// 用户md5key
|
||||
/// </summary>
|
||||
public string dedeUserIDCkMd5;
|
||||
/// <summary>
|
||||
/// accesskey 或 浏览器中的cookie
|
||||
/// </summary>
|
||||
public string sessData;
|
||||
/// <summary>
|
||||
/// 登录cookie
|
||||
/// </summary>
|
||||
public string biliJct;
|
||||
}
|
||||
}
|
69
HMIcode/SmallProject/OpenBLive/Runtime/Data/Dm.cs
Normal file
69
HMIcode/SmallProject/OpenBLive/Runtime/Data/Dm.cs
Normal file
@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace OpenBLive.Runtime.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// 弹幕数据 https://open-live.bilibili.com/document/f9ce25be-312e-1f4a-85fd-fef21f1637f8
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public struct Dm
|
||||
{
|
||||
/// <summary>
|
||||
/// 用户UID(即将废弃)
|
||||
/// </summary>
|
||||
[JsonProperty("uid")] public long uid;
|
||||
|
||||
/// <summary>
|
||||
/// 用户open_id
|
||||
/// </summary>
|
||||
[JsonProperty("open_id")] public string open_id;
|
||||
|
||||
/// <summary>
|
||||
/// 用户昵称
|
||||
/// </summary>
|
||||
[JsonProperty("uname")] public string userName;
|
||||
|
||||
/// <summary>
|
||||
/// 用户头像
|
||||
/// </summary>
|
||||
[JsonProperty("uface")] public string userFace;
|
||||
|
||||
/// <summary>
|
||||
/// 弹幕发送时间秒级时间戳
|
||||
/// </summary>
|
||||
[JsonProperty("timestamp")] public long timestamp;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 弹幕内容
|
||||
/// </summary>
|
||||
[JsonProperty("msg")] public string msg;
|
||||
|
||||
/// <summary>
|
||||
/// 粉丝勋章等级
|
||||
/// </summary>
|
||||
[JsonProperty("fans_medal_level")] public long fansMedalLevel;
|
||||
|
||||
/// <summary>
|
||||
/// 粉丝勋章名
|
||||
/// </summary>
|
||||
[JsonProperty("fans_medal_name")] public string fansMedalName;
|
||||
|
||||
/// <summary>
|
||||
/// 佩戴的粉丝勋章佩戴状态
|
||||
/// </summary>
|
||||
[JsonProperty("fans_medal_wearing_status")]
|
||||
public bool fansMedalWearingStatus;
|
||||
|
||||
/// <summary>
|
||||
/// 大航海等级
|
||||
/// </summary>
|
||||
[JsonProperty("guard_level")] public long guardLevel;
|
||||
|
||||
/// <summary>
|
||||
/// 弹幕接收的直播间
|
||||
/// </summary>
|
||||
[JsonProperty("room_id")] public long roomId;
|
||||
}
|
||||
}
|
37
HMIcode/SmallProject/OpenBLive/Runtime/Data/Enter.cs
Normal file
37
HMIcode/SmallProject/OpenBLive/Runtime/Data/Enter.cs
Normal file
@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace OpenBLive.Runtime.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// 进入房间数据 https://open-live.bilibili.com/document/f9ce25be-312e-1f4a-85fd-fef21f1637f8
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public struct Enter
|
||||
{
|
||||
/// <summary>
|
||||
/// 用户昵称
|
||||
/// </summary>
|
||||
[JsonProperty("uname")] public string uname;
|
||||
|
||||
/// <summary>
|
||||
/// 用户唯一标识
|
||||
/// </summary>
|
||||
[JsonProperty("open_id")] public string open_id;
|
||||
|
||||
/// <summary>
|
||||
/// 用户头像
|
||||
/// </summary>
|
||||
[JsonProperty("uface")] public string uface;
|
||||
|
||||
/// <summary>
|
||||
/// 时间秒级时间戳
|
||||
/// </summary>
|
||||
[JsonProperty("timestamp")] public long timestamp;
|
||||
|
||||
/// <summary>
|
||||
/// 发生的直播间
|
||||
/// </summary>
|
||||
[JsonProperty("room_id")] public long room_id;
|
||||
}
|
||||
}
|
16
HMIcode/SmallProject/OpenBLive/Runtime/Data/GameIds.cs
Normal file
16
HMIcode/SmallProject/OpenBLive/Runtime/Data/GameIds.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
namespace OpenBLive.Runtime.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// 互动玩法心跳 https://open-live.bilibili.com/doc/2/1/3
|
||||
/// </summary>
|
||||
public struct GameIds
|
||||
{
|
||||
/// <summary>
|
||||
/// 玩法场次
|
||||
/// </summary>
|
||||
[JsonProperty("game_ids")]
|
||||
public string[] gameIds;
|
||||
}
|
||||
}
|
53
HMIcode/SmallProject/OpenBLive/Runtime/Data/Guard.cs
Normal file
53
HMIcode/SmallProject/OpenBLive/Runtime/Data/Guard.cs
Normal file
@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace OpenBLive.Runtime.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// 大航海数据 https://open-live.bilibili.com/document/f9ce25be-312e-1f4a-85fd-fef21f1637f8
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public struct Guard
|
||||
{
|
||||
/// <summary>
|
||||
/// 大航海等级
|
||||
/// </summary>r
|
||||
[JsonProperty("guard_level")] public long guardLevel;
|
||||
|
||||
/// <summary>
|
||||
/// 大航海数量
|
||||
/// </summary>
|
||||
[JsonProperty("guard_num")] public long guardNum;
|
||||
|
||||
/// <summary>
|
||||
/// 大航海单位(正常单位为“月”,如为其他内容,无视guard_num以本字段内容为准,例如*3天)
|
||||
/// </summary>
|
||||
[JsonProperty("guard_unit")] public string guardUnit;
|
||||
|
||||
/// <summary>
|
||||
/// 粉丝勋章等级
|
||||
/// </summary>
|
||||
[JsonProperty("fans_medal_level")] public long fansMedalLevel;
|
||||
|
||||
/// <summary>
|
||||
/// 粉丝勋章名
|
||||
/// </summary>
|
||||
[JsonProperty("fans_medal_name")] public string fansMedalName;
|
||||
|
||||
/// <summary>
|
||||
/// 佩戴的粉丝勋章佩戴状态
|
||||
/// </summary>
|
||||
[JsonProperty("fans_medal_wearing_status")]
|
||||
public bool fansMedalWearingStatus;
|
||||
|
||||
/// <summary>
|
||||
/// 赠送大航海的用户数据
|
||||
/// </summary>
|
||||
[JsonProperty("user_info")] public UserInfo userInfo;
|
||||
|
||||
/// <summary>
|
||||
/// 房间号
|
||||
/// </summary>
|
||||
[JsonProperty("room_id")] public long roomID;
|
||||
}
|
||||
}
|
67
HMIcode/SmallProject/OpenBLive/Runtime/Data/Like.cs
Normal file
67
HMIcode/SmallProject/OpenBLive/Runtime/Data/Like.cs
Normal file
@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace OpenBLive.Runtime.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// 点赞数据 https://open-live.bilibili.com/document/f9ce25be-312e-1f4a-85fd-fef21f1637f8
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public struct Like
|
||||
{
|
||||
/// <summary>
|
||||
/// 用户昵称
|
||||
/// </summary>
|
||||
[JsonProperty("uname")] public string uname;
|
||||
|
||||
/// <summary>
|
||||
/// 用户UID(即将废弃)
|
||||
/// </summary>
|
||||
[JsonProperty("uid")] public long uid;
|
||||
|
||||
/// <summary>
|
||||
/// 用户唯一标识(2024-03-11后上线)
|
||||
/// </summary>
|
||||
[JsonProperty("open_id")] public string open_id;
|
||||
|
||||
/// <summary>
|
||||
/// 用户头像
|
||||
/// </summary>
|
||||
[JsonProperty("uface")] public string uface;
|
||||
|
||||
/// <summary>
|
||||
/// 时间秒级时间戳
|
||||
/// </summary>
|
||||
[JsonProperty("timestamp")] public long timestamp;
|
||||
|
||||
/// <summary>
|
||||
/// 发生的直播间
|
||||
/// </summary>
|
||||
[JsonProperty("room_id")] public long room_id;
|
||||
|
||||
/// <summary>
|
||||
/// 点赞文案
|
||||
/// </summary>
|
||||
[JsonProperty("like_text")] public string like_text;
|
||||
|
||||
/// <summary>
|
||||
/// 对单个用户最近2秒的点赞次数聚合
|
||||
/// </summary>
|
||||
[JsonProperty("like_count")] public long unamelike_count;
|
||||
|
||||
/// <summary>
|
||||
/// 该房间粉丝勋章佩戴情况
|
||||
/// </summary>
|
||||
[JsonProperty("fans_medal_wearing_status")] public bool fans_medal_wearing_status;
|
||||
|
||||
/// <summary>
|
||||
/// 粉丝勋章名
|
||||
/// </summary>
|
||||
[JsonProperty("fans_medal_name")] public string fans_medal_name;
|
||||
|
||||
/// <summary>
|
||||
/// 对应房间勋章信息
|
||||
/// </summary>
|
||||
[JsonProperty("fans_medal_level")] public long fans_medal_level;
|
||||
}
|
||||
}
|
38
HMIcode/SmallProject/OpenBLive/Runtime/Data/LiveEnd.cs
Normal file
38
HMIcode/SmallProject/OpenBLive/Runtime/Data/LiveEnd.cs
Normal file
@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace OpenBLive.Runtime.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// 结束直播数据 https://open-live.bilibili.com/document/f9ce25be-312e-1f4a-85fd-fef21f1637f8
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public struct LiveEnd
|
||||
{
|
||||
/// <summary>
|
||||
/// 用户唯一标识
|
||||
/// </summary>
|
||||
[JsonProperty("open_id")] public string open_id;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 时间秒级时间戳
|
||||
/// </summary>
|
||||
[JsonProperty("timestamp")] public long timestamp;
|
||||
|
||||
/// <summary>
|
||||
/// 发生的直播间
|
||||
/// </summary>
|
||||
[JsonProperty("room_id")] public long room_id;
|
||||
|
||||
/// <summary>
|
||||
/// 开播时的标题
|
||||
/// </summary>
|
||||
[JsonProperty("title")] public string title;
|
||||
|
||||
/// <summary>
|
||||
/// 开播的分区ID
|
||||
/// </summary>
|
||||
[JsonProperty("area_id")] public long area_id;
|
||||
}
|
||||
}
|
38
HMIcode/SmallProject/OpenBLive/Runtime/Data/LiveStart.cs
Normal file
38
HMIcode/SmallProject/OpenBLive/Runtime/Data/LiveStart.cs
Normal file
@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace OpenBLive.Runtime.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// 开始直播数据 https://open-live.bilibili.com/document/f9ce25be-312e-1f4a-85fd-fef21f1637f8
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public struct LiveStart
|
||||
{
|
||||
/// <summary>
|
||||
/// 用户唯一标识
|
||||
/// </summary>
|
||||
[JsonProperty("open_id")] public string open_id;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 时间秒级时间戳
|
||||
/// </summary>
|
||||
[JsonProperty("timestamp")] public long timestamp;
|
||||
|
||||
/// <summary>
|
||||
/// 发生的直播间
|
||||
/// </summary>
|
||||
[JsonProperty("room_id")] public long room_id;
|
||||
|
||||
/// <summary>
|
||||
/// 开播时的标题
|
||||
/// </summary>
|
||||
[JsonProperty("title")] public string title;
|
||||
|
||||
/// <summary>
|
||||
/// 开播的分区ID
|
||||
/// </summary>
|
||||
[JsonProperty("area_id")] public long area_id;
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace OpenBLive.Runtime.Data
|
||||
{
|
||||
public struct LoginStatusData
|
||||
{
|
||||
[JsonProperty("url")]
|
||||
public string Url { get; set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace OpenBLive.Runtime.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// 登录成功
|
||||
/// </summary>
|
||||
public struct LoginStatusReady
|
||||
{
|
||||
[JsonProperty("code")] public int code;
|
||||
[JsonProperty("status")] public bool status;
|
||||
[JsonProperty("data")] public LoginStatusData data;
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace OpenBLive.Runtime.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// 扫码中未登录
|
||||
/// </summary>
|
||||
public struct LoginStatusScanning
|
||||
{
|
||||
[JsonProperty("status")] public bool status;
|
||||
[JsonProperty("data")] public int data;
|
||||
[JsonProperty("message")] public string message;
|
||||
}
|
||||
}
|
12
HMIcode/SmallProject/OpenBLive/Runtime/Data/LoginUrl.cs
Normal file
12
HMIcode/SmallProject/OpenBLive/Runtime/Data/LoginUrl.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace OpenBLive.Runtime.Data
|
||||
{
|
||||
public struct LoginUrl
|
||||
{
|
||||
[JsonProperty("data")]
|
||||
public LoginUrlData data;
|
||||
[JsonProperty("status")]
|
||||
public bool status;
|
||||
}
|
||||
}
|
11
HMIcode/SmallProject/OpenBLive/Runtime/Data/LoginUrlData.cs
Normal file
11
HMIcode/SmallProject/OpenBLive/Runtime/Data/LoginUrlData.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using Newtonsoft.Json;
|
||||
namespace OpenBLive.Runtime.Data
|
||||
{
|
||||
public struct LoginUrlData
|
||||
{
|
||||
[JsonProperty("oauthKey")]
|
||||
public string oauthKey;
|
||||
[JsonProperty("url")]
|
||||
public string url;
|
||||
}
|
||||
}
|
138
HMIcode/SmallProject/OpenBLive/Runtime/Data/Packet.cs
Normal file
138
HMIcode/SmallProject/OpenBLive/Runtime/Data/Packet.cs
Normal file
@ -0,0 +1,138 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
#if UNITY_2021_2_OR_NEWER || NET5_0_OR_GREATER
|
||||
using System.Buffers;
|
||||
#else
|
||||
using System.Net;
|
||||
#endif
|
||||
|
||||
|
||||
namespace OpenBLive.Runtime.Data
|
||||
{
|
||||
public struct Packet
|
||||
{
|
||||
private static readonly Packet s_NoBodyHeartBeatPacket = new Packet()
|
||||
{
|
||||
Header = new PacketHeader()
|
||||
{
|
||||
HeaderLength = PacketHeader.KPacketHeaderLength,
|
||||
SequenceId = 1,
|
||||
ProtocolVersion = ProtocolVersion.HeartBeat,
|
||||
Operation = Operation.HeartBeat
|
||||
}
|
||||
};
|
||||
|
||||
public PacketHeader Header;
|
||||
|
||||
public int Length => Header.PacketLength;
|
||||
|
||||
public byte[] PacketBody;
|
||||
#if UNITY_2021_2_OR_NEWER || NET5_0_OR_GREATER
|
||||
public Packet(ReadOnlySpan<byte> bytes)
|
||||
{
|
||||
var headerBuffer = bytes[0..PacketHeader.KPacketHeaderLength];
|
||||
Header = new PacketHeader(headerBuffer);
|
||||
PacketBody = bytes[Header.HeaderLength..Header.PacketLength].ToArray();
|
||||
}
|
||||
#else
|
||||
public Packet(byte[] bytes)
|
||||
{
|
||||
var headerBuffer = new ArraySegment<byte>(bytes, 0, PacketHeader.KPacketHeaderLength);
|
||||
Header = new PacketHeader(headerBuffer);
|
||||
var body = new ArraySegment<byte>(bytes, Header.HeaderLength, Header.BodyLength).ToArray();
|
||||
PacketBody = body;
|
||||
}
|
||||
#endif
|
||||
public Packet(Operation operation, byte[] body = null)
|
||||
{
|
||||
Header = new PacketHeader
|
||||
{
|
||||
Operation = operation,
|
||||
ProtocolVersion = ProtocolVersion.UnCompressed,
|
||||
PacketLength = PacketHeader.KPacketHeaderLength + (body?.Length ?? 0)
|
||||
};
|
||||
PacketBody = body;
|
||||
}
|
||||
|
||||
public byte[] ToBytes
|
||||
{
|
||||
get
|
||||
{
|
||||
if (PacketBody != null)
|
||||
Header.PacketLength = Header.HeaderLength + PacketBody.Length;
|
||||
else
|
||||
Header.PacketLength = Header.HeaderLength;
|
||||
var arr = new byte[Header.PacketLength];
|
||||
#if UNITY_2021_2_OR_NEWER || NET5_0_OR_GREATER
|
||||
Array.Copy(((ReadOnlySpan<byte>) Header).ToArray(), arr, Header.HeaderLength);
|
||||
#else
|
||||
Array.Copy((byte[]) Header, arr, Header.HeaderLength);
|
||||
#endif
|
||||
if (PacketBody != null)
|
||||
Array.Copy(PacketBody, 0, arr, Header.HeaderLength, PacketBody.Length);
|
||||
return arr;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成附带msg信息的心跳包
|
||||
/// </summary>
|
||||
/// <param name="msg">需要带的信息</param>
|
||||
/// <returns>心跳包</returns>
|
||||
public static Packet HeartBeat(string msg)
|
||||
{
|
||||
return HeartBeat(Encoding.UTF8.GetBytes(msg));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成附带msg信息的心跳包
|
||||
/// </summary>
|
||||
/// <param name="msg">需要带的信息</param>
|
||||
/// <returns>心跳包</returns>
|
||||
public static Packet HeartBeat(byte[] msg = null)
|
||||
{
|
||||
if (msg == null) return s_NoBodyHeartBeatPacket;
|
||||
return new Packet()
|
||||
{
|
||||
Header = new PacketHeader()
|
||||
{
|
||||
PacketLength = PacketHeader.KPacketHeaderLength + msg.Length,
|
||||
ProtocolVersion = ProtocolVersion.HeartBeat,
|
||||
Operation = Operation.HeartBeat,
|
||||
SequenceId = 1,
|
||||
HeaderLength = PacketHeader.KPacketHeaderLength
|
||||
},
|
||||
PacketBody = msg
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成验证用数据包
|
||||
/// </summary>
|
||||
/// <param name="token">http请求获取的token</param>
|
||||
/// <param name="protocolVersion">协议版本</param>
|
||||
/// <returns>验证请求数据包</returns>
|
||||
public static Packet Authority(string token,
|
||||
ProtocolVersion protocolVersion = ProtocolVersion.Brotli)
|
||||
{
|
||||
var obj = Encoding.UTF8.GetBytes(token);
|
||||
|
||||
return new Packet
|
||||
{
|
||||
Header = new PacketHeader
|
||||
{
|
||||
Operation = Operation.Authority,
|
||||
ProtocolVersion = ProtocolVersion.HeartBeat,
|
||||
SequenceId = 1,
|
||||
HeaderLength = PacketHeader.KPacketHeaderLength,
|
||||
PacketLength = PacketHeader.KPacketHeaderLength + obj.Length
|
||||
},
|
||||
PacketBody = obj
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
180
HMIcode/SmallProject/OpenBLive/Runtime/Data/PacketHeader.cs
Normal file
180
HMIcode/SmallProject/OpenBLive/Runtime/Data/PacketHeader.cs
Normal file
@ -0,0 +1,180 @@
|
||||
using System;
|
||||
#if UNITY_2021_2_OR_NEWER || NET5_0_OR_GREATER
|
||||
using System.Buffers.Binary;
|
||||
#else
|
||||
using System.Net;
|
||||
using OpenBLive.Runtime.Utilities;
|
||||
#endif
|
||||
|
||||
namespace OpenBLive.Runtime.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// 弹幕数据包头部
|
||||
/// </summary>
|
||||
public struct PacketHeader
|
||||
{
|
||||
public const int KPacketHeaderLength = 16;
|
||||
|
||||
public int PacketLength;
|
||||
public short HeaderLength;
|
||||
public ProtocolVersion ProtocolVersion;
|
||||
public Operation Operation;
|
||||
public int SequenceId;
|
||||
|
||||
public int BodyLength => PacketLength - HeaderLength;
|
||||
|
||||
|
||||
#if UNITY_2021_2_OR_NEWER || NET5_0_OR_GREATER
|
||||
/// <summary>
|
||||
/// 构造方法
|
||||
/// </summary>
|
||||
/// <param name="bytes">弹幕头16字节</param>
|
||||
public PacketHeader(ReadOnlySpan<byte> bytes)
|
||||
{
|
||||
if (bytes.Length < KPacketHeaderLength) throw new ArgumentException("No Supported Protocol Header");
|
||||
|
||||
var b = bytes;
|
||||
PacketLength = BinaryPrimitives.ReadInt32BigEndian(b[0..4]);
|
||||
HeaderLength = BinaryPrimitives.ReadInt16BigEndian(b[4..6]);
|
||||
ProtocolVersion = (ProtocolVersion)BinaryPrimitives.ReadInt16BigEndian(b[6..8]);
|
||||
Operation = (Operation)BinaryPrimitives.ReadInt32BigEndian(b[8..12]);
|
||||
SequenceId = BinaryPrimitives.ReadInt32BigEndian(b[12..16]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成弹幕协议的头部
|
||||
/// </summary>
|
||||
/// <returns>所对应的弹幕头部byte数组</returns>
|
||||
public static explicit operator ReadOnlySpan<byte>(PacketHeader header) => GetBytes(header.PacketLength,
|
||||
header.HeaderLength, header.ProtocolVersion, header.Operation, header.SequenceId);
|
||||
|
||||
/// <summary>
|
||||
/// 生成弹幕协议的头部
|
||||
/// </summary>
|
||||
/// <param name="packetLength">消息数据包长度</param>
|
||||
/// <param name="headerLength">头部长度</param>
|
||||
/// <param name="protocolVersion">弹幕协议版本</param>
|
||||
/// <param name="operation">数据包操作</param>
|
||||
/// <param name="sequenceId">序列号</param>
|
||||
/// <returns></returns>
|
||||
public static byte[] GetBytes(int packetLength, short headerLength, ProtocolVersion protocolVersion,
|
||||
Operation operation, int sequenceId = 1)
|
||||
{
|
||||
var bytes = new byte[KPacketHeaderLength].AsSpan();
|
||||
BinaryPrimitives.WriteInt32BigEndian(bytes[0..4], packetLength);
|
||||
BinaryPrimitives.WriteInt16BigEndian(bytes[4..6], headerLength);
|
||||
BinaryPrimitives.WriteInt16BigEndian(bytes[6..8], (short)protocolVersion);
|
||||
BinaryPrimitives.WriteInt32BigEndian(bytes[8..12], (int)operation);
|
||||
BinaryPrimitives.WriteInt32BigEndian(bytes[12..16], sequenceId);
|
||||
|
||||
return bytes.ToArray();
|
||||
}
|
||||
#else
|
||||
/// <summary>
|
||||
/// 构造方法
|
||||
/// </summary>
|
||||
/// <param name="bytes">弹幕头16字节</param>
|
||||
public PacketHeader(ArraySegment<byte> bytes)
|
||||
{
|
||||
if (bytes.Count < KPacketHeaderLength) throw new ArgumentException("No Supported Protocol Header");
|
||||
|
||||
var b = bytes.Array;
|
||||
PacketLength = EndianBitConverter.BigEndian.ToInt32(b, 0);
|
||||
HeaderLength = EndianBitConverter.BigEndian.ToInt16(b, 4);
|
||||
ProtocolVersion = (ProtocolVersion) EndianBitConverter.BigEndian.ToInt16(b, 6);
|
||||
Operation = (Operation) EndianBitConverter.BigEndian.ToInt32(b, 8);
|
||||
SequenceId = EndianBitConverter.BigEndian.ToInt32(b, 12);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成弹幕协议的头部
|
||||
/// </summary>
|
||||
/// <returns>所对应的弹幕头部byte数组</returns>
|
||||
public static explicit operator byte[](PacketHeader header) => GetBytes(header.PacketLength,
|
||||
header.HeaderLength, header.ProtocolVersion, header.Operation, header.SequenceId);
|
||||
|
||||
/// <summary>
|
||||
/// 生成弹幕协议的头部
|
||||
/// </summary>
|
||||
/// <param name="packetLength">消息数据包长度</param>
|
||||
/// <param name="headerLength">头部长度</param>
|
||||
/// <param name="protocolVersion">弹幕协议版本</param>
|
||||
/// <param name="operation">数据包操作</param>
|
||||
/// <param name="sequenceId">序列号</param>
|
||||
/// <returns></returns>
|
||||
public static byte[] GetBytes(int packetLength, short headerLength, ProtocolVersion protocolVersion,
|
||||
Operation operation, int sequenceId = 1)
|
||||
{
|
||||
var bytes = new byte[KPacketHeaderLength];
|
||||
var pl = EndianBitConverter.BigEndian.GetBytes(packetLength);
|
||||
var hl = EndianBitConverter.BigEndian.GetBytes(headerLength);
|
||||
var pv = EndianBitConverter.BigEndian.GetBytes((short) protocolVersion);
|
||||
var ot = EndianBitConverter.BigEndian.GetBytes((int) operation);
|
||||
var si = EndianBitConverter.BigEndian.GetBytes(sequenceId);
|
||||
Buffer.BlockCopy(pl, 0, bytes, 0, pl.Length);
|
||||
Buffer.BlockCopy(hl, 0, bytes, pl.Length, hl.Length);
|
||||
Buffer.BlockCopy(pv, 0, bytes, pl.Length + hl.Length, pv.Length);
|
||||
Buffer.BlockCopy(ot, 0, bytes, pl.Length + hl.Length + pv.Length, ot.Length);
|
||||
Buffer.BlockCopy(si, 0, bytes, pl.Length + hl.Length + pv.Length + ot.Length, si.Length);
|
||||
return bytes;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 操作数据
|
||||
/// </summary>
|
||||
public enum Operation
|
||||
{
|
||||
/// <summary>
|
||||
/// 心跳包
|
||||
/// </summary>
|
||||
HeartBeat = 2,
|
||||
|
||||
/// <summary>
|
||||
/// 服务器心跳回应(包含人气信息)
|
||||
/// </summary>
|
||||
HeartBeatResponse = 3,
|
||||
|
||||
/// <summary>
|
||||
/// 服务器消息(正常消息)
|
||||
/// </summary>
|
||||
ServerNotify = 5,
|
||||
|
||||
/// <summary>
|
||||
/// 客户端认证请求
|
||||
/// </summary>
|
||||
Authority = 7,
|
||||
|
||||
/// <summary>
|
||||
/// 认证回应
|
||||
/// </summary>
|
||||
AuthorityResponse = 8
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 弹幕协议版本
|
||||
/// </summary>
|
||||
public enum ProtocolVersion
|
||||
{
|
||||
/// <summary>
|
||||
/// 未压缩数据
|
||||
/// </summary>
|
||||
UnCompressed = 0,
|
||||
|
||||
/// <summary>
|
||||
/// 心跳数据
|
||||
/// </summary>
|
||||
HeartBeat = 1,
|
||||
|
||||
/// <summary>
|
||||
/// zlib数据
|
||||
/// </summary>
|
||||
Zlib = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Br数据
|
||||
/// </summary>
|
||||
Brotli = 3
|
||||
}
|
||||
}
|
93
HMIcode/SmallProject/OpenBLive/Runtime/Data/SendGift.cs
Normal file
93
HMIcode/SmallProject/OpenBLive/Runtime/Data/SendGift.cs
Normal file
@ -0,0 +1,93 @@
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace OpenBLive.Runtime.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// 礼物数据 https://open-live.bilibili.com/document/f9ce25be-312e-1f4a-85fd-fef21f1637f8
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public struct SendGift
|
||||
{
|
||||
/// <summary>
|
||||
/// 房间号
|
||||
/// </summary>
|
||||
[JsonProperty("room_id")] public long roomId;
|
||||
|
||||
/// <summary>
|
||||
/// 购买的用户UID(即将废弃)
|
||||
/// </summary>
|
||||
[JsonProperty("uid")] public long uid;
|
||||
|
||||
/// <summary>
|
||||
/// 购买的用户open_id
|
||||
/// </summary>
|
||||
[JsonProperty("open_id")] public string open_id;
|
||||
|
||||
/// <summary>
|
||||
/// 送礼用户昵称
|
||||
/// </summary>
|
||||
[JsonProperty("uname")] public string userName;
|
||||
|
||||
/// <summary>
|
||||
/// 送礼用户头像
|
||||
/// </summary>
|
||||
[JsonProperty("uface")] public string userFace;
|
||||
|
||||
/// <summary>
|
||||
/// 道具id(盲盒:爆出道具id)
|
||||
/// </summary>
|
||||
[JsonProperty("gift_id")] public long giftId;
|
||||
|
||||
/// <summary>
|
||||
/// 道具名(盲盒:爆出道具名)
|
||||
/// </summary>
|
||||
[JsonProperty("gift_name")] public string giftName;
|
||||
|
||||
/// <summary>
|
||||
/// 赠送道具数量
|
||||
/// </summary>
|
||||
[JsonProperty("gift_num")] public long giftNum;
|
||||
|
||||
/// <summary>
|
||||
/// 支付金额(1000 = 1元 = 10电池),盲盒:爆出道具的价值
|
||||
/// </summary>
|
||||
[JsonProperty("price")] public long price;
|
||||
|
||||
/// <summary>
|
||||
/// 是否真的花钱(电池道具)
|
||||
/// </summary>
|
||||
[JsonProperty("paid")] public bool paid;
|
||||
|
||||
/// <summary>
|
||||
/// 粉丝勋章等级
|
||||
/// </summary>
|
||||
[JsonProperty("fans_medal_level")] public long fansMedalLevel;
|
||||
|
||||
/// <summary>
|
||||
/// 粉丝勋章名
|
||||
/// </summary>
|
||||
[JsonProperty("fans_medal_name")] public string fansMedalName;
|
||||
|
||||
/// <summary>
|
||||
/// 佩戴的粉丝勋章佩戴状态
|
||||
/// </summary>
|
||||
[JsonProperty("fans_medal_wearing_status")]
|
||||
public bool fansMedalWearingStatus;
|
||||
|
||||
/// <summary>
|
||||
/// 大航海等级
|
||||
/// </summary>
|
||||
[JsonProperty("guard_level")] public long guardLevel;
|
||||
|
||||
/// <summary>
|
||||
/// 收礼时间秒级时间戳
|
||||
/// </summary>
|
||||
[JsonProperty("timestamp")] public long timestamp;
|
||||
|
||||
/// <summary>
|
||||
/// 主播信息
|
||||
/// </summary>
|
||||
[JsonProperty("anchor_info")] public AnchorInfo anchorInfo;
|
||||
}
|
||||
}
|
88
HMIcode/SmallProject/OpenBLive/Runtime/Data/SuperChat.cs
Normal file
88
HMIcode/SmallProject/OpenBLive/Runtime/Data/SuperChat.cs
Normal file
@ -0,0 +1,88 @@
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace OpenBLive.Runtime.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// 付费留言数据 https://open-live.bilibili.com/document/f9ce25be-312e-1f4a-85fd-fef21f1637f8
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public struct SuperChat
|
||||
{
|
||||
/// <summary>
|
||||
/// 直播间ID
|
||||
/// </summary>
|
||||
[JsonProperty("room_id")] public long roomId;
|
||||
|
||||
/// <summary>
|
||||
/// 购买的用户UID(即将废弃)
|
||||
/// </summary>
|
||||
[JsonProperty("uid")] public long uid;
|
||||
|
||||
/// <summary>
|
||||
/// 购买的用户open_id
|
||||
/// </summary>
|
||||
[JsonProperty("open_id")] public string open_id;
|
||||
|
||||
/// <summary>
|
||||
/// 购买的用户昵称
|
||||
/// </summary>
|
||||
[JsonProperty("uname")] public string userName;
|
||||
|
||||
/// <summary>
|
||||
/// 购买用户头像
|
||||
/// </summary>
|
||||
[JsonProperty("uface")] public string userFace;
|
||||
|
||||
/// <summary>
|
||||
/// 留言id(风控场景下撤回留言需要)
|
||||
/// </summary>
|
||||
[JsonProperty("message_id")] public long messageId;
|
||||
|
||||
/// <summary>
|
||||
/// 留言内容
|
||||
/// </summary>
|
||||
[JsonProperty("message")] public string message;
|
||||
|
||||
/// <summary>
|
||||
/// 支付金额(元)
|
||||
/// </summary>
|
||||
[JsonProperty("rmb")] public long rmb;
|
||||
|
||||
/// <summary>
|
||||
/// 赠送时间秒级
|
||||
/// </summary>
|
||||
[JsonProperty("timestamp")] public long timeStamp;
|
||||
|
||||
/// <summary>
|
||||
/// 生效开始时间
|
||||
/// </summary>
|
||||
[JsonProperty("start_time")] public long startTime;
|
||||
|
||||
/// <summary>
|
||||
/// 生效结束时间
|
||||
/// </summary>
|
||||
[JsonProperty("end_time")] public long endTime;
|
||||
|
||||
/// <summary>
|
||||
/// 对应房间大航海等级
|
||||
/// </summary>
|
||||
[JsonProperty("guard_level")] public long guardLevel;
|
||||
|
||||
/// <summary>
|
||||
/// 对应房间勋章信息
|
||||
/// </summary>
|
||||
[JsonProperty("fans_medal_level")] public long fansMedalLevel;
|
||||
|
||||
/// <summary>
|
||||
/// 对应房间勋章名字
|
||||
/// </summary>
|
||||
[JsonProperty("fans_medal_name")] public string fansMedalName;
|
||||
|
||||
/// <summary>
|
||||
/// 当前佩戴的粉丝勋章佩戴状态
|
||||
/// </summary>
|
||||
[JsonProperty("fans_medal_wearing_status")]
|
||||
public bool fansMedalWearingStatus;
|
||||
}
|
||||
}
|
22
HMIcode/SmallProject/OpenBLive/Runtime/Data/SuperChatDel.cs
Normal file
22
HMIcode/SmallProject/OpenBLive/Runtime/Data/SuperChatDel.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace OpenBLive.Runtime.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// 付费留言数据下线数据 https://open-live.bilibili.com/document/f9ce25be-312e-1f4a-85fd-fef21f1637f8
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public struct SuperChatDel
|
||||
{
|
||||
/// <summary>
|
||||
/// 直播间ID
|
||||
/// </summary>
|
||||
[JsonProperty("room_id")] public long roomId;
|
||||
|
||||
/// <summary>
|
||||
/// 留言id
|
||||
/// </summary>
|
||||
[JsonProperty("message_ids")] public long[] messageIds;
|
||||
}
|
||||
}
|
32
HMIcode/SmallProject/OpenBLive/Runtime/Data/UserInfo.cs
Normal file
32
HMIcode/SmallProject/OpenBLive/Runtime/Data/UserInfo.cs
Normal file
@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace OpenBLive.Runtime.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// 赠送大航海的用户数据 https://open-live.bilibili.com/document/f9ce25be-312e-1f4a-85fd-fef21f1637f8
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public struct UserInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// 购买大航海的用户UID(即将废弃)
|
||||
/// </summary>
|
||||
[JsonProperty("uid")] public long uid;
|
||||
|
||||
/// <summary>
|
||||
/// 购买大航海的用户open_id
|
||||
/// </summary>
|
||||
[JsonProperty("open_id")] public string open_id;
|
||||
|
||||
/// <summary>
|
||||
/// 购买大航海的用户昵称
|
||||
/// </summary>
|
||||
[JsonProperty("uname")] public string userName;
|
||||
|
||||
/// <summary>
|
||||
/// 购买大航海的用户头像
|
||||
/// </summary>
|
||||
[JsonProperty("uface")] public string userFace;
|
||||
}
|
||||
}
|
12
HMIcode/SmallProject/OpenBLive/Runtime/Data/WebsocketInfo.cs
Normal file
12
HMIcode/SmallProject/OpenBLive/Runtime/Data/WebsocketInfo.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace OpenBLive.Runtime.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// 服务器返回的Websocket长链接信息 https://open-live.bilibili.com/document/657d8e34-f926-a133-16c0-300c1afc6e6b
|
||||
/// </summary>
|
||||
public struct WebsocketInfo
|
||||
{
|
||||
public int code;
|
||||
public string message;
|
||||
public WebsocketInfoData data;
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace OpenBLive.Runtime.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// 服务器返回的Websocket长链接信息 https://open-live.bilibili.com/document/657d8e34-f926-a133-16c0-300c1afc6e6b
|
||||
/// </summary>
|
||||
public struct WebsocketInfoData
|
||||
{
|
||||
/// <summary>
|
||||
/// ip地址
|
||||
/// </summary>
|
||||
[JsonProperty("ip")]
|
||||
public List<string> ip;
|
||||
/// <summary>
|
||||
/// host地址 可能是ip 也可能是域名
|
||||
/// </summary>
|
||||
[JsonProperty("host")]
|
||||
public List<string> host;
|
||||
/// <summary>
|
||||
/// 长连使用的请求json体 第三方无需关注内容,建立长连时使用即可
|
||||
/// </summary>
|
||||
[JsonProperty("auth_body")]
|
||||
public string authBody;
|
||||
/// <summary>
|
||||
/// tcp 端口号
|
||||
/// </summary>
|
||||
[JsonProperty("tcp_port")]
|
||||
public List<int> tcpPort;
|
||||
/// <summary>
|
||||
/// ws 端口号
|
||||
/// </summary>
|
||||
[JsonProperty("ws_port")]
|
||||
public List<int> wsPort;
|
||||
/// <summary>
|
||||
/// wss 端口号
|
||||
/// </summary>
|
||||
[JsonProperty("wss_port")]
|
||||
public List<int> wssPort;
|
||||
}
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace OpenBLive.Runtime
|
||||
{
|
||||
public delegate void HeartBeatSucceed();
|
||||
|
||||
public delegate void HeartBeatError(string json);
|
||||
|
||||
public class InteractivePlayHeartBeat : IDisposable
|
||||
{
|
||||
public event HeartBeatSucceed HeartBeatSucceed;
|
||||
public event HeartBeatError HeartBeatError;
|
||||
private readonly CancellationTokenSource m_Cancellation;
|
||||
private readonly string[] m_GameIds;
|
||||
private readonly int m_Time;
|
||||
|
||||
public InteractivePlayHeartBeat(string gameId, int time = 20000, CancellationTokenSource cancellation = null)
|
||||
{
|
||||
m_GameIds = new[] { gameId };
|
||||
m_Time = time;
|
||||
m_Cancellation = cancellation ?? new CancellationTokenSource();
|
||||
}
|
||||
|
||||
public InteractivePlayHeartBeat(string[] gameIds, int time = 20000, CancellationTokenSource cancellation = null)
|
||||
{
|
||||
m_GameIds = gameIds;
|
||||
m_Time = time;
|
||||
m_Cancellation = cancellation ?? new CancellationTokenSource();
|
||||
}
|
||||
|
||||
private async Task HeartBeatTask()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
string res = "";
|
||||
if (m_GameIds.Length == 1)
|
||||
{
|
||||
res = await BApi.HeartBeatInteractivePlay(m_GameIds[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = await BApi.BatchHeartBeatInteractivePlay(m_GameIds);
|
||||
}
|
||||
|
||||
var json = JObject.Parse(res);
|
||||
var code = json["code"]!.ToObject<int>();
|
||||
if (code == 0)
|
||||
{
|
||||
HeartBeatSucceed?.Invoke();
|
||||
}
|
||||
else
|
||||
{
|
||||
HeartBeatError?.Invoke(res);
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
HeartBeatError?.Invoke(e.Message);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (m_Cancellation)
|
||||
{
|
||||
case { IsCancellationRequested: true }:
|
||||
case null:
|
||||
return;
|
||||
default:
|
||||
//默认20秒一次心跳
|
||||
await Task.Delay(m_Time);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
var task = HeartBeatTask();
|
||||
if (task.Status == TaskStatus.Created)
|
||||
task.Start();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
m_Cancellation.Cancel();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
m_Cancellation.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
BIN
HMIcode/SmallProject/OpenBLive/Runtime/Textures/22.png
Normal file
BIN
HMIcode/SmallProject/OpenBLive/Runtime/Textures/22.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 41 KiB |
@ -0,0 +1,57 @@
|
||||
// Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
|
||||
namespace OpenBLive.Runtime.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// A big-endian BitConverter that converts base data types to an array of bytes, and an array of bytes to base data types. All conversions are in
|
||||
/// big-endian format regardless of machine architecture.
|
||||
/// </summary>
|
||||
internal class BigEndianBitConverter : EndianBitConverter
|
||||
{
|
||||
// Instance available from EndianBitConverter.BigEndian
|
||||
internal BigEndianBitConverter() { }
|
||||
|
||||
public override bool IsLittleEndian { get; } = false;
|
||||
|
||||
public override byte[] GetBytes(short value)
|
||||
{
|
||||
return new byte[] { (byte)(value >> 8), (byte)value };
|
||||
}
|
||||
|
||||
public override byte[] GetBytes(int value)
|
||||
{
|
||||
return new byte[] { (byte)(value >> 24), (byte)(value >> 16), (byte)(value >> 8), (byte)value };
|
||||
}
|
||||
|
||||
public override byte[] GetBytes(long value)
|
||||
{
|
||||
return new byte[] {
|
||||
(byte)(value >> 56), (byte)(value >> 48), (byte)(value >> 40), (byte)(value >> 32),
|
||||
(byte)(value >> 24), (byte)(value >> 16), (byte)(value >> 8), (byte)value
|
||||
};
|
||||
}
|
||||
|
||||
public override short ToInt16(byte[] value, int startIndex)
|
||||
{
|
||||
this.CheckArguments(value, startIndex, sizeof(short));
|
||||
|
||||
return (short)((value[startIndex] << 8) | (value[startIndex + 1]));
|
||||
}
|
||||
|
||||
public override int ToInt32(byte[] value, int startIndex)
|
||||
{
|
||||
this.CheckArguments(value, startIndex, sizeof(int));
|
||||
|
||||
return (value[startIndex] << 24) | (value[startIndex + 1] << 16) | (value[startIndex + 2] << 8) | (value[startIndex + 3]);
|
||||
}
|
||||
|
||||
public override long ToInt64(byte[] value, int startIndex)
|
||||
{
|
||||
this.CheckArguments(value, startIndex, sizeof(long));
|
||||
|
||||
int highBytes = (value[startIndex] << 24) | (value[startIndex + 1] << 16) | (value[startIndex + 2] << 8) | (value[startIndex + 3]);
|
||||
int lowBytes = (value[startIndex + 4] << 24) | (value[startIndex + 5] << 16) | (value[startIndex + 6] << 8) | (value[startIndex + 7]);
|
||||
return ((uint)lowBytes | ((long)highBytes << 32));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,345 @@
|
||||
// Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
|
||||
namespace OpenBLive.Runtime.Utilities
|
||||
{
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
/// <summary>
|
||||
/// A BitConverter with a specific endianness that converts base data types to an array of bytes, and an array of bytes to base data types, regardless of
|
||||
/// machine architecture. Access the little-endian and big-endian converters with their respective properties.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The EndianBitConverter implementations provide the same interface as <see cref="System.BitConverter"/>, but exclude those methods which perform the
|
||||
/// same on both big-endian and little-endian machines (such as <see cref="BitConverter.ToString(byte[])"/>). However, <see cref="GetBytes(bool)"/> is
|
||||
/// included for consistency.
|
||||
/// </remarks>
|
||||
public abstract class EndianBitConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// Get an instance of a <see cref="LittleEndianBitConverter"/>, a BitConverter which performs all conversions in little-endian format regardless of
|
||||
/// machine architecture.
|
||||
/// </summary>
|
||||
public static EndianBitConverter LittleEndian { get; } = new LittleEndianBitConverter();
|
||||
|
||||
/// <summary>
|
||||
/// Get an instance of a <see cref="BigEndianBitConverter"/>, a BitConverter which performs all conversions in big-endian format regardless of
|
||||
/// machine architecture.
|
||||
/// </summary>
|
||||
public static EndianBitConverter BigEndian { get; } = new BigEndianBitConverter();
|
||||
|
||||
/// <summary>
|
||||
/// Indicates the byte order ("endianness") in which data should be converted.
|
||||
/// </summary>
|
||||
public abstract bool IsLittleEndian { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the specified Boolean value as a byte array.
|
||||
/// </summary>
|
||||
/// <param name="value">A Boolean value.</param>
|
||||
/// <returns>A byte array with length 1.</returns>
|
||||
/// <remarks>You can convert a byte array back to a <see cref="Boolean"/> value by calling the <see cref="ToBoolean(byte[], int)"/> method.</remarks>
|
||||
public byte[] GetBytes(bool value)
|
||||
{
|
||||
return new byte[] { value ? (byte)1 : (byte)0 };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the specified Unicode character value as an array of bytes.
|
||||
/// </summary>
|
||||
/// <param name="value">A character to convert.</param>
|
||||
/// <returns>An array of bytes with length 2.</returns>
|
||||
/// <remarks>You can convert a byte array back to a <see cref="Char"/> value by calling the <see cref="ToChar(byte[], int)"/> method.</remarks>
|
||||
public byte[] GetBytes(char value)
|
||||
{
|
||||
return this.GetBytes((short)value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the specified double-precision floating point value as an array of bytes.
|
||||
/// </summary>
|
||||
/// <param name="value">The number to convert.</param>
|
||||
/// <returns>An array of bytes with length 8.</returns>
|
||||
/// <remarks>You can convert a byte array back to a <see cref="Double"/> value by calling the <see cref="ToDouble(byte[], int)"/> method.</remarks>
|
||||
public byte[] GetBytes(double value)
|
||||
{
|
||||
long val = BitConverter.DoubleToInt64Bits(value);
|
||||
return this.GetBytes(val);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the specified 16-bit signed integer value as an array of bytes.
|
||||
/// </summary>
|
||||
/// <param name="value">The number to convert.</param>
|
||||
/// <returns>An array of bytes with length 2.</returns>
|
||||
/// <remarks>You can convert a byte array back to a <see cref="Int16"/> value by calling the <see cref="ToInt16(byte[], int)"/> method.</remarks>
|
||||
public abstract byte[] GetBytes(short value);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the specified 32-bit signed integer value as an array of bytes.
|
||||
/// </summary>
|
||||
/// <param name="value">The number to convert.</param>
|
||||
/// <returns>An array of bytes with length 4.</returns>
|
||||
/// <remarks>You can convert a byte array back to a <see cref="Int32"/> value by calling the <see cref="ToInt32(byte[], int)"/> method.</remarks>
|
||||
public abstract byte[] GetBytes(int value);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the specified 64-bit signed integer value as an array of bytes.
|
||||
/// </summary>
|
||||
/// <param name="value">The number to convert.</param>
|
||||
/// <returns>An array of bytes with length 8.</returns>
|
||||
/// <remarks>You can convert a byte array back to a <see cref="Int64"/> value by calling the <see cref="ToInt64(byte[], int)"/> method.</remarks>
|
||||
public abstract byte[] GetBytes(long value);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the specified single-precision floating point value as an array of bytes.
|
||||
/// </summary>
|
||||
/// <param name="value">The number to convert.</param>
|
||||
/// <returns>An array of bytes with length 4.</returns>
|
||||
/// <remarks>You can convert a byte array back to a <see cref="Single"/> value by calling the <see cref="ToSingle(byte[], int)"/> method.</remarks>
|
||||
public byte[] GetBytes(float value)
|
||||
{
|
||||
int val = new SingleConverter(value).GetIntValue();
|
||||
return this.GetBytes(val);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the specified 16-bit unsigned integer value as an array of bytes.
|
||||
/// </summary>
|
||||
/// <param name="value">The number to convert. </param>
|
||||
/// <returns>An array of bytes with length 2.</returns>
|
||||
/// <remarks>You can convert a byte array back to a <see cref="UInt16"/> value by calling the <see cref="ToUInt16(byte[], int)"/> method.</remarks>
|
||||
|
||||
public byte[] GetBytes(ushort value)
|
||||
{
|
||||
return this.GetBytes((short)value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the specified 32-bit unsigned integer value as an array of bytes.
|
||||
/// </summary>
|
||||
/// <param name="value">The number to convert.</param>
|
||||
/// <returns>An array of bytes with length 4.</returns>
|
||||
/// <remarks>You can convert a byte array back to a <see cref="UInt32"/> value by calling the <see cref="ToUInt32(byte[], int)"/> method.</remarks>
|
||||
|
||||
public byte[] GetBytes(uint value)
|
||||
{
|
||||
return this.GetBytes((int)value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the specified 64-bit unsigned integer value as an array of bytes.
|
||||
/// </summary>
|
||||
/// <param name="value">The number to convert.</param>
|
||||
/// <returns>An array of bytes with length 8.</returns>
|
||||
/// <remarks>You can convert a byte array back to a <see cref="UInt64"/> value by calling the <see cref="ToUInt64(byte[], int)"/> method.</remarks>
|
||||
|
||||
public byte[] GetBytes(ulong value)
|
||||
{
|
||||
return this.GetBytes((long)value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a Boolean value converted from the byte at a specified position in a byte array.
|
||||
/// </summary>
|
||||
/// <param name="value">A byte array.</param>
|
||||
/// <param name="startIndex">The index of the byte within <paramref name="value"/>.</param>
|
||||
/// <returns>
|
||||
/// true if the byte at <paramref name="startIndex"/> in <paramref name="value"/> is nonzero; otherwise, false.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="value"/> is null.</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// <paramref name="startIndex"/> is less than zero or greater than the length of <paramref name="value"/> minus 1.
|
||||
/// </exception>
|
||||
public bool ToBoolean(byte[] value, int startIndex)
|
||||
{
|
||||
this.CheckArguments(value, startIndex, 1);
|
||||
|
||||
return value[startIndex] != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a Unicode character converted from two bytes at a specified position in a byte array.
|
||||
/// </summary>
|
||||
/// <param name="value">An array.</param>
|
||||
/// <param name="startIndex">The starting position within <paramref name="value"/>.</param>
|
||||
/// <returns>A character formed by two bytes beginning at <paramref name="startIndex"/>.</returns>
|
||||
/// <remarks>
|
||||
/// The ToChar method converts the bytes from index <paramref name="startIndex"/> to <paramref name="startIndex"/> + 1 to a <see cref="Char"/> value.
|
||||
/// </remarks>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="value"/> is null.</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// <paramref name="startIndex"/> is less than zero or greater than the length of <paramref name="value"/> minus 2.
|
||||
/// </exception>
|
||||
public char ToChar(byte[] value, int startIndex)
|
||||
{
|
||||
return (char)this.ToInt16(value, startIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a double-precision floating point number converted from eight bytes at a specified position in a byte array.
|
||||
/// </summary>
|
||||
/// <param name="value">An array of bytes.</param>
|
||||
/// <param name="startIndex">The starting position within <paramref name="value"/>.</param>
|
||||
/// <returns>A double precision floating point number formed by eight bytes beginning at <paramref name="startIndex"/>.</returns>
|
||||
/// <remarks>
|
||||
/// The ToDouble method converts the bytes from index <paramref name="startIndex"/> to <paramref name="startIndex"/> + 7 to a <see cref="Double"/>
|
||||
/// value.
|
||||
/// </remarks>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="value"/> is null.</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// <paramref name="startIndex"/> is less than zero or greater than the length of <paramref name="value"/> minus 8.
|
||||
/// </exception>
|
||||
public double ToDouble(byte[] value, int startIndex)
|
||||
{
|
||||
long val = this.ToInt64(value, startIndex);
|
||||
return BitConverter.Int64BitsToDouble(val);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a 16-bit signed integer converted from two bytes at a specified position in a byte array.
|
||||
/// </summary>
|
||||
/// <param name="value">An array of bytes.</param>
|
||||
/// <param name="startIndex">The starting position within <paramref name="value"/>.</param>
|
||||
/// <returns>A 16-bit signed integer formed by two bytes beginning at <paramref name="startIndex"/>.</returns>
|
||||
/// <remarks>
|
||||
/// The ToInt16 method converts the bytes from index <paramref name="startIndex"/> to <paramref name="startIndex"/> + 1 to an <see cref="Int16"/>
|
||||
/// value.
|
||||
/// </remarks>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="value"/> is null.</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// <paramref name="startIndex"/> is less than zero or greater than the length of <paramref name="value"/> minus 2.
|
||||
/// </exception>
|
||||
public abstract short ToInt16(byte[] value, int startIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a 32-bit signed integer converted from four bytes at a specified position in a byte array.
|
||||
/// </summary>
|
||||
/// <param name="value">An array of bytes. </param>
|
||||
/// <param name="startIndex">The starting position within <paramref name="value"/>.</param>
|
||||
/// <returns>A 32-bit signed integer formed by four bytes beginning at <paramref name="startIndex"/>.</returns>
|
||||
/// <remarks>
|
||||
/// The ToInt32 method converts the bytes from index <paramref name="startIndex"/> to <paramref name="startIndex"/> + 3 to an <see cref="Int32"/>
|
||||
/// value.
|
||||
/// </remarks>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="value"/> is null.</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// <paramref name="startIndex"/> is less than zero or greater than the length of <paramref name="value"/> minus 4.
|
||||
/// </exception>
|
||||
public abstract int ToInt32(byte[] value, int startIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a 64-bit signed integer converted from eight bytes at a specified position in a byte array.
|
||||
/// </summary>
|
||||
/// <param name="value">An array of bytes.</param>
|
||||
/// <param name="startIndex">The starting position within <paramref name="value"/>.</param>
|
||||
/// <returns>A 64-bit signed integer formed by eight bytes beginning at <paramref name="startIndex"/>.</returns>
|
||||
/// <remarks>
|
||||
/// The ToInt64 method converts the bytes from index <paramref name="startIndex"/> to <paramref name="startIndex"/> + 7 to an <see cref="Int64"/>
|
||||
/// value.
|
||||
/// </remarks>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="value"/> is null.</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// <paramref name="startIndex"/> is less than zero or greater than the length of <paramref name="value"/> minus 8.
|
||||
/// </exception>
|
||||
public abstract long ToInt64(byte[] value, int startIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a single-precision floating point number converted from four bytes at a specified position in a byte array.
|
||||
/// </summary>
|
||||
/// <param name="value">An array of bytes.</param>
|
||||
/// <param name="startIndex">The starting position within <paramref name="value"/>.</param>
|
||||
/// <returns>A single-precision floating point number formed by four bytes beginning at <paramref name="startIndex"/>.</returns>
|
||||
/// <remarks>
|
||||
/// The ToSingle method converts the bytes from index <paramref name="startIndex"/> to <paramref name="startIndex"/> + 3 to a <see cref="Single"/>
|
||||
/// value.
|
||||
/// </remarks>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="value"/> is null.</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// <paramref name="startIndex"/> is less than zero or greater than the length of <paramref name="value"/> minus 4.
|
||||
/// </exception>
|
||||
public float ToSingle(byte[] value, int startIndex)
|
||||
{
|
||||
int val = this.ToInt32(value, startIndex);
|
||||
return new SingleConverter(val).GetFloatValue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a 16-bit unsigned integer converted from two bytes at a specified position in a byte array.
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="startIndex">The starting position within <paramref name="value"/>.</param>
|
||||
/// <returns>A 16-bit unsigned integer formed by two bytes beginning at <paramref name="startIndex"/>.</returns>
|
||||
/// <remarks>
|
||||
/// The ToUInt16 method converts the bytes from index <paramref name="startIndex"/> to <paramref name="startIndex"/> + 1 to a <see cref="UInt16"/>
|
||||
/// value.
|
||||
/// </remarks>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="value"/> is null.</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// <paramref name="startIndex"/> is less than zero or greater than the length of <paramref name="value"/> minus 2.
|
||||
/// </exception>
|
||||
|
||||
public ushort ToUInt16(byte[] value, int startIndex)
|
||||
{
|
||||
return (ushort)this.ToInt16(value, startIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a 32-bit unsigned integer converted from four bytes at a specified position in a byte array.
|
||||
/// </summary>
|
||||
/// <param name="value">An array of bytes. </param>
|
||||
/// <param name="startIndex">The starting position within <paramref name="value"/>.</param>
|
||||
/// <returns>A 32-bit unsigned integer formed by four bytes beginning at <paramref name="startIndex"/>.</returns>
|
||||
/// <remarks>
|
||||
/// The ToUInt32 method converts the bytes from index <paramref name="startIndex"/> to <paramref name="startIndex"/> + 3 to a <see cref="UInt32"/>
|
||||
/// value.
|
||||
/// </remarks>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="value"/> is null.</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// <paramref name="startIndex"/> is less than zero or greater than the length of <paramref name="value"/> minus 4.
|
||||
/// </exception>
|
||||
|
||||
public uint ToUInt32(byte[] value, int startIndex)
|
||||
{
|
||||
return (uint)this.ToInt32(value, startIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a 64-bit unsigned integer converted from eight bytes at a specified position in a byte array.
|
||||
/// </summary>
|
||||
/// <param name="value">An array of bytes. </param>
|
||||
/// <param name="startIndex">The starting position within <paramref name="value"/>.</param>
|
||||
/// <returns>A 64-bit unsigned integer formed by the eight bytes beginning at <paramref name="startIndex"/>.</returns>
|
||||
/// <remarks>
|
||||
/// The ToUInt64 method converts the bytes from index <paramref name="startIndex"/> to <paramref name="startIndex"/> + 7 to a <see cref="UInt64"/>
|
||||
/// value.
|
||||
/// </remarks>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="value"/> is null.</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// <paramref name="startIndex"/> is less than zero or greater than the length of <paramref name="value"/> minus 8.
|
||||
/// </exception>
|
||||
|
||||
public ulong ToUInt64(byte[] value, int startIndex)
|
||||
{
|
||||
return (ulong)this.ToInt64(value, startIndex);
|
||||
}
|
||||
|
||||
// Testing showed that this method wasn't automatically being inlined, and doing so offers a significant performance improvement.
|
||||
#if !NET40
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
#endif
|
||||
internal void CheckArguments(byte[] value, int startIndex, int byteLength)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
// confirms startIndex is not negative or too far along the byte array
|
||||
if ((uint)startIndex > value.Length - byteLength)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
109
HMIcode/SmallProject/OpenBLive/Runtime/Utilities/HttpUtility.cs
Normal file
109
HMIcode/SmallProject/OpenBLive/Runtime/Utilities/HttpUtility.cs
Normal file
@ -0,0 +1,109 @@
|
||||
#if UNITY_2020_3_OR_NEWER
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.Text;
|
||||
using UnityEngine.Networking;
|
||||
|
||||
namespace OpenBLive.Runtime.Utilities
|
||||
{
|
||||
public static class HttpUtility
|
||||
{
|
||||
private sealed class HttpQsCollection : NameValueCollection
|
||||
{
|
||||
public override string ToString ()
|
||||
{
|
||||
int count = Count;
|
||||
if (count == 0)
|
||||
return "";
|
||||
StringBuilder sb = new StringBuilder ();
|
||||
string [] keys = AllKeys;
|
||||
for (int i = 0; i < count; i++) {
|
||||
sb.AppendFormat ("{0}={1}&", keys [i], this [keys [i]]);
|
||||
}
|
||||
if (sb.Length > 0)
|
||||
sb.Length--;
|
||||
return sb.ToString ();
|
||||
}
|
||||
}
|
||||
public static NameValueCollection ParseQueryString(string query)
|
||||
{
|
||||
return ParseQueryString (query, Encoding.UTF8);
|
||||
}
|
||||
|
||||
private static NameValueCollection ParseQueryString (string query, Encoding encoding)
|
||||
{
|
||||
if (query == null)
|
||||
throw new ArgumentNullException ("query");
|
||||
if (encoding == null)
|
||||
throw new ArgumentNullException ("encoding");
|
||||
if (query.Length == 0 || (query.Length == 1 && query[0] == '?'))
|
||||
return new HttpQsCollection ();
|
||||
if (query[0] == '?')
|
||||
query = query.Substring (1);
|
||||
|
||||
NameValueCollection result = new HttpQsCollection ();
|
||||
ParseQueryString (query, encoding, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void ParseQueryString(string query, Encoding encoding, NameValueCollection result)
|
||||
{
|
||||
if (query.Length == 0)
|
||||
return;
|
||||
|
||||
var decodedLength = query.Length;
|
||||
var namePos = 0;
|
||||
var first = true;
|
||||
|
||||
while (namePos <= decodedLength)
|
||||
{
|
||||
int valuePos = -1, valueEnd = -1;
|
||||
for (var q = namePos; q < decodedLength; q++)
|
||||
{
|
||||
if ((valuePos == -1) && (query[q] == '='))
|
||||
{
|
||||
valuePos = q + 1;
|
||||
}
|
||||
else if (query[q] == '&')
|
||||
{
|
||||
valueEnd = q;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (first)
|
||||
{
|
||||
first = false;
|
||||
if (query[namePos] == '?')
|
||||
namePos++;
|
||||
}
|
||||
|
||||
string name;
|
||||
if (valuePos == -1)
|
||||
{
|
||||
name = null;
|
||||
valuePos = namePos;
|
||||
}
|
||||
else
|
||||
{
|
||||
name = UnityWebRequest.UnEscapeURL(query.Substring(namePos, valuePos - namePos - 1), encoding);
|
||||
}
|
||||
if (valueEnd < 0)
|
||||
{
|
||||
namePos = -1;
|
||||
valueEnd = query.Length;
|
||||
}
|
||||
else
|
||||
{
|
||||
namePos = valueEnd + 1;
|
||||
}
|
||||
var value = UnityWebRequest.UnEscapeURL(query.Substring(valuePos, valueEnd - valuePos), encoding);
|
||||
|
||||
result.Add(name, value);
|
||||
if (namePos == -1)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -0,0 +1,57 @@
|
||||
// Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
|
||||
namespace OpenBLive.Runtime.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// A little-endian BitConverter that converts base data types to an array of bytes, and an array of bytes to base data types. All conversions are in
|
||||
/// little-endian format regardless of machine architecture.
|
||||
/// </summary>
|
||||
internal class LittleEndianBitConverter : EndianBitConverter
|
||||
{
|
||||
// Instance available from EndianBitConverter.LittleEndian
|
||||
internal LittleEndianBitConverter() { }
|
||||
|
||||
public override bool IsLittleEndian { get; } = true;
|
||||
|
||||
public override byte[] GetBytes(short value)
|
||||
{
|
||||
return new byte[] { (byte)value, (byte)(value >> 8) };
|
||||
}
|
||||
|
||||
public override byte[] GetBytes(int value)
|
||||
{
|
||||
return new byte[] { (byte)value, (byte)(value >> 8), (byte)(value >> 16), (byte)(value >> 24) };
|
||||
}
|
||||
|
||||
public override byte[] GetBytes(long value)
|
||||
{
|
||||
return new byte[] {
|
||||
(byte)value, (byte)(value >> 8), (byte)(value >> 16), (byte)(value >> 24),
|
||||
(byte)(value >> 32), (byte)(value >> 40), (byte)(value >> 48), (byte)(value >> 56)
|
||||
};
|
||||
}
|
||||
|
||||
public override short ToInt16(byte[] value, int startIndex)
|
||||
{
|
||||
this.CheckArguments(value, startIndex, sizeof(short));
|
||||
|
||||
return (short)((value[startIndex]) | (value[startIndex + 1] << 8));
|
||||
}
|
||||
|
||||
public override int ToInt32(byte[] value, int startIndex)
|
||||
{
|
||||
this.CheckArguments(value, startIndex, sizeof(int));
|
||||
|
||||
return (value[startIndex]) | (value[startIndex + 1] << 8) | (value[startIndex + 2] << 16) | (value[startIndex + 3] << 24);
|
||||
}
|
||||
|
||||
public override long ToInt64(byte[] value, int startIndex)
|
||||
{
|
||||
this.CheckArguments(value, startIndex, sizeof(long));
|
||||
|
||||
int lowBytes = (value[startIndex]) | (value[startIndex + 1] << 8) | (value[startIndex + 2] << 16) | (value[startIndex + 3] << 24);
|
||||
int highBytes = (value[startIndex + 4]) | (value[startIndex + 5] << 8) | (value[startIndex + 6] << 16) | (value[startIndex + 7] << 24);
|
||||
return ((uint)lowBytes | ((long)highBytes << 32));
|
||||
}
|
||||
}
|
||||
}
|
31
HMIcode/SmallProject/OpenBLive/Runtime/Utilities/Logger.cs
Normal file
31
HMIcode/SmallProject/OpenBLive/Runtime/Utilities/Logger.cs
Normal file
@ -0,0 +1,31 @@
|
||||
#if NET5_0_OR_GREATER
|
||||
using System;
|
||||
#else
|
||||
using UnityEngine;
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
namespace OpenBLive.Runtime.Utilities
|
||||
{
|
||||
public static class Logger
|
||||
{
|
||||
public static void LogError(string logInfo)
|
||||
{
|
||||
#if NET5_0_OR_GREATER
|
||||
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}:{logInfo}");
|
||||
#else
|
||||
Debug.LogError(logInfo);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void Log(string logInfo)
|
||||
{
|
||||
#if NET5_0_OR_GREATER
|
||||
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}:{logInfo}");
|
||||
#else
|
||||
Debug.Log(logInfo);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
151
HMIcode/SmallProject/OpenBLive/Runtime/Utilities/SignUtility.cs
Normal file
151
HMIcode/SmallProject/OpenBLive/Runtime/Utilities/SignUtility.cs
Normal file
@ -0,0 +1,151 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
#if NET5_0_OR_GREATER
|
||||
using System.Net;
|
||||
#elif UNITY_2020_3_OR_NEWER
|
||||
using UnityEngine.Networking;
|
||||
#endif
|
||||
|
||||
namespace OpenBLive.Runtime.Utilities
|
||||
{
|
||||
public static class SignUtility
|
||||
{
|
||||
#region AccessKey
|
||||
|
||||
/// <summary>
|
||||
/// 开放平台的access_key_secret,请妥善保管以防泄露
|
||||
/// </summary>
|
||||
public static string accessKeySecret = "";
|
||||
|
||||
/// <summary>
|
||||
/// 开放平台的access_key_id,请妥善保管以防泄露
|
||||
/// </summary>
|
||||
public static string accessKeyId = "";
|
||||
#endregion
|
||||
|
||||
private static Dictionary<string, string> OrderAndMd5(string jsonParam)
|
||||
{
|
||||
var keyValuePairs = new Dictionary<string, string>
|
||||
{
|
||||
{"x-bili-content-md5", Md5(jsonParam)},
|
||||
{"x-bili-timestamp", DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds.ToString("f0")},
|
||||
{"x-bili-signature-method", "HMAC-SHA256"},
|
||||
{"x-bili-signature-nonce", Guid.NewGuid().ToString()},
|
||||
{"x-bili-accesskeyid", accessKeyId},
|
||||
{"x-bili-signature-version", "1.0"}
|
||||
};
|
||||
Dictionary<string, string> sortDic =
|
||||
keyValuePairs.OrderBy(x => x.Key).ToDictionary(x => x.Key, x => x.Value);
|
||||
return sortDic;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// MD5加密
|
||||
/// </summary>
|
||||
private static string Md5(this string source)
|
||||
{
|
||||
//MD5类是抽象类
|
||||
MD5 md5 = MD5.Create();
|
||||
//需要将字符串转成字节数组
|
||||
byte[] buffer = Encoding.UTF8.GetBytes(source);
|
||||
//加密后是一个字节类型的数组,这里要注意编码UTF8/Unicode等的选择
|
||||
byte[] md5Buffer = md5.ComputeHash(buffer);
|
||||
// 通过使用循环,将字节类型的数组转换为字符串,此字符串是常规字符格式化所得
|
||||
|
||||
return md5Buffer.Aggregate<byte, string>(null, (current, b) => current + b.ToString("x2"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算签名
|
||||
/// </summary>
|
||||
private static string CalculateSignature(Dictionary<string, string> keyValuePairs)
|
||||
{
|
||||
string sig = string.Empty;
|
||||
foreach (var item in keyValuePairs)
|
||||
{
|
||||
if (string.IsNullOrEmpty(sig))
|
||||
{
|
||||
sig += item.Key + ":" + item.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
sig += "\n" + item.Key + ":" + item.Value;
|
||||
}
|
||||
}
|
||||
|
||||
return HmacSHA256(sig, accessKeySecret);
|
||||
}
|
||||
|
||||
private static string HmacSHA256(string message, string secret)
|
||||
{
|
||||
secret ??= "";
|
||||
var encoding = new UTF8Encoding();
|
||||
byte[] keyByte = encoding.GetBytes(secret);
|
||||
byte[] messageBytes = encoding.GetBytes(message);
|
||||
using var hash256 = new HMACSHA256(keyByte);
|
||||
byte[] hash = hash256.ComputeHash(messageBytes);
|
||||
StringBuilder builder = new StringBuilder();
|
||||
foreach (var t in hash)
|
||||
{
|
||||
builder.Append(t.ToString("x2"));
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
#if NET5_0_OR_GREATER
|
||||
public static void SetReqHeader(HttpWebRequest req, string jsonParam, string cookie = null)
|
||||
{
|
||||
var sortDic = OrderAndMd5(jsonParam);
|
||||
var auth = CalculateSignature(sortDic);
|
||||
foreach (var item in sortDic)
|
||||
{
|
||||
req.Headers.Add(item.Key, item.Value);
|
||||
}
|
||||
|
||||
req.Headers.Add("Authorization", auth);
|
||||
req.Accept = "application/json";
|
||||
req.ContentType = "application/json";
|
||||
|
||||
|
||||
if (cookie != null)
|
||||
{
|
||||
req.Headers.Add("Cookie", cookie);
|
||||
}
|
||||
|
||||
var bytes = Encoding.UTF8.GetBytes(jsonParam);
|
||||
req.ContentLength = bytes.Length;
|
||||
using Stream writer = req.GetRequestStream();
|
||||
writer.Write(bytes, 0, bytes.Length);
|
||||
writer.Close();
|
||||
}
|
||||
|
||||
|
||||
|
||||
#elif UNITY_2020_3_OR_NEWER
|
||||
public static void SetReqHeader(UnityWebRequest webRequest, string jsonParam, string cookie = null)
|
||||
{
|
||||
var sortDic = OrderAndMd5(jsonParam);
|
||||
var auth = CalculateSignature(sortDic);
|
||||
foreach (var item in sortDic)
|
||||
{
|
||||
webRequest.SetRequestHeader(item.Key, item.Value);
|
||||
}
|
||||
|
||||
webRequest.SetRequestHeader("Authorization", auth);
|
||||
webRequest.SetRequestHeader("Accept", "application/json");
|
||||
webRequest.SetRequestHeader("Content-Type", "application/json");
|
||||
if (cookie != null)
|
||||
{
|
||||
webRequest.SetRequestHeader("Cookie", cookie);
|
||||
}
|
||||
|
||||
var bytes = Encoding.UTF8.GetBytes(jsonParam);
|
||||
webRequest.uploadHandler = new UploadHandlerRaw(bytes);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
// Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
|
||||
namespace OpenBLive.Runtime.Utilities
|
||||
{
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// Converts between Single (float) and Int32 (int), as System.BitConverter does not have a method to do this in all .NET versions.
|
||||
// A union is used instead of an unsafe pointer cast so we don't have to worry about the trusted environment implications.
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
internal struct SingleConverter
|
||||
{
|
||||
// map int value to offset zero
|
||||
[FieldOffset(0)]
|
||||
private int intValue;
|
||||
|
||||
// map float value to offset zero - intValue and floatValue now take the same position in memory
|
||||
[FieldOffset(0)]
|
||||
private float floatValue;
|
||||
|
||||
internal SingleConverter(int intValue)
|
||||
{
|
||||
this.floatValue = 0;
|
||||
this.intValue = intValue;
|
||||
}
|
||||
|
||||
internal SingleConverter(float floatValue)
|
||||
{
|
||||
this.intValue = 0;
|
||||
this.floatValue = floatValue;
|
||||
}
|
||||
|
||||
internal int GetIntValue() => this.intValue;
|
||||
|
||||
internal float GetFloatValue() => this.floatValue;
|
||||
}
|
||||
}
|
15
HMIcode/SmallProject/OpenBLive/Runtime/WebSocket/LICENSE.txt
Normal file
15
HMIcode/SmallProject/OpenBLive/Runtime/WebSocket/LICENSE.txt
Normal file
@ -0,0 +1,15 @@
|
||||
Copyright 2019 Endel Dreyer <endel.dreyer@gmail.com>
|
||||
Copyright 2018 Jiri Hybek <jiri@hybek.cz>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
851
HMIcode/SmallProject/OpenBLive/Runtime/WebSocket/WebSocket.cs
Normal file
851
HMIcode/SmallProject/OpenBLive/Runtime/WebSocket/WebSocket.cs
Normal file
@ -0,0 +1,851 @@
|
||||
#if !NET5_0_OR_GREATER
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net.WebSockets;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using AOT;
|
||||
using System.Runtime.InteropServices;
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
public class MainThreadUtil : MonoBehaviour
|
||||
{
|
||||
public static MainThreadUtil Instance { get; private set; }
|
||||
public static SynchronizationContext synchronizationContext { get; private set; }
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
|
||||
public static void Setup()
|
||||
{
|
||||
Instance = new GameObject("MainThreadUtil")
|
||||
.AddComponent<MainThreadUtil>();
|
||||
synchronizationContext = SynchronizationContext.Current;
|
||||
}
|
||||
|
||||
public static void Run(IEnumerator waitForUpdate)
|
||||
{
|
||||
synchronizationContext.Post(_ => Instance.StartCoroutine(
|
||||
waitForUpdate), null);
|
||||
}
|
||||
|
||||
void Awake()
|
||||
{
|
||||
gameObject.hideFlags = HideFlags.HideAndDontSave;
|
||||
DontDestroyOnLoad(gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
public class WaitForUpdate : CustomYieldInstruction
|
||||
{
|
||||
public override bool keepWaiting
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public MainThreadAwaiter GetAwaiter()
|
||||
{
|
||||
var awaiter = new MainThreadAwaiter();
|
||||
MainThreadUtil.Run(CoroutineWrapper(this, awaiter));
|
||||
return awaiter;
|
||||
}
|
||||
|
||||
public class MainThreadAwaiter : INotifyCompletion
|
||||
{
|
||||
Action continuation;
|
||||
|
||||
public bool IsCompleted { get; set; }
|
||||
|
||||
public void GetResult() { }
|
||||
|
||||
public void Complete()
|
||||
{
|
||||
IsCompleted = true;
|
||||
continuation?.Invoke();
|
||||
}
|
||||
|
||||
void INotifyCompletion.OnCompleted(Action continuation)
|
||||
{
|
||||
this.continuation = continuation;
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerator CoroutineWrapper(IEnumerator theWorker, MainThreadAwaiter awaiter)
|
||||
{
|
||||
yield return theWorker;
|
||||
awaiter.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
namespace NativeWebSocket
|
||||
{
|
||||
public delegate void WebSocketOpenEventHandler();
|
||||
public delegate void WebSocketMessageEventHandler(byte[] data);
|
||||
public delegate void WebSocketErrorEventHandler(string errorMsg);
|
||||
public delegate void WebSocketCloseEventHandler(WebSocketCloseCode closeCode);
|
||||
|
||||
public enum WebSocketCloseCode
|
||||
{
|
||||
/* Do NOT use NotSet - it's only purpose is to indicate that the close code cannot be parsed. */
|
||||
NotSet = 0,
|
||||
Normal = 1000,
|
||||
Away = 1001,
|
||||
ProtocolError = 1002,
|
||||
UnsupportedData = 1003,
|
||||
Undefined = 1004,
|
||||
NoStatus = 1005,
|
||||
Abnormal = 1006,
|
||||
InvalidData = 1007,
|
||||
PolicyViolation = 1008,
|
||||
TooBig = 1009,
|
||||
MandatoryExtension = 1010,
|
||||
ServerError = 1011,
|
||||
TlsHandshakeFailure = 1015
|
||||
}
|
||||
|
||||
public enum WebSocketState
|
||||
{
|
||||
Connecting,
|
||||
Open,
|
||||
Closing,
|
||||
Closed
|
||||
}
|
||||
|
||||
public interface IWebSocket
|
||||
{
|
||||
event WebSocketOpenEventHandler OnOpen;
|
||||
event WebSocketMessageEventHandler OnMessage;
|
||||
event WebSocketErrorEventHandler OnError;
|
||||
event WebSocketCloseEventHandler OnClose;
|
||||
|
||||
WebSocketState State { get; }
|
||||
}
|
||||
|
||||
public static class WebSocketHelpers
|
||||
{
|
||||
public static WebSocketCloseCode ParseCloseCodeEnum(int closeCode)
|
||||
{
|
||||
|
||||
if (WebSocketCloseCode.IsDefined(typeof(WebSocketCloseCode), closeCode))
|
||||
{
|
||||
return (WebSocketCloseCode)closeCode;
|
||||
}
|
||||
else
|
||||
{
|
||||
return WebSocketCloseCode.Undefined;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static WebSocketException GetErrorMessageFromCode(int errorCode, Exception inner)
|
||||
{
|
||||
switch (errorCode)
|
||||
{
|
||||
case -1:
|
||||
return new WebSocketUnexpectedException("WebSocket instance not found.", inner);
|
||||
case -2:
|
||||
return new WebSocketInvalidStateException("WebSocket is already connected or in connecting state.", inner);
|
||||
case -3:
|
||||
return new WebSocketInvalidStateException("WebSocket is not connected.", inner);
|
||||
case -4:
|
||||
return new WebSocketInvalidStateException("WebSocket is already closing.", inner);
|
||||
case -5:
|
||||
return new WebSocketInvalidStateException("WebSocket is already closed.", inner);
|
||||
case -6:
|
||||
return new WebSocketInvalidStateException("WebSocket is not in open state.", inner);
|
||||
case -7:
|
||||
return new WebSocketInvalidArgumentException("Cannot close WebSocket. An invalid code was specified or reason is too long.", inner);
|
||||
default:
|
||||
return new WebSocketUnexpectedException("Unknown error.", inner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class WebSocketException : Exception
|
||||
{
|
||||
public WebSocketException() { }
|
||||
public WebSocketException(string message) : base(message) { }
|
||||
public WebSocketException(string message, Exception inner) : base(message, inner) { }
|
||||
}
|
||||
|
||||
public class WebSocketUnexpectedException : WebSocketException
|
||||
{
|
||||
public WebSocketUnexpectedException() { }
|
||||
public WebSocketUnexpectedException(string message) : base(message) { }
|
||||
public WebSocketUnexpectedException(string message, Exception inner) : base(message, inner) { }
|
||||
}
|
||||
|
||||
public class WebSocketInvalidArgumentException : WebSocketException
|
||||
{
|
||||
public WebSocketInvalidArgumentException() { }
|
||||
public WebSocketInvalidArgumentException(string message) : base(message) { }
|
||||
public WebSocketInvalidArgumentException(string message, Exception inner) : base(message, inner) { }
|
||||
}
|
||||
|
||||
public class WebSocketInvalidStateException : WebSocketException
|
||||
{
|
||||
public WebSocketInvalidStateException() { }
|
||||
public WebSocketInvalidStateException(string message) : base(message) { }
|
||||
public WebSocketInvalidStateException(string message, Exception inner) : base(message, inner) { }
|
||||
}
|
||||
|
||||
public class WaitForBackgroundThread
|
||||
{
|
||||
public ConfiguredTaskAwaitable.ConfiguredTaskAwaiter GetAwaiter()
|
||||
{
|
||||
return Task.Run(() => { }).ConfigureAwait(false).GetAwaiter();
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_WEBGL && !UNITY_EDITOR
|
||||
|
||||
/// <summary>
|
||||
/// WebSocket class bound to JSLIB.
|
||||
/// </summary>
|
||||
public class WebSocket : IWebSocket {
|
||||
|
||||
/* WebSocket JSLIB functions */
|
||||
[DllImport ("__Internal")]
|
||||
public static extern int WebSocketConnect (int instanceId);
|
||||
|
||||
[DllImport ("__Internal")]
|
||||
public static extern int WebSocketClose (int instanceId, int code, string reason);
|
||||
|
||||
[DllImport ("__Internal")]
|
||||
public static extern int WebSocketSend (int instanceId, byte[] dataPtr, int dataLength);
|
||||
|
||||
[DllImport ("__Internal")]
|
||||
public static extern int WebSocketSendText (int instanceId, string message);
|
||||
|
||||
[DllImport ("__Internal")]
|
||||
public static extern int WebSocketGetState (int instanceId);
|
||||
|
||||
protected int instanceId;
|
||||
|
||||
public event WebSocketOpenEventHandler OnOpen;
|
||||
public event WebSocketMessageEventHandler OnMessage;
|
||||
public event WebSocketErrorEventHandler OnError;
|
||||
public event WebSocketCloseEventHandler OnClose;
|
||||
|
||||
public WebSocket (string url, Dictionary<string, string> headers = null) {
|
||||
if (!WebSocketFactory.isInitialized) {
|
||||
WebSocketFactory.Initialize ();
|
||||
}
|
||||
|
||||
int instanceId = WebSocketFactory.WebSocketAllocate (url);
|
||||
WebSocketFactory.instances.Add (instanceId, this);
|
||||
|
||||
this.instanceId = instanceId;
|
||||
}
|
||||
|
||||
public WebSocket (string url, string subprotocol, Dictionary<string, string> headers = null) {
|
||||
if (!WebSocketFactory.isInitialized) {
|
||||
WebSocketFactory.Initialize ();
|
||||
}
|
||||
|
||||
int instanceId = WebSocketFactory.WebSocketAllocate (url);
|
||||
WebSocketFactory.instances.Add (instanceId, this);
|
||||
|
||||
WebSocketFactory.WebSocketAddSubProtocol(instanceId, subprotocol);
|
||||
|
||||
this.instanceId = instanceId;
|
||||
}
|
||||
|
||||
public WebSocket (string url, List<string> subprotocols, Dictionary<string, string> headers = null) {
|
||||
if (!WebSocketFactory.isInitialized) {
|
||||
WebSocketFactory.Initialize ();
|
||||
}
|
||||
|
||||
int instanceId = WebSocketFactory.WebSocketAllocate (url);
|
||||
WebSocketFactory.instances.Add (instanceId, this);
|
||||
|
||||
foreach (string subprotocol in subprotocols) {
|
||||
WebSocketFactory.WebSocketAddSubProtocol(instanceId, subprotocol);
|
||||
}
|
||||
|
||||
this.instanceId = instanceId;
|
||||
}
|
||||
|
||||
~WebSocket () {
|
||||
WebSocketFactory.HandleInstanceDestroy (this.instanceId);
|
||||
}
|
||||
|
||||
public int GetInstanceId () {
|
||||
return this.instanceId;
|
||||
}
|
||||
|
||||
public Task Connect () {
|
||||
int ret = WebSocketConnect (this.instanceId);
|
||||
|
||||
if (ret < 0)
|
||||
throw WebSocketHelpers.GetErrorMessageFromCode (ret, null);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void CancelConnection () {
|
||||
if (State == WebSocketState.Open)
|
||||
Close (WebSocketCloseCode.Abnormal);
|
||||
}
|
||||
|
||||
public Task Close (WebSocketCloseCode code = WebSocketCloseCode.Normal, string reason = null) {
|
||||
int ret = WebSocketClose (this.instanceId, (int) code, reason);
|
||||
|
||||
if (ret < 0)
|
||||
throw WebSocketHelpers.GetErrorMessageFromCode (ret, null);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task Send (byte[] data) {
|
||||
int ret = WebSocketSend (this.instanceId, data, data.Length);
|
||||
|
||||
if (ret < 0)
|
||||
throw WebSocketHelpers.GetErrorMessageFromCode (ret, null);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task SendText (string message) {
|
||||
int ret = WebSocketSendText (this.instanceId, message);
|
||||
|
||||
if (ret < 0)
|
||||
throw WebSocketHelpers.GetErrorMessageFromCode (ret, null);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public WebSocketState State {
|
||||
get {
|
||||
int state = WebSocketGetState (this.instanceId);
|
||||
|
||||
if (state < 0)
|
||||
throw WebSocketHelpers.GetErrorMessageFromCode (state, null);
|
||||
|
||||
switch (state) {
|
||||
case 0:
|
||||
return WebSocketState.Connecting;
|
||||
|
||||
case 1:
|
||||
return WebSocketState.Open;
|
||||
|
||||
case 2:
|
||||
return WebSocketState.Closing;
|
||||
|
||||
case 3:
|
||||
return WebSocketState.Closed;
|
||||
|
||||
default:
|
||||
return WebSocketState.Closed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void DelegateOnOpenEvent () {
|
||||
this.OnOpen?.Invoke ();
|
||||
}
|
||||
|
||||
public void DelegateOnMessageEvent (byte[] data) {
|
||||
this.OnMessage?.Invoke (data);
|
||||
}
|
||||
|
||||
public void DelegateOnErrorEvent (string errorMsg) {
|
||||
this.OnError?.Invoke (errorMsg);
|
||||
}
|
||||
|
||||
public void DelegateOnCloseEvent (int closeCode) {
|
||||
this.OnClose?.Invoke (WebSocketHelpers.ParseCloseCodeEnum (closeCode));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
public class WebSocket : IWebSocket
|
||||
{
|
||||
public event WebSocketOpenEventHandler OnOpen;
|
||||
public event WebSocketMessageEventHandler OnMessage;
|
||||
public event WebSocketErrorEventHandler OnError;
|
||||
public event WebSocketCloseEventHandler OnClose;
|
||||
|
||||
private Uri uri;
|
||||
private Dictionary<string, string> headers;
|
||||
private List<string> subprotocols;
|
||||
private ClientWebSocket m_Socket = new ClientWebSocket();
|
||||
|
||||
private CancellationTokenSource m_TokenSource;
|
||||
private CancellationToken m_CancellationToken;
|
||||
|
||||
private readonly object OutgoingMessageLock = new object();
|
||||
private readonly object IncomingMessageLock = new object();
|
||||
|
||||
private bool isSending = false;
|
||||
private List<ArraySegment<byte>> sendBytesQueue = new List<ArraySegment<byte>>();
|
||||
private List<ArraySegment<byte>> sendTextQueue = new List<ArraySegment<byte>>();
|
||||
|
||||
public WebSocket(string url, Dictionary<string, string> headers = null)
|
||||
{
|
||||
uri = new Uri(url);
|
||||
|
||||
if (headers == null)
|
||||
{
|
||||
this.headers = new Dictionary<string, string>();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
subprotocols = new List<string>();
|
||||
|
||||
string protocol = uri.Scheme;
|
||||
if (!protocol.Equals("ws") && !protocol.Equals("wss"))
|
||||
throw new ArgumentException("Unsupported protocol: " + protocol);
|
||||
}
|
||||
|
||||
public WebSocket(string url, string subprotocol, Dictionary<string, string> headers = null)
|
||||
{
|
||||
uri = new Uri(url);
|
||||
|
||||
if (headers == null)
|
||||
{
|
||||
this.headers = new Dictionary<string, string>();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
subprotocols = new List<string> {subprotocol};
|
||||
|
||||
string protocol = uri.Scheme;
|
||||
if (!protocol.Equals("ws") && !protocol.Equals("wss"))
|
||||
throw new ArgumentException("Unsupported protocol: " + protocol);
|
||||
}
|
||||
|
||||
public WebSocket(string url, List<string> subprotocols, Dictionary<string, string> headers = null)
|
||||
{
|
||||
uri = new Uri(url);
|
||||
|
||||
if (headers == null)
|
||||
{
|
||||
this.headers = new Dictionary<string, string>();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
this.subprotocols = subprotocols;
|
||||
|
||||
string protocol = uri.Scheme;
|
||||
if (!protocol.Equals("ws") && !protocol.Equals("wss"))
|
||||
throw new ArgumentException("Unsupported protocol: " + protocol);
|
||||
}
|
||||
|
||||
public void CancelConnection()
|
||||
{
|
||||
m_TokenSource?.Cancel();
|
||||
}
|
||||
|
||||
public async Task Connect()
|
||||
{
|
||||
try
|
||||
{
|
||||
m_TokenSource = new CancellationTokenSource();
|
||||
m_CancellationToken = m_TokenSource.Token;
|
||||
|
||||
m_Socket = new ClientWebSocket();
|
||||
|
||||
foreach (var header in headers)
|
||||
{
|
||||
m_Socket.Options.SetRequestHeader(header.Key, header.Value);
|
||||
}
|
||||
|
||||
foreach (string subprotocol in subprotocols) {
|
||||
m_Socket.Options.AddSubProtocol(subprotocol);
|
||||
}
|
||||
|
||||
await m_Socket.ConnectAsync(uri, m_CancellationToken);
|
||||
OnOpen?.Invoke();
|
||||
|
||||
await Receive();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnError?.Invoke(ex.Message);
|
||||
OnClose?.Invoke(WebSocketCloseCode.Abnormal);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (m_Socket != null)
|
||||
{
|
||||
m_TokenSource.Cancel();
|
||||
m_Socket.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public WebSocketState State
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (m_Socket.State)
|
||||
{
|
||||
case System.Net.WebSockets.WebSocketState.Connecting:
|
||||
return WebSocketState.Connecting;
|
||||
|
||||
case System.Net.WebSockets.WebSocketState.Open:
|
||||
return WebSocketState.Open;
|
||||
|
||||
case System.Net.WebSockets.WebSocketState.CloseSent:
|
||||
case System.Net.WebSockets.WebSocketState.CloseReceived:
|
||||
return WebSocketState.Closing;
|
||||
|
||||
case System.Net.WebSockets.WebSocketState.Closed:
|
||||
return WebSocketState.Closed;
|
||||
|
||||
default:
|
||||
return WebSocketState.Closed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Task Send(byte[] bytes)
|
||||
{
|
||||
// return m_Socket.SendAsync(buffer, WebSocketMessageType.Binary, true, CancellationToken.None);
|
||||
return SendMessage(sendBytesQueue, WebSocketMessageType.Binary, new ArraySegment<byte>(bytes));
|
||||
}
|
||||
|
||||
public Task SendText(string message)
|
||||
{
|
||||
var encoded = Encoding.UTF8.GetBytes(message);
|
||||
|
||||
// m_Socket.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None);
|
||||
return SendMessage(sendTextQueue, WebSocketMessageType.Text, new ArraySegment<byte>(encoded, 0, encoded.Length));
|
||||
}
|
||||
|
||||
private async Task SendMessage(List<ArraySegment<byte>> queue, WebSocketMessageType messageType, ArraySegment<byte> buffer)
|
||||
{
|
||||
// Return control to the calling method immediately.
|
||||
// await Task.Yield ();
|
||||
|
||||
// Make sure we have data.
|
||||
if (buffer.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// The state of the connection is contained in the context Items dictionary.
|
||||
bool sending;
|
||||
|
||||
lock (OutgoingMessageLock)
|
||||
{
|
||||
sending = isSending;
|
||||
|
||||
// If not, we are now.
|
||||
if (!isSending)
|
||||
{
|
||||
isSending = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!sending)
|
||||
{
|
||||
// Lock with a timeout, just in case.
|
||||
if (!Monitor.TryEnter(m_Socket, 1000))
|
||||
{
|
||||
// If we couldn't obtain exclusive access to the socket in one second, something is wrong.
|
||||
await m_Socket.CloseAsync(WebSocketCloseStatus.InternalServerError, string.Empty, m_CancellationToken);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Send the message synchronously.
|
||||
var t = m_Socket.SendAsync(buffer, messageType, true, m_CancellationToken);
|
||||
t.Wait(m_CancellationToken);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Monitor.Exit(m_Socket);
|
||||
}
|
||||
|
||||
// Note that we've finished sending.
|
||||
lock (OutgoingMessageLock)
|
||||
{
|
||||
isSending = false;
|
||||
}
|
||||
|
||||
// Handle any queued messages.
|
||||
await HandleQueue(queue, messageType);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add the message to the queue.
|
||||
lock (OutgoingMessageLock)
|
||||
{
|
||||
queue.Add(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleQueue(List<ArraySegment<byte>> queue, WebSocketMessageType messageType)
|
||||
{
|
||||
var buffer = new ArraySegment<byte>();
|
||||
lock (OutgoingMessageLock)
|
||||
{
|
||||
// Check for an item in the queue.
|
||||
if (queue.Count > 0)
|
||||
{
|
||||
// Pull it off the top.
|
||||
buffer = queue[0];
|
||||
queue.RemoveAt(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Send that message.
|
||||
if (buffer.Count > 0)
|
||||
{
|
||||
await SendMessage(queue, messageType, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
private List<byte[]> m_MessageList = new List<byte[]>();
|
||||
|
||||
// simple dispatcher for queued messages.
|
||||
public void DispatchMessageQueue()
|
||||
{
|
||||
if (m_MessageList.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List<byte[]> messageListCopy;
|
||||
|
||||
lock (IncomingMessageLock)
|
||||
{
|
||||
messageListCopy = new List<byte[]>(m_MessageList);
|
||||
m_MessageList.Clear();
|
||||
}
|
||||
|
||||
var len = messageListCopy.Count;
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
OnMessage?.Invoke(messageListCopy[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Receive()
|
||||
{
|
||||
WebSocketCloseCode closeCode = WebSocketCloseCode.Abnormal;
|
||||
await new WaitForBackgroundThread();
|
||||
|
||||
ArraySegment<byte> buffer = new ArraySegment<byte>(new byte[8192]);
|
||||
try
|
||||
{
|
||||
while (m_Socket.State == System.Net.WebSockets.WebSocketState.Open)
|
||||
{
|
||||
WebSocketReceiveResult result = null;
|
||||
|
||||
using (var ms = new MemoryStream())
|
||||
{
|
||||
do
|
||||
{
|
||||
result = await m_Socket.ReceiveAsync(buffer, m_CancellationToken);
|
||||
ms.Write(buffer.Array, buffer.Offset, result.Count);
|
||||
}
|
||||
while (!result.EndOfMessage);
|
||||
|
||||
ms.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
if (result.MessageType == WebSocketMessageType.Text)
|
||||
{
|
||||
lock (IncomingMessageLock)
|
||||
{
|
||||
m_MessageList.Add(ms.ToArray());
|
||||
}
|
||||
|
||||
//using (var reader = new StreamReader(ms, Encoding.UTF8))
|
||||
//{
|
||||
// string message = reader.ReadToEnd();
|
||||
// OnMessage?.Invoke(this, new MessageEventArgs(message));
|
||||
//}
|
||||
}
|
||||
else if (result.MessageType == WebSocketMessageType.Binary)
|
||||
{
|
||||
|
||||
lock (IncomingMessageLock)
|
||||
{
|
||||
m_MessageList.Add(ms.ToArray());
|
||||
}
|
||||
}
|
||||
else if (result.MessageType == WebSocketMessageType.Close)
|
||||
{
|
||||
await Close();
|
||||
closeCode = WebSocketHelpers.ParseCloseCodeEnum((int)result.CloseStatus);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
m_TokenSource.Cancel();
|
||||
}
|
||||
finally
|
||||
{
|
||||
await new WaitForUpdate();
|
||||
OnClose?.Invoke(closeCode);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Close()
|
||||
{
|
||||
if (State == WebSocketState.Open)
|
||||
{
|
||||
await m_Socket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, m_CancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
///
|
||||
/// Factory
|
||||
///
|
||||
|
||||
/// <summary>
|
||||
/// Class providing static access methods to work with JSLIB WebSocket or WebSocketSharp interface
|
||||
/// </summary>
|
||||
public static class WebSocketFactory
|
||||
{
|
||||
|
||||
#if UNITY_WEBGL && !UNITY_EDITOR
|
||||
/* Map of websocket instances */
|
||||
public static Dictionary<Int32, WebSocket> instances = new Dictionary<Int32, WebSocket> ();
|
||||
|
||||
/* Delegates */
|
||||
public delegate void OnOpenCallback (int instanceId);
|
||||
public delegate void OnMessageCallback (int instanceId, System.IntPtr msgPtr, int msgSize);
|
||||
public delegate void OnErrorCallback (int instanceId, System.IntPtr errorPtr);
|
||||
public delegate void OnCloseCallback (int instanceId, int closeCode);
|
||||
|
||||
/* WebSocket JSLIB callback setters and other functions */
|
||||
[DllImport ("__Internal")]
|
||||
public static extern int WebSocketAllocate (string url);
|
||||
|
||||
[DllImport ("__Internal")]
|
||||
public static extern int WebSocketAddSubProtocol (int instanceId, string subprotocol);
|
||||
|
||||
[DllImport ("__Internal")]
|
||||
public static extern void WebSocketFree (int instanceId);
|
||||
|
||||
[DllImport ("__Internal")]
|
||||
public static extern void WebSocketSetOnOpen (OnOpenCallback callback);
|
||||
|
||||
[DllImport ("__Internal")]
|
||||
public static extern void WebSocketSetOnMessage (OnMessageCallback callback);
|
||||
|
||||
[DllImport ("__Internal")]
|
||||
public static extern void WebSocketSetOnError (OnErrorCallback callback);
|
||||
|
||||
[DllImport ("__Internal")]
|
||||
public static extern void WebSocketSetOnClose (OnCloseCallback callback);
|
||||
|
||||
/* If callbacks was initialized and set */
|
||||
public static bool isInitialized = false;
|
||||
|
||||
/*
|
||||
* Initialize WebSocket callbacks to JSLIB
|
||||
*/
|
||||
public static void Initialize () {
|
||||
|
||||
WebSocketSetOnOpen (DelegateOnOpenEvent);
|
||||
WebSocketSetOnMessage (DelegateOnMessageEvent);
|
||||
WebSocketSetOnError (DelegateOnErrorEvent);
|
||||
WebSocketSetOnClose (DelegateOnCloseEvent);
|
||||
|
||||
isInitialized = true;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when instance is destroyed (by destructor)
|
||||
/// Method removes instance from map and free it in JSLIB implementation
|
||||
/// </summary>
|
||||
/// <param name="instanceId">Instance identifier.</param>
|
||||
public static void HandleInstanceDestroy (int instanceId) {
|
||||
|
||||
instances.Remove (instanceId);
|
||||
WebSocketFree (instanceId);
|
||||
|
||||
}
|
||||
|
||||
[MonoPInvokeCallback (typeof (OnOpenCallback))]
|
||||
public static void DelegateOnOpenEvent (int instanceId) {
|
||||
|
||||
WebSocket instanceRef;
|
||||
|
||||
if (instances.TryGetValue (instanceId, out instanceRef)) {
|
||||
instanceRef.DelegateOnOpenEvent ();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[MonoPInvokeCallback (typeof (OnMessageCallback))]
|
||||
public static void DelegateOnMessageEvent (int instanceId, System.IntPtr msgPtr, int msgSize) {
|
||||
|
||||
WebSocket instanceRef;
|
||||
|
||||
if (instances.TryGetValue (instanceId, out instanceRef)) {
|
||||
byte[] msg = new byte[msgSize];
|
||||
Marshal.Copy (msgPtr, msg, 0, msgSize);
|
||||
|
||||
instanceRef.DelegateOnMessageEvent (msg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[MonoPInvokeCallback (typeof (OnErrorCallback))]
|
||||
public static void DelegateOnErrorEvent (int instanceId, System.IntPtr errorPtr) {
|
||||
|
||||
WebSocket instanceRef;
|
||||
|
||||
if (instances.TryGetValue (instanceId, out instanceRef)) {
|
||||
|
||||
string errorMsg = Marshal.PtrToStringAuto (errorPtr);
|
||||
instanceRef.DelegateOnErrorEvent (errorMsg);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[MonoPInvokeCallback (typeof (OnCloseCallback))]
|
||||
public static void DelegateOnCloseEvent (int instanceId, int closeCode) {
|
||||
|
||||
WebSocket instanceRef;
|
||||
|
||||
if (instances.TryGetValue (instanceId, out instanceRef)) {
|
||||
instanceRef.DelegateOnCloseEvent (closeCode);
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Create WebSocket client instance
|
||||
/// </summary>
|
||||
/// <returns>The WebSocket instance.</returns>
|
||||
/// <param name="url">WebSocket valid URL.</param>
|
||||
public static WebSocket CreateInstance(string url)
|
||||
{
|
||||
return new WebSocket(url);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
333
HMIcode/SmallProject/OpenBLive/Runtime/WebSocket/WebSocket.jslib
Normal file
333
HMIcode/SmallProject/OpenBLive/Runtime/WebSocket/WebSocket.jslib
Normal file
@ -0,0 +1,333 @@
|
||||
|
||||
var LibraryWebSocket = {
|
||||
$webSocketState: {
|
||||
/*
|
||||
* Map of instances
|
||||
*
|
||||
* Instance structure:
|
||||
* {
|
||||
* url: string,
|
||||
* ws: WebSocket
|
||||
* }
|
||||
*/
|
||||
instances: {},
|
||||
|
||||
/* Last instance ID */
|
||||
lastId: 0,
|
||||
|
||||
/* Event listeners */
|
||||
onOpen: null,
|
||||
onMesssage: null,
|
||||
onError: null,
|
||||
onClose: null,
|
||||
|
||||
/* Debug mode */
|
||||
debug: false
|
||||
},
|
||||
|
||||
/**
|
||||
* Set onOpen callback
|
||||
*
|
||||
* @param callback Reference to C# static function
|
||||
*/
|
||||
WebSocketSetOnOpen: function(callback) {
|
||||
|
||||
webSocketState.onOpen = callback;
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Set onMessage callback
|
||||
*
|
||||
* @param callback Reference to C# static function
|
||||
*/
|
||||
WebSocketSetOnMessage: function(callback) {
|
||||
|
||||
webSocketState.onMessage = callback;
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Set onError callback
|
||||
*
|
||||
* @param callback Reference to C# static function
|
||||
*/
|
||||
WebSocketSetOnError: function(callback) {
|
||||
|
||||
webSocketState.onError = callback;
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Set onClose callback
|
||||
*
|
||||
* @param callback Reference to C# static function
|
||||
*/
|
||||
WebSocketSetOnClose: function(callback) {
|
||||
|
||||
webSocketState.onClose = callback;
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Allocate new WebSocket instance struct
|
||||
*
|
||||
* @param url Server URL
|
||||
*/
|
||||
WebSocketAllocate: function(url) {
|
||||
|
||||
var urlStr = UTF8ToString(url);
|
||||
var id = webSocketState.lastId++;
|
||||
|
||||
webSocketState.instances[id] = {
|
||||
subprotocols: [],
|
||||
url: urlStr,
|
||||
ws: null
|
||||
};
|
||||
|
||||
return id;
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Add subprotocol to instance
|
||||
*
|
||||
* @param instanceId Instance ID
|
||||
* @param subprotocol Subprotocol name to add to instance
|
||||
*/
|
||||
WebSocketAddSubProtocol: function(instanceId, subprotocol) {
|
||||
|
||||
var subprotocolStr = UTF8ToString(subprotocol);
|
||||
webSocketState.instances[instanceId].subprotocols.push(subprotocolStr);
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove reference to WebSocket instance
|
||||
*
|
||||
* If socket is not closed function will close it but onClose event will not be emitted because
|
||||
* this function should be invoked by C# WebSocket destructor.
|
||||
*
|
||||
* @param instanceId Instance ID
|
||||
*/
|
||||
WebSocketFree: function(instanceId) {
|
||||
|
||||
var instance = webSocketState.instances[instanceId];
|
||||
|
||||
if (!instance) return 0;
|
||||
|
||||
// Close if not closed
|
||||
if (instance.ws && instance.ws.readyState < 2)
|
||||
instance.ws.close();
|
||||
|
||||
// Remove reference
|
||||
delete webSocketState.instances[instanceId];
|
||||
|
||||
return 0;
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Connect WebSocket to the server
|
||||
*
|
||||
* @param instanceId Instance ID
|
||||
*/
|
||||
WebSocketConnect: function(instanceId) {
|
||||
|
||||
var instance = webSocketState.instances[instanceId];
|
||||
if (!instance) return -1;
|
||||
|
||||
if (instance.ws !== null)
|
||||
return -2;
|
||||
|
||||
instance.ws = new WebSocket(instance.url, instance.subprotocols);
|
||||
|
||||
instance.ws.binaryType = 'arraybuffer';
|
||||
|
||||
instance.ws.onopen = function() {
|
||||
|
||||
if (webSocketState.debug)
|
||||
console.log("[JSLIB WebSocket] Connected.");
|
||||
|
||||
if (webSocketState.onOpen)
|
||||
Module.dynCall_vi(webSocketState.onOpen, instanceId);
|
||||
|
||||
};
|
||||
|
||||
instance.ws.onmessage = function(ev) {
|
||||
|
||||
if (webSocketState.debug)
|
||||
console.log("[JSLIB WebSocket] Received message:", ev.data);
|
||||
|
||||
if (webSocketState.onMessage === null)
|
||||
return;
|
||||
|
||||
if (ev.data instanceof ArrayBuffer) {
|
||||
|
||||
var dataBuffer = new Uint8Array(ev.data);
|
||||
|
||||
var buffer = _malloc(dataBuffer.length);
|
||||
HEAPU8.set(dataBuffer, buffer);
|
||||
|
||||
try {
|
||||
Module.dynCall_viii(webSocketState.onMessage, instanceId, buffer, dataBuffer.length);
|
||||
} finally {
|
||||
_free(buffer);
|
||||
}
|
||||
|
||||
} else {
|
||||
var dataBuffer = (new TextEncoder()).encode(ev.data);
|
||||
|
||||
var buffer = _malloc(dataBuffer.length);
|
||||
HEAPU8.set(dataBuffer, buffer);
|
||||
|
||||
try {
|
||||
Module.dynCall_viii(webSocketState.onMessage, instanceId, buffer, dataBuffer.length);
|
||||
} finally {
|
||||
_free(buffer);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
instance.ws.onerror = function(ev) {
|
||||
|
||||
if (webSocketState.debug)
|
||||
console.log("[JSLIB WebSocket] Error occured.");
|
||||
|
||||
if (webSocketState.onError) {
|
||||
|
||||
var msg = "WebSocket error.";
|
||||
var length = lengthBytesUTF8(msg) + 1;
|
||||
var buffer = _malloc(length);
|
||||
stringToUTF8(msg, buffer, length);
|
||||
|
||||
try {
|
||||
Module.dynCall_vii(webSocketState.onError, instanceId, msgBuffer);
|
||||
} finally {
|
||||
_free(msgBuffer);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
instance.ws.onclose = function(ev) {
|
||||
|
||||
if (webSocketState.debug)
|
||||
console.log("[JSLIB WebSocket] Closed.");
|
||||
|
||||
if (webSocketState.onClose)
|
||||
Module.dynCall_vii(webSocketState.onClose, instanceId, ev.code);
|
||||
|
||||
delete instance.ws;
|
||||
|
||||
};
|
||||
|
||||
return 0;
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Close WebSocket connection
|
||||
*
|
||||
* @param instanceId Instance ID
|
||||
* @param code Close status code
|
||||
* @param reasonPtr Pointer to reason string
|
||||
*/
|
||||
WebSocketClose: function(instanceId, code, reasonPtr) {
|
||||
|
||||
var instance = webSocketState.instances[instanceId];
|
||||
if (!instance) return -1;
|
||||
|
||||
if (!instance.ws)
|
||||
return -3;
|
||||
|
||||
if (instance.ws.readyState === 2)
|
||||
return -4;
|
||||
|
||||
if (instance.ws.readyState === 3)
|
||||
return -5;
|
||||
|
||||
var reason = ( reasonPtr ? UTF8ToString(reasonPtr) : undefined );
|
||||
|
||||
try {
|
||||
instance.ws.close(code, reason);
|
||||
} catch(err) {
|
||||
return -7;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Send message over WebSocket
|
||||
*
|
||||
* @param instanceId Instance ID
|
||||
* @param bufferPtr Pointer to the message buffer
|
||||
* @param length Length of the message in the buffer
|
||||
*/
|
||||
WebSocketSend: function(instanceId, bufferPtr, length) {
|
||||
|
||||
var instance = webSocketState.instances[instanceId];
|
||||
if (!instance) return -1;
|
||||
|
||||
if (!instance.ws)
|
||||
return -3;
|
||||
|
||||
if (instance.ws.readyState !== 1)
|
||||
return -6;
|
||||
|
||||
instance.ws.send(HEAPU8.buffer.slice(bufferPtr, bufferPtr + length));
|
||||
|
||||
return 0;
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Send text message over WebSocket
|
||||
*
|
||||
* @param instanceId Instance ID
|
||||
* @param bufferPtr Pointer to the message buffer
|
||||
* @param length Length of the message in the buffer
|
||||
*/
|
||||
WebSocketSendText: function(instanceId, message) {
|
||||
|
||||
var instance = webSocketState.instances[instanceId];
|
||||
if (!instance) return -1;
|
||||
|
||||
if (!instance.ws)
|
||||
return -3;
|
||||
|
||||
if (instance.ws.readyState !== 1)
|
||||
return -6;
|
||||
|
||||
instance.ws.send(UTF8ToString(message));
|
||||
|
||||
return 0;
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Return WebSocket readyState
|
||||
*
|
||||
* @param instanceId Instance ID
|
||||
*/
|
||||
WebSocketGetState: function(instanceId) {
|
||||
|
||||
var instance = webSocketState.instances[instanceId];
|
||||
if (!instance) return -1;
|
||||
|
||||
if (instance.ws)
|
||||
return instance.ws.readyState;
|
||||
else
|
||||
return 3;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
autoAddDeps(LibraryWebSocket, '$webSocketState');
|
||||
mergeInto(LibraryManager.library, LibraryWebSocket);
|
188
HMIcode/SmallProject/OpenBLive/Runtime/WebSocketBLiveClient.cs
Normal file
188
HMIcode/SmallProject/OpenBLive/Runtime/WebSocketBLiveClient.cs
Normal file
@ -0,0 +1,188 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using OpenBLive.Runtime.Data;
|
||||
using System.Text;
|
||||
using OpenBLive.Runtime.Utilities;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenBLive.Client.Data;
|
||||
|
||||
#if NET5_0_OR_GREATER
|
||||
using Websocket.Client;
|
||||
using System.Net.WebSockets;
|
||||
#elif UNITY_2020_3_OR_NEWER
|
||||
using NativeWebSocket;
|
||||
#endif
|
||||
|
||||
namespace OpenBLive.Runtime
|
||||
{
|
||||
public class WebSocketBLiveClient : BLiveClient
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// wss 长连地址
|
||||
/// </summary>
|
||||
public IList<string> WssLink;
|
||||
|
||||
#if NET5_0_OR_GREATER
|
||||
public WebsocketClient clientWebSocket;
|
||||
#elif UNITY_2020_3_OR_NEWER
|
||||
public WebSocket ws;
|
||||
#endif
|
||||
|
||||
public WebSocketBLiveClient(AppStartInfo info)
|
||||
{
|
||||
var websocketInfo = info.Data.WebsocketInfo;
|
||||
|
||||
WssLink = websocketInfo.WssLink;
|
||||
token = websocketInfo.AuthBody;
|
||||
}
|
||||
|
||||
public WebSocketBLiveClient(IList<string> wssLink, string authBody)
|
||||
{
|
||||
WssLink = wssLink;
|
||||
token = authBody;
|
||||
}
|
||||
|
||||
|
||||
public override async void Connect()
|
||||
{
|
||||
var url = WssLink.FirstOrDefault();
|
||||
if (string.IsNullOrEmpty(url))
|
||||
{
|
||||
throw new Exception("wsslink is invalid");
|
||||
}
|
||||
#if NET5_0_OR_GREATER
|
||||
Disconnect();
|
||||
|
||||
clientWebSocket = new WebsocketClient(new Uri(url));
|
||||
clientWebSocket.MessageReceived.Subscribe(e =>
|
||||
ProcessPacket(e.Binary));
|
||||
clientWebSocket.DisconnectionHappened.Subscribe(e =>
|
||||
{
|
||||
if (e.CloseStatus == WebSocketCloseStatus.Empty)
|
||||
Console.WriteLine("WS CLOSED");
|
||||
else
|
||||
Console.WriteLine("WS ERROR: " + e.Exception.Message);
|
||||
});
|
||||
|
||||
await clientWebSocket.Start();
|
||||
if (clientWebSocket.IsStarted)
|
||||
OnOpen();
|
||||
|
||||
|
||||
#elif UNITY_2020_3_OR_NEWER
|
||||
//尝试释放已连接的ws
|
||||
if (ws != null && ws.State != WebSocketState.Closed)
|
||||
{
|
||||
await ws.Close();
|
||||
}
|
||||
|
||||
ws = new WebSocket(url);
|
||||
ws.OnOpen += OnOpen;
|
||||
ws.OnMessage += data =>
|
||||
{
|
||||
ProcessPacket(data);
|
||||
};
|
||||
ws.OnError += msg => { Logger.LogError("WebSocket Error Message: " + msg); };
|
||||
ws.OnClose += code => { Logger.Log("WebSocket Close: " + code); };
|
||||
await ws.Connect();
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 带有重连
|
||||
/// </summary>
|
||||
/// <param name="timeout">ReconnectTimeout ErrorReconnectTimeout</param>
|
||||
public override async void Connect(TimeSpan timeout)
|
||||
{
|
||||
var url = WssLink.FirstOrDefault();
|
||||
if (string.IsNullOrEmpty(url))
|
||||
{
|
||||
throw new Exception("wsslink is invalid");
|
||||
}
|
||||
#if NET5_0_OR_GREATER
|
||||
clientWebSocket?.Stop(WebSocketCloseStatus.Empty, string.Empty);
|
||||
clientWebSocket?.Dispose();
|
||||
|
||||
clientWebSocket = new WebsocketClient(new Uri(url));
|
||||
clientWebSocket.MessageReceived.Subscribe(e =>
|
||||
{
|
||||
//Console.WriteLine(e.Binary.Length);
|
||||
ProcessPacket(e.Binary);
|
||||
});
|
||||
clientWebSocket.DisconnectionHappened.Subscribe(e =>
|
||||
{
|
||||
if (e.CloseStatus == WebSocketCloseStatus.Empty)
|
||||
Console.WriteLine("WS CLOSED");
|
||||
else if (e?.Exception != null)
|
||||
Console.WriteLine("WS ERROR: " + e?.Exception?.Message);
|
||||
});
|
||||
await clientWebSocket.Start();
|
||||
clientWebSocket.IsReconnectionEnabled = true;
|
||||
clientWebSocket.ReconnectTimeout = timeout;
|
||||
clientWebSocket.ErrorReconnectTimeout = timeout;
|
||||
clientWebSocket.ReconnectionHappened.Subscribe(e =>
|
||||
{
|
||||
SendAsync(Packet.Authority(token));
|
||||
});
|
||||
if (clientWebSocket.IsStarted)
|
||||
OnOpen();
|
||||
|
||||
|
||||
|
||||
#elif UNITY_2020_3_OR_NEWER
|
||||
//尝试释放已连接的ws
|
||||
if (ws != null && ws.State != WebSocketState.Closed)
|
||||
{
|
||||
await ws.Close();
|
||||
}
|
||||
|
||||
ws = new WebSocket(url);
|
||||
ws.OnOpen += OnOpen;
|
||||
ws.OnMessage += data =>
|
||||
{
|
||||
ProcessPacket(data);
|
||||
};
|
||||
ws.OnError += msg => { Logger.LogError("WebSocket Error Message: " + msg); };
|
||||
ws.OnClose += code => { Logger.Log("WebSocket Close: " + code); };
|
||||
await ws.Connect();
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
public override void Disconnect()
|
||||
{
|
||||
#if NET5_0_OR_GREATER
|
||||
clientWebSocket?.Stop(WebSocketCloseStatus.Empty, string.Empty);
|
||||
clientWebSocket?.Dispose();
|
||||
#elif UNITY_2020_3_OR_NEWER
|
||||
ws?.Close();
|
||||
ws = null;
|
||||
#endif
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
Disconnect();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public override void Send(byte[] packet)
|
||||
{
|
||||
#if NET5_0_OR_GREATER
|
||||
clientWebSocket?.Send(packet);
|
||||
#elif UNITY_2020_3_OR_NEWER
|
||||
if (ws.State == WebSocketState.Open)
|
||||
{
|
||||
ws.Send(packet);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
public override void Send(Packet packet) => Send(packet.ToBytes);
|
||||
public override Task SendAsync(byte[] packet) => Task.Run(() => Send(packet));
|
||||
protected override Task SendAsync(Packet packet) => SendAsync(packet.ToBytes);
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "bilibili.OpenBLive",
|
||||
"rootNamespace": "",
|
||||
"references": [],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
51
HMIcode/SmallProject/SmallProject.sln
Normal file
51
HMIcode/SmallProject/SmallProject.sln
Normal file
@ -0,0 +1,51 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.7.34024.191
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SmallProject", "SmallProject\SmallProject.csproj", "{7F07C712-757E-496D-8274-ABF4CBA88233}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenBLive", "OpenBLive\OpenBLive.csproj", "{49E5E897-C08C-47B4-A9A4-7776A44815B0}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yolov5Net.Scorer", "Yolov5Net.Scorer\Yolov5Net.Scorer.csproj", "{0BA03139-55E4-483E-B7B7-67849D56E74A}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|x64 = Debug|x64
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|x64 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{7F07C712-757E-496D-8274-ABF4CBA88233}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7F07C712-757E-496D-8274-ABF4CBA88233}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7F07C712-757E-496D-8274-ABF4CBA88233}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{7F07C712-757E-496D-8274-ABF4CBA88233}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{7F07C712-757E-496D-8274-ABF4CBA88233}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7F07C712-757E-496D-8274-ABF4CBA88233}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7F07C712-757E-496D-8274-ABF4CBA88233}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{7F07C712-757E-496D-8274-ABF4CBA88233}.Release|x64.Build.0 = Release|Any CPU
|
||||
{49E5E897-C08C-47B4-A9A4-7776A44815B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{49E5E897-C08C-47B4-A9A4-7776A44815B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{49E5E897-C08C-47B4-A9A4-7776A44815B0}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{49E5E897-C08C-47B4-A9A4-7776A44815B0}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{49E5E897-C08C-47B4-A9A4-7776A44815B0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{49E5E897-C08C-47B4-A9A4-7776A44815B0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{49E5E897-C08C-47B4-A9A4-7776A44815B0}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{49E5E897-C08C-47B4-A9A4-7776A44815B0}.Release|x64.Build.0 = Release|Any CPU
|
||||
{0BA03139-55E4-483E-B7B7-67849D56E74A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{0BA03139-55E4-483E-B7B7-67849D56E74A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0BA03139-55E4-483E-B7B7-67849D56E74A}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{0BA03139-55E4-483E-B7B7-67849D56E74A}.Debug|x64.Build.0 = Debug|x64
|
||||
{0BA03139-55E4-483E-B7B7-67849D56E74A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0BA03139-55E4-483E-B7B7-67849D56E74A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{0BA03139-55E4-483E-B7B7-67849D56E74A}.Release|x64.ActiveCfg = Release|x64
|
||||
{0BA03139-55E4-483E-B7B7-67849D56E74A}.Release|x64.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {50CBC801-D0DF-4382-9BBD-5065AEB7DF34}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
165
HMIcode/SmallProject/SmallProject/Aliyun/AiAssistant.cs
Normal file
165
HMIcode/SmallProject/SmallProject/Aliyun/AiAssistant.cs
Normal file
@ -0,0 +1,165 @@
|
||||
using NAudio.Wave;
|
||||
using Newtonsoft.Json;
|
||||
using SmallProject.Aliyun.Models;
|
||||
using SmallProject.Logger;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.WebSockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SmallProject.Aliyun
|
||||
{
|
||||
internal class AiAssistant
|
||||
{
|
||||
private static readonly string workspace_id = "llm-zfcwus2axt2ik8ya";
|
||||
private static readonly string apiKey = "sk-87479f81a3494310b1baf77aa72c21d9"; // 替换为你的API Key
|
||||
private static readonly string app_id = "mm_2f63ea3eca2b48e3a7f1e65fffc4";
|
||||
private static readonly string webSocketUrl = "wss://dashscope.aliyuncs.com/api-ws/v1/inference";
|
||||
private static readonly int sampleRate = 16000; // 设置采样率为16kHz
|
||||
private static ClientWebSocket socket;
|
||||
private static WaveInEvent waveInEvent;
|
||||
|
||||
private AssistantState state = AssistantState.None;
|
||||
public async Task Init()
|
||||
{
|
||||
string outputFilePath = Path.Combine(Directory.GetCurrentDirectory(), "output.wav");
|
||||
|
||||
//var recorder = new AudioRecorder(outputFilePath);
|
||||
//recorder.StartRecording();
|
||||
//JLog.Info("开始录音");
|
||||
//Thread.Sleep(2000);
|
||||
//recorder.StopRecording();
|
||||
//JLog.Info($"录音完成,文件已保存至: {outputFilePath}");
|
||||
|
||||
// 初始化WebSocket连接
|
||||
socket = new ClientWebSocket();
|
||||
socket.Options.SetRequestHeader("Authorization", $"Bearer {apiKey}");
|
||||
|
||||
await socket.ConnectAsync(new Uri(webSocketUrl), CancellationToken.None);
|
||||
JLog.Info("已连接到WebSocket服务器...");
|
||||
|
||||
// 发送Start指令
|
||||
var msg = new SocketMessage();
|
||||
msg.payload.input.workspace_id = workspace_id;
|
||||
msg.payload.input.app_id = app_id;
|
||||
string startMessage = JsonConvert.SerializeObject(msg);
|
||||
await SendMessage(socket, startMessage);
|
||||
|
||||
// 开始录音并实时发送音频数据
|
||||
StartRecordingAndSendData();
|
||||
|
||||
// 接收服务端返回的消息
|
||||
await ReceiveMessages(socket);
|
||||
}
|
||||
|
||||
void StartRecordingAndSendData()
|
||||
{
|
||||
waveInEvent = new WaveInEvent
|
||||
{
|
||||
WaveFormat = new WaveFormat(sampleRate, 1), // 单声道,16kHz
|
||||
BufferMilliseconds = 100 // 每次捕获100ms的数据
|
||||
};
|
||||
|
||||
waveInEvent.DataAvailable += WaveInEvent_DataAvailable;
|
||||
|
||||
waveInEvent.StartRecording();
|
||||
JLog.Info("开始录音...");
|
||||
}
|
||||
|
||||
private void WaveInEvent_DataAvailable(object? sender, WaveInEventArgs e)
|
||||
{
|
||||
//JLog.Info("here");
|
||||
if (socket.State == WebSocketState.Open&& state == AssistantState.Listening)
|
||||
{
|
||||
if (e.Buffer.Length == 0) return;
|
||||
// 直接发送PCM格式的音频数据
|
||||
socket.SendAsync(new ArraySegment<byte>(e.Buffer, 0, e.BytesRecorded),
|
||||
WebSocketMessageType.Binary, e.BytesRecorded < waveInEvent.WaveFormat.AverageBytesPerSecond / 10, CancellationToken.None);
|
||||
}
|
||||
}
|
||||
|
||||
async Task SendMessage(ClientWebSocket socket, string message)
|
||||
{
|
||||
var buffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes(message));
|
||||
await socket.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None);
|
||||
JLog.Info($"发送消息: {message}");
|
||||
}
|
||||
|
||||
async Task ReceiveMessages(ClientWebSocket socket)
|
||||
{
|
||||
var buffer = new byte[1024 * 4];
|
||||
while (socket.State == WebSocketState.Open)
|
||||
{
|
||||
var result = await socket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
|
||||
if (result.MessageType == WebSocketMessageType.Close)
|
||||
{
|
||||
await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None);
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
string receivedText = Encoding.UTF8.GetString(buffer, 0, result.Count);
|
||||
var res = JsonConvert.DeserializeObject<SocketReceive>(receivedText);
|
||||
if(res.payload.output.state.Equals(AssistantState.Listening.ToString()))
|
||||
{
|
||||
state = AssistantState.Listening;
|
||||
|
||||
}
|
||||
JLog.Info($"收到服务端消息: {receivedText}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
public class AudioRecorder
|
||||
{
|
||||
private readonly string _outputFilePath;
|
||||
private WaveInEvent _waveIn;
|
||||
private WaveFileWriter _writer;
|
||||
|
||||
public AudioRecorder(string outputFilePath)
|
||||
{
|
||||
_outputFilePath = outputFilePath;
|
||||
}
|
||||
|
||||
public void StartRecording()
|
||||
{
|
||||
if (_waveIn != null) return;
|
||||
|
||||
_waveIn = new WaveInEvent
|
||||
{
|
||||
WaveFormat = new WaveFormat(16000, 1) // 16kHz, 单声道(适合语音识别)
|
||||
};
|
||||
|
||||
_writer = new WaveFileWriter(_outputFilePath, _waveIn.WaveFormat);
|
||||
|
||||
_waveIn.DataAvailable += (s, e) =>
|
||||
{
|
||||
_writer.Write(e.Buffer, 0, e.BytesRecorded);
|
||||
};
|
||||
|
||||
_waveIn.RecordingStopped += (s, e) =>
|
||||
{
|
||||
_writer.Dispose();
|
||||
_writer = null;
|
||||
_waveIn.Dispose();
|
||||
_waveIn = null;
|
||||
};
|
||||
|
||||
_waveIn.StartRecording();
|
||||
}
|
||||
|
||||
public void StopRecording()
|
||||
{
|
||||
_waveIn?.StopRecording();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SmallProject.Aliyun.Models
|
||||
{
|
||||
public enum AssistantState
|
||||
{
|
||||
None,
|
||||
Listening,
|
||||
Speed
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Printing;
|
||||
using System.Security.Policy;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SmallProject.Aliyun.Models
|
||||
{
|
||||
internal class SocketMessage
|
||||
{
|
||||
public Header header { get; set; } = new Header();
|
||||
|
||||
public Payload payload { get; set; } = new Payload();
|
||||
}
|
||||
|
||||
public class Header
|
||||
{
|
||||
public string action { get; set; } = "run-task";
|
||||
|
||||
public string task_id { get; set; } = Guid.NewGuid().ToString("N");
|
||||
|
||||
public string streaming { get; set; } = "duplex";
|
||||
}
|
||||
|
||||
public class Payload
|
||||
{
|
||||
public string task_group { get; set; } = "aigc";
|
||||
|
||||
public string task { get; set; } = "multimodal-generation";
|
||||
|
||||
public string function { get; set; } = "generation";
|
||||
|
||||
public string model { get; set; } = "multimodal-dialog";
|
||||
|
||||
public Input input { get; set; } = new Input();
|
||||
|
||||
public Parameters parameters { get; set; } = new Parameters();
|
||||
}
|
||||
|
||||
public class Input
|
||||
{
|
||||
public string directive { get; set; } = "Start";
|
||||
|
||||
public string workspace_id { get; set; }
|
||||
|
||||
public string app_id { get; set; }
|
||||
|
||||
}
|
||||
|
||||
public class Parameters
|
||||
{
|
||||
public Upstream upstream { get; set; } = new Upstream();
|
||||
|
||||
public Client_info client_info = new Client_info();
|
||||
}
|
||||
|
||||
public class Upstream
|
||||
{
|
||||
public string type { get; set; } = "AudioOnly";
|
||||
|
||||
public string mode { get; set; } = "duplex";
|
||||
}
|
||||
|
||||
public class Client_info
|
||||
{
|
||||
public string user_id { get; set; } = Guid.NewGuid().ToString("N");
|
||||
public Device device { get; set; } = new Device();
|
||||
}
|
||||
|
||||
public class Device
|
||||
{
|
||||
public string uuid { get; set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SmallProject.Aliyun.Models
|
||||
{
|
||||
internal class SocketReceive
|
||||
{
|
||||
public ReceiveHeader header { get; set; } = new ReceiveHeader();
|
||||
|
||||
public ReceivePayload payload { get; set; } = new ReceivePayload();
|
||||
}
|
||||
|
||||
public class ReceiveHeader
|
||||
{
|
||||
public string @event { get; set; } = string.Empty;
|
||||
|
||||
public string task_id { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class ReceivePayload
|
||||
{
|
||||
public Output output { get; set; } = new Output();
|
||||
public Usage usage { get; set; } = new Usage();
|
||||
}
|
||||
|
||||
public class Output
|
||||
{
|
||||
public string @event{ get; set; } = string.Empty;
|
||||
public string dialog_id { get; set; } = string.Empty;
|
||||
public string state { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class Usage
|
||||
{
|
||||
public int invoke { get; set; } = 0;
|
||||
|
||||
public int model_x { get; set; } = 0;
|
||||
}
|
||||
}
|
13
HMIcode/SmallProject/SmallProject/App.xaml
Normal file
13
HMIcode/SmallProject/SmallProject/App.xaml
Normal file
@ -0,0 +1,13 @@
|
||||
<Application x:Class="SmallProject.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:SmallProject"
|
||||
StartupUri="MainWindow.xaml">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/Rubyer;component/Themes/Generic.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
28
HMIcode/SmallProject/SmallProject/App.xaml.cs
Normal file
28
HMIcode/SmallProject/SmallProject/App.xaml.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using SmallProject.Configs;
|
||||
using SmallProject.Devices.Arm;
|
||||
using SmallProject.Serials;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Configuration;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
||||
namespace SmallProject
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for App.xaml
|
||||
/// </summary>
|
||||
public partial class App : Application
|
||||
{
|
||||
public static JConfiguration JConfig { get; set; }
|
||||
public static Core Core { get; set; }
|
||||
|
||||
public App()
|
||||
{
|
||||
JConfig = ConfigResposity.ReadConfigs();
|
||||
Core = new Core();
|
||||
}
|
||||
}
|
||||
}
|
10
HMIcode/SmallProject/SmallProject/AssemblyInfo.cs
Normal file
10
HMIcode/SmallProject/SmallProject/AssemblyInfo.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using System.Windows;
|
||||
|
||||
[assembly: ThemeInfo(
|
||||
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
|
||||
//(used if a resource is not found in the page,
|
||||
// or application resource dictionaries)
|
||||
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
|
||||
//(used if a resource is not found in the page,
|
||||
// app, or any theme specific resource dictionaries)
|
||||
)]
|
239
HMIcode/SmallProject/SmallProject/BiliBili/BStationLive.cs
Normal file
239
HMIcode/SmallProject/SmallProject/BiliBili/BStationLive.cs
Normal file
@ -0,0 +1,239 @@
|
||||
using OpenBLive.Client;
|
||||
using OpenBLive.Client.Data;
|
||||
using OpenBLive.Runtime.Utilities;
|
||||
using OpenBLive.Runtime;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using OpenBLive.Runtime.Data;
|
||||
using SmallProject.Logger;
|
||||
using System.Threading;
|
||||
|
||||
namespace SmallProject.BiliBili
|
||||
{
|
||||
internal class BStationLive
|
||||
{
|
||||
//初始化于测试的参数
|
||||
public const string AccessKeyId = "";//填入你的accessKeyId,可以在直播创作者服务中心【个人资料】页面获取(https://open-live.bilibili.com/open-manage)
|
||||
public const string AccessKeySecret = "";//填入你的accessKeySecret,可以在直播创作者服务中心【个人资料】页面获取(https://open-live.bilibili.com/open-manage)
|
||||
public const string AppId = "";//填入你的appId,可以在直播创作者服务中心【我的项目】页面创建应用后获取(https://open-live.bilibili.com/open-manage)
|
||||
public const string Code = "";//填入你的主播身份码Code,可以在互动玩法首页,右下角【身份码】处获取(互玩首页:https://play-live.bilibili.com/)
|
||||
|
||||
public static IBApiClient bApiClient = new BApiClient();
|
||||
public static string game_id = string.Empty;
|
||||
public static bool IsLive = false;
|
||||
|
||||
public async Task Init()
|
||||
{
|
||||
//是否为测试环境(一般用户可无视,给专业对接测试使用)
|
||||
BApi.isTestEnv = false;
|
||||
|
||||
SignUtility.accessKeyId = AccessKeyId;
|
||||
SignUtility.accessKeySecret = AccessKeySecret;
|
||||
var appId = AppId;
|
||||
var code = Code;
|
||||
|
||||
|
||||
var startInfoLive = new AppStartInfo();
|
||||
if (!string.IsNullOrEmpty(appId))
|
||||
{
|
||||
startInfoLive = await bApiClient.StartInteractivePlay(code, appId);
|
||||
if (startInfoLive?.Code != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var gameId = startInfoLive?.Data?.GameInfo?.GameId;
|
||||
if (gameId != null)
|
||||
{
|
||||
game_id = gameId;
|
||||
IsLive = true;
|
||||
//JLog.InfoLive("成功开启,开始心跳,场次ID: " + gameId);
|
||||
|
||||
//心跳API(用于保持在线)
|
||||
InteractivePlayHeartBeat m_PlayHeartBeat = new InteractivePlayHeartBeat(gameId);
|
||||
m_PlayHeartBeat.HeartBeatError += M_PlayHeartBeat_HeartBeatError;
|
||||
m_PlayHeartBeat.HeartBeatSucceed += M_PlayHeartBeat_HeartBeatSucceed;
|
||||
m_PlayHeartBeat.Start();
|
||||
|
||||
//长链接(用户持续接收服务器推送消息)
|
||||
WebSocketBLiveClient m_WebSocketBLiveClient;
|
||||
m_WebSocketBLiveClient = new WebSocketBLiveClient(startInfoLive.GetWssLink(), startInfoLive.GetAuthBody());
|
||||
m_WebSocketBLiveClient.OnDanmaku += WebSocketBLiveClientOnDanmaku;//弹幕事件
|
||||
m_WebSocketBLiveClient.OnGift += WebSocketBLiveClientOnGift;//礼物事件
|
||||
m_WebSocketBLiveClient.OnGuardBuy += WebSocketBLiveClientOnGuardBuy;//大航海事件
|
||||
m_WebSocketBLiveClient.OnSuperChat += WebSocketBLiveClientOnSuperChat;//SC事件
|
||||
m_WebSocketBLiveClient.OnLike += M_WebSocketBLiveClient_OnLike;//点赞事件(点赞需要直播间开播才会触发推送)
|
||||
m_WebSocketBLiveClient.OnEnter += M_WebSocketBLiveClient_OnEnter;//观众进入房间事件
|
||||
m_WebSocketBLiveClient.OnLiveStart += M_WebSocketBLiveClient_OnLiveStart;//直播间开始直播事件
|
||||
m_WebSocketBLiveClient.OnLiveEnd += M_WebSocketBLiveClient_OnLiveEnd;//直播间停止直播事件
|
||||
//m_WebSocketBLiveClient.Connect();//正常连接
|
||||
m_WebSocketBLiveClient.Connect(TimeSpan.FromSeconds(10));//失败后30秒重连
|
||||
}
|
||||
else
|
||||
{
|
||||
JLog.InfoLive("开启玩法错误: " + startInfoLive.ToString());
|
||||
}
|
||||
//await Task.Run(async () =>
|
||||
//{
|
||||
// var closeTime = int.Parse(closeTimeStr);
|
||||
// await Task.Delay(closeTime * 1000);
|
||||
// var ret = await bApiClient.EndInteractivePlay(appId, gameId);
|
||||
// IsLive = false;
|
||||
// Console.WriteLine("关闭玩法: " + ret.ToString());
|
||||
// return;
|
||||
//});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private static void M_WebSocketBLiveClient_OnLiveEnd(LiveEnd liveEnd)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder($"直播间[{liveEnd.room_id}]直播结束,分区ID:【{liveEnd.area_id}】,标题为【{liveEnd.title}】");
|
||||
JLog.InfoLive(sb.ToString());
|
||||
}
|
||||
|
||||
private static void M_WebSocketBLiveClient_OnLiveStart(LiveStart liveStart)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder($"直播间[{liveStart.room_id}]开始直播,分区ID:【{liveStart.area_id}】,标题为【{liveStart.title}】");
|
||||
JLog.InfoLive(sb.ToString());
|
||||
}
|
||||
|
||||
private static void M_WebSocketBLiveClient_OnEnter(Enter enter)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder($"用户[{enter.uname}]进入房间");
|
||||
JLog.InfoLive(sb.ToString());
|
||||
}
|
||||
|
||||
private static void M_WebSocketBLiveClient_OnLike(Like like)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder($"用户[{like.uname}]点赞了{like.unamelike_count}次");
|
||||
JLog.InfoLive(sb.ToString());
|
||||
}
|
||||
|
||||
private static void M_PlayHeartBeat_HeartBeatSucceed()
|
||||
{
|
||||
//JLog.InfoLive("心跳成功");
|
||||
}
|
||||
|
||||
private static void M_PlayHeartBeat_HeartBeatError(string json)
|
||||
{
|
||||
JsonConvert.DeserializeObject<EmptyInfo>(json);
|
||||
//JLog.InfoLive("心跳失败" + json);
|
||||
}
|
||||
|
||||
private static void WebSocketBLiveClientOnSuperChat(SuperChat superChat)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder($"用户[{superChat.userName}]发送了{superChat.rmb}元的醒目留言内容:{superChat.message}");
|
||||
JLog.InfoLive(sb.ToString());
|
||||
}
|
||||
|
||||
private static void WebSocketBLiveClientOnGuardBuy(Guard guard)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder($"用户[{guard.userInfo.userName}]充值了{(guard.guardUnit == "月" ? (guard.guardNum + "个月") : guard.guardUnit.TrimStart('*'))}[{(guard.guardLevel == 1 ? "总督" : guard.guardLevel == 2 ? "提督" : "舰长")}]大航海");
|
||||
JLog.InfoLive(sb.ToString());
|
||||
}
|
||||
|
||||
private static void WebSocketBLiveClientOnGift(SendGift sendGift)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder($"用户[{sendGift.userName}]赠送了{sendGift.giftNum}个[{sendGift.giftName}]");
|
||||
JLog.InfoLive(sb.ToString());
|
||||
|
||||
}
|
||||
|
||||
private static void WebSocketBLiveClientOnDanmaku(Dm dm)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder($"用户[{dm.userName}]发送弹幕:{dm.msg}");
|
||||
JLog.InfoLive(sb.ToString());
|
||||
var curr = App.Core.Jyker.currentJoints;
|
||||
var joint1 = curr.a[0];
|
||||
var joint2 = curr.a[1];
|
||||
var joint3 = curr.a[2];
|
||||
switch (dm.msg)
|
||||
{
|
||||
case "111":
|
||||
if (joint1 > 30)
|
||||
{
|
||||
JLog.InfoLive("左不了了");
|
||||
return;
|
||||
}
|
||||
App.Core.Jyker.Move(new double[]{joint1+20, curr.a[1], curr.a[2]
|
||||
, curr.a[3], curr.a[4], curr.a[5] });
|
||||
break;
|
||||
case "222":
|
||||
if (joint1 < -90)
|
||||
{
|
||||
JLog.InfoLive("右不了了");
|
||||
return;
|
||||
}
|
||||
App.Core.Jyker.Move(new double[]{ joint1-20, curr.a[1], curr.a[2]
|
||||
, curr.a[3], curr.a[4], curr.a[5] });
|
||||
break;
|
||||
case "333":
|
||||
if (joint2 > 150)
|
||||
{
|
||||
JLog.InfoLive("前不了了");
|
||||
return;
|
||||
}
|
||||
App.Core.Jyker.Move(new double[]{ curr.a[0], joint2+10, joint3-10
|
||||
, curr.a[3], curr.a[4], curr.a[5] });
|
||||
break;
|
||||
case "444":
|
||||
if (joint2 - 1 < -90)
|
||||
{
|
||||
JLog.InfoLive("退不了了");
|
||||
return;
|
||||
}
|
||||
App.Core.Jyker.Move(new double[]{ curr.a[0], joint2-10, joint3+10
|
||||
, curr.a[3], curr.a[4], curr.a[5] });
|
||||
break;
|
||||
case "555":
|
||||
if (curr.a[4] - 10 < -90)
|
||||
{
|
||||
JLog.InfoLive("脖子没那么长啦");
|
||||
return;
|
||||
}
|
||||
App.Core.Jyker.Move(new double[]{ curr.a[0], curr.a[1], curr.a[2]
|
||||
, curr.a[3], curr.a[4]-20, curr.a[5] });
|
||||
break;
|
||||
case "666":
|
||||
if (curr.a[4] + 10 > 90)
|
||||
{
|
||||
JLog.InfoLive("脖子没那么长啦");
|
||||
return;
|
||||
}
|
||||
App.Core.Jyker.Move(new double[]{ curr.a[0], curr.a[1], curr.a[2]
|
||||
, curr.a[3], curr.a[4]+20, curr.a[5] });
|
||||
break;
|
||||
case "777":
|
||||
App.Core.Jyker.Move(new double[]{curr.a[0], curr.a[1], curr.a[2]
|
||||
, curr.a[3], curr.a[4], 120 });
|
||||
Thread.Sleep(1000);
|
||||
App.Core.Jyker.GetStatus(6);
|
||||
Thread.Sleep(200);
|
||||
JLog.Info($"Current {App.Core.Jyker.motorJ[5].Current}");
|
||||
if (App.Core.Jyker.motorJ[5].Current < 0.5)
|
||||
{
|
||||
JLog.Info("没抓到东西");
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
JLog.Info("抓住了");
|
||||
return;
|
||||
}
|
||||
case "888":
|
||||
App.Core.Jyker.Move(new double[]{curr.a[0], curr.a[1], curr.a[2]
|
||||
, curr.a[3], curr.a[4], 0 });
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
71
HMIcode/SmallProject/SmallProject/Configs/ConfigResposity.cs
Normal file
71
HMIcode/SmallProject/SmallProject/Configs/ConfigResposity.cs
Normal file
@ -0,0 +1,71 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
using Formatting = Newtonsoft.Json.Formatting;
|
||||
|
||||
namespace SmallProject.Configs
|
||||
{
|
||||
public class ConfigResposity
|
||||
{
|
||||
private static string DataDic = "./DATA";
|
||||
private static string ReadConfigFile = $"{DataDic}/Config.data";
|
||||
|
||||
/// <summary>
|
||||
/// 读取产型数据
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static JConfiguration ReadConfigs()
|
||||
{
|
||||
lock (DataDic)
|
||||
{
|
||||
if (!Directory.Exists(DataDic))
|
||||
{
|
||||
Directory.CreateDirectory(DataDic);
|
||||
}
|
||||
if (!File.Exists(ReadConfigFile))
|
||||
{
|
||||
using (File.Create(ReadConfigFile)) { }
|
||||
}
|
||||
var json = File.ReadAllText(ReadConfigFile, Encoding.UTF8);
|
||||
var conf = JsonConvert.DeserializeObject<JConfiguration>(json);
|
||||
if (conf == null)
|
||||
{
|
||||
return new JConfiguration();
|
||||
}
|
||||
return conf;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写入产型数据
|
||||
/// </summary>
|
||||
/// <param name="productTypes"></param>
|
||||
public static void WriteConfigs(JConfiguration conf)
|
||||
{
|
||||
lock (DataDic)
|
||||
{
|
||||
if (!Directory.Exists(DataDic))
|
||||
{
|
||||
Directory.CreateDirectory(DataDic);
|
||||
}
|
||||
if (!File.Exists(ReadConfigFile))
|
||||
{
|
||||
File.Create(ReadConfigFile);
|
||||
}
|
||||
using (FileStream fileStream = File.Create(ReadConfigFile)) //打开文件流
|
||||
{
|
||||
var str = JsonConvert.SerializeObject(conf, Formatting.Indented); //序列化工程文件
|
||||
byte[] by = ASCIIEncoding.UTF8.GetBytes(str); //把序列化的工程文件转成字节流
|
||||
fileStream.Write(by, 0, by.Length); //字节流写入到文件
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
54
HMIcode/SmallProject/SmallProject/Configs/JConfiguration.cs
Normal file
54
HMIcode/SmallProject/SmallProject/Configs/JConfiguration.cs
Normal file
@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SmallProject.Configs
|
||||
{
|
||||
public class JConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// J1 减速比
|
||||
/// </summary>
|
||||
public int ReductionJ1 { get; set; } = 30;
|
||||
/// <summary>
|
||||
/// J2 减速比
|
||||
/// </summary>
|
||||
public int ReductionJ2 { get; set; } = 50;
|
||||
/// <summary>
|
||||
/// J3 减速比
|
||||
/// </summary>
|
||||
public int ReductionJ3 { get; set; } = 30;
|
||||
/// <summary>
|
||||
/// J4 减速比
|
||||
/// </summary>
|
||||
public int ReductionJ4 { get; set; } = 30;
|
||||
/// <summary>
|
||||
/// J5 减速比
|
||||
/// </summary>
|
||||
public int ReductionJ5 { get; set; } = 30;
|
||||
/// <summary>
|
||||
/// J6 减速比
|
||||
/// </summary>
|
||||
public int ReductionJ6 { get; set; } = 1;
|
||||
|
||||
// D_BASE = 0, L_BASE = 161.5, L_ARM = 170, D_ELBOW = 70, L_FOREARM = 117, L_WRIST = 97
|
||||
public float L_BASE { get; set; } = 0;
|
||||
public float D_BASE { get; set; } = 161.5f;
|
||||
public float L_ARM { get; set; } = 170;
|
||||
public float D_ELBOW { get; set; } = 70;
|
||||
public float L_FOREARM { get; set; } = 117;
|
||||
public float L_WRIST { get; set; } = 97;
|
||||
|
||||
//机械臂速度限制 单位 raw/s
|
||||
public float Speed { get; set; } = 0.2f;
|
||||
//最大电流值 单位(A)
|
||||
public float Current { get; set; } = 1f;
|
||||
|
||||
//小智连接的端点
|
||||
public string EndpointUrl { get; set; } = "wss://api.xiaozhi.me/mcp/?token=eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjE5NDgyOCwiYWdlbnRJZCI6MTE1MTY5LCJlbmRwb2ludElkIjoiYWdlbnRfMTE1MTY5IiwicHVycG9zZSI6Im1jcC1lbmRwb2ludCIsImlhdCI6MTc1MjgwMTE1NX0.iNZDJOTaAz1pzpvTxnsX42mixld2zbmyAiOvof4q0lv_3tMGXoVa8YK7lCO4qxWc3n1H4yd-dAOg95cW2kjkSQ";
|
||||
|
||||
public string FindModelPath { get; set; } = "DETECT_IMAGE";
|
||||
}
|
||||
}
|
43
HMIcode/SmallProject/SmallProject/Core.cs
Normal file
43
HMIcode/SmallProject/SmallProject/Core.cs
Normal file
@ -0,0 +1,43 @@
|
||||
using SmallProject.Aliyun;
|
||||
using SmallProject.Configs;
|
||||
using SmallProject.Devices.Arm;
|
||||
using SmallProject.Serials;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SmallProject
|
||||
{
|
||||
public class Core
|
||||
{
|
||||
|
||||
public string ViewName = "";
|
||||
|
||||
public Core() {
|
||||
Serial = new Serial();
|
||||
Jyker = new JykerArm();
|
||||
|
||||
|
||||
//定时任务
|
||||
Task.Run(() =>
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
Loop?.Invoke();
|
||||
Thread.Sleep(20);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public Serial Serial { get; set; }
|
||||
|
||||
public JykerArm Jyker { get; set; }
|
||||
|
||||
public event Action Loop;
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SmallProject.Devices.Arm.CtrlStep
|
||||
{
|
||||
/// <summary>
|
||||
/// 已包含减速器的步进电机
|
||||
/// </summary>
|
||||
public class CtrlStepMotor
|
||||
{
|
||||
/// <summary>
|
||||
/// 回零之后偏差角度
|
||||
/// </summary>
|
||||
public double OffsetAngle { get; set; } = 0;
|
||||
/// <summary>
|
||||
/// 最大扭转角
|
||||
/// </summary>
|
||||
public double AngleLimitMax { get; set; } = 180;
|
||||
/// <summary>
|
||||
/// 最小扭转角
|
||||
/// </summary>
|
||||
public double AngleLimitMin { get; set; } = -0.01;
|
||||
|
||||
/// <summary>
|
||||
/// 加速度
|
||||
/// </summary>
|
||||
public double Acceleration { get; set; } = 0;
|
||||
/// <summary>
|
||||
/// 当前电流(比例得出力矩)
|
||||
/// </summary>
|
||||
public float Current { get; set; }
|
||||
/// <summary>
|
||||
/// 速度
|
||||
/// </summary>
|
||||
public double Velocity { get; set; } = 50;
|
||||
/// <summary>
|
||||
/// 当前角度
|
||||
/// </summary>
|
||||
public double Angle { get; set; } = 0;
|
||||
/// <summary>
|
||||
/// 减速比
|
||||
/// </summary>
|
||||
public double Reduction { get; set; } = 30;
|
||||
/// <summary>
|
||||
/// 电机方向
|
||||
/// </summary>
|
||||
public int Direction { get; set; } = 1;
|
||||
/// <summary>
|
||||
/// 是否执行完命令
|
||||
/// </summary>
|
||||
public bool IsFinish { get; set; } = true;
|
||||
|
||||
}
|
||||
}
|
175
HMIcode/SmallProject/SmallProject/Devices/Arm/JykerArm.cs
Normal file
175
HMIcode/SmallProject/SmallProject/Devices/Arm/JykerArm.cs
Normal file
@ -0,0 +1,175 @@
|
||||
using SmallProject.Configs;
|
||||
using SmallProject.Devices.Arm.CtrlStep;
|
||||
using SmallProject.Devices.Arm.Kinematic.Models;
|
||||
using SmallProject.Devices.Arm.Kinematic;
|
||||
using SmallProject.Logger;
|
||||
using SmallProject.Serials.Slcan;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SixLabors.ImageSharp.Processing.Processors.Transforms;
|
||||
using Microsoft.VisualBasic.Logging;
|
||||
|
||||
namespace SmallProject.Devices.Arm
|
||||
{
|
||||
public class JykerArm
|
||||
{
|
||||
//角度转换成脉冲 3200 为 360°
|
||||
public const double DEG_TO_PULSE = 8.888888889;
|
||||
|
||||
public Dof6kinematic dof6Solver;
|
||||
public CtrlStepMotor[] motorJ;
|
||||
public Joint6D_t currentJoints;
|
||||
public Joint6D_t prepareJoints;
|
||||
public Pose6D_t preparePose6D;
|
||||
|
||||
public JykerArm() {
|
||||
dof6Solver = new Dof6kinematic(App.JConfig);
|
||||
motorJ = new CtrlStepMotor[6] {
|
||||
new CtrlStepMotor(){ AngleLimitMin =-90.1,AngleLimitMax = 90.1,Reduction = App.JConfig.ReductionJ1}
|
||||
,new CtrlStepMotor(){ AngleLimitMin = -90.1,AngleLimitMax = 90.1 , Direction =-1, OffsetAngle = -90,Reduction = App.JConfig.ReductionJ2 }
|
||||
,new CtrlStepMotor(){ AngleLimitMin = -0.1,AngleLimitMax= 180.1,OffsetAngle = 180 , Direction = 1,Reduction = App.JConfig.ReductionJ3}
|
||||
,new CtrlStepMotor(){ AngleLimitMin = -0.1,AngleLimitMax = 180.1 , Direction =-1,Reduction = App.JConfig.ReductionJ4}
|
||||
,new CtrlStepMotor(){ AngleLimitMin = -90.1 , AngleLimitMax = 90.1,Reduction = App.JConfig.ReductionJ5}
|
||||
,new CtrlStepMotor() { AngleLimitMin = -180.1,AngleLimitMax = 180.1,Reduction = App.JConfig.ReductionJ6}
|
||||
};
|
||||
preparePose6D = new Pose6D_t();
|
||||
prepareJoints = Joint6D_t.defult;
|
||||
currentJoints = Joint6D_t.defult;
|
||||
}
|
||||
|
||||
public void LoopStatus()
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
GetStatus(i + 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void GetStatus(int Id)
|
||||
{
|
||||
var motor = motorJ[Id - 1];
|
||||
motor.Current = 0;
|
||||
var frame = SlcanParser.ParseSlcanFrameStr(Id, 0x21);
|
||||
App.Core?.Serial?.PushDataToQueue(frame);
|
||||
}
|
||||
|
||||
//更新电流
|
||||
public void RecieveCurrent(int Id,float value)
|
||||
{
|
||||
var motor = motorJ[Id - 1];
|
||||
motor.Current = Math.Abs(value);
|
||||
}
|
||||
//更新速度
|
||||
public void RecieveVelocity(int Id, float velocity)
|
||||
{
|
||||
var motor = motorJ[Id - 1];
|
||||
motor.Velocity = Math.Abs(velocity);
|
||||
}
|
||||
//更新位置
|
||||
public void RecievePos(int Id, float value,bool isFinish)
|
||||
{
|
||||
var motor = motorJ[Id - 1];
|
||||
motor.Angle = value/ motor.Reduction;
|
||||
motor.IsFinish = isFinish;
|
||||
JLog.Info(""+motor.Angle);
|
||||
}
|
||||
|
||||
//更新机械臂位置
|
||||
public void Move(double[] angles)
|
||||
{
|
||||
if (angles.Length!=6)
|
||||
{
|
||||
JLog.Info("机械臂角度参数缺失");
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < angles.Length; i++)
|
||||
{
|
||||
if (angles[i] > motorJ[i].AngleLimitMax
|
||||
|| angles[i] < motorJ[i].AngleLimitMin)
|
||||
{
|
||||
JLog.Info("机械臂设置角度超限");
|
||||
return;
|
||||
}
|
||||
}
|
||||
var speed = App.JConfig.Speed;
|
||||
if (speed <= 0)
|
||||
{
|
||||
JLog.Info("速度设置为0 ,请从新设置");
|
||||
return;
|
||||
}
|
||||
|
||||
//设置新坐标值
|
||||
prepareJoints = new Joint6D_t(angles);
|
||||
dof6Solver.SolveFK(prepareJoints, preparePose6D);
|
||||
|
||||
var angleList = (prepareJoints - currentJoints).a;
|
||||
var maxAngle = angleList.Max(t => Math.Abs(t));
|
||||
var time = maxAngle / speed;
|
||||
JLog.Info($"time:{time}");
|
||||
for (int i = 0; i < angles.Length; i++)
|
||||
{
|
||||
if (i == 5) time = 1;
|
||||
//if (i == 3||i==4||i==5 ) continue;
|
||||
motorJ[i].Angle = prepareJoints.a[i] - Joint6D_t.defult.a[i];
|
||||
var moveAngle = Convert.ToSingle(motorJ[i].Angle * motorJ[i].Direction * motorJ[i].Reduction / 360);
|
||||
|
||||
var frame = SlcanParser.ParseSlcanFrameStr(i + 1, 0x06
|
||||
, moveAngle, (float)time);
|
||||
JLog.Info($"moveAngle[{i}]:{moveAngle} time: {time}");
|
||||
App.Core.Serial.PushDataToQueue(frame);
|
||||
|
||||
}
|
||||
//更新位置
|
||||
currentJoints = prepareJoints;
|
||||
|
||||
|
||||
|
||||
}
|
||||
//设置当前位置为零位
|
||||
public void ApplyHomePosition()
|
||||
{
|
||||
var canFrame = SlcanParser.ParseSlcanFrameStr(0, 0x15);
|
||||
App.Core.Serial?.PushDataToQueue(canFrame);
|
||||
}
|
||||
//设置立刻停止
|
||||
public void StopNow()
|
||||
{
|
||||
var canFrame = SlcanParser.ParseSlcanFrameStr(0, 0x04);
|
||||
App.Core.Serial?.PushDataToQueue(canFrame);
|
||||
}
|
||||
//生成逆解解算结果
|
||||
public bool SolveIK(double[] vals)
|
||||
{
|
||||
var preparePose6D = new Pose6D_t(vals);
|
||||
var res =dof6Solver.AfterSolveIK(preparePose6D, prepareJoints, currentJoints, motorJ, out Joint6D_t _outputJoints);
|
||||
if(res)
|
||||
{
|
||||
prepareJoints = _outputJoints;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
//设置轴的最大限制电流
|
||||
public void SetLimitCurrent(int Id,float current)
|
||||
{
|
||||
if(current>3000)
|
||||
{
|
||||
JLog.Info("电流最大设置为3000mA.");
|
||||
return;
|
||||
}
|
||||
if(current<=0)
|
||||
{
|
||||
JLog.Info("电流设置值有误.");
|
||||
return;
|
||||
}
|
||||
var canFrame = SlcanParser.ParseSlcanFrameStr(Id, 0x12, current / 1000);
|
||||
App.Core.Serial?.PushDataToQueue(canFrame);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,601 @@
|
||||
using SmallProject.Configs;
|
||||
using SmallProject.Devices.Arm.CtrlStep;
|
||||
using SmallProject.Devices.Arm.Kinematic.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SmallProject.Devices.Arm.Kinematic
|
||||
{
|
||||
public class Dof6kinematic
|
||||
{
|
||||
public const double RAD_TO_DEG = 57.295777754771045f;
|
||||
double[,] DH_matrix;
|
||||
double[] L1_base = new double[3];
|
||||
double[] L2_arm = new double[3];
|
||||
double[] L3_elbow = new double[3];
|
||||
double[] L6_wrist = new double[3];
|
||||
|
||||
double l_se_2;
|
||||
double l_se;
|
||||
double l_ew_2;
|
||||
double l_ew;
|
||||
double atan_e;
|
||||
|
||||
const double M_PI = Math.PI;
|
||||
const double M_PI_2 = Math.PI / 2;
|
||||
|
||||
JConfiguration armConfig;
|
||||
|
||||
void MatMultiply(double[] _matrix1, double[] _matrix2, double[] _matrixOut
|
||||
, int _m, int _l, int _n)
|
||||
{
|
||||
double tmp;
|
||||
int i, j, k;
|
||||
for (i = 0; i < _m; i++)
|
||||
{
|
||||
for (j = 0; j < _n; j++)
|
||||
{
|
||||
tmp = 0.0f;
|
||||
for (k = 0; k < _l; k++)
|
||||
{
|
||||
tmp += _matrix1[_l * i + k] * _matrix2[_n * k + j];
|
||||
}
|
||||
_matrixOut[_n * i + j] = tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RotMatToEulerAngle(double[] _rotationM, double[] _eulerAngles)
|
||||
{
|
||||
double A, B, C, cb;
|
||||
|
||||
if (Math.Abs(_rotationM[6]) >= 1.0 - 0.0001)
|
||||
{
|
||||
if (_rotationM[6] < 0)
|
||||
{
|
||||
A = 0.0f;
|
||||
B = Math.PI / 2;
|
||||
C = Math.Atan2(_rotationM[1], _rotationM[4]);
|
||||
}
|
||||
else
|
||||
{
|
||||
A = 0.0f;
|
||||
B = -Math.PI / 2;
|
||||
C = -Math.Atan2(_rotationM[1], _rotationM[4]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
B = Math.Atan2(-_rotationM[6], Math.Sqrt(_rotationM[0] * _rotationM[0] + _rotationM[3] * _rotationM[3]));
|
||||
cb = Math.Cos(B);
|
||||
A = Math.Atan2(_rotationM[3] / cb, _rotationM[0] / cb);
|
||||
C = Math.Atan2(_rotationM[7] / cb, _rotationM[8] / cb);
|
||||
}
|
||||
|
||||
_eulerAngles[3] = C;
|
||||
_eulerAngles[4] = B;
|
||||
_eulerAngles[5] = A;
|
||||
}
|
||||
|
||||
void EulerAngleToRotMat(double[] _eulerAngles, double[] _rotationM)
|
||||
{
|
||||
double ca, cb, cc, sa, sb, sc;
|
||||
|
||||
cc = Math.Cos(_eulerAngles[0]);
|
||||
cb = Math.Cos(_eulerAngles[1]);
|
||||
ca = Math.Cos(_eulerAngles[2]);
|
||||
sc = Math.Sin(_eulerAngles[0]);
|
||||
sb = Math.Sin(_eulerAngles[1]);
|
||||
sa = Math.Sin(_eulerAngles[2]);
|
||||
|
||||
_rotationM[0] = ca * cb;
|
||||
_rotationM[1] = ca * sb * sc - sa * cc;
|
||||
_rotationM[2] = ca * sb * cc + sa * sc;
|
||||
_rotationM[3] = sa * cb;
|
||||
_rotationM[4] = sa * sb * sc + ca * cc;
|
||||
_rotationM[5] = sa * sb * cc - ca * sc;
|
||||
_rotationM[6] = -sb;
|
||||
_rotationM[7] = cb * sc;
|
||||
_rotationM[8] = cb * cc;
|
||||
}
|
||||
|
||||
public Dof6kinematic(JConfiguration armConfig)
|
||||
{
|
||||
this.armConfig = armConfig;
|
||||
|
||||
double[,] tmp_DH_matrix = new double[6, 4]{
|
||||
{ 0.0f, armConfig.L_BASE, armConfig.D_BASE, -Math.PI/2},
|
||||
{ -Math.PI/2, 0.0f, armConfig.L_ARM, 0.0f},
|
||||
{ Math.PI/2, armConfig.D_ELBOW, 0.0f, Math.PI/2},
|
||||
{ 0.0f, armConfig.L_FOREARM, 0.0f, -Math.PI/2},
|
||||
{ 0.0f, 0.0f, 0.0f, Math.PI/2},
|
||||
{ 0.0f, armConfig.L_WRIST, 0.0f, 0.0f}
|
||||
};
|
||||
DH_matrix = tmp_DH_matrix;
|
||||
|
||||
L1_base = new double[] { armConfig.D_BASE, -armConfig.L_BASE, 0.0f };
|
||||
L2_arm = new double[] { armConfig.L_ARM, 0.0f, 0.0f };
|
||||
L3_elbow = new double[] { -armConfig.D_ELBOW, 0.0f, armConfig.L_FOREARM };
|
||||
L6_wrist = new double[] { 0.0f, 0.0f, armConfig.L_WRIST };
|
||||
|
||||
l_se_2 = armConfig.L_ARM * armConfig.L_ARM;
|
||||
l_se = armConfig.L_ARM;
|
||||
l_ew_2 = armConfig.L_FOREARM * armConfig.L_FOREARM + armConfig.D_ELBOW * armConfig.D_ELBOW;
|
||||
l_ew = 0;
|
||||
atan_e = 0;
|
||||
|
||||
|
||||
}
|
||||
//正解
|
||||
public bool SolveFK(Joint6D_t joint, Pose6D_t pose)
|
||||
{
|
||||
double[] q_in = new double[6];
|
||||
double[] q = new double[6];
|
||||
double cosq, sinq;
|
||||
double cosa, sina;
|
||||
double[] P06 = new double[6];
|
||||
double[] R06 = new double[9];
|
||||
double[][] R = new double[6][] { new double[9], new double[9], new double[9], new double[9], new double[9], new double[9] };
|
||||
double[] R02 = new double[9];
|
||||
double[] R03 = new double[9];
|
||||
double[] R04 = new double[9];
|
||||
double[] R05 = new double[9];
|
||||
double[] L0_bs = new double[3];
|
||||
double[] L0_se = new double[3];
|
||||
double[] L0_ew = new double[3];
|
||||
double[] L0_wt = new double[3];
|
||||
|
||||
for (int i = 0; i < 6; i++)
|
||||
q_in[i] = joint.a[i] / RAD_TO_DEG;
|
||||
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
q[i] = q_in[i] + DH_matrix[i, 0];
|
||||
cosq = Math.Cos(q[i]);
|
||||
sinq = Math.Sin(q[i]);
|
||||
cosa = Math.Cos(DH_matrix[i, 3]);
|
||||
sina = Math.Sin(DH_matrix[i, 3]);
|
||||
|
||||
R[i][0] = cosq;
|
||||
R[i][1] = -cosa * sinq;
|
||||
R[i][2] = sina * sinq;
|
||||
R[i][3] = sinq;
|
||||
R[i][4] = cosa * cosq;
|
||||
R[i][5] = -sina * cosq;
|
||||
R[i][6] = 0.0f;
|
||||
R[i][7] = sina;
|
||||
R[i][8] = cosa;
|
||||
}
|
||||
MatMultiply(R[0], R[1], R02, 3, 3, 3);
|
||||
MatMultiply(R02, R[2], R03, 3, 3, 3);
|
||||
MatMultiply(R03, R[3], R04, 3, 3, 3);
|
||||
MatMultiply(R04, R[4], R05, 3, 3, 3);
|
||||
MatMultiply(R05, R[5], R06, 3, 3, 3);
|
||||
|
||||
MatMultiply(R[0], L1_base, L0_bs, 3, 3, 1);
|
||||
MatMultiply(R02, L2_arm, L0_se, 3, 3, 1);
|
||||
MatMultiply(R03, L3_elbow, L0_ew, 3, 3, 1);
|
||||
MatMultiply(R06, L6_wrist, L0_wt, 3, 3, 1);
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
P06[i] = L0_bs[i] + L0_se[i] + L0_ew[i] + L0_wt[i];
|
||||
|
||||
RotMatToEulerAngle(R06, P06);
|
||||
|
||||
pose.X = (float)P06[0];
|
||||
pose.Y = (float)P06[1];
|
||||
pose.Z = (float)P06[2];
|
||||
pose.A = (float)(P06[3] * RAD_TO_DEG);
|
||||
pose.B = (float)(P06[4] * RAD_TO_DEG);
|
||||
pose.C = (float)(P06[5] * RAD_TO_DEG);
|
||||
|
||||
//pose.A = (float)(P06[3]);
|
||||
//pose.B = (float)(P06[4]);
|
||||
//pose.C = (float)(P06[5]);
|
||||
pose.R = R06.ToArray();
|
||||
|
||||
return true;
|
||||
}
|
||||
//逆解
|
||||
public bool SolveIK(Pose6D_t _inputPose6D, Joint6D_t _lastJoint6D, out IKSolves_t _outputSolves)
|
||||
{
|
||||
_outputSolves = new IKSolves_t();
|
||||
|
||||
double[] qs = new double[2];
|
||||
double[][] qa = new double[2][] { new double[2], new double[2] };
|
||||
double[][] qw = new double[2][] { new double[3], new double[3] };
|
||||
double cosqs, sinqs;
|
||||
double[] cosqa = new double[2], sinqa = new double[2];
|
||||
double cosqw, sinqw;
|
||||
double[] P06 = new double[6];
|
||||
double[] R06 = new double[9];
|
||||
double[] P0_w = new double[3];
|
||||
double[] P1_w = new double[3];
|
||||
double[] L0_wt = new double[3];
|
||||
double[] L1_sw = new double[3];
|
||||
double[] R10 = new double[9];
|
||||
double[] R31 = new double[9];
|
||||
double[] R30 = new double[9];
|
||||
double[] R36 = new double[9];
|
||||
double l_sw_2, l_sw, atan_a, acos_a, acos_e;
|
||||
|
||||
int ind_arm, ind_elbow, ind_wrist;
|
||||
int i;
|
||||
|
||||
if (0 == l_ew)
|
||||
{
|
||||
l_ew = Math.Sqrt(l_ew_2);
|
||||
atan_e = Math.Atan(armConfig.D_ELBOW / armConfig.L_FOREARM);
|
||||
}
|
||||
|
||||
P06[0] = _inputPose6D.X;
|
||||
P06[1] = _inputPose6D.Y;
|
||||
P06[2] = _inputPose6D.Z;
|
||||
if (!_inputPose6D.hasR)
|
||||
{
|
||||
P06[3] = _inputPose6D.A / RAD_TO_DEG;
|
||||
P06[4] = _inputPose6D.B / RAD_TO_DEG;
|
||||
P06[5] = _inputPose6D.C / RAD_TO_DEG;
|
||||
EulerAngleToRotMat(P06.Skip(3).ToArray(), R06);
|
||||
}
|
||||
else
|
||||
{
|
||||
Array.Copy(R06, _inputPose6D.R, 9);
|
||||
}
|
||||
for (i = 0; i < 2; i++)
|
||||
{
|
||||
qs[i] = _lastJoint6D.a[0];
|
||||
qa[i][0] = _lastJoint6D.a[1];
|
||||
qa[i][1] = _lastJoint6D.a[2];
|
||||
qw[i][0] = _lastJoint6D.a[3];
|
||||
qw[i][1] = _lastJoint6D.a[4];
|
||||
qw[i][2] = _lastJoint6D.a[5];
|
||||
}
|
||||
MatMultiply(R06, L6_wrist, L0_wt, 3, 3, 1);
|
||||
for (i = 0; i < 3; i++)
|
||||
{
|
||||
P0_w[i] = P06[i] - L0_wt[i];
|
||||
}
|
||||
if (Math.Sqrt(P0_w[0] * P0_w[0] + P0_w[1] * P0_w[1]) <= 0.000001)
|
||||
{
|
||||
qs[0] = _lastJoint6D.a[0];
|
||||
qs[1] = _lastJoint6D.a[0];
|
||||
for (i = 0; i < 4; i++)
|
||||
{
|
||||
_outputSolves.solFlag[0 + i][0] = -1;
|
||||
_outputSolves.solFlag[4 + i][0] = -1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
qs[0] = Math.Atan2(P0_w[1], P0_w[0]);
|
||||
qs[1] = Math.Atan2(-P0_w[1], -P0_w[0]);
|
||||
for (i = 0; i < 4; i++)
|
||||
{
|
||||
_outputSolves.solFlag[0 + i][0] = 1;
|
||||
_outputSolves.solFlag[4 + i][0] = 1;
|
||||
}
|
||||
}
|
||||
for (ind_arm = 0; ind_arm < 2; ind_arm++)
|
||||
{
|
||||
cosqs = Math.Cos(qs[ind_arm] + DH_matrix[0, 0]);
|
||||
sinqs = Math.Sin(qs[ind_arm] + DH_matrix[0, 0]);
|
||||
|
||||
R10[0] = cosqs;
|
||||
R10[1] = sinqs;
|
||||
R10[2] = 0.0f;
|
||||
R10[3] = 0.0f;
|
||||
R10[4] = 0.0f;
|
||||
R10[5] = -1.0f;
|
||||
R10[6] = -sinqs;
|
||||
R10[7] = cosqs;
|
||||
R10[8] = 0.0f;
|
||||
|
||||
MatMultiply(R10, P0_w, P1_w, 3, 3, 1);
|
||||
for (i = 0; i < 3; i++)
|
||||
{
|
||||
L1_sw[i] = P1_w[i] - L1_base[i];
|
||||
}
|
||||
l_sw_2 = L1_sw[0] * L1_sw[0] + L1_sw[1] * L1_sw[1];
|
||||
l_sw = Math.Sqrt(l_sw_2);
|
||||
|
||||
if (Math.Abs(l_se + l_ew - l_sw) <= 0.000001)
|
||||
{
|
||||
qa[0][0] = Math.Atan2(L1_sw[1], L1_sw[0]);
|
||||
qa[1][0] = qa[0][0];
|
||||
qa[0][1] = 0.0f;
|
||||
qa[1][1] = 0.0f;
|
||||
if (l_sw > l_se + l_ew)
|
||||
{
|
||||
for (i = 0; i < 2; i++)
|
||||
{
|
||||
_outputSolves.solFlag[4 * ind_arm + 0 + i][1] = 0;
|
||||
_outputSolves.solFlag[4 * ind_arm + 2 + i][1] = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (i = 0; i < 2; i++)
|
||||
{
|
||||
_outputSolves.solFlag[4 * ind_arm + 0 + i][1] = 1;
|
||||
_outputSolves.solFlag[4 * ind_arm + 2 + i][1] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (Math.Abs(l_sw - Math.Abs(l_se - l_ew)) <= 0.000001)
|
||||
{
|
||||
qa[0][0] = Math.Atan2(L1_sw[1], L1_sw[0]);
|
||||
qa[1][0] = qa[0][0];
|
||||
if (0 == ind_arm)
|
||||
{
|
||||
qa[0][1] = M_PI;
|
||||
qa[1][1] = -M_PI;
|
||||
}
|
||||
else
|
||||
{
|
||||
qa[0][1] = -M_PI;
|
||||
qa[1][1] = M_PI;
|
||||
}
|
||||
if (l_sw < Math.Abs(l_se - l_ew))
|
||||
{
|
||||
for (i = 0; i < 2; i++)
|
||||
{
|
||||
_outputSolves.solFlag[4 * ind_arm + 0 + i][1] = 0;
|
||||
_outputSolves.solFlag[4 * ind_arm + 2 + i][1] = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (i = 0; i < 2; i++)
|
||||
{
|
||||
_outputSolves.solFlag[4 * ind_arm + 0 + i][1] = 1;
|
||||
_outputSolves.solFlag[4 * ind_arm + 2 + i][1] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
atan_a = Math.Atan2(L1_sw[1], L1_sw[0]);
|
||||
acos_a = 0.5f * (l_se_2 + l_sw_2 - l_ew_2) / (l_se * l_sw);
|
||||
if (acos_a >= 1.0f) acos_a = 0.0f;
|
||||
else if (acos_a <= -1.0f) acos_a = M_PI;
|
||||
else acos_a = Math.Acos(acos_a);
|
||||
acos_e = 0.5f * (l_se_2 + l_ew_2 - l_sw_2) / (l_se * l_ew);
|
||||
if (acos_e >= 1.0f) acos_e = 0.0f;
|
||||
else if (acos_e <= -1.0f) acos_e = M_PI;
|
||||
else acos_e = Math.Acos(acos_e);
|
||||
if (0 == ind_arm)
|
||||
{
|
||||
qa[0][0] = atan_a - acos_a + M_PI_2;
|
||||
qa[0][1] = atan_e - acos_e + M_PI;
|
||||
qa[1][0] = atan_a + acos_a + M_PI_2;
|
||||
qa[1][1] = atan_e + acos_e - M_PI;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
qa[0][0] = atan_a + acos_a + M_PI_2;
|
||||
qa[0][1] = atan_e + acos_e - M_PI;
|
||||
qa[1][0] = atan_a - acos_a + M_PI_2;
|
||||
qa[1][1] = atan_e - acos_e + M_PI;
|
||||
}
|
||||
for (i = 0; i < 2; i++)
|
||||
{
|
||||
_outputSolves.solFlag[4 * ind_arm + 0 + i][1] = 1;
|
||||
_outputSolves.solFlag[4 * ind_arm + 2 + i][1] = 1;
|
||||
}
|
||||
}
|
||||
for (ind_elbow = 0; ind_elbow < 2; ind_elbow++)
|
||||
{
|
||||
cosqa[0] = Math.Cos(qa[ind_elbow][0] + DH_matrix[1, 0]);
|
||||
sinqa[0] = Math.Sin(qa[ind_elbow][0] + DH_matrix[1, 0]);
|
||||
cosqa[1] = Math.Cos(qa[ind_elbow][1] + DH_matrix[2, 0]);
|
||||
sinqa[1] = Math.Sin(qa[ind_elbow][1] + DH_matrix[2, 0]);
|
||||
|
||||
R31[0] = cosqa[0] * cosqa[1] - sinqa[0] * sinqa[1];
|
||||
R31[1] = cosqa[0] * sinqa[1] + sinqa[0] * cosqa[1];
|
||||
R31[2] = 0.0f;
|
||||
R31[3] = 0.0f;
|
||||
R31[4] = 0.0f;
|
||||
R31[5] = 1.0f;
|
||||
R31[6] = cosqa[0] * sinqa[1] + sinqa[0] * cosqa[1];
|
||||
R31[7] = -cosqa[0] * cosqa[1] + sinqa[0] * sinqa[1];
|
||||
R31[8] = 0.0f;
|
||||
|
||||
MatMultiply(R31, R10, R30, 3, 3, 3);
|
||||
MatMultiply(R30, R06, R36, 3, 3, 3);
|
||||
|
||||
if (R36[8] >= 1.0 - 0.000001)
|
||||
{
|
||||
cosqw = 1.0f;
|
||||
qw[0][1] = 0.0f;
|
||||
qw[1][1] = 0.0f;
|
||||
}
|
||||
else if (R36[8] <= -1.0 + 0.000001)
|
||||
{
|
||||
cosqw = -1.0f;
|
||||
if (0 == ind_arm)
|
||||
{
|
||||
qw[0][1] = M_PI;
|
||||
qw[1][1] = -M_PI;
|
||||
}
|
||||
else
|
||||
{
|
||||
qw[0][1] = -M_PI;
|
||||
qw[1][1] = M_PI;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cosqw = R36[8];
|
||||
if (0 == ind_arm)
|
||||
{
|
||||
qw[0][1] = Math.Acos(cosqw);
|
||||
qw[1][1] = -Math.Acos(cosqw);
|
||||
}
|
||||
else
|
||||
{
|
||||
qw[0][1] = -Math.Acos(cosqw);
|
||||
qw[1][1] = Math.Acos(cosqw);
|
||||
}
|
||||
}
|
||||
if (1.0f == cosqw || -1.0f == cosqw)
|
||||
{
|
||||
if (0 == ind_arm)
|
||||
{
|
||||
qw[0][0] = _lastJoint6D.a[3];
|
||||
cosqw = Math.Cos(_lastJoint6D.a[3] + DH_matrix[3, 0]);
|
||||
sinqw = Math.Sin(_lastJoint6D.a[3] + DH_matrix[3, 0]);
|
||||
qw[0][2] = Math.Atan2(cosqw * R36[3] - sinqw * R36[0], cosqw * R36[0] + sinqw * R36[3]);
|
||||
qw[1][2] = _lastJoint6D.a[5];
|
||||
cosqw = Math.Cos(_lastJoint6D.a[5] + DH_matrix[5, 0]);
|
||||
sinqw = Math.Sin(_lastJoint6D.a[5] + DH_matrix[5, 0]);
|
||||
qw[1][0] = Math.Atan2(cosqw * R36[3] - sinqw * R36[0], cosqw * R36[0] + sinqw * R36[3]);
|
||||
}
|
||||
else
|
||||
{
|
||||
qw[0][2] = _lastJoint6D.a[5];
|
||||
cosqw = Math.Cos(_lastJoint6D.a[5] + DH_matrix[5, 0]);
|
||||
sinqw = Math.Sin(_lastJoint6D.a[5] + DH_matrix[5, 0]);
|
||||
qw[0][0] = Math.Atan2(cosqw * R36[3] - sinqw * R36[0], cosqw * R36[0] + sinqw * R36[3]);
|
||||
qw[1][0] = _lastJoint6D.a[3];
|
||||
cosqw = Math.Cos(_lastJoint6D.a[3] + DH_matrix[3, 0]);
|
||||
sinqw = Math.Sin(_lastJoint6D.a[3] + DH_matrix[3, 0]);
|
||||
qw[1][2] = Math.Atan2(cosqw * R36[3] - sinqw * R36[0], cosqw * R36[0] + sinqw * R36[3]);
|
||||
}
|
||||
_outputSolves.solFlag[4 * ind_arm + 2 * ind_elbow + 0][2] = -1;
|
||||
_outputSolves.solFlag[4 * ind_arm + 2 * ind_elbow + 1][2] = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (0 == ind_arm)
|
||||
{
|
||||
qw[0][0] = Math.Atan2(R36[5], R36[2]);
|
||||
qw[1][0] = Math.Atan2(-R36[5], -R36[2]);
|
||||
qw[0][2] = Math.Atan2(R36[7], -R36[6]);
|
||||
qw[1][2] = Math.Atan2(-R36[7], R36[6]);
|
||||
}
|
||||
else
|
||||
{
|
||||
qw[0][0] = Math.Atan2(-R36[5], -R36[2]);
|
||||
qw[1][0] = Math.Atan2(R36[5], R36[2]);
|
||||
qw[0][2] = Math.Atan2(-R36[7], R36[6]);
|
||||
qw[1][2] = Math.Atan2(R36[7], -R36[6]);
|
||||
}
|
||||
_outputSolves.solFlag[4 * ind_arm + 2 * ind_elbow + 0][2] = 1;
|
||||
_outputSolves.solFlag[4 * ind_arm + 2 * ind_elbow + 1][2] = 1;
|
||||
}
|
||||
for (ind_wrist = 0; ind_wrist < 2; ind_wrist++)
|
||||
{
|
||||
if (qs[ind_arm] > M_PI)
|
||||
_outputSolves.config[4 * ind_arm + 2 * ind_elbow + ind_wrist].a[0] =
|
||||
qs[ind_arm] - M_PI;
|
||||
else if (qs[ind_arm] < -M_PI)
|
||||
_outputSolves.config[4 * ind_arm + 2 * ind_elbow + ind_wrist].a[0] =
|
||||
qs[ind_arm] + M_PI;
|
||||
else
|
||||
_outputSolves.config[4 * ind_arm + 2 * ind_elbow + ind_wrist].a[0] = qs[ind_arm];
|
||||
|
||||
for (i = 0; i < 2; i++)
|
||||
{
|
||||
if (qa[ind_elbow][i] > M_PI)
|
||||
_outputSolves.config[4 * ind_arm + 2 * ind_elbow + ind_wrist].a[1 + i] =
|
||||
qa[ind_elbow][i] - M_PI;
|
||||
else if (qa[ind_elbow][i] < -M_PI)
|
||||
_outputSolves.config[4 * ind_arm + 2 * ind_elbow + ind_wrist].a[1 + i] =
|
||||
qa[ind_elbow][i] + M_PI;
|
||||
else
|
||||
_outputSolves.config[4 * ind_arm + 2 * ind_elbow + ind_wrist].a[1 + i] =
|
||||
qa[ind_elbow][i];
|
||||
}
|
||||
|
||||
for (i = 0; i < 3; i++)
|
||||
{
|
||||
if (qw[ind_wrist][i] > M_PI)
|
||||
_outputSolves.config[4 * ind_arm + 2 * ind_elbow + ind_wrist].a[3 + i] =
|
||||
qw[ind_wrist][i] - M_PI;
|
||||
else if (qw[ind_wrist][i] < -M_PI)
|
||||
_outputSolves.config[4 * ind_arm + 2 * ind_elbow + ind_wrist].a[3 + i] =
|
||||
qw[ind_wrist][i] + M_PI;
|
||||
else
|
||||
_outputSolves.config[4 * ind_arm + 2 * ind_elbow + ind_wrist].a[3 + i] =
|
||||
qw[ind_wrist][i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var one in _outputSolves.config)
|
||||
{
|
||||
for (int j = 0; j < one.a.Length; j++)
|
||||
{
|
||||
one.a[j] = one.a[j] * RAD_TO_DEG;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//逆解后选出最优解
|
||||
public bool AfterSolveIK(Pose6D_t _inputPose6D, Joint6D_t _currentJoints, Joint6D_t _lastJoint6D
|
||||
, CtrlStepMotor[] motorJ,out Joint6D_t _outputJoints)
|
||||
{
|
||||
_outputJoints = new Joint6D_t();
|
||||
SolveIK(_inputPose6D, _lastJoint6D, out IKSolves_t _outputSolves);
|
||||
bool[] valid = new bool[8];
|
||||
int validCnt = 0;
|
||||
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
valid[i] = true;
|
||||
|
||||
for (int j = 0; j < 6; j++)
|
||||
{
|
||||
if (_outputSolves.config[i].a[j] > motorJ[j].AngleLimitMax ||
|
||||
_outputSolves.config[i].a[j] < motorJ[j].AngleLimitMin)
|
||||
{
|
||||
valid[i] = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (valid[i]) validCnt++;
|
||||
}
|
||||
|
||||
if (validCnt > 0)
|
||||
{
|
||||
double min = 1000;
|
||||
int indexConfig = 0;
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
if (valid[i])
|
||||
{
|
||||
Joint6D_t tmp = _currentJoints - _lastJoint6D;
|
||||
double maxAngle = tmp.a.Max();
|
||||
if (maxAngle < min)
|
||||
{
|
||||
min = maxAngle;
|
||||
indexConfig = i;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
_outputJoints = new Joint6D_t(_outputSolves.config[indexConfig].a[0]
|
||||
, _outputSolves.config[indexConfig].a[1]
|
||||
, _outputSolves.config[indexConfig].a[2]
|
||||
, _outputSolves.config[indexConfig].a[3]
|
||||
, _outputSolves.config[indexConfig].a[4]
|
||||
, _outputSolves.config[indexConfig].a[5]);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SmallProject.Devices.Arm.Kinematic.Models
|
||||
{
|
||||
public class IKSolves_t
|
||||
{
|
||||
public Joint6D_t[] config = new Joint6D_t[8] {
|
||||
new Joint6D_t(), new Joint6D_t()
|
||||
, new Joint6D_t(), new Joint6D_t()
|
||||
,new Joint6D_t(), new Joint6D_t()
|
||||
,new Joint6D_t(), new Joint6D_t() };
|
||||
public int[][] solFlag = new int[8][] { new int[3], new int[3], new int[3], new int[3], new int[3], new int[3], new int[3], new int[3] };
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SmallProject.Devices.Arm.Kinematic.Models
|
||||
{
|
||||
public class Joint6D_t
|
||||
{
|
||||
public Joint6D_t(double a1, double a2, double a3, double a4, double a5, double a6)
|
||||
{
|
||||
a = new double[] { a1, a2, a3, a4, a5, a6 };
|
||||
}
|
||||
|
||||
public Joint6D_t(double[] a)
|
||||
{
|
||||
this.a = a;
|
||||
}
|
||||
|
||||
public Joint6D_t() { }
|
||||
|
||||
public double[] a = new double[6];
|
||||
|
||||
public static Joint6D_t operator -(Joint6D_t _joints1, Joint6D_t _joints2)
|
||||
{
|
||||
Joint6D_t tmp = new Joint6D_t();
|
||||
for (int i = 0; i < 6; i++)
|
||||
tmp.a[i] = _joints1.a[i] - _joints2.a[i];
|
||||
return tmp;
|
||||
}
|
||||
|
||||
public static Joint6D_t defult = new Joint6D_t(0, 0, 180, 0, -90, 0);
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SmallProject.Devices.Arm.Kinematic.Models
|
||||
{
|
||||
public class Pose6D_t
|
||||
{
|
||||
|
||||
public Pose6D_t()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public Pose6D_t(double x, double y, double z, double a, double b, double c)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
Z = z;
|
||||
A = a;
|
||||
B = b;
|
||||
C = c;
|
||||
}
|
||||
|
||||
public Pose6D_t(double[] vals)
|
||||
{
|
||||
X = vals[0];
|
||||
Y = vals[1];
|
||||
Z = vals[2];
|
||||
A = vals[3];
|
||||
B = vals[4];
|
||||
C = vals[5];
|
||||
}
|
||||
//x 坐标
|
||||
public double X { get; set; }
|
||||
//y 坐标
|
||||
public double Y { get; set; }
|
||||
//z 坐标
|
||||
public double Z { get; set; }
|
||||
//角度 a
|
||||
public double A { get; set; }
|
||||
//角度 b
|
||||
public double B { get; set; }
|
||||
//角度 c
|
||||
public double C { get; set; }
|
||||
//变换矩阵
|
||||
public double[] R { get; set; }
|
||||
//是否存在变换矩阵
|
||||
public bool hasR { get; set; } = false;
|
||||
}
|
||||
}
|
54
HMIcode/SmallProject/SmallProject/Dialogs/ConfigDialog.xaml
Normal file
54
HMIcode/SmallProject/SmallProject/Dialogs/ConfigDialog.xaml
Normal file
@ -0,0 +1,54 @@
|
||||
<rubyer:RubyerWindow x:Class="SmallProject.Dialogs.ConfigDialog"
|
||||
xmlns:rubyer="http://rubyer.io/winfx/xaml/toolkit"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:SmallProject.Dialogs"
|
||||
mc:Ignorable="d"
|
||||
Title="系统设置" Height="600" Width="700">
|
||||
<Grid rubyer:GridHelper.ColumnDefinitions="300,*">
|
||||
<Grid rubyer:GridHelper.RowDefinitions="40,40,40,40,40,40,40,40,50,20,*" >
|
||||
<Grid Grid.Row="0" rubyer:GridHelper.ColumnDefinitions="80,*" Margin="5">
|
||||
<TextBlock Grid.Column="0">J1减速比</TextBlock>
|
||||
<rubyer:NumericBox x:Name="tb_ReductionJ1" Grid.Column="1" Interval="1" NumericType="Int" Value="{Binding ReductionJ1}"/>
|
||||
</Grid>
|
||||
<Grid Grid.Row="1" rubyer:GridHelper.ColumnDefinitions="80,*" Margin="5">
|
||||
<TextBlock Grid.Column="0">J2减速比</TextBlock>
|
||||
<rubyer:NumericBox x:Name="tb_ReductionJ2" Grid.Column="1" Interval="1" NumericType="Int" Value="{Binding ReductionJ2}"/>
|
||||
</Grid>
|
||||
<Grid Grid.Row="2" rubyer:GridHelper.ColumnDefinitions="80,*" Margin="5">
|
||||
<TextBlock Grid.Column="0">J3减速比</TextBlock>
|
||||
<rubyer:NumericBox x:Name="tb_ReductionJ3" Grid.Column="1" Interval="1" NumericType="Int" Value="{Binding ReductionJ3}"/>
|
||||
</Grid>
|
||||
<Grid Grid.Row="3" rubyer:GridHelper.ColumnDefinitions="80,*" Margin="5">
|
||||
<TextBlock Grid.Column="0">J4减速比</TextBlock>
|
||||
<rubyer:NumericBox x:Name="tb_ReductionJ4" Grid.Column="1" Interval="1" NumericType="Int" Value="{Binding ReductionJ4}"/>
|
||||
</Grid>
|
||||
<Grid Grid.Row="4" rubyer:GridHelper.ColumnDefinitions="80,*" Margin="5">
|
||||
<TextBlock Grid.Column="0">J5减速比</TextBlock>
|
||||
<rubyer:NumericBox x:Name="tb_ReductionJ5" Grid.Column="1" Interval="1" NumericType="Int" Value="{Binding ReductionJ5}"/>
|
||||
</Grid>
|
||||
<Grid Grid.Row="5" rubyer:GridHelper.ColumnDefinitions="80,*" Margin="5">
|
||||
<TextBlock Grid.Column="0">J6减速比</TextBlock>
|
||||
<rubyer:NumericBox x:Name="tb_ReductionJ6" Grid.Column="1" Interval="1" NumericType="Int" Value="{Binding ReductionJ6}"/>
|
||||
</Grid>
|
||||
<Grid Grid.Row="6" rubyer:GridHelper.ColumnDefinitions="80,*" Margin="5">
|
||||
<TextBlock Grid.Column="0">速度</TextBlock>
|
||||
<rubyer:NumericBox x:Name="tb_Speed" Grid.Column="1" Interval="2" NumericType="Double" Value="{Binding Speed}"/>
|
||||
</Grid>
|
||||
<Grid Grid.Row="7" rubyer:GridHelper.ColumnDefinitions="80,*" Margin="5">
|
||||
<TextBlock Grid.Column="0">电流(A)</TextBlock>
|
||||
<rubyer:NumericBox x:Name="tb_Current" Grid.Column="1" Interval="2" NumericType="Double" Value="{Binding Current}"/>
|
||||
</Grid>
|
||||
<Grid Grid.Row="8" rubyer:GridHelper.ColumnDefinitions="*,*">
|
||||
<Button Margin="3,8" Grid.Column="1" x:Name="EnterOK" Click="EnterOK_Click" >
|
||||
<StackPanel rubyer:PanelHelper.Spacing="8" Orientation="Horizontal" >
|
||||
<TextBlock FontSize="12" Text="应用" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</rubyer:RubyerWindow>
|
@ -0,0 +1,43 @@
|
||||
using SmallProject.Configs;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace SmallProject.Dialogs
|
||||
{
|
||||
/// <summary>
|
||||
/// ConfigDialog.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class ConfigDialog : Rubyer.RubyerWindow
|
||||
{
|
||||
public ConfigDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
//加载配置项
|
||||
this.DataContext = App.JConfig;
|
||||
this.Loaded += ConfigDialog_Loaded;
|
||||
}
|
||||
|
||||
//加载
|
||||
private void ConfigDialog_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
}
|
||||
|
||||
private void EnterOK_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var conf = App.JConfig;
|
||||
ConfigResposity.WriteConfigs(conf);
|
||||
Rubyer.MessageBoxR.Success("保存成功。");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
<rubyer:RubyerWindow x:Class="SmallProject.Dialogs.MotorConfigDialog"
|
||||
xmlns:rubyer="http://rubyer.io/winfx/xaml/toolkit"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:SmallProject.Dialogs"
|
||||
mc:Ignorable="d"
|
||||
Title="电机设置" Height="600" Width="700"
|
||||
>
|
||||
<Grid rubyer:GridHelper.ColumnDefinitions="300,*">
|
||||
<Grid rubyer:GridHelper.RowDefinitions="40,40,40,40,40,40,40,40,50,20,*" >
|
||||
<Grid Grid.Row="1" rubyer:GridHelper.ColumnDefinitions="100,*" Margin="5">
|
||||
<TextBlock Grid.Column="0">电机ID</TextBlock>
|
||||
<rubyer:NumericBox x:Name="tb_ID" Grid.Column="1" Interval="2" NumericType="Int" Value="1"/>
|
||||
</Grid>
|
||||
<Grid Grid.Row="2" rubyer:GridHelper.ColumnDefinitions="100,*" Margin="5">
|
||||
<TextBlock Grid.Column="0">电流限制值(mA)</TextBlock>
|
||||
<rubyer:NumericBox x:Name="tb_Current" Grid.Column="1" Interval="2" NumericType="Double" Value="{Binding Current}"/>
|
||||
</Grid>
|
||||
<Grid Grid.Row="3" rubyer:GridHelper.ColumnDefinitions="*,*">
|
||||
<Button Margin="3,8" Grid.Column="0" x:Name="bt_Read" Click="bt_Read_Click" >
|
||||
<StackPanel rubyer:PanelHelper.Spacing="8" Orientation="Horizontal" >
|
||||
<TextBlock FontSize="12" Text="读取信息" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Margin="3,8" Grid.Column="1" x:Name="bt_EnterOK" Click="bt_EnterOK_Click" >
|
||||
<StackPanel rubyer:PanelHelper.Spacing="8" Orientation="Horizontal" >
|
||||
<TextBlock FontSize="12" Text="下发指令" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</rubyer:RubyerWindow>
|
@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace SmallProject.Dialogs
|
||||
{
|
||||
/// <summary>
|
||||
/// MotorConfigDialog.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class MotorConfigDialog : Rubyer.RubyerWindow
|
||||
{
|
||||
public MotorConfigDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
//读取信息
|
||||
private void bt_Read_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void bt_EnterOK_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var id = (int)tb_ID.Value;
|
||||
var value = (float)tb_Current.Value;
|
||||
App.Core.Jyker.SetLimitCurrent(id, value);
|
||||
}
|
||||
}
|
||||
}
|
44
HMIcode/SmallProject/SmallProject/Logger/JLog.cs
Normal file
44
HMIcode/SmallProject/SmallProject/Logger/JLog.cs
Normal file
@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SmallProject.Logger
|
||||
{
|
||||
internal class JLog
|
||||
{
|
||||
internal static event Action<string> MessageEvent;
|
||||
internal static event Action<string> MessageEventLive;
|
||||
|
||||
/// <summary>
|
||||
/// 打印错误信息
|
||||
/// </summary>
|
||||
/// <param name="msg"></param>
|
||||
internal static void Error(Exception msg)
|
||||
{
|
||||
Masuit.Tools.Logging.LogManager.Error(msg);
|
||||
//MessageEvent?.Invoke(msg.Message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 打印普通信息
|
||||
/// </summary>
|
||||
/// <param name="msg"></param>
|
||||
internal static void Info(string msg)
|
||||
{
|
||||
Masuit.Tools.Logging.LogManager.Info(msg);
|
||||
MessageEvent?.Invoke(msg);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 打印普通信息
|
||||
/// </summary>
|
||||
/// <param name="msg"></param>
|
||||
internal static void InfoLive(string msg)
|
||||
{
|
||||
Masuit.Tools.Logging.LogManager.Info(msg);
|
||||
MessageEventLive?.Invoke(msg);
|
||||
}
|
||||
}
|
||||
}
|
23
HMIcode/SmallProject/SmallProject/MCP/JykerControlMCP.cs
Normal file
23
HMIcode/SmallProject/SmallProject/MCP/JykerControlMCP.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using SmallProject.MCP.Xiaozhi;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SmallProject.MCP.Tools;
|
||||
using Newtonsoft.Json;
|
||||
using SmallProject.MCP.Models;
|
||||
|
||||
namespace SmallProject.MCP
|
||||
{
|
||||
internal class JykerControlMCP
|
||||
{
|
||||
public async Task Init()
|
||||
{
|
||||
McpPip pi = new McpPip();
|
||||
pi.Init();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
48
HMIcode/SmallProject/SmallProject/MCP/Models/RequestBody.cs
Normal file
48
HMIcode/SmallProject/SmallProject/MCP/Models/RequestBody.cs
Normal file
@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SmallProject.MCP.Models
|
||||
{
|
||||
public class RequestBody
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Jsonrpc { get; set; }
|
||||
public string Method { get; set; }
|
||||
public Params Params { get; set; }
|
||||
}
|
||||
|
||||
public class Params
|
||||
{
|
||||
public string ProtocolVersion { get; set; }
|
||||
public CapabilitiesD Capabilities { get; set; }
|
||||
public ClientInfo ClientInfo { get; set; }
|
||||
|
||||
public string Name{get;set;}
|
||||
public Dictionary<string, object> Arguments { get; set; } = new Dictionary<string, object>();
|
||||
}
|
||||
|
||||
public class CapabilitiesD
|
||||
{
|
||||
public Sampling Sampling { get; set; }
|
||||
public Roots Roots { get; set; }
|
||||
}
|
||||
|
||||
public class Sampling
|
||||
{
|
||||
// sampling 是一个空对象 {}
|
||||
}
|
||||
|
||||
public class Roots
|
||||
{
|
||||
public bool ListChanged { get; set; }
|
||||
}
|
||||
|
||||
public class ClientInfo
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Version { get; set; }
|
||||
}
|
||||
}
|
64
HMIcode/SmallProject/SmallProject/MCP/Models/ResultBody.cs
Normal file
64
HMIcode/SmallProject/SmallProject/MCP/Models/ResultBody.cs
Normal file
@ -0,0 +1,64 @@
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion.Internal;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SmallProject.MCP.Models
|
||||
{
|
||||
public class ResultBody
|
||||
{
|
||||
public string jsonrpc { get; set; } = "2.0";
|
||||
public int id { get; set; }
|
||||
public dynamic result { get; set; }
|
||||
|
||||
public bool isError { get; set; } = false;
|
||||
}
|
||||
|
||||
public class ToolRoot
|
||||
{
|
||||
public List<Tool> Tools { get; set; } = new List<Tool>();
|
||||
}
|
||||
|
||||
public class Tool
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Description { get; set; }
|
||||
public InputSchema InputSchema { get; set; } = new InputSchema();
|
||||
}
|
||||
|
||||
public class InputSchema
|
||||
{
|
||||
public Dictionary<string, Property> Properties { get; set; } = new Dictionary<string, Property>();
|
||||
public string[] Required { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Type { get; set; }
|
||||
}
|
||||
|
||||
public class Property
|
||||
{
|
||||
public string Title { get; set; }
|
||||
public string Type { get; set; }
|
||||
}
|
||||
|
||||
public class ContentRoot
|
||||
{
|
||||
public List<ResultText> content = new List<ResultText>();
|
||||
}
|
||||
public class ResultText
|
||||
{
|
||||
public string type { get; set; } = "text";
|
||||
|
||||
public string text { get; set; }
|
||||
}
|
||||
|
||||
public class ResultContent
|
||||
{
|
||||
public bool success { get; set; } = true;
|
||||
|
||||
public object result { get; set; }
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
using SmallProject.MCP.Models;
|
||||
using SmallProject.MCP.Tools;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SmallProject.MCP.Serializers
|
||||
{
|
||||
internal class ToolsSerializer
|
||||
{
|
||||
|
||||
|
||||
public static List<Tool> SerializeTool<T>()
|
||||
{
|
||||
List<Tool> tools = new List<Tool>();
|
||||
Type type = typeof(T);
|
||||
// 获取所有公共实例方法
|
||||
var methods = type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
.Where(t=>t.DeclaringType==type);
|
||||
foreach (MethodInfo method in methods)
|
||||
{
|
||||
Tool tool = new Tool();
|
||||
// 获取方法的参数
|
||||
ParameterInfo[] parameters = method.GetParameters();
|
||||
if (parameters.Length > 0)
|
||||
{
|
||||
foreach (ParameterInfo param in parameters)
|
||||
{
|
||||
|
||||
tool.InputSchema.Properties.Add(param.Name, new Property { Title = param.Name.ToUpper()
|
||||
, Type = (param.ParameterType == typeof(int)? "integer":"text")
|
||||
});
|
||||
}
|
||||
tool.InputSchema.Required = parameters.Select(t => t.Name).ToArray();
|
||||
}
|
||||
|
||||
// 获取属性
|
||||
var desc = method.GetCustomAttribute<DescriptionAttribute>();
|
||||
|
||||
|
||||
tool.Name = method.Name;
|
||||
tool.Description = desc.Description;
|
||||
tool.InputSchema.Title = $"{method.Name}Arguments" ;
|
||||
tool.InputSchema.Type = "object";
|
||||
tools.Add(tool);
|
||||
}
|
||||
|
||||
return tools;
|
||||
}
|
||||
}
|
||||
}
|
167
HMIcode/SmallProject/SmallProject/MCP/Tools/JykerControl.cs
Normal file
167
HMIcode/SmallProject/SmallProject/MCP/Tools/JykerControl.cs
Normal file
@ -0,0 +1,167 @@
|
||||
using HalconDotNet;
|
||||
using NAudio.CoreAudioApi;
|
||||
using SmallProject.Logger;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SmallProject.MCP.Tools
|
||||
{
|
||||
public class JykerControl
|
||||
{
|
||||
//[Description("For mathamatical calculation, always use this tool to calculate the result of a add b")]
|
||||
//public long calculator(long a, long b)
|
||||
//{
|
||||
// return a + b;
|
||||
//}
|
||||
|
||||
[Description("用 前 后 左 右 上 下 六个命令控制方向,如果返回移动了就是移动成功了,没有就是没有成功。")]
|
||||
public string jyker_move(string direction)
|
||||
{
|
||||
var curr = App.Core.Jyker.currentJoints;
|
||||
JLog.Info(direction);
|
||||
var joint1 = curr.a[0];
|
||||
var joint2 = curr.a[1];
|
||||
var joint3 = curr.a[2];
|
||||
|
||||
switch (direction)
|
||||
{
|
||||
case "前":
|
||||
|
||||
App.Core.Jyker.Move(new double[]{ curr.a[0], joint2+10, joint3-10
|
||||
, curr.a[3], curr.a[4], curr.a[5] });
|
||||
break;
|
||||
case "后":
|
||||
if(joint2-1 < -90)
|
||||
{
|
||||
return "退不了了";
|
||||
}
|
||||
App.Core.Jyker.Move(new double[]{ curr.a[0], joint2-10, joint3+10
|
||||
, curr.a[3], curr.a[4], curr.a[5] });
|
||||
break;
|
||||
case "左":
|
||||
if(joint1>30)
|
||||
{
|
||||
return "左不了了";
|
||||
}
|
||||
App.Core.Jyker.Move(new double[]{joint1+20, curr.a[1], curr.a[2]
|
||||
, curr.a[3], curr.a[4], curr.a[5] });
|
||||
break;
|
||||
case "右":
|
||||
if(joint1<-90)
|
||||
{
|
||||
return "右不了了";
|
||||
}
|
||||
App.Core.Jyker.Move(new double[]{ joint1-20, curr.a[1], curr.a[2]
|
||||
, curr.a[3], curr.a[4], curr.a[5] });
|
||||
break;
|
||||
case "上":
|
||||
if (curr.a[4] - 10 < -90)
|
||||
{
|
||||
return "脖子没那么长啦";
|
||||
}
|
||||
App.Core.Jyker.Move(new double[]{ curr.a[0], curr.a[1], curr.a[2]
|
||||
, curr.a[3], curr.a[4]-20, curr.a[5] });
|
||||
break;
|
||||
case "下":
|
||||
if (curr.a[4] + 10 > 90)
|
||||
{
|
||||
return "脖子没那么长啦";
|
||||
}
|
||||
App.Core.Jyker.Move(new double[]{ curr.a[0], curr.a[1], curr.a[2]
|
||||
, curr.a[3], curr.a[4]+20, curr.a[5] });
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return "移动了";
|
||||
}
|
||||
|
||||
[Description("这个方法会返回看到了什么,传入一个direction 的参数,下 代表往下看 上 代表往上看")]
|
||||
public string jyker_view(string direction)
|
||||
{
|
||||
var curr = App.Core.Jyker.currentJoints;
|
||||
switch (direction)
|
||||
{
|
||||
case "上":
|
||||
if(curr.a[4] - 10<-90)
|
||||
{
|
||||
return "脖子没那么长啦";
|
||||
}
|
||||
App.Core.Jyker.Move(new double[]{ curr.a[0], curr.a[1], curr.a[2]
|
||||
, curr.a[3], curr.a[4]-20, curr.a[5] });
|
||||
break;
|
||||
case "下":
|
||||
if (curr.a[4]+10>90)
|
||||
{
|
||||
return "脖子没那么长啦";
|
||||
}
|
||||
App.Core.Jyker.Move(new double[]{ curr.a[0], curr.a[1], curr.a[2]
|
||||
, curr.a[3], curr.a[4]+20, curr.a[5] });
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
JLog.Info("我看到了");
|
||||
return "苹果";
|
||||
}
|
||||
|
||||
[Description("这个方法传入一个参数state ,如果是抓,就是抓住,如果是放,就是放开")]
|
||||
public string jyker_clumporopen(string state)
|
||||
{
|
||||
var curr = App.Core.Jyker.currentJoints;
|
||||
var joint6 = curr.a[5];
|
||||
JLog.Info(state);
|
||||
switch (state)
|
||||
{
|
||||
case "抓":
|
||||
App.Core.Jyker.Move(new double[]{curr.a[0], curr.a[1], curr.a[2]
|
||||
, curr.a[3], curr.a[4], 100 });
|
||||
Thread.Sleep(1000);
|
||||
App.Core.Jyker.GetStatus(6);
|
||||
Thread.Sleep(200);
|
||||
JLog.Info($"Current {App.Core.Jyker.motorJ[5].Current}");
|
||||
if (App.Core.Jyker.motorJ[5].Current<0.5)
|
||||
{
|
||||
JLog.Info("没抓到东西");
|
||||
return "没抓到东西";
|
||||
}
|
||||
else
|
||||
{
|
||||
JLog.Info("抓住了");
|
||||
return "抓住了";
|
||||
}
|
||||
case "放":
|
||||
App.Core.Jyker.Move(new double[]{curr.a[0], curr.a[1], curr.a[2]
|
||||
, curr.a[3], curr.a[4], 0 });
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return "ok";
|
||||
}
|
||||
|
||||
[Description("当有人跟你说这个东西是什么的时候,麻烦告诉一下系统,让系统知道这个东西是什么 传入的参数就是物体的名字")]
|
||||
public string jyker_thisis(string thing)
|
||||
{
|
||||
JLog.Info($"jyker_thisis {thing}");
|
||||
App.Core.ViewName = thing;
|
||||
//创建shapemodel
|
||||
|
||||
return "知道了";
|
||||
}
|
||||
|
||||
[Description("当阿然叫你富贵的时候麻烦告诉一下系统 ")]
|
||||
public string jyker_wakeup()
|
||||
{
|
||||
JLog.Info("唤醒了");
|
||||
return "ok";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
206
HMIcode/SmallProject/SmallProject/MCP/Xiaozhi/McpPip.cs
Normal file
206
HMIcode/SmallProject/SmallProject/MCP/Xiaozhi/McpPip.cs
Normal file
@ -0,0 +1,206 @@
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Internal;
|
||||
using NAudio.CoreAudioApi;
|
||||
using Newtonsoft.Json;
|
||||
using SmallProject.Logger;
|
||||
using SmallProject.MCP.Models;
|
||||
using SmallProject.MCP.Serializers;
|
||||
using SmallProject.MCP.Tools;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.WebSockets;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Shapes;
|
||||
using static System.Net.Mime.MediaTypeNames;
|
||||
|
||||
namespace SmallProject.MCP.Xiaozhi
|
||||
{
|
||||
|
||||
internal class McpPip
|
||||
{
|
||||
|
||||
private const string INITIAL_BACKOFF_ENV = "INITIAL_BACKOFF";
|
||||
private const string MAX_BACKOFF_ENV = "MAX_BACKOFF";
|
||||
|
||||
private static int reconnectAttempt = 0;
|
||||
private static double backoff = 1.0;
|
||||
private static readonly Random Random = new();
|
||||
|
||||
private static readonly string InitialBackoffStr = Environment.GetEnvironmentVariable(INITIAL_BACKOFF_ENV) ?? "1";
|
||||
private static readonly string MaxBackoffStr = Environment.GetEnvironmentVariable(MAX_BACKOFF_ENV) ?? "600";
|
||||
|
||||
private static readonly double INITIAL_BACKOFF = double.TryParse(InitialBackoffStr, out var val) ? val : 1;
|
||||
private static readonly double MAX_BACKOFF = double.TryParse(MaxBackoffStr, out var val) ? val : 600;
|
||||
|
||||
private static Dictionary<string, Object> ToolsDictionary = new Dictionary<string, Object>();
|
||||
|
||||
private static ResultBody toolsListBody = new ResultBody();
|
||||
|
||||
public async Task Init()
|
||||
{
|
||||
await AddTools<JykerControl>();
|
||||
|
||||
|
||||
var endpointUrl = App.JConfig.EndpointUrl;
|
||||
|
||||
if (string.IsNullOrEmpty(endpointUrl))
|
||||
{
|
||||
JLog.Info("小智接入点为空。。");
|
||||
return;
|
||||
}
|
||||
|
||||
Console.CancelKeyPress += (sender, eventArgs) =>
|
||||
{
|
||||
//JLog.Info("Received interrupt signal, shutting down...");
|
||||
eventArgs.Cancel = true;
|
||||
return;
|
||||
};
|
||||
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ConnectWithRetry(endpointUrl);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
JLog.Error(ex);
|
||||
await Task.Delay(TimeSpan.FromSeconds(backoff));
|
||||
backoff = Math.Min(backoff * 2, MAX_BACKOFF);
|
||||
reconnectAttempt++;
|
||||
}
|
||||
Thread.Sleep(50);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ConnectWithRetry(string uri)
|
||||
{
|
||||
using var cts = new CancellationTokenSource();
|
||||
using var client = new ClientWebSocket();
|
||||
|
||||
JLog.Info($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] Connecting to WebSocket server...");
|
||||
|
||||
await client.ConnectAsync(new Uri(uri), cts.Token);
|
||||
|
||||
JLog.Info($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] Successfully connected to WebSocket server");
|
||||
|
||||
reconnectAttempt = 0;
|
||||
backoff = INITIAL_BACKOFF;
|
||||
|
||||
var receiveTask = PipeWebSocketReceive(client, cts.Token);
|
||||
var sendTask = PipeWebSocketSend(client, cts.Token);
|
||||
|
||||
await Task.WhenAll(receiveTask, sendTask);
|
||||
|
||||
}
|
||||
private async Task PipeWebSocketReceive(ClientWebSocket ws, CancellationToken token)
|
||||
{
|
||||
var buffer = new byte[4096];
|
||||
|
||||
while (ws.State == WebSocketState.Open)
|
||||
{
|
||||
var result = await ws.ReceiveAsync(buffer, token);
|
||||
|
||||
if (result.MessageType == WebSocketMessageType.Close)
|
||||
{
|
||||
await ws.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, token);
|
||||
break;
|
||||
}
|
||||
|
||||
var message = Encoding.UTF8.GetString(buffer, 0, result.Count);
|
||||
JLog.Info(message);
|
||||
var res =JsonConvert.DeserializeObject<RequestBody>(message);
|
||||
switch (res.Method)
|
||||
{
|
||||
case "initialize":
|
||||
//JLog.Info("initialize");
|
||||
var b = @"{""jsonrpc"":""2.0"",""id"":0,""result"":{""protocolVersion"":""2024-11-05"",""capabilities"":{""experimental"":{},""prompts"":{""listChanged"":false},""resources"":{""subscribe"":false,""listChanged"":false},""tools"":{""listChanged"":false}},""serverInfo"":{""name"":""JykerContrl"",""version"":""1.12.0""}}}";
|
||||
await ws.SendAsync(Encoding.UTF8.GetBytes(b), WebSocketMessageType.Text, true, token);
|
||||
break;
|
||||
case "notifications/initialized":
|
||||
//JLog.Info("notifications/initialized");
|
||||
break;
|
||||
case "tools/list":
|
||||
//JLog.Info("tools/list");
|
||||
//var a = @"{""jsonrpc"":""2.0"",""id"":"+res.Id+@",""result"":{""tools"":[{""name"":""calculator"",""description"":""For mathamatical calculation, always use this tool to calculate the result of a add b. You can use 'math' or 'random' directly, without 'import'."",""inputSchema"":{""properties"":{""a"":{""title"":""A"",""type"":""integer""},""b"":{""title"":""B"",""type"":""integer""}},""required"":[""a"",""b""],""title"":""calculatorArguments"",""type"":""object""}}]}}";
|
||||
toolsListBody.id = res.Id;
|
||||
var body = JsonConvert.SerializeObject(toolsListBody).ToLower();
|
||||
await ws.SendAsync(Encoding.UTF8.GetBytes(body), WebSocketMessageType.Text, true, token);
|
||||
break;
|
||||
case "tools/call":
|
||||
var oneTool = ToolsDictionary[res.Params.Name];
|
||||
if(oneTool!=null)
|
||||
{
|
||||
Type type = oneTool.GetType();
|
||||
|
||||
// 获取并调用公共方法
|
||||
MethodInfo publicMethod = type.GetMethod(res.Params.Name, BindingFlags.Public | BindingFlags.Instance);
|
||||
if (publicMethod != null)
|
||||
{
|
||||
var MethodResult = publicMethod
|
||||
.Invoke(oneTool, res.Params.Arguments.Select(t=>t.Value).ToArray());
|
||||
|
||||
var resbody = new ResultBody();
|
||||
resbody.id = res.Id;
|
||||
resbody.result = new ContentRoot { content = new List<ResultText>() };
|
||||
var ResultText = new ResultText();
|
||||
ResultText.text = JsonConvert.SerializeObject(new ResultContent
|
||||
{
|
||||
result = MethodResult
|
||||
}, Formatting.Indented).Replace("\r","");
|
||||
resbody.result.content.Add(ResultText);
|
||||
var json = JsonConvert.SerializeObject(resbody);
|
||||
await ws.SendAsync(Encoding.UTF8.GetBytes(json), WebSocketMessageType.Text, true, token);
|
||||
//JLog.Info(json);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "ping":
|
||||
//JLog.Info("ping");
|
||||
var bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(new ResultBody() { id = res.Id}).ToLower());
|
||||
await ws.SendAsync(bytes, WebSocketMessageType.Text, true, token);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PipeWebSocketSend(ClientWebSocket ws, CancellationToken token)
|
||||
{
|
||||
string line = "";
|
||||
var bytes = Encoding.UTF8.GetBytes(line);
|
||||
await ws.SendAsync(bytes, WebSocketMessageType.Text, true, token);
|
||||
}
|
||||
/// <summary>
|
||||
/// 添加工具
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public async Task AddTools<T>() where T :new ()
|
||||
{
|
||||
var tools = ToolsSerializer.SerializeTool<T>();
|
||||
foreach (var tool in tools)
|
||||
{
|
||||
T t = new T();
|
||||
ToolsDictionary.Add(tool.Name.ToLower(), t);
|
||||
}
|
||||
if(toolsListBody.result==null)
|
||||
{
|
||||
toolsListBody.result = new ToolRoot { Tools = tools };
|
||||
}
|
||||
else
|
||||
{
|
||||
toolsListBody.result.tools.AddRange(tools);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
351
HMIcode/SmallProject/SmallProject/MainWindow.xaml
Normal file
351
HMIcode/SmallProject/SmallProject/MainWindow.xaml
Normal file
@ -0,0 +1,351 @@
|
||||
<rubyer:RubyerWindow x:Class="SmallProject.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:rubyer="http://rubyer.io/winfx/xaml/toolkit"
|
||||
mc:Ignorable="d"
|
||||
TitleHeight="45" Height="1920" Width="1080"
|
||||
d:DesignWidth="1920"
|
||||
d:DesignHeight="1080"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
WindowState="Maximized"
|
||||
Title="JYKER">
|
||||
<rubyer:RubyerWindow.TitleBarContent>
|
||||
<Menu
|
||||
HorizontalAlignment="Right"
|
||||
rubyer:HeaderHelper.Foreground="{DynamicResource WhiteForeground}"
|
||||
rubyer:MenuHelper.IconWidth="30"
|
||||
Foreground="{DynamicResource DefaultForeground}"
|
||||
WindowChrome.IsHitTestVisibleInChrome="True">
|
||||
<MenuItem x:Name="mt_Config" >
|
||||
<MenuItem.Header>
|
||||
<StackPanel rubyer:PanelHelper.Spacing="8"
|
||||
Orientation="Horizontal"
|
||||
TextBlock.Foreground="{DynamicResource WhiteForeground}">
|
||||
<rubyer:Icon Type="Settings2Fill" />
|
||||
<TextBlock FontSize="16" Text="系统设置" />
|
||||
</StackPanel>
|
||||
</MenuItem.Header>
|
||||
</MenuItem>
|
||||
<MenuItem x:Name="mt_MotorConfig" >
|
||||
<MenuItem.Header>
|
||||
<StackPanel rubyer:PanelHelper.Spacing="8"
|
||||
Orientation="Horizontal"
|
||||
TextBlock.Foreground="{DynamicResource WhiteForeground}">
|
||||
<rubyer:Icon Type="Settings2Fill" />
|
||||
<TextBlock FontSize="16" Text="电机设置" />
|
||||
</StackPanel>
|
||||
</MenuItem.Header>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</rubyer:RubyerWindow.TitleBarContent>
|
||||
<Grid rubyer:GridHelper.ColumnDefinitions="*,600,300">
|
||||
<Grid Grid.Column="0" Margin="10">
|
||||
<Image x:Name="ImageBig" ></Image>
|
||||
<Grid rubyer:GridHelper.ColumnDefinitions="*" VerticalAlignment="Top" HorizontalAlignment="Right">
|
||||
<TextBlock FontSize="60" Grid.Column="0" Foreground="Red" Panel.ZIndex="3" x:Name="ResultTextInPic" ></TextBlock>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid Grid.Column="1" rubyer:GridHelper.RowDefinitions="300,200,*">
|
||||
<GroupBox Header="直播间信息">
|
||||
<TextBox BorderThickness="0" x:Name="tbLogLive" Style="{StaticResource BigTextBox}" FontSize="20"/>
|
||||
</GroupBox>
|
||||
<GroupBox Header="点位循环记录" Visibility="Collapsed">
|
||||
<DataGrid x:Name="dg_JointRecord" AutoGenerateColumns="False" BorderThickness="1" CanUserAddRows="False" GridLinesVisibility="All" IsReadOnly="True" SelectionMode="Single">
|
||||
<DataGrid.Columns>
|
||||
<DataGridCheckBoxColumn Width="20" Binding="{Binding IsAjust}" Header="是否检测抓取"/>
|
||||
<DataGridTextColumn Width="auto" Binding="{Binding J1}" Header="J1" />
|
||||
<DataGridTextColumn Width="auto" Binding="{Binding J2}" Header="J2" />
|
||||
<DataGridTextColumn Width="auto" Binding="{Binding J3}" Header="J3" />
|
||||
<DataGridTextColumn Width="auto" Binding="{Binding J4}" Header="J4" />
|
||||
<DataGridTextColumn Width="auto" Binding="{Binding J5}" Header="J5" />
|
||||
<DataGridTextColumn Width="auto" Binding="{Binding J6}" Header="J6" />
|
||||
|
||||
<DataGridTextColumn Width="auto" Binding="{Binding X}" Header="X" />
|
||||
<DataGridTextColumn Width="auto" Binding="{Binding Y}" Header="Y" />
|
||||
<DataGridTextColumn Width="auto" Binding="{Binding Z}" Header="Z" />
|
||||
<DataGridTextColumn Width="auto" Binding="{Binding A}" Header="A" />
|
||||
<DataGridTextColumn Width="auto" Binding="{Binding B}" Header="B" />
|
||||
<DataGridTextColumn Width="auto" Binding="{Binding C}" Header="C" />
|
||||
<DataGridTextColumn Visibility="Collapsed" x:Name="AddTime" Binding="{Binding AddTime}" Header="AddTime" />
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</GroupBox>
|
||||
<GroupBox Header="夹爪控制" Grid.Row="1" x:Name="gb_ClawControl" IsEnabled="False">
|
||||
<Grid rubyer:GridHelper.RowDefinitions="40,40,40,40,*">
|
||||
<Grid rubyer:GridHelper.ColumnDefinitions="*,*,*,*" >
|
||||
<Grid Grid.Column="0" Margin="2">
|
||||
<Button x:Name="bt_ClawHome" >夹爪回零</Button>
|
||||
</Grid>
|
||||
<Grid Grid.Column="1" Margin="2">
|
||||
<Button x:Name="bt_ClawStop" >夹爪关闭堵转</Button>
|
||||
</Grid>
|
||||
<Grid Grid.Column="2" Margin="2">
|
||||
<Button x:Name="bt_ClawLoopStart" >开始监视信息</Button>
|
||||
</Grid>
|
||||
<Grid Grid.Column="3" Margin="2">
|
||||
<Button x:Name="bt_ClawLoopEnd" IsEnabled="False" >停止监视信息</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid Grid.Row="1" rubyer:GridHelper.ColumnDefinitions="60,*,50,60,*,50">
|
||||
<Grid Grid.Column="0" Margin="2">
|
||||
<TextBlock>末端夹角:</TextBlock>
|
||||
</Grid>
|
||||
<Grid Grid.Column="1" Margin="2">
|
||||
<Slider Margin="0 12 0 0" Value="0" x:Name="pg_ClawAngle" Minimum="0" Maximum="90"></Slider>
|
||||
</Grid>
|
||||
<Grid Grid.Column="2" Margin="2">
|
||||
<TextBlock x:Name="tb_ClawAngle">0°</TextBlock>
|
||||
</Grid>
|
||||
<Grid Grid.Column="3" Margin="2">
|
||||
<TextBlock>末端长度:</TextBlock>
|
||||
</Grid>
|
||||
<Grid Grid.Column="4" Margin="2">
|
||||
<ProgressBar Margin="0 16 0 0" Value="0" x:Name="pg_ClawLength"></ProgressBar>
|
||||
</Grid>
|
||||
<Grid Grid.Column="5" Margin="2">
|
||||
<TextBlock x:Name="tb_ClawLength">0mm</TextBlock>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid Grid.Row="2" rubyer:GridHelper.ColumnDefinitions="60,*,50,60,*,50">
|
||||
<Grid Grid.Column="0" Margin="2">
|
||||
<TextBlock>末端受力:</TextBlock>
|
||||
</Grid>
|
||||
<Grid Grid.Column="1" Margin="2">
|
||||
<ProgressBar Margin="0 16 0 0" Value="0" x:Name="pg_ClawPower" Minimum="0" Maximum="2000"></ProgressBar>
|
||||
</Grid>
|
||||
<Grid Grid.Column="2" Margin="2">
|
||||
<TextBlock x:Name="tb_ClawPower" >0</TextBlock>
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
</GroupBox>
|
||||
<GroupBox Header="力矩信息" Grid.Row="2" x:Name="gb_ArmControl" IsEnabled="False">
|
||||
<Grid rubyer:GridHelper.RowDefinitions="240,50">
|
||||
<Grid rubyer:GridHelper.RowDefinitions="40,40,40,40,40,40,40" rubyer:GridHelper.ColumnDefinitions="40,200,50">
|
||||
|
||||
<TextBlock Grid.Column="0" Grid.Row="0">J1:</TextBlock>
|
||||
<ProgressBar Grid.Column="1" Grid.Row="0" Margin="0 18 0 0" Value="0" Minimum="0" Maximum="2000" x:Name="pb_pJ1"></ProgressBar>
|
||||
<TextBlock Grid.Column="2" Grid.Row="0" Margin="6" x:Name="tb_pJ1">0</TextBlock>
|
||||
|
||||
<TextBlock Grid.Column="0" Grid.Row="1">J2:</TextBlock>
|
||||
<ProgressBar Grid.Column="1" Grid.Row="1" Margin="0 18 0 0" Value="0" Minimum="0" Maximum="2000" x:Name="pb_pJ2"></ProgressBar>
|
||||
<TextBlock Grid.Column="2" Grid.Row="1" Margin="6" x:Name="tb_pJ2">0</TextBlock>
|
||||
|
||||
<TextBlock Grid.Column="0" Grid.Row="2">J3:</TextBlock>
|
||||
<ProgressBar Grid.Column="1" Grid.Row="2" Margin="0 18 0 0" Value="0" Minimum="0" Maximum="2000" x:Name="pb_pJ3"></ProgressBar>
|
||||
<TextBlock Grid.Column="2" Grid.Row="2" Margin="6" x:Name="tb_pJ3">0</TextBlock>
|
||||
|
||||
<TextBlock Grid.Column="0" Grid.Row="3">J4:</TextBlock>
|
||||
<ProgressBar Grid.Column="1" Grid.Row="3" Margin="0 18 0 0" Value="0" Minimum="0" Maximum="2000" x:Name="pb_pJ4"></ProgressBar>
|
||||
<TextBlock Grid.Column="2" Grid.Row="3" Margin="6" x:Name="tb_pJ4">0</TextBlock>
|
||||
|
||||
<TextBlock Grid.Column="0" Grid.Row="4">J5:</TextBlock>
|
||||
<ProgressBar Grid.Column="1" Grid.Row="4" Margin="0 18 0 0" Value="0" Minimum="0" Maximum="2000" x:Name="pb_pJ5"></ProgressBar>
|
||||
<TextBlock Grid.Column="2" Grid.Row="4" Margin="6" x:Name="tb_pJ5">0</TextBlock>
|
||||
|
||||
<TextBlock Grid.Column="0" Grid.Row="5">J6:</TextBlock>
|
||||
<ProgressBar Grid.Column="1" Grid.Row="5" Margin="0 18 0 0" Value="0" Minimum="0" Maximum="2000" x:Name="pb_pJ6"></ProgressBar>
|
||||
<TextBlock Grid.Column="2" Grid.Row="5" Margin="6" x:Name="tb_pJ6">0</TextBlock>
|
||||
|
||||
</Grid>
|
||||
<Grid rubyer:GridHelper.RowDefinitions="40" rubyer:GridHelper.ColumnDefinitions="125,125" Grid.Row="1">
|
||||
<Grid Grid.Row="0" Margin="2">
|
||||
<Button x:Name="bt_ArmLoopStart" >开始监视信息</Button>
|
||||
</Grid>
|
||||
<Grid Grid.Column="3" Margin="2">
|
||||
<Button x:Name="bt_ArmLoopEnd" IsEnabled="False" >停止监视信息</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
|
||||
</GroupBox>
|
||||
</Grid>
|
||||
<Grid Grid.Column="2" rubyer:GridHelper.RowDefinitions="100,610,*" Margin="5,2,5,5">
|
||||
|
||||
<GroupBox Grid.Row="0" Header="机器手连接" Margin="5,0,5,0">
|
||||
<Grid rubyer:GridHelper.RowDefinitions="30,30,30,*">
|
||||
<Grid rubyer:GridHelper.ColumnDefinitions="auto,*">
|
||||
<Grid Margin="2">
|
||||
<Label>端口:</Label>
|
||||
</Grid>
|
||||
<Grid Margin="2" Grid.Column="1">
|
||||
<ComboBox x:Name="cb_ComList" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid Grid.Row="1" rubyer:GridHelper.ColumnDefinitions="*,*">
|
||||
<Button x:Name="bt_Link" Margin="2" >手动连接</Button>
|
||||
<Button x:Name="bt_LinkAuto" Margin="2" Grid.Column="1" >自动连接</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
<GroupBox Grid.Row="1" Header="操作" Margin="5,0,5,0">
|
||||
<Grid rubyer:GridHelper.RowDefinitions="30,30,30,30,30,30,20,30,30,30,30,30,30,30,30,30,30,30,30,*">
|
||||
|
||||
<Grid rubyer:GridHelper.ColumnDefinitions="*,*" Grid.Row="0">
|
||||
<Grid Grid.Column="0" Margin="2">
|
||||
<TextBlock>X:</TextBlock>
|
||||
</Grid>
|
||||
<Grid Grid.Column="1" Margin="2">
|
||||
<TextBox x:Name="tb_X">0</TextBox>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid rubyer:GridHelper.ColumnDefinitions="*,*" Grid.Row="1">
|
||||
<Grid Grid.Column="0" Margin="2">
|
||||
<TextBlock>Y:</TextBlock>
|
||||
</Grid>
|
||||
<Grid Grid.Column="1" Margin="2">
|
||||
<TextBox x:Name="tb_Y">0</TextBox>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid rubyer:GridHelper.ColumnDefinitions="*,*" Grid.Row="2">
|
||||
<Grid Grid.Column="0" Margin="2">
|
||||
<TextBlock>Z:</TextBlock>
|
||||
</Grid>
|
||||
<Grid Grid.Column="1" Margin="2">
|
||||
<TextBox x:Name="tb_Z">0</TextBox>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid rubyer:GridHelper.ColumnDefinitions="*,*" Grid.Row="3">
|
||||
<Grid Grid.Column="0" Margin="2">
|
||||
<TextBlock>Angle_A:</TextBlock>
|
||||
</Grid>
|
||||
<Grid Grid.Column="1" Margin="2">
|
||||
<TextBox x:Name="tb_A">0</TextBox>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid rubyer:GridHelper.ColumnDefinitions="*,*" Grid.Row="4">
|
||||
<Grid Grid.Column="0" Margin="2">
|
||||
<TextBlock>Angle_B:</TextBlock>
|
||||
</Grid>
|
||||
<Grid Grid.Column="1" Margin="2">
|
||||
<TextBox x:Name="tb_B">0</TextBox>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid rubyer:GridHelper.ColumnDefinitions="*,*" Grid.Row="5">
|
||||
<Grid Grid.Column="0" Margin="2">
|
||||
<TextBlock>Angle_C:</TextBlock>
|
||||
</Grid>
|
||||
<Grid Grid.Column="1" Margin="2">
|
||||
<TextBox x:Name="tb_C">0</TextBox>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Grid rubyer:GridHelper.ColumnDefinitions="*,*" Grid.Row="7">
|
||||
<Grid Grid.Column="0" Margin="2">
|
||||
<TextBlock>Joint1:</TextBlock>
|
||||
</Grid>
|
||||
<Grid Grid.Column="1" Margin="2">
|
||||
<TextBox x:Name="tb_Joint1">0</TextBox>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid rubyer:GridHelper.ColumnDefinitions="*,*" Grid.Row="8">
|
||||
<Grid Grid.Column="0" Margin="2">
|
||||
<TextBlock>Joint2:</TextBlock>
|
||||
</Grid>
|
||||
<Grid Grid.Column="1" Margin="2">
|
||||
<TextBox x:Name="tb_Joint2">0</TextBox>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid rubyer:GridHelper.ColumnDefinitions="*,*" Grid.Row="9">
|
||||
<Grid Grid.Column="0" Margin="2">
|
||||
<TextBlock>Joint3:</TextBlock>
|
||||
</Grid>
|
||||
<Grid Grid.Column="1" Margin="2">
|
||||
<TextBox x:Name="tb_Joint3">0</TextBox>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid rubyer:GridHelper.ColumnDefinitions="*,*" Grid.Row="10">
|
||||
<Grid Grid.Column="0" Margin="2">
|
||||
<TextBlock>Joint4:</TextBlock>
|
||||
</Grid>
|
||||
<Grid Grid.Column="1" Margin="2">
|
||||
<TextBox x:Name="tb_Joint4">0</TextBox>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid rubyer:GridHelper.ColumnDefinitions="*,*" Grid.Row="11">
|
||||
<Grid Grid.Column="0" Margin="2">
|
||||
<TextBlock>Joint5:</TextBlock>
|
||||
</Grid>
|
||||
<Grid Grid.Column="1" Margin="2">
|
||||
<TextBox x:Name="tb_Joint5">0</TextBox>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid rubyer:GridHelper.ColumnDefinitions="*,*" Grid.Row="12">
|
||||
<Grid Grid.Column="0" Margin="2">
|
||||
<TextBlock>Joint6:</TextBlock>
|
||||
</Grid>
|
||||
<Grid Grid.Column="1" Margin="2">
|
||||
<TextBox x:Name="tb_Joint6">0</TextBox>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid rubyer:GridHelper.ColumnDefinitions="*,*" Grid.Row="13">
|
||||
<Grid Grid.Column="0" Margin="2">
|
||||
<Button x:Name="bt_ApplyHomePosition" >设置当前位置为零</Button>
|
||||
</Grid>
|
||||
<Grid Grid.Column="1" Margin="2">
|
||||
<Button x:Name="bt_StopNow" >立即停止</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<!--<Grid rubyer:GridHelper.ColumnDefinitions="*,*" Grid.Row="14">
|
||||
<Grid Grid.Column="0" Margin="2">
|
||||
<Button x:Name="bt_FK" Height="26" VerticalAlignment="Top" >正解计算</Button>
|
||||
</Grid>
|
||||
<Grid Grid.Column="1" Margin="2">
|
||||
<Button x:Name="bt_IK">逆解计算</Button>
|
||||
</Grid>
|
||||
</Grid>-->
|
||||
<Grid rubyer:GridHelper.ColumnDefinitions="*,*" Grid.Row="14">
|
||||
<Grid Grid.Column="0" Margin="2">
|
||||
<Button x:Name="bt_MoveJoint" >运动机械臂</Button>
|
||||
</Grid>
|
||||
<Grid Grid.Column="1" Margin="2">
|
||||
<Button x:Name="bt_GetCurrentAngle" >获取当前位置</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid rubyer:GridHelper.ColumnDefinitions="*,*" Grid.Row="15">
|
||||
<Grid Grid.Column="0" Margin="2">
|
||||
<Button x:Name="bt_MoveArmHand" >手动移动机械臂</Button>
|
||||
</Grid>
|
||||
<Grid Grid.Column="1" Margin="2">
|
||||
<Button x:Name="bt_AddRecord" >记录点位</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid rubyer:GridHelper.ColumnDefinitions="*,*" Grid.Row="16">
|
||||
<Grid Grid.Column="0" Margin="2">
|
||||
<Button x:Name="bt_MoveLoop" >循环运动</Button>
|
||||
</Grid>
|
||||
<Grid Grid.Column="1" Margin="2">
|
||||
<Button x:Name="bt_MoveLoopStop" Cursor="Hand">停止循环</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid rubyer:GridHelper.ColumnDefinitions="*,*" Grid.Row="17">
|
||||
<Grid Grid.Column="0" Margin="2">
|
||||
<Button x:Name="bt_DeleteRecord" >删除记录</Button>
|
||||
</Grid>
|
||||
<Grid Grid.Column="1" Margin="2">
|
||||
<Button x:Name="bt_ConnectAi" Cursor="Hand">连接语音助手</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid rubyer:GridHelper.ColumnDefinitions="*,*" Grid.Row="18">
|
||||
<Grid Grid.Column="0" Margin="2">
|
||||
<Button x:Name="bt_StartDetect" >开启视频</Button>
|
||||
</Grid>
|
||||
<Grid Grid.Column="1" Margin="2" Visibility="Hidden">
|
||||
<Button x:Name="bt_aa" Cursor="Hand">连接语音助手</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
<GroupBox Grid.Row="2" Margin="5,0,5,0" Header="日志信息" >
|
||||
<TextBox BorderThickness="0" x:Name="tbLog" Style="{StaticResource BigTextBox}" FontSize="12"/>
|
||||
</GroupBox>
|
||||
</Grid>
|
||||
|
||||
|
||||
</Grid>
|
||||
</rubyer:RubyerWindow>
|
170
HMIcode/SmallProject/SmallProject/MainWindow.xaml.cs
Normal file
170
HMIcode/SmallProject/SmallProject/MainWindow.xaml.cs
Normal file
@ -0,0 +1,170 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Ports;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
using System.Xml.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Drawing;
|
||||
using Rubyer;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Windows.Forms;
|
||||
using SmallProject.WindowController;
|
||||
using SmallProject.Dialogs;
|
||||
|
||||
namespace SmallProject
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for MainWindow.xaml
|
||||
/// </summary>
|
||||
public partial class MainWindow : Rubyer.RubyerWindow
|
||||
{
|
||||
|
||||
JykerConnetContrl JykerConnetContrl;
|
||||
JykerMoveContrl JykerMoveContrl;
|
||||
LogContrl LogContrl;
|
||||
JykerStatusContrl JykerStatusContrl;
|
||||
JykerKinematicContrl JykerKinematicContrl;
|
||||
JykerViewContrl JykerViewContrl;
|
||||
LogLiveContrl LogLiveContrl;
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
//窗体加载
|
||||
this.Loaded += MainWindow_Loaded;
|
||||
//mt_Config 事件监听
|
||||
mt_Config.Click += Mt_Config_Click;
|
||||
mt_MotorConfig.Click += Mt_MotorConfig_Click;
|
||||
|
||||
}
|
||||
//弹出电机配置页面
|
||||
private void Mt_MotorConfig_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
MotorConfigDialog motorConfigDialog = new MotorConfigDialog();
|
||||
motorConfigDialog.ShowDialog();
|
||||
}
|
||||
|
||||
//弹出配置页面
|
||||
private void Mt_Config_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ConfigDialog configDialog = new ConfigDialog();
|
||||
configDialog.ShowDialog();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
icon();
|
||||
|
||||
//ContextMenuStrip
|
||||
contextMenu();
|
||||
|
||||
//保证窗体显示在上方。
|
||||
wsl = WindowState;
|
||||
|
||||
Rubyer.ThemeManager.SwitchThemeMode(Rubyer.Enums.ThemeMode.Dark);
|
||||
this.StateChanged += MainWindow_StateChanged;
|
||||
|
||||
//初始化控制器
|
||||
JykerConnetContrl = new JykerConnetContrl(this);
|
||||
LogContrl = new LogContrl(this);
|
||||
JykerMoveContrl = new JykerMoveContrl(this);
|
||||
JykerStatusContrl = new JykerStatusContrl(this);
|
||||
JykerKinematicContrl = new JykerKinematicContrl(this);
|
||||
JykerViewContrl = new JykerViewContrl(this);
|
||||
LogLiveContrl = new LogLiveContrl(this);
|
||||
}
|
||||
|
||||
|
||||
#region 托盘右键菜单
|
||||
WindowState ws;
|
||||
WindowState wsl;
|
||||
NotifyIcon notifyIcon;
|
||||
private void MainWindow_StateChanged(object? sender, EventArgs e)
|
||||
{
|
||||
ws = this.WindowState;
|
||||
if (ws == WindowState.Minimized)
|
||||
{
|
||||
this.Hide();
|
||||
}
|
||||
}
|
||||
|
||||
private void icon()
|
||||
{
|
||||
string path = System.IO.Path.GetFullPath(@"Static\icon.ico");
|
||||
if (File.Exists(path))
|
||||
{
|
||||
this.notifyIcon = new NotifyIcon();
|
||||
this.notifyIcon.BalloonTipText = "Jyker"; //设置程序启动时显示的文本
|
||||
this.notifyIcon.Text = "Jyker";//最小化到托盘时,鼠标点击时显示的文本
|
||||
System.Drawing.Icon icon = new System.Drawing.Icon(path);//程序图标
|
||||
this.notifyIcon.Icon = icon;
|
||||
this.notifyIcon.Visible = true;
|
||||
notifyIcon.MouseDoubleClick += NotifyIcon_MouseDoubleClick;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void NotifyIcon_MouseDoubleClick(object? sender, System.Windows.Forms.MouseEventArgs e)
|
||||
{
|
||||
this.Show();
|
||||
WindowState = wsl;
|
||||
}
|
||||
private void contextMenu()
|
||||
{
|
||||
ContextMenuStrip cms = new ContextMenuStrip();
|
||||
|
||||
//关联 NotifyIcon 和 ContextMenuStrip
|
||||
notifyIcon.ContextMenuStrip = cms;
|
||||
|
||||
System.Windows.Forms.ToolStripMenuItem exitMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
exitMenuItem.Text = "退出";
|
||||
exitMenuItem.Click += new EventHandler(exitMenuItem_Click);
|
||||
|
||||
System.Windows.Forms.ToolStripMenuItem hideMenumItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
hideMenumItem.Text = "隐藏";
|
||||
hideMenumItem.Click += new EventHandler(hideMenumItem_Click);
|
||||
|
||||
System.Windows.Forms.ToolStripMenuItem showMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
showMenuItem.Text = "显示";
|
||||
showMenuItem.Click += new EventHandler(showMenuItem_Click);
|
||||
|
||||
cms.Items.Add(exitMenuItem);
|
||||
cms.Items.Add(hideMenumItem);
|
||||
cms.Items.Add(showMenuItem);
|
||||
}
|
||||
|
||||
private void exitMenuItem_Click(object? sender, EventArgs e)
|
||||
{
|
||||
notifyIcon.Visible = false;
|
||||
|
||||
System.Windows.Application.Current.Shutdown();
|
||||
}
|
||||
|
||||
private void hideMenumItem_Click(object? sender, EventArgs e)
|
||||
{
|
||||
this.Hide();
|
||||
}
|
||||
|
||||
private void showMenuItem_Click(object? sender, EventArgs e)
|
||||
{
|
||||
this.Show();
|
||||
this.Activate();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
139
HMIcode/SmallProject/SmallProject/Serials/Serial.cs
Normal file
139
HMIcode/SmallProject/SmallProject/Serials/Serial.cs
Normal file
@ -0,0 +1,139 @@
|
||||
using RJCP.IO.Ports;
|
||||
using SharpCompress.Compressors.Xz;
|
||||
using SmallProject.Devices.Arm;
|
||||
using SmallProject.Logger;
|
||||
using SmallProject.Serials.Slcan;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SmallProject.Serials
|
||||
{
|
||||
public class Serial
|
||||
{
|
||||
|
||||
private Queue<string> DataToSend;
|
||||
|
||||
public void PushDataToQueue(string data)
|
||||
{
|
||||
if(!OpenResult)
|
||||
{
|
||||
JLog.Info("请先打开连接");
|
||||
return;
|
||||
}
|
||||
DataToSend.Enqueue(data);
|
||||
}
|
||||
|
||||
private bool OpenResult;
|
||||
|
||||
//串口工具
|
||||
private static SerialPortStream stream;
|
||||
public bool Open(string name)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (stream != null && stream.IsOpen)
|
||||
{
|
||||
stream.Dispose();
|
||||
}
|
||||
stream = new SerialPortStream(name, 115200, 8, RJCP.IO.Ports.Parity.None, RJCP.IO.Ports.StopBits.One);
|
||||
stream.ReadTimeout = 1000;
|
||||
|
||||
stream.DataReceived += Stream_DataReceived;
|
||||
stream.Open();
|
||||
OpenResult = true;
|
||||
DataToSend = new Queue<string>();
|
||||
LoopSend();
|
||||
//设置波特率
|
||||
PushDataToQueue("S8\r");
|
||||
//开始
|
||||
PushDataToQueue("O\r");
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
OpenResult = false;
|
||||
|
||||
}
|
||||
return OpenResult;
|
||||
}
|
||||
|
||||
//循环发送数据
|
||||
private void LoopSend()
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
while(OpenResult)
|
||||
{
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < DataToSend.Count; i++)
|
||||
{
|
||||
if (DataToSend.TryDequeue(out string? send))
|
||||
{
|
||||
stream.Write(send);
|
||||
JLog.Info(send);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
JLog.Error(e);
|
||||
}
|
||||
await Task.Delay(50);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//数据接收
|
||||
private void Stream_DataReceived(object? sender, SerialDataReceivedEventArgs e)
|
||||
{
|
||||
var sp = sender as SerialPortStream;
|
||||
var recieveText = sp.ReadExisting();
|
||||
var frames =recieveText.Split('\r');
|
||||
|
||||
foreach(var frame in frames)
|
||||
{
|
||||
if (string.IsNullOrEmpty(frame)) continue;
|
||||
var aFrame = SlcanParser.ParseSlcanFrame(frame);
|
||||
if(aFrame!=null)
|
||||
{
|
||||
switch(aFrame.Cmd) {
|
||||
case 0x21:
|
||||
//电流信息
|
||||
var current = BitConverter.ToSingle(aFrame.Data.Take(4).ToArray());
|
||||
App.Core.Jyker.RecieveCurrent(aFrame.Id, current);
|
||||
JLog.Info($"电流 {current}");
|
||||
break;
|
||||
case 0x22:
|
||||
// 速度信息
|
||||
var velocity = BitConverter.ToSingle(aFrame.Data.Take(4).ToArray());
|
||||
App.Core.Jyker.RecieveVelocity(aFrame.Id, velocity);
|
||||
break;
|
||||
case 0x23:
|
||||
//角度信息
|
||||
var angle = BitConverter.ToSingle(aFrame.Data.Take(4).ToArray());
|
||||
var isFinish = BitConverter.ToBoolean(aFrame.Data.Skip(4).Take(1).ToArray());
|
||||
App.Core.Jyker.RecievePos(aFrame.Id, angle, isFinish);
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool SerialDispose()
|
||||
{
|
||||
//关闭CAN 传输
|
||||
stream.Write("C/r");
|
||||
//关闭串口
|
||||
stream.Close();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
31
HMIcode/SmallProject/SmallProject/Serials/Slcan/CanFrame.cs
Normal file
31
HMIcode/SmallProject/SmallProject/Serials/Slcan/CanFrame.cs
Normal file
@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SmallProject.Serials.Slcan
|
||||
{
|
||||
public class CanFrame
|
||||
{
|
||||
public int Id { get; set; } = 0;
|
||||
public int Cmd { get; set; }
|
||||
public byte[] Data { get; set; } = new byte[0];
|
||||
public bool IsExtended { get; set; }
|
||||
public bool IsRtr { get; set; }
|
||||
public byte Dlc { get; set; } =0;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"ID: 0x{Id:X8}, DLC: {Dlc}, Data: {BitConverter.ToString(Data)}";
|
||||
}
|
||||
|
||||
public string ToStr()
|
||||
{
|
||||
var IdCmd = (Id << 7 | Cmd).ToString("X3");
|
||||
var DlcStr = Dlc.ToString("X1");
|
||||
var DataStr = string.Concat(Data.Select(t => t.ToString("X2")));
|
||||
return $"t{IdCmd}{DlcStr}{DataStr}\r";
|
||||
}
|
||||
}
|
||||
}
|
111
HMIcode/SmallProject/SmallProject/Serials/Slcan/SlcanParser.cs
Normal file
111
HMIcode/SmallProject/SmallProject/Serials/Slcan/SlcanParser.cs
Normal file
@ -0,0 +1,111 @@
|
||||
using SmallProject.Logger;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SmallProject.Serials.Slcan
|
||||
{
|
||||
public static class SlcanParser
|
||||
{
|
||||
public static CanFrame ParseSlcanFrame(string frame)
|
||||
{
|
||||
try
|
||||
{
|
||||
char cmd = frame[0];
|
||||
var canFrame = new CanFrame();
|
||||
|
||||
switch (cmd)
|
||||
{
|
||||
case 't': // 标准帧
|
||||
canFrame.IsExtended = false;
|
||||
canFrame.IsRtr = false;
|
||||
break;
|
||||
case 'T': // 扩展帧
|
||||
canFrame.IsExtended = true;
|
||||
canFrame.IsRtr = false;
|
||||
break;
|
||||
case 'r': // 远程标准帧
|
||||
canFrame.IsExtended = false;
|
||||
canFrame.IsRtr = true;
|
||||
break;
|
||||
case 'R': // 远程扩展帧
|
||||
canFrame.IsExtended = true;
|
||||
canFrame.IsRtr = true;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException("Unsupported frame type.");
|
||||
}
|
||||
|
||||
|
||||
// 解析 ID 和 DLC
|
||||
var IdCmd = frame.Substring(1, 3);
|
||||
canFrame.Id = Convert.ToInt32(IdCmd, 16) >> 7;
|
||||
canFrame.Cmd = Convert.ToInt32(IdCmd, 16) & 0x7F;
|
||||
canFrame.Dlc = byte.Parse(frame.Substring(4, 1));
|
||||
|
||||
if (canFrame.Dlc>0)
|
||||
{
|
||||
canFrame.Data = new byte[canFrame.Dlc];
|
||||
for (int i = 0; i < canFrame.Dlc; i++)
|
||||
{
|
||||
var oneByte = byte.Parse(frame.Substring(5 + i * 2, 2), System.Globalization.NumberStyles.HexNumber);
|
||||
canFrame.Data[i] = oneByte;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
canFrame.Data = Array.Empty<byte>();
|
||||
}
|
||||
|
||||
return canFrame;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
JLog.Error(e);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static CanFrame ParseSlcanFrame(int Id, int Cmd, float value, bool resACK = true)
|
||||
{
|
||||
return ParseSlcanFrame(Id, Cmd, value,0, resACK);
|
||||
}
|
||||
|
||||
public static CanFrame ParseSlcanFrame(int Id, int Cmd, float value1, float value2, bool resACK = true)
|
||||
{
|
||||
CanFrame canFrame = new CanFrame();
|
||||
canFrame.Id = Id;
|
||||
canFrame.Cmd = Cmd;
|
||||
canFrame.Dlc = 8;
|
||||
canFrame.Data = new byte[8];
|
||||
byte[] bytes1 = BitConverter.GetBytes(value1);
|
||||
for (int i = 0; i < bytes1.Length; i++)
|
||||
{
|
||||
canFrame.Data[i] = bytes1[i];
|
||||
}
|
||||
byte[] bytes2 = BitConverter.GetBytes(value2);
|
||||
for (int i = 0; i < bytes2.Length; i++)
|
||||
{
|
||||
canFrame.Data[i + 4] = bytes2[i];
|
||||
}
|
||||
return canFrame;
|
||||
}
|
||||
|
||||
public static string ParseSlcanFrameStr(int Id, int Cmd)
|
||||
{
|
||||
return ParseSlcanFrame(Id, Cmd, 0, 0).ToStr();
|
||||
}
|
||||
public static string ParseSlcanFrameStr(int Id, int Cmd, float value,bool resACK = true)
|
||||
{
|
||||
return ParseSlcanFrame(Id, Cmd, value, resACK).ToStr();
|
||||
}
|
||||
public static string ParseSlcanFrameStr(int Id, int Cmd, float value1, float value2)
|
||||
{
|
||||
return ParseSlcanFrame(Id, Cmd, value1, value2).ToStr();
|
||||
}
|
||||
}
|
||||
}
|
45
HMIcode/SmallProject/SmallProject/SmallProject.csproj
Normal file
45
HMIcode/SmallProject/SmallProject/SmallProject.csproj
Normal file
@ -0,0 +1,45 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net7.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWPF>true</UseWPF>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="HalconDotNet" Version="19.11.0" />
|
||||
<PackageReference Include="Hardcodet.NotifyIcon.Wpf" Version="2.0.1" />
|
||||
<PackageReference Include="Masuit.Tools.Core" Version="2025.4.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.7" />
|
||||
<PackageReference Include="Microsoft.ML.OnnxRuntime.Gpu" Version="1.22.1" />
|
||||
<PackageReference Include="NAudio" Version="2.2.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="OpenCvSharp4" Version="4.11.0.20250507" />
|
||||
<PackageReference Include="OpenCvSharp4.Extensions" Version="4.11.0.20250507" />
|
||||
<PackageReference Include="OpenCvSharp4.runtime.win" Version="4.11.0.20250507" />
|
||||
<PackageReference Include="Rubyer" Version="2.18.7" />
|
||||
<PackageReference Include="SerialPortStream" Version="2.4.2" />
|
||||
<PackageReference Include="System.Management" Version="9.0.6" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Static\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\OpenBLive\OpenBLive.csproj" />
|
||||
<ProjectReference Include="..\Yolov5Net.Scorer\Yolov5Net.Scorer.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="Static\icon.ico">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="yolov8n-pose.onnx">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
BIN
HMIcode/SmallProject/SmallProject/Static/icon.ico
Normal file
BIN
HMIcode/SmallProject/SmallProject/Static/icon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
BIN
HMIcode/SmallProject/SmallProject/Static/icon.jpeg
Normal file
BIN
HMIcode/SmallProject/SmallProject/Static/icon.jpeg
Normal file
Binary file not shown.
After Width: | Height: | Size: 401 KiB |
31
HMIcode/SmallProject/SmallProject/Utils/DrawToBitmap.cs
Normal file
31
HMIcode/SmallProject/SmallProject/Utils/DrawToBitmap.cs
Normal file
@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SmallProject.Utils
|
||||
{
|
||||
public class DrawToBitmap
|
||||
{
|
||||
public static void DrawRectangle(Bitmap b, float X, float Y, float width, float height
|
||||
, int thickness = 10, string text = "", float fontSize = 50.0f)
|
||||
{
|
||||
using (Graphics grapPic = Graphics.FromImage(b))
|
||||
{
|
||||
|
||||
using (Pen pen = new Pen(Color.FromArgb(255, Color.Red), thickness))
|
||||
{
|
||||
Brush whiteBrush = new SolidBrush(Color.FromArgb(220, Color.Red)); // 画文字用
|
||||
|
||||
System.Drawing.Font font = new System.Drawing.Font("微软雅黑", fontSize, System.Drawing.FontStyle.Bold);
|
||||
grapPic.DrawRectangle(pen, X, Y, width, height);
|
||||
//定义字体
|
||||
grapPic.DrawString(text, font, whiteBrush, (X + width / 2) * 1f, (Y + height / 2) * 1f);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
1376
HMIcode/SmallProject/SmallProject/Utils/PicUtil.cs
Normal file
1376
HMIcode/SmallProject/SmallProject/Utils/PicUtil.cs
Normal file
File diff suppressed because it is too large
Load Diff
192
HMIcode/SmallProject/SmallProject/Utils/PicUtilHalcon.cs
Normal file
192
HMIcode/SmallProject/SmallProject/Utils/PicUtilHalcon.cs
Normal file
@ -0,0 +1,192 @@
|
||||
using HalconDotNet;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SmallProject.Utils
|
||||
{
|
||||
public partial class PicUtil
|
||||
{
|
||||
public static HObject BitmapToHobject(Bitmap bitmap)
|
||||
{
|
||||
int height = bitmap.Height;//图像的高度
|
||||
int width = bitmap.Width;//图像的宽度
|
||||
|
||||
|
||||
Rectangle imgRect = new Rectangle(0, 0, width, height);
|
||||
BitmapData bitData = bitmap.LockBits(imgRect, ImageLockMode.ReadOnly, bitmap.PixelFormat);
|
||||
HObject image;
|
||||
|
||||
//由于Bitmap图像每行的字节数必须保持为4的倍数,因此在行的字节数不满足这个条件时,会对行进行补充,步幅数Stride表示的就是补充过后的每行的字节数,也成为扫描宽度
|
||||
int stride = bitData.Stride;
|
||||
switch (bitmap.PixelFormat)
|
||||
{
|
||||
case PixelFormat.Format8bppIndexed:
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
int count = height * width;
|
||||
byte[] data = new byte[count];
|
||||
byte* bptr = (byte*)bitData.Scan0;
|
||||
fixed (byte* pData = data)
|
||||
{
|
||||
for (int i = 0; i < height; i++)
|
||||
{
|
||||
for (int j = 0; j < width; j++)
|
||||
{
|
||||
/*
|
||||
*
|
||||
如果直接使用GenImage1,传入BitData的Scan0(图像首元素的指针)作为内存指针的话,如果图像不满足行为4的倍数,那么填充的部分也会参与进来,从而导致图像扭曲
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
//舍去填充的部分
|
||||
data[i * width + j] = bptr[i * stride + j];
|
||||
}
|
||||
|
||||
}
|
||||
HOperatorSet.GenImage1(out image, "byte", width, height, new IntPtr(pData));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
break;
|
||||
case PixelFormat.Format24bppRgb:
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
int count = height * width * 3;//24位的BitMap每个像素三个字节
|
||||
byte[] data = new byte[count];
|
||||
byte* bptr = (byte*)bitData.Scan0;
|
||||
fixed (byte* pData = data)
|
||||
{
|
||||
for (int i = 0; i < height; i++)
|
||||
{
|
||||
for (int j = 0; j < width * 3; j++)
|
||||
{
|
||||
//每个通道的像素需一一对应
|
||||
data[i * width * 3 + j] = bptr[i * stride + j];
|
||||
}
|
||||
}
|
||||
HOperatorSet.GenImageInterleaved(out image, new IntPtr(pData), "bgr", bitmap.Width, bitmap.Height, 0, "byte", bitmap.Width, bitmap.Height, 0, 0, -1, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
int count = height * width;
|
||||
byte[] data = new byte[count];
|
||||
byte* bptr = (byte*)bitData.Scan0;
|
||||
fixed (byte* pData = data)
|
||||
{
|
||||
for (int i = 0; i < height; i++)
|
||||
{
|
||||
for (int j = 0; j < width; j++)
|
||||
{
|
||||
data[i * width + j] = bptr[i * stride + j];
|
||||
}
|
||||
|
||||
}
|
||||
HOperatorSet.GenImage1(out image, "byte", width, height, new IntPtr(pData));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
break;
|
||||
}
|
||||
bitmap.UnlockBits(bitData);
|
||||
return image;
|
||||
}
|
||||
public static Bitmap HObject2Bitmap8(HObject image)
|
||||
{
|
||||
Bitmap res;
|
||||
HTuple hpoint, type, width, height;
|
||||
const int Alpha = 255;
|
||||
HOperatorSet.GetImagePointer1(image, out hpoint, out type, out width, out height);
|
||||
res = new Bitmap(width, height, PixelFormat.Format8bppIndexed);
|
||||
ColorPalette pal = res.Palette;
|
||||
for (int i = 0; i <= 255; i++)
|
||||
{ pal.Entries[i] = Color.FromArgb(Alpha, i, i, i); }
|
||||
|
||||
res.Palette = pal; Rectangle rect = new Rectangle(0, 0, width, height);
|
||||
BitmapData bitmapData = res.LockBits(rect, ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
|
||||
int PixelSize = Bitmap.GetPixelFormatSize(bitmapData.PixelFormat) / 8;
|
||||
IntPtr ptr1 = bitmapData.Scan0;
|
||||
IntPtr ptr2 = hpoint; int bytes = width * height;
|
||||
byte[] rgbvalues = new byte[bytes];
|
||||
System.Runtime.InteropServices.Marshal.Copy(ptr2, rgbvalues, 0, bytes);
|
||||
System.Runtime.InteropServices.Marshal.Copy(rgbvalues, 0, ptr1, bytes);
|
||||
res.UnlockBits(bitmapData);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public static Bitmap HObject2Bitmap24(HObject hObject)
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
HTuple pointer, w, h;
|
||||
//下面这个语句的PNG即为HObject类型
|
||||
HOperatorSet.GetImagePointer3(hObject, out HTuple r, out HTuple g, out HTuple b, out pointer, out w, out h);
|
||||
|
||||
byte[] red = new byte[w * h];
|
||||
byte[] green = new byte[w * h];
|
||||
byte[] blue = new byte[w * h];
|
||||
// 将指针指向地址的值取出来放到byte数组中
|
||||
Marshal.Copy(r, red, 0, w * h);
|
||||
Marshal.Copy(g, green, 0, w * h);
|
||||
Marshal.Copy(b, blue, 0, w * h);
|
||||
|
||||
Bitmap bitmap = new Bitmap(w, h, System.Drawing.Imaging.PixelFormat.Format32bppRgb);
|
||||
Rectangle rect2 = new Rectangle(0, 0, w, h);
|
||||
BitmapData bitmapData2 = bitmap.LockBits(rect2, ImageLockMode.ReadWrite, System.Drawing.Imaging.PixelFormat.Format32bppRgb);
|
||||
unsafe
|
||||
{
|
||||
byte* bptr2 = (byte*)bitmapData2.Scan0;
|
||||
|
||||
Parallel.For(0, w * h, i => {
|
||||
bptr2[i * 4] = blue[i];
|
||||
bptr2[i * 4 + 1] = green[i];
|
||||
bptr2[i * 4 + 2] = red[i];
|
||||
bptr2[i * 4 + 3] = 255;
|
||||
});
|
||||
}
|
||||
bitmap.UnlockBits(bitmapData2);
|
||||
return bitmap;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static HObject Bitmap2HObjectBpp24(Bitmap bmp)
|
||||
{
|
||||
try
|
||||
{
|
||||
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
|
||||
|
||||
BitmapData srcBmpData = bmp.LockBits(rect, ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
|
||||
HOperatorSet.GenImageInterleaved(out HObject image, srcBmpData.Scan0, "bgrx", bmp.Width, bmp.Height, 0, "byte", 0, 0, 0, 0, -1, 0);
|
||||
bmp.UnlockBits(srcBmpData);
|
||||
return image;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,141 @@
|
||||
using Microsoft.Win32;
|
||||
using SmallProject.Devices.Arm;
|
||||
using SmallProject.Logger;
|
||||
using SmallProject.Serials;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Management;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using static System.Windows.Forms.VisualStyles.VisualStyleElement.Window;
|
||||
|
||||
namespace SmallProject.WindowController
|
||||
{
|
||||
class JykerConnetContrl
|
||||
{
|
||||
private MainWindow M;
|
||||
|
||||
public JykerConnetContrl(MainWindow m)
|
||||
{
|
||||
M = m;
|
||||
m.bt_Link.Click += bt_Link_Click;
|
||||
m.bt_LinkAuto.Click += bt_LinkAuto_Click;
|
||||
LoadComList();
|
||||
}
|
||||
|
||||
//加载串口列表
|
||||
private List<string> LoadComList()
|
||||
{
|
||||
var list = new List<string>();
|
||||
const string keyPath = @"HARDWARE\DEVICEMAP\SERIALCOMM";
|
||||
using (RegistryKey key = Registry.LocalMachine.OpenSubKey(keyPath))
|
||||
{
|
||||
if (key != null)
|
||||
{
|
||||
M.Dispatcher.Invoke(() => {
|
||||
M.cb_ComList.Items.Clear();
|
||||
foreach (string valueName in key.GetValueNames())
|
||||
{
|
||||
if (!valueName.Contains("USB")) continue;
|
||||
string portName = key.GetValue(valueName) as string;
|
||||
var caption = $"{portName}_{valueName}";
|
||||
Match match = Regex.Match(caption, @"COM\d+");
|
||||
if (match.Success)
|
||||
{
|
||||
string comPortName = match.Value; // 提取匹配到的 COM 名称
|
||||
M.cb_ComList.Items.Add(comPortName);
|
||||
list.Add(comPortName);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
//自动连接
|
||||
private void bt_LinkAuto_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if(M.cb_ComList.Items.Count==0)
|
||||
{
|
||||
JLog.Info("没有搜索到连接口,请检查硬件连接");
|
||||
}
|
||||
|
||||
if(M.cb_ComList.Items.Count>1)
|
||||
{
|
||||
JLog.Info("搜索到多个连接口,请选择一个");
|
||||
}
|
||||
|
||||
M.cb_ComList.SelectedIndex = 0;
|
||||
//自动连接
|
||||
var name = M.cb_ComList.SelectedItem + "";
|
||||
var res = App.Core.Serial.Open(name);
|
||||
if (res)
|
||||
{
|
||||
M.cb_ComList.IsEnabled = false;
|
||||
M.bt_Link.Content = "断开连接";
|
||||
SetButtomState(true);
|
||||
M.gb_ClawControl.IsEnabled = true;
|
||||
M.gb_ArmControl.IsEnabled = true;
|
||||
M.bt_LinkAuto.IsEnabled = false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
//手动连接
|
||||
private void bt_Link_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
|
||||
|
||||
|
||||
if (M.bt_Link.Content.ToString() == "断开连接")
|
||||
{
|
||||
M.bt_LinkAuto.IsEnabled = true;
|
||||
M.cb_ComList.IsEnabled = true;
|
||||
M.bt_Link.Content = "手动连接";
|
||||
SetButtomState(false);
|
||||
M.gb_ClawControl.IsEnabled = false;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
var name = M.cb_ComList.SelectedItem + "";
|
||||
var res = App.Core.Serial.Open(name);
|
||||
if (res)
|
||||
{
|
||||
M.cb_ComList.IsEnabled = false;
|
||||
M.bt_Link.Content = "断开连接";
|
||||
SetButtomState(true);
|
||||
M.gb_ClawControl.IsEnabled = true;
|
||||
M.gb_ArmControl.IsEnabled = true;
|
||||
M.bt_LinkAuto.IsEnabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
//设置按钮状态
|
||||
private void SetButtomState(bool State = false)
|
||||
{
|
||||
M.Dispatcher.Invoke(() =>
|
||||
{
|
||||
M.bt_ApplyHomePosition.IsEnabled = State;
|
||||
M.bt_StopNow.IsEnabled = State;
|
||||
//M.bt_FK.IsEnabled = State;
|
||||
//M.bt_IK.IsEnabled = State;
|
||||
M.bt_MoveJoint.IsEnabled = State;
|
||||
M.bt_GetCurrentAngle.IsEnabled = State;
|
||||
M.bt_AddRecord.IsEnabled = State;
|
||||
M.bt_MoveArmHand.IsEnabled = State;
|
||||
M.bt_MoveLoop.IsEnabled = State;
|
||||
M.bt_MoveLoopStop.IsEnabled = State;
|
||||
M.bt_DeleteRecord.IsEnabled = State;
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,137 @@
|
||||
using Microsoft.VisualBasic.Logging;
|
||||
using SmallProject.Devices.Arm.Kinematic.Models;
|
||||
using SmallProject.Logger;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Management;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace SmallProject.WindowController
|
||||
{
|
||||
//正解逆解坐标实时解算
|
||||
class JykerKinematicContrl
|
||||
{
|
||||
private MainWindow M;
|
||||
|
||||
public JykerKinematicContrl(MainWindow m)
|
||||
{
|
||||
M = m;
|
||||
|
||||
//初始化值
|
||||
Init();
|
||||
|
||||
//绑定事件
|
||||
m.tb_X.TextChanged += Tb_IK;
|
||||
m.tb_Y.TextChanged += Tb_IK;
|
||||
m.tb_Z.TextChanged += Tb_IK;
|
||||
m.tb_A.TextChanged += Tb_IK;
|
||||
m.tb_B.TextChanged += Tb_IK;
|
||||
m.tb_C.TextChanged += Tb_IK;
|
||||
|
||||
m.tb_Joint1.TextChanged += Tb_FK;
|
||||
m.tb_Joint2.TextChanged += Tb_FK;
|
||||
m.tb_Joint3.TextChanged += Tb_FK;
|
||||
m.tb_Joint4.TextChanged += Tb_FK;
|
||||
m.tb_Joint5.TextChanged += Tb_FK;
|
||||
m.tb_Joint6.TextChanged += Tb_FK;
|
||||
}
|
||||
|
||||
private void Init()
|
||||
{
|
||||
var joint = App.Core.Jyker.prepareJoints;
|
||||
M.tb_Joint1.Text = Math.Round(joint.a[0], 2) + "";
|
||||
M.tb_Joint2.Text = Math.Round(joint.a[1], 2) + "";
|
||||
M.tb_Joint3.Text = Math.Round(joint.a[2], 2) + "";
|
||||
M.tb_Joint4.Text = Math.Round(joint.a[3], 2) + "";
|
||||
M.tb_Joint5.Text = Math.Round(joint.a[4], 2) + "";
|
||||
M.tb_Joint6.Text = Math.Round(joint.a[5], 2) + "";
|
||||
|
||||
var pose6 = App.Core.Jyker.preparePose6D;
|
||||
App.Core.Jyker.dof6Solver.SolveFK(App.Core.Jyker.prepareJoints, App.Core.Jyker.preparePose6D);
|
||||
M.tb_X.Text = Math.Round(pose6.X, 2) + "";
|
||||
M.tb_Y.Text = Math.Round(pose6.Y, 2) + "";
|
||||
M.tb_Z.Text = Math.Round(pose6.Z, 2) + "";
|
||||
M.tb_A.Text = Math.Round(pose6.A, 2) + "";
|
||||
M.tb_B.Text = Math.Round(pose6.B, 2) + "";
|
||||
M.tb_C.Text = Math.Round(pose6.C, 2) + "";
|
||||
}
|
||||
|
||||
//正解
|
||||
private void Tb_FK(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
double.TryParse(M.tb_Joint1.Text, out double a1);
|
||||
double.TryParse(M.tb_Joint2.Text, out double a2);
|
||||
double.TryParse(M.tb_Joint3.Text, out double a3);
|
||||
double.TryParse(M.tb_Joint4.Text, out double a4);
|
||||
double.TryParse(M.tb_Joint5.Text, out double a5);
|
||||
double.TryParse(M.tb_Joint6.Text, out double a6);
|
||||
|
||||
App.Core.Jyker.prepareJoints = new Joint6D_t(a1, a2, a3, a4, a5, a6);
|
||||
App.Core.Jyker.dof6Solver.SolveFK(App.Core.Jyker.prepareJoints, App.Core.Jyker.preparePose6D);
|
||||
|
||||
|
||||
|
||||
//更新正解位姿
|
||||
var pose6 = App.Core.Jyker.preparePose6D;
|
||||
M.tb_X.TextChanged -= Tb_IK;
|
||||
M.tb_Y.TextChanged -= Tb_IK;
|
||||
M.tb_Z.TextChanged -= Tb_IK;
|
||||
M.tb_A.TextChanged -= Tb_IK;
|
||||
M.tb_B.TextChanged -= Tb_IK;
|
||||
M.tb_C.TextChanged -= Tb_IK;
|
||||
M.tb_X.Text = Math.Round(pose6.X, 2) + "";
|
||||
M.tb_Y.Text = Math.Round(pose6.Y, 2) + "";
|
||||
M.tb_Z.Text = Math.Round(pose6.Z, 2) + "";
|
||||
M.tb_A.Text = Math.Round(pose6.A, 2) + "";
|
||||
M.tb_B.Text = Math.Round(pose6.B, 2) + "";
|
||||
M.tb_C.Text = Math.Round(pose6.C, 2) + "";
|
||||
M.tb_X.TextChanged += Tb_IK;
|
||||
M.tb_Y.TextChanged += Tb_IK;
|
||||
M.tb_Z.TextChanged += Tb_IK;
|
||||
M.tb_A.TextChanged += Tb_IK;
|
||||
M.tb_B.TextChanged += Tb_IK;
|
||||
M.tb_C.TextChanged += Tb_IK;
|
||||
}
|
||||
|
||||
//逆解
|
||||
private void Tb_IK(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
double.TryParse(M.tb_X.Text, out double x);
|
||||
double.TryParse(M.tb_Y.Text, out double y);
|
||||
double.TryParse(M.tb_Z.Text, out double z);
|
||||
double.TryParse(M.tb_A.Text, out double a);
|
||||
double.TryParse(M.tb_B.Text, out double b);
|
||||
double.TryParse(M.tb_C.Text, out double c);
|
||||
|
||||
var res = App.Core.Jyker.SolveIK(new double[] { x, y, z, a, b, c });
|
||||
if (!res)
|
||||
{
|
||||
JLog.Info("逆解无解");
|
||||
}
|
||||
var joint = App.Core.Jyker.prepareJoints;
|
||||
|
||||
|
||||
M.tb_Joint1.TextChanged -= Tb_FK;
|
||||
M.tb_Joint2.TextChanged -= Tb_FK;
|
||||
M.tb_Joint3.TextChanged -= Tb_FK;
|
||||
M.tb_Joint4.TextChanged -= Tb_FK;
|
||||
M.tb_Joint5.TextChanged -= Tb_FK;
|
||||
M.tb_Joint6.TextChanged -= Tb_FK;
|
||||
M.tb_Joint1.Text = Math.Round(joint.a[0], 2) + "";
|
||||
M.tb_Joint2.Text = Math.Round(joint.a[1], 2) + "";
|
||||
M.tb_Joint3.Text = Math.Round(joint.a[2], 2) + "";
|
||||
M.tb_Joint4.Text = Math.Round(joint.a[3], 2) + "";
|
||||
M.tb_Joint5.Text = Math.Round(joint.a[4], 2) + "";
|
||||
M.tb_Joint6.Text = Math.Round(joint.a[5], 2) + "";
|
||||
M.tb_Joint1.TextChanged += Tb_FK;
|
||||
M.tb_Joint2.TextChanged += Tb_FK;
|
||||
M.tb_Joint3.TextChanged += Tb_FK;
|
||||
M.tb_Joint4.TextChanged += Tb_FK;
|
||||
M.tb_Joint5.TextChanged += Tb_FK;
|
||||
M.tb_Joint6.TextChanged += Tb_FK;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
using SmallProject.Aliyun;
|
||||
using SmallProject.MCP;
|
||||
using SmallProject.Serials.Slcan;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SmallProject.WindowController
|
||||
{
|
||||
internal class JykerMoveContrl
|
||||
{
|
||||
private MainWindow M;
|
||||
private JykerControlMCP jykerControlMCP;
|
||||
|
||||
public JykerMoveContrl(MainWindow m)
|
||||
{
|
||||
M = m;
|
||||
m.bt_ApplyHomePosition.Click += Bt_ApplyHomePosition_Click; ;
|
||||
m.bt_MoveJoint.Click += Bt_MoveJoint_Click;
|
||||
m.bt_StopNow.Click += Bt_StopNow_Click;
|
||||
m.bt_ConnectAi.Click += Bt_ConnectAi_Click;
|
||||
}
|
||||
|
||||
//连接语音助手
|
||||
private void Bt_ConnectAi_Click(object sender, System.Windows.RoutedEventArgs e)
|
||||
{
|
||||
//AiAssistant aiAssistant = new AiAssistant();
|
||||
//aiAssistant.Init();
|
||||
Task.Run(async () =>
|
||||
{
|
||||
jykerControlMCP = new JykerControlMCP();
|
||||
await jykerControlMCP.Init();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
//设置立刻停止
|
||||
private void Bt_StopNow_Click(object sender, System.Windows.RoutedEventArgs e)
|
||||
{
|
||||
App.Core.Jyker.StopNow();
|
||||
}
|
||||
|
||||
//设置当前位置为0位
|
||||
private void Bt_ApplyHomePosition_Click(object sender, System.Windows.RoutedEventArgs e)
|
||||
{
|
||||
App.Core.Jyker.ApplyHomePosition();
|
||||
}
|
||||
|
||||
|
||||
//运动机械臂
|
||||
private void Bt_MoveJoint_Click(object sender, System.Windows.RoutedEventArgs e)
|
||||
{
|
||||
double.TryParse(M.tb_Joint1.Text, out double a1);
|
||||
double.TryParse(M.tb_Joint2.Text, out double a2);
|
||||
double.TryParse(M.tb_Joint3.Text, out double a3);
|
||||
double.TryParse(M.tb_Joint4.Text, out double a4);
|
||||
double.TryParse(M.tb_Joint5.Text, out double a5);
|
||||
double.TryParse(M.tb_Joint6.Text, out double a6);
|
||||
|
||||
App.Core.Jyker.Move(new double[6] { a1, a2, a3, a4, a5, a6 });
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace SmallProject.WindowController
|
||||
{
|
||||
public class JykerStatusContrl
|
||||
{
|
||||
private MainWindow M;
|
||||
|
||||
public JykerStatusContrl(MainWindow m)
|
||||
{
|
||||
M = m;
|
||||
m.bt_ArmLoopStart.Click += Bt_ArmLoopStart_Click;
|
||||
m.bt_ArmLoopEnd.Click += Bt_ArmLoopEnd_Click;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//监听机械臂各个轴的状态
|
||||
private void Bt_ArmLoopStart_Click(object sender, System.Windows.RoutedEventArgs e)
|
||||
{
|
||||
App.Core.Loop += LoopStatus;
|
||||
App.Core.Loop += App.Core.Jyker.LoopStatus;
|
||||
M.bt_ArmLoopStart.IsEnabled = false;
|
||||
M.bt_ArmLoopEnd.IsEnabled = true;
|
||||
}
|
||||
//结束监听机械臂各个轴的状态
|
||||
private void Bt_ArmLoopEnd_Click(object sender, System.Windows.RoutedEventArgs e)
|
||||
{
|
||||
App.Core.Loop -= LoopStatus;
|
||||
App.Core.Loop -= App.Core.Jyker.LoopStatus;
|
||||
M.bt_ArmLoopStart.IsEnabled = true;
|
||||
M.bt_ArmLoopEnd.IsEnabled = false;
|
||||
}
|
||||
|
||||
//循环获取电机信息
|
||||
public void LoopStatus()
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
M.Dispatcher.Invoke(() =>
|
||||
{
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
var pb = M.FindName($"pb_pJ{i + 1}") as ProgressBar;
|
||||
var tb = M.FindName($"tb_pJ{i + 1}") as TextBlock;
|
||||
if(pb!=null&&tb!=null)
|
||||
{
|
||||
pb.Value = App.Core.Jyker.motorJ[i].Current*1000;
|
||||
tb.Text = App.Core.Jyker.motorJ[i].Current*1000 + "";
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
Thread.Sleep(50);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,205 @@
|
||||
using Emgu.CV;
|
||||
using HalconDotNet;
|
||||
using Masuit.Tools.Strings;
|
||||
using Microsoft.VisualBasic.Logging;
|
||||
using NAudio.CoreAudioApi;
|
||||
using OpenCvSharp;
|
||||
using OpenCvSharp.Extensions;
|
||||
using SmallProject.Aliyun.Models;
|
||||
using SmallProject.Logger;
|
||||
using SmallProject.Utils;
|
||||
using SmallProject.YOLO;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media.Media3D;
|
||||
using Yolov5Net.Scorer;
|
||||
using static System.Formats.Asn1.AsnWriter;
|
||||
|
||||
namespace SmallProject.WindowController
|
||||
{
|
||||
class JykerViewContrl
|
||||
{
|
||||
private MainWindow M;
|
||||
bool IsVideo = false;
|
||||
Bitmap bitmap;
|
||||
Dictionary<HTuple,string> shapeModels = new Dictionary<HTuple,string>();
|
||||
public JykerViewContrl(MainWindow m)
|
||||
{
|
||||
M = m;
|
||||
M.bt_StartDetect.Click += Bt_StartDetect_Click;
|
||||
//加载model
|
||||
LoadModels();
|
||||
}
|
||||
|
||||
private void LoadModels()
|
||||
{
|
||||
string path = App.JConfig.FindModelPath;
|
||||
string searchPattern = "*.model";
|
||||
string[] files = Directory.GetFiles(path, searchPattern, SearchOption.AllDirectories);
|
||||
|
||||
if (files.Length > 0)
|
||||
{
|
||||
foreach (string file in files)
|
||||
{
|
||||
HOperatorSet.ReadShapeModel(file, out HTuple modelID);
|
||||
shapeModels.Add(modelID, file.Replace(".model",""));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void Bt_StartDetect_Click(object sender, System.Windows.RoutedEventArgs e)
|
||||
{
|
||||
|
||||
if (!IsVideo)
|
||||
{
|
||||
IsVideo = true;
|
||||
Task.Run(() => {
|
||||
try
|
||||
{
|
||||
// 创建视频捕获对象并打开默认摄像头(0)
|
||||
using (var capture = new OpenCvSharp.VideoCapture(1))
|
||||
{
|
||||
if (!capture.IsOpened())
|
||||
{
|
||||
JLog.Info("好像没有找到摄像头哦");
|
||||
return;
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (!IsVideo)
|
||||
{
|
||||
M.Dispatcher.Invoke(() =>
|
||||
{
|
||||
M.bt_StartDetect.Content = "开启视频";
|
||||
});
|
||||
break;
|
||||
}
|
||||
M.Dispatcher.Invoke(() =>
|
||||
{
|
||||
M.bt_StartDetect.Content = "运行中";
|
||||
});
|
||||
|
||||
OpenCvSharp.Mat frame = new OpenCvSharp.Mat();
|
||||
|
||||
// 从摄像头中读取当前帧图像
|
||||
bool success = capture.Read(frame);
|
||||
|
||||
if (!success || frame.Empty())
|
||||
continue;
|
||||
|
||||
// 在这里进行处理或显示图像
|
||||
bitmap = BitmapConverter.ToBitmap(frame);
|
||||
M.Dispatcher.Invoke(() =>
|
||||
{
|
||||
|
||||
if(shapeModels.Count>0)
|
||||
{
|
||||
var hv_ModelIDs = new HTuple();
|
||||
var NumMatches = new HTuple();
|
||||
foreach (var mod in shapeModels)
|
||||
{
|
||||
hv_ModelIDs = hv_ModelIDs.TupleConcat(mod.Key);
|
||||
NumMatches = NumMatches.TupleConcat(1);
|
||||
}
|
||||
var Hobj = PicUtil.BitmapToHobject(bitmap);
|
||||
//Image, ModelIDs, -0.39, 0.78, 0.9, 1.1, 0.5, 1, \
|
||||
//0.5, 'least_squares', 0, 0.9, Row, Column, Angle,\
|
||||
//Scale, Score, Model
|
||||
HOperatorSet.FindScaledShapeModels(Hobj, hv_ModelIDs, -0.39
|
||||
, 0.78, 0.2, 0.9, 1.1, NumMatches, 0.5, "interpolation", 0, 0.9, out HTuple hv_Row,
|
||||
out HTuple hv_Column, out HTuple hv_Angle, out HTuple hv_Scale, out HTuple hv_Score, out HTuple modelID);
|
||||
if(hv_Scale.Length>0)
|
||||
{
|
||||
|
||||
//border.findShapeModelEntity.Angle = Math.Abs(((double)hv_Angle) / Math.PI * 180);
|
||||
//border.findShapeModelEntity.Score = (double)hv_Score;
|
||||
//border.findShapeModelEntity.X = hv_Column - width / 2;
|
||||
//border.findShapeModelEntity.Y = hv_Row - height / 2;
|
||||
//border.findShapeModelEntity.Width = width;
|
||||
//border.findShapeModelEntity.Height = height;
|
||||
var name = shapeModels[modelID];
|
||||
HOperatorSet.GetImageSize(Hobj, out HTuple width, out HTuple height);
|
||||
float x, y, w, h;
|
||||
x = hv_Column - width / 2;
|
||||
y = hv_Row - height / 2;
|
||||
w = width;
|
||||
h = height;
|
||||
DrawToBitmap.DrawRectangle(bitmap, x, y, w, h,
|
||||
text: $"{name}({hv_Score})");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//AreaDetectYolo8 areaDetectBarError = new AreaDetectYolo8("yolov8n");
|
||||
//var resList = areaDetectBarError.getPrediction(bitmap);
|
||||
//if (resList.Count() > 0)
|
||||
//{
|
||||
// foreach (var item in resList)
|
||||
// {
|
||||
// //if (item.Label.Id != 0) continue;
|
||||
// DrawToBitmap.DrawRectangle(bitmap, item.Rectangle.X, item.Rectangle.Y, item.Rectangle.Width, item.Rectangle.Height,
|
||||
// text: $"{item.Label.Title}({item.Score})");
|
||||
// }
|
||||
//}
|
||||
|
||||
M.ImageBig.Source = PicUtil.ToBitmapSource(bitmap);
|
||||
|
||||
if (!string.IsNullOrEmpty(App.Core.ViewName))
|
||||
{
|
||||
string dir = App.JConfig.FindModelPath;
|
||||
if(!Directory.Exists(dir))
|
||||
{
|
||||
Directory.CreateDirectory(dir);
|
||||
}
|
||||
PicUtil.SaveImageToFile(PicUtil.ToBitmapSource(bitmap), $"{dir}/{App.Core.ViewName}.jpg");
|
||||
HOperatorSet.ReadImage(out HObject part, $"{dir}/{App.Core.ViewName}.jpg");
|
||||
HOperatorSet.CreateScaledShapeModel(part, 5
|
||||
, (new HTuple(-45)).TupleRad()
|
||||
, (new HTuple(90)).TupleRad(), "auto"
|
||||
, 0.8,1.0, "auto", "none", "ignore_global_polarity", 40, 10, out HTuple hv_ModelID);
|
||||
HOperatorSet.WriteShapeModel(hv_ModelID, $"{dir}/{App.Core.ViewName}.model");
|
||||
shapeModels.Add(hv_ModelID, App.Core.ViewName);
|
||||
App.Core.ViewName = "";
|
||||
}
|
||||
});
|
||||
|
||||
Thread.Sleep(50);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
M.Dispatcher.Invoke(() =>
|
||||
{
|
||||
M.bt_StartDetect.Content = "开启视频";
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
IsVideo = false;
|
||||
M.Dispatcher.Invoke(() => {
|
||||
M.bt_StartDetect.Content = "开启视频";
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
using SmallProject.Logger;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using static System.Net.Mime.MediaTypeNames;
|
||||
|
||||
namespace SmallProject.WindowController
|
||||
{
|
||||
internal class LogContrl
|
||||
{
|
||||
private MainWindow M;
|
||||
|
||||
public LogContrl(MainWindow m)
|
||||
{
|
||||
M = m;
|
||||
JLog.MessageEvent += Log_MessageEvent;
|
||||
}
|
||||
int richTextLine = 0;
|
||||
//打印日志到桌面
|
||||
private void Log_MessageEvent(string text)
|
||||
{
|
||||
try
|
||||
{
|
||||
var str = $"{DateTime.Now.ToString("HH:mm:ss")}:{text}\n";
|
||||
M.Dispatcher.Invoke(() =>
|
||||
{
|
||||
M.tbLog.AppendText(str);
|
||||
M.tbLog.ScrollToEnd();
|
||||
if (richTextLine >= 3000)
|
||||
{
|
||||
M.tbLog.Text = string.Empty;
|
||||
richTextLine = 0;
|
||||
}
|
||||
richTextLine++;
|
||||
});
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
using SmallProject.BiliBili;
|
||||
using SmallProject.Logger;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SmallProject.WindowController
|
||||
{
|
||||
internal class LogLiveContrl
|
||||
{
|
||||
private MainWindow M;
|
||||
|
||||
public LogLiveContrl(MainWindow m)
|
||||
{
|
||||
M = m;
|
||||
JLog.MessageEventLive += Log_MessageEvent;
|
||||
|
||||
BStationLive bStationLive = new BStationLive();
|
||||
bStationLive.Init();
|
||||
}
|
||||
int richTextLine = 0;
|
||||
//打印日志到桌面
|
||||
private void Log_MessageEvent(string text)
|
||||
{
|
||||
try
|
||||
{
|
||||
var str = $"{text}\n";
|
||||
M.Dispatcher.Invoke(() =>
|
||||
{
|
||||
M.tbLogLive.AppendText(str);
|
||||
M.tbLogLive.ScrollToEnd();
|
||||
if (richTextLine >= 3000)
|
||||
{
|
||||
M.tbLogLive.Text = string.Empty;
|
||||
richTextLine = 0;
|
||||
}
|
||||
richTextLine++;
|
||||
});
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
287
HMIcode/SmallProject/SmallProject/YOLO/YoloV8PoseOutput.cs
Normal file
287
HMIcode/SmallProject/SmallProject/YOLO/YoloV8PoseOutput.cs
Normal file
@ -0,0 +1,287 @@
|
||||
using Microsoft.ML.OnnxRuntime;
|
||||
using Microsoft.ML.OnnxRuntime.Tensors;
|
||||
using OpenCvSharp;
|
||||
using OpenCvSharp.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Ink;
|
||||
|
||||
namespace SmallProject.YOLO
|
||||
{
|
||||
public class YoloV8PoseOutput
|
||||
{
|
||||
public class Prediction
|
||||
{
|
||||
public float X { get; set; }
|
||||
public float Y { get; set; }
|
||||
public float Width { get; set; }
|
||||
public float Height { get; set; }
|
||||
public float Confidence { get; set; }
|
||||
public int Class { get; set; }
|
||||
public Keypoint[] Keypoints { get; set; }
|
||||
}
|
||||
|
||||
public class Keypoint
|
||||
{
|
||||
public float X { get; set; }
|
||||
public float Y { get; set; }
|
||||
public float Confidence { get; set; }
|
||||
}
|
||||
|
||||
//加载模型并运算
|
||||
public static List<Prediction> GetPredictions(System.Drawing.Bitmap bitmap, out System.Drawing.Bitmap outImage)
|
||||
{
|
||||
// 设置目标尺寸 (如 YOLOv8 需要 640x640)
|
||||
int targetWidth = 640;
|
||||
int targetHeight = 640;
|
||||
outImage = bitmap;
|
||||
// 预处理图像
|
||||
DenseTensor<float> inputTensor = Preprocess(bitmap, targetWidth, targetHeight
|
||||
, out float scalarR, out float paddingWidth, out float paddingHeight);
|
||||
|
||||
// 加载 ONNX 模型
|
||||
string modelPath = "yolov8n-pose.onnx";
|
||||
using (var session = new InferenceSession(modelPath))
|
||||
{
|
||||
// 创建模型输入
|
||||
var inputs = new List<NamedOnnxValue>
|
||||
{
|
||||
NamedOnnxValue.CreateFromTensor("images", inputTensor)
|
||||
};
|
||||
|
||||
// 运行推理
|
||||
using (var results = session.Run(inputs))
|
||||
{
|
||||
// 获取输出
|
||||
var outputName = session.OutputMetadata.Keys.First();
|
||||
var outputTensor = results.First(r => r.Name == outputName).AsTensor<float>();
|
||||
var outputArray = outputTensor.ToArray();
|
||||
// 解析输出 (假设 numPredictions = 1 和 numKeypoints = 17)
|
||||
int numPredictions = outputTensor.Dimensions[2];
|
||||
int numKeypoints = (outputTensor.Dimensions[1] - 5) / 3; // 根据实际模型输出调整
|
||||
Prediction[] predictions = ParseOutput(outputArray, numPredictions, numKeypoints, scalarR, paddingWidth, paddingHeight);
|
||||
|
||||
// 在图像上绘制检测结果
|
||||
DrawPredictions(bitmap, predictions);
|
||||
|
||||
// 保存或显示结果图像
|
||||
//bitmap.Save("annotated_image.jpg");
|
||||
|
||||
return predictions.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//原图缩放成 640*640
|
||||
private static Mat Letterbox(Mat image, out float scalarR, out float paddingWidth, out float paddingHeight
|
||||
, Size newShape = default(Size), Scalar color = default(Scalar), bool scaleup = true)
|
||||
{
|
||||
if (newShape == default(Size))
|
||||
{
|
||||
newShape = new Size(640, 640);
|
||||
}
|
||||
if (color == default(Scalar))
|
||||
{
|
||||
color = new Scalar(114, 114, 114); // 默认灰色填充
|
||||
}
|
||||
|
||||
// 原图像大小
|
||||
int originalHeight = image.Height;
|
||||
int originalWidth = image.Width;
|
||||
|
||||
// 缩放比例
|
||||
scalarR = Math.Min((float)newShape.Width / originalWidth, (float)newShape.Height / originalHeight);
|
||||
|
||||
// 只进行下采样, 因为上采样会让图片模糊
|
||||
if (!scaleup)
|
||||
{
|
||||
scalarR = (float)Math.Min(scalarR, 1.0);
|
||||
}
|
||||
|
||||
// 计算调整后的图像大小
|
||||
int newUnpadWidth = (int)Math.Round(originalWidth * scalarR);
|
||||
int newUnpadHeight = (int)Math.Round(originalHeight * scalarR);
|
||||
|
||||
// 计算填充量
|
||||
paddingWidth = (newShape.Width - newUnpadWidth) / 2.0f; // width padding
|
||||
paddingHeight = (newShape.Height - newUnpadHeight) / 2.0f; // height padding
|
||||
|
||||
// 调整图像大小
|
||||
Mat resizedImage = new Mat();
|
||||
Cv2.Resize(image, resizedImage, new Size(newUnpadWidth, newUnpadHeight));
|
||||
|
||||
// 计算四周的填充
|
||||
int top = (int)Math.Round(paddingHeight - 0.1);
|
||||
int bottom = (int)Math.Round(paddingHeight + 0.1);
|
||||
int left = (int)Math.Round(paddingWidth - 0.1);
|
||||
int right = (int)Math.Round(paddingWidth + 0.1);
|
||||
|
||||
// 添加填充
|
||||
Mat paddedImage = new Mat();
|
||||
Cv2.CopyMakeBorder(resizedImage, paddedImage, top, bottom, left, right, BorderTypes.Constant, color);
|
||||
|
||||
return paddedImage;
|
||||
}
|
||||
|
||||
//过滤output数据
|
||||
private static Prediction[] ParseOutput(float[] output, int numPredictions, int numKeypoints, float scalarR, float paddingWidth, float paddingHeight)
|
||||
{
|
||||
int numAttributes = 4 + 1 + (3 * numKeypoints); // 4 for bbox, 1 for confidence, 1 for class, 3 * numKeypoints for keypoints
|
||||
List<Prediction> predictions = new List<Prediction>();
|
||||
|
||||
for (int i = 0; i < numPredictions; i++)
|
||||
{
|
||||
|
||||
Prediction prediction = new Prediction
|
||||
{
|
||||
X = (output[i] - paddingWidth) / scalarR,
|
||||
Y = (output[i + 1 * numPredictions] - paddingHeight) / scalarR,
|
||||
Width = (output[i + 2 * numPredictions]) / scalarR,
|
||||
Height = (output[i + 3 * numPredictions]) / scalarR,
|
||||
Confidence = output[i + 4 * numPredictions],
|
||||
Keypoints = new Keypoint[numKeypoints]
|
||||
};
|
||||
|
||||
//只要 conf>0.7 的
|
||||
if (prediction.Confidence < 0.7) continue;
|
||||
|
||||
for (int j = 0; j < numKeypoints; j++)
|
||||
{
|
||||
int keypointOffset = i + (5 + (j * 3)) * numPredictions;
|
||||
prediction.Keypoints[j] = new Keypoint
|
||||
{
|
||||
X = (output[keypointOffset] - paddingWidth) / scalarR,
|
||||
Y = (output[keypointOffset + 1 * numPredictions] - paddingHeight) / scalarR,
|
||||
Confidence = output[keypointOffset + 2 * numPredictions]
|
||||
};
|
||||
}
|
||||
|
||||
predictions.Add(prediction);
|
||||
}
|
||||
//极大值抑制
|
||||
predictions = NonMaximumSuppression(predictions);
|
||||
return predictions.ToArray();
|
||||
}
|
||||
|
||||
//在图上画上检测结果
|
||||
private static void DrawPredictions(System.Drawing.Bitmap bitmap, Prediction[] predictions)
|
||||
{
|
||||
using (System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(bitmap))
|
||||
{
|
||||
using (System.Drawing.Pen pen = new System.Drawing.Pen(System.Drawing.Color.Red, 2))
|
||||
{
|
||||
using (System.Drawing.SolidBrush brush = new System.Drawing.SolidBrush(System.Drawing.Color.Blue))
|
||||
{
|
||||
foreach (var pred in predictions)
|
||||
{
|
||||
// Draw bounding box
|
||||
g.DrawRectangle(pen, pred.X - pred.Width / 2, pred.Y - pred.Height / 2, pred.Width, pred.Height);
|
||||
|
||||
// Draw keypoints
|
||||
foreach (var keypoint in pred.Keypoints)
|
||||
{
|
||||
if (keypoint.Confidence > 0.5) // Optional: Draw only confident keypoints
|
||||
{
|
||||
g.FillEllipse(brush, keypoint.X - 3, keypoint.Y - 3, 6, 6);
|
||||
}
|
||||
}
|
||||
|
||||
// 绘制骨架连接
|
||||
int[][] skeleton = new int[][]
|
||||
{
|
||||
new int[]{0, 1}, new int[]{0, 2}, new int[]{1, 3}, new int[]{2, 4},
|
||||
new int[]{5, 6}, new int[]{5, 7}, new int[]{7, 9}, new int[]{6, 8}, new int[]{8, 10},
|
||||
new int[]{11, 12}, new int[]{11, 13}, new int[]{13, 15},
|
||||
new int[]{12, 14}, new int[]{ 14, 16}
|
||||
};
|
||||
|
||||
foreach (var bone in skeleton)
|
||||
{
|
||||
int kpt1 = bone[0], kpt2 = bone[1];
|
||||
float x1 = pred.Keypoints[kpt1].X;
|
||||
float y1 = pred.Keypoints[kpt1].Y;
|
||||
float x2 = pred.Keypoints[kpt2].X;
|
||||
float y2 = pred.Keypoints[kpt2].Y;
|
||||
|
||||
g.DrawLine(pen, x1, y1, x2, y2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//图像归一化
|
||||
private static DenseTensor<float> Preprocess(System.Drawing.Bitmap bitmap, int targetWidth, int targetHeight
|
||||
, out float scalarR, out float paddingWidth, out float paddingHeight)
|
||||
{
|
||||
// 1. 调整图像大小
|
||||
var mat = Letterbox(BitmapConverter.ToMat(bitmap), out scalarR, out paddingWidth, out paddingHeight);
|
||||
var resizedBitmap = BitmapConverter.ToBitmap(mat);
|
||||
// 2. 创建浮点数组存储图像数据 (channels, height, width)
|
||||
int channels = 3; // RGB
|
||||
var tensor = new DenseTensor<float>(new[] { 1, channels, targetHeight, targetWidth });
|
||||
|
||||
// 3. 逐像素填充数组,归一化为 [0, 1]
|
||||
for (int y = 0; y < targetHeight; y++)
|
||||
{
|
||||
for (int x = 0; x < targetWidth; x++)
|
||||
{
|
||||
System.Drawing.Color color = resizedBitmap.GetPixel(x, y);
|
||||
tensor[0, 0, y, x] = color.B / 255f; // Blue channel
|
||||
tensor[0, 1, y, x] = color.R / 255f; // Red channel
|
||||
tensor[0, 2, y, x] = color.G / 255f; // Green channel
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return tensor;
|
||||
}
|
||||
|
||||
//极大值抑制处理图像框
|
||||
private static List<Prediction> NonMaximumSuppression(List<Prediction> predictions, float iouThreshold = 0.5f)
|
||||
{
|
||||
// 按置信度降序排序
|
||||
var sortedPredictions = predictions.OrderByDescending(p => p.Confidence).ToList();
|
||||
List<Prediction> result = new List<Prediction>();
|
||||
|
||||
while (sortedPredictions.Count > 0)
|
||||
{
|
||||
// 取出置信度最高的检测框
|
||||
var bestPrediction = sortedPredictions[0];
|
||||
result.Add(bestPrediction);
|
||||
sortedPredictions.RemoveAt(0);
|
||||
|
||||
// 移除与当前检测框重叠度大于阈值的检测框
|
||||
sortedPredictions = sortedPredictions.Where(p => IoU(bestPrediction, p) < iouThreshold).ToList();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//计算图像相交区域
|
||||
private static float IoU(Prediction boxA, Prediction boxB)
|
||||
{
|
||||
// 计算重叠区域的坐标
|
||||
float x1 = Math.Max(boxA.X, boxB.X);
|
||||
float y1 = Math.Max(boxA.Y, boxB.Y);
|
||||
float x2 = Math.Min(boxA.X + boxA.Width, boxB.X + boxB.Width);
|
||||
float y2 = Math.Min(boxA.Y + boxA.Height, boxB.Y + boxB.Height);
|
||||
|
||||
// 计算重叠区域的面积
|
||||
float interArea = Math.Max(0, x2 - x1) * Math.Max(0, y2 - y1);
|
||||
|
||||
// 计算两个检测框的面积
|
||||
float boxAArea = boxA.Width * boxA.Height;
|
||||
float boxBArea = boxB.Width * boxB.Height;
|
||||
|
||||
// 计算交并比(IoU)
|
||||
return interArea / (boxAArea + boxBArea - interArea);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user