mirror of
https://github.com/scratchfoundation/scratch-link.git
synced 2025-07-08 19:43:59 -04:00
124 lines
5.3 KiB
C#
124 lines
5.3 KiB
C#
// <copyright file="PeripheralSession.cs" company="Scratch Foundation">
|
|
// Copyright (c) Scratch Foundation. All rights reserved.
|
|
// </copyright>
|
|
|
|
namespace ScratchLink;
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Text.Json;
|
|
using System.Threading.Tasks;
|
|
using Fleck;
|
|
using ScratchLink.Extensions;
|
|
using ScratchLink.JsonRpc;
|
|
|
|
/// <summary>
|
|
/// A kind of session which discovers and connects to peripheral devices by some sort of address.
|
|
/// One session can search for, connect to, and interact with one peripheral device.
|
|
/// Handles address privacy.
|
|
/// </summary>
|
|
/// <typeparam name="TDiscoveredPeripheral">The type used to track discovered peripheral devices. Passed to <c>DoConnect</c>.</typeparam>
|
|
/// <typeparam name="TPeripheralAddress">The type of address (UUID, path, etc.) used by this session to identify a peripheral device.</typeparam>
|
|
public abstract class PeripheralSession<TDiscoveredPeripheral, TPeripheralAddress> : Session
|
|
where TDiscoveredPeripheral : class
|
|
{
|
|
private readonly Dictionary<TPeripheralAddress, string> peripheralAddressToId = new ();
|
|
private readonly Dictionary<string, TDiscoveredPeripheral> discoveredPeripherals = new ();
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="PeripheralSession{TPeripheral, TPeripheralAddress}"/> class.
|
|
/// </summary>
|
|
/// <param name="webSocket">The WebSocket which this session will use for communication.</param>
|
|
public PeripheralSession(IWebSocketConnection webSocket)
|
|
: base(webSocket)
|
|
{
|
|
this.Handlers["connect"] = this.HandleConnect;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether this session is connected to a peripheral device.
|
|
/// </summary>
|
|
protected abstract bool IsConnected { get; }
|
|
|
|
/// <summary>
|
|
/// Implement the JSON-RPC "connect" request to connect to a particular peripheral device.
|
|
/// Valid in the discovery state; transitions to connected state on success.
|
|
/// </summary>
|
|
/// <param name="methodName">The name of the method being called ("connect").</param>
|
|
/// <param name="args">
|
|
/// A JSON object containing the ID of a peripheral found by the most recent discovery request.
|
|
/// </param>
|
|
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
|
protected async Task<object> HandleConnect(string methodName, JsonElement? args)
|
|
{
|
|
if (this.IsConnected)
|
|
{
|
|
throw JsonRpc2Error.InvalidRequest("cannot connect when already connected").ToException();
|
|
}
|
|
|
|
var peripheralId = args?.TryGetProperty("peripheralId")?.GetString();
|
|
|
|
if (peripheralId == null)
|
|
{
|
|
throw JsonRpc2Error.InvalidParams("connect request must include peripheralId").ToException();
|
|
}
|
|
|
|
var discoveredPeripheral = this.GetDiscoveredPeripheral(peripheralId);
|
|
|
|
if (discoveredPeripheral == null)
|
|
{
|
|
throw JsonRpc2Error.InvalidRequest(string.Format("peripheral {0} not available for connection", peripheralId)).ToException();
|
|
}
|
|
|
|
return await this.DoConnect(discoveredPeripheral, args);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Platform-specific implementation for connecting to a peripheral device.
|
|
/// </summary>
|
|
/// <param name="discoveredPeripheral">The requested peripheral device.</param>
|
|
/// <param name="args">
|
|
/// A JSON object containing the args passed by the client, in case the platform-specific implementation needs them.
|
|
/// </param>
|
|
/// <returns>A <see cref="Task{TResult}"/> representing the result of the asynchronous operation.</returns>
|
|
protected abstract Task<object> DoConnect(TDiscoveredPeripheral discoveredPeripheral, JsonElement? args);
|
|
|
|
/// <summary>
|
|
/// Store the peripheral in the "discovered peripherals" list using a session-specific peripheral ID.
|
|
/// Storing a peripheral with the same address several times during the same session will result in the same ID each time.
|
|
/// </summary>
|
|
/// <param name="discoveredPeripheral">The peripheral information being registered.</param>
|
|
/// <param name="peripheralAddress">The peripheral device's address.</param>
|
|
/// <returns>An anonymized, session-specific peripheral ID.</returns>
|
|
protected string RegisterPeripheral(TDiscoveredPeripheral discoveredPeripheral, TPeripheralAddress peripheralAddress)
|
|
{
|
|
if (!this.peripheralAddressToId.TryGetValue(peripheralAddress, out var peripheralId))
|
|
{
|
|
peripheralId = Guid.NewGuid().ToString();
|
|
this.peripheralAddressToId[peripheralAddress] = peripheralId;
|
|
}
|
|
|
|
this.discoveredPeripherals[peripheralId] = discoveredPeripheral;
|
|
|
|
return peripheralId;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieve a peripheral registered during discovery.
|
|
/// </summary>
|
|
/// <param name="peripheralId">The anonymized peripheral ID.</param>
|
|
/// <returns>The peripheral if found, otherwise null.</returns>
|
|
protected TDiscoveredPeripheral GetDiscoveredPeripheral(string peripheralId)
|
|
{
|
|
return this.discoveredPeripherals.GetValueOrDefault(peripheralId, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clear all peripherals registered during discovery.
|
|
/// The mapping of peripheral address to ID will not be cleared. To clear that, start a new session.
|
|
/// </summary>
|
|
protected void ClearDiscoveredPeripherals()
|
|
{
|
|
this.discoveredPeripherals.Clear();
|
|
}
|
|
}
|