private async Task DidReceiveRequest(JObject request, Func <JToken, Task> sendResult) { var method = request["method"]?.ToObject <string>(); if (string.IsNullOrWhiteSpace(method)) { throw JsonRpcException.InvalidRequest("method value missing or not a string"); } // optional: dictionary of parameters by name var parameters = request["params"]?.ToObject <JObject>() ?? new JObject(); async Task resultHandler(JToken result, JsonRpcException error) { if (error != null) { throw error; } await sendResult(result); }; _sessionLock.Wait(); try { await DidReceiveCall(method, parameters, resultHandler); } finally { _sessionLock.Release(); } }
/// <summary> /// Handle the client's request to connect to a particular peripheral. /// Valid in the discovery state; transitions to connected state on success. /// </summary> /// <param name="parameters"> /// A JSON object containing the UUID of a peripheral found by the most recent discovery request /// </param> private async Task Connect(JObject parameters) { if (_services != null) { throw JsonRpcException.InvalidRequest("already connected to peripheral"); } var peripheralId = parameters["peripheralId"].ToObject <ulong>(); if (!_reportedPeripherals.Contains(peripheralId)) { // the client may only connect to devices that were returned by the current discovery request throw JsonRpcException.InvalidParams($"invalid peripheral ID: {peripheralId}"); } _peripheral = await BluetoothLEDevice.FromBluetoothAddressAsync(peripheralId); var servicesResult = await _peripheral.GetGattServicesAsync(BluetoothCacheMode.Uncached); if (servicesResult.Status != GattCommunicationStatus.Success) { throw JsonRpcException.ApplicationError($"failed to enumerate GATT services: {servicesResult.Status}"); } _peripheral.ConnectionStatusChanged += OnPeripheralStatusChanged; _services = servicesResult.Services; // cache all characteristics in all services foreach (var service in _services) { var characteristicsResult = await service.GetCharacteristicsAsync(BluetoothCacheMode.Uncached); if (characteristicsResult.Status != GattCommunicationStatus.Success) { continue; } foreach (var characteristic in characteristicsResult.Characteristics) { _cachedCharacteristics.Add(characteristic.Uuid, characteristic); } } // collect optional services plus all services from all filters // Note: this modifies _optionalServices for convenience since we know it'll go away soon. _allowedServices = _optionalServices ?? new HashSet <Guid>(); _allowedServices = _filters .Where(filter => filter.RequiredServices?.Count > 0) .Aggregate(_allowedServices, (result, filter) => { result.UnionWith(filter.RequiredServices); return(result); }); // clean up resources used by discovery _watcher.Stop(); _watcher = null; _reportedPeripherals.Clear(); _optionalServices = null; }
private async Task <JToken> SendMessage(JObject parameters) { string key = ""; foreach (string item in s_writerMap.Keys) { key = item; } DataWriter _socketWriter = s_writerMap[key]; if (_socketWriter == null) { throw JsonRpcException.InvalidRequest("Not connected to peripheral"); } var data = EncodingHelpers.DecodeBuffer(parameters); try { _socketWriter.WriteBytes(data); await _socketWriter.StoreAsync(); } catch (ObjectDisposedException) { throw JsonRpcException.InvalidRequest("Not connected to peripheral"); } return(data.Length); }
/// <summary> /// Search for peripherals which match the filter information provided in the parameters. /// Valid in the initial state; transitions to discovery state on success. /// </summary> /// <param name="parameters"> /// JSON object containing at least one filter, and optionally an "optionalServices" list. See /// <a href="https://webbluetoothcg.github.io/web-bluetooth/#dictdef-requestdeviceoptions">here</a> for more /// information, but note that the "acceptAllDevices" property is ignored. /// </param> private void Discover(JObject parameters) { if (_services != null) { throw JsonRpcException.InvalidRequest("cannot discover when connected"); } var jsonFilters = parameters["filters"]?.ToObject <JArray>(); if (jsonFilters == null || jsonFilters.Count < 1) { throw JsonRpcException.InvalidParams("discovery request must include filters"); } var newFilters = jsonFilters.Select(filter => new BLEScanFilter(filter)).ToList(); if (newFilters.Any(filter => filter.IsEmpty)) { throw JsonRpcException.InvalidParams("discovery request includes empty filter"); } HashSet <Guid> newOptionalServices = null; if (parameters.TryGetValue("optionalServices", out var optionalServicesToken)) { var optionalServicesArray = (JArray)optionalServicesToken; newOptionalServices = new HashSet <Guid>(optionalServicesArray.Select(GattHelpers.GetServiceUuid)); } if (_watcher?.Status == BluetoothLEAdvertisementWatcherStatus.Started) { _watcher.Received -= OnAdvertisementReceived; _watcher.Stop(); } _watcher = new BluetoothLEAdvertisementWatcher() { SignalStrengthFilter = { InRangeThresholdInDBm = MinimumSignalStrength, OutOfRangeThresholdInDBm = MinimumSignalStrength - SignalStrengthMargin, OutOfRangeTimeout = TimeSpan.FromMilliseconds(OutOfRangeTimeout) } }; _reportedPeripherals.Clear(); _filters = newFilters; _optionalServices = newOptionalServices; _watcher.Received += OnAdvertisementReceived; _watcher.ScanningMode = BluetoothLEScanningMode.Active; _watcher.Start(); }
private async Task Connect(JObject parameters) { if (_connectedSocket?.Information.RemoteHostName != null) { throw JsonRpcException.InvalidRequest("Already connected"); } var id = parameters["peripheralId"]?.ToObject <string>(); var address = Convert.ToUInt64(id, 16); var bluetoothDevice = await BluetoothDevice.FromBluetoothAddressAsync(address); if (!bluetoothDevice.DeviceInformation.Pairing.IsPaired) { if (parameters.TryGetValue("pin", out var pin)) { _pairingCode = (string)pin; } var pairingResult = await Pair(bluetoothDevice); if (pairingResult != DevicePairingResultStatus.Paired && pairingResult != DevicePairingResultStatus.AlreadyPaired) { throw JsonRpcException.ApplicationError("Could not automatically pair with peripheral"); } } var services = await bluetoothDevice.GetRfcommServicesForIdAsync(RfcommServiceId.SerialPort, BluetoothCacheMode.Uncached); if (services.Services.Count > 0) { _connectedSocket = new StreamSocket(); await _connectedSocket.ConnectAsync(services.Services[0].ConnectionHostName, services.Services[0].ConnectionServiceName); _socketWriter = new DataWriter(_connectedSocket.OutputStream); _socketReader = new DataReader(_connectedSocket.InputStream) { ByteOrder = ByteOrder.LittleEndian }; ListenForMessages(); } else { throw JsonRpcException.ApplicationError("Cannot read services from peripheral"); } }
private async Task <JToken> SendMessage(JObject parameters) { if (_socketWriter == null) { throw JsonRpcException.InvalidRequest("Not connected to peripheral"); } var data = EncodingHelpers.DecodeBuffer(parameters); try { _socketWriter.WriteBytes(data); await _socketWriter.StoreAsync(); } catch (ObjectDisposedException) { throw JsonRpcException.InvalidRequest("Not connected to peripheral"); } return(data.Length); }
private async Task DidReceiveResponse(JObject response) { var requestId = response["id"]?.ToObject <RequestId?>(); if (!requestId.HasValue) { throw JsonRpcException.InvalidRequest("response ID value missing or wrong type"); } if (!_completionHandlers.TryGetValue(requestId.Value, out var completionHandler)) { throw JsonRpcException.InvalidRequest("response ID does not correspond to any open request"); } var error = response["error"]?.ToObject <JsonRpcException>(); try { if (error != null) { await completionHandler(null, error); } else { var result = response["result"]; await completionHandler(result, null); } } catch (Exception e) { var remoteMessage = $"exception encountered while handling response {requestId}"; Debug.Print(remoteMessage); Debug.Print($"The exception was: {e}"); throw JsonRpcException.ApplicationError(remoteMessage); } }
private async Task DidReceiveMessage(string message, Func <string, Task> sendResponseText) { var encoding = Encoding.UTF8; JToken responseId = null; async Task SendResponseInternal(JToken result, JsonRpcException error) { var response = MakeResponse(responseId, result, error); var responseText = JsonConvert.SerializeObject(response); Console.WriteLine("REP:" + responseText); await sendResponseText(responseText); } async Task SendResponse(JToken result, JsonRpcException error) { try { await SendResponseInternal(result, error); } catch (Exception firstError) { try { Debug.Print($"Could not encode response: {firstError}"); await SendResponseInternal(null, JsonRpcException.ApplicationError("Could not encode response")); } catch (Exception secondError) { Debug.Print($"Could not report response encoding failure: {secondError}"); } } } try { Console.WriteLine("Rec:" + message); var json = JObject.Parse(message); // do this as early as possible so that error responses can include it. responseId = json["id"]; // property "jsonrpc" must be exactly "2.0" if ((string)json["jsonrpc"] != "2.0") { throw JsonRpcException.InvalidRequest("unrecognized JSON-RPC version string"); } if (json["method"] != null) { await DidReceiveRequest(json, async result => await SendResponse(result, null)); } else if (json["result"] != null || json["error"] != null) { await DidReceiveResponse(json); } else { throw JsonRpcException.InvalidRequest("message is neither request nor response"); } } catch (JsonRpcException jsonRpcException) { await SendResponse(null, jsonRpcException); } catch (Exception e) { var jsonRpcException = JsonRpcException.ApplicationError($"Unhandled error encountered during call: {e}"); await SendResponse(null, jsonRpcException); } }