/// <summary>
        /// Handle the client's request to write a value to a particular service characteristic.
        /// </summary>
        /// <param name="parameters">
        /// The IDs of the service & characteristic along with the message and optionally the message encoding.
        /// </param>
        /// <returns>The number of decoded bytes written</returns>
        private async Task <JToken> Write(JObject parameters)
        {
            var buffer   = EncodingHelpers.DecodeBuffer(parameters);
            var endpoint = await GetEndpoint("write request", parameters, GattHelpers.BlockListStatus.ExcludeWrites);

            Console.Write(endpoint.AttributeHandle);
            Console.Write(" ");
            foreach (byte bt in buffer)
            {
                Console.Write(bt + "  ");
            }
            Console.WriteLine("");

            var withResponse = (parameters["withResponse"]?.ToObject <bool>() ?? false) ||
                               !endpoint.CharacteristicProperties.HasFlag(GattCharacteristicProperties.WriteWithoutResponse);

            var result = await endpoint.WriteValueWithResultAsync(buffer.AsBuffer(),
                                                                  withResponse?GattWriteOption.WriteWithResponse : GattWriteOption.WriteWithoutResponse);

            switch (result.Status)
            {
            case GattCommunicationStatus.Success:
                return(buffer.Length);

            case GattCommunicationStatus.ProtocolError:
                throw JsonRpcException.ApplicationError($"Error while attempting to write: {result.Status} {result.ProtocolError}");     // "ProtocolError 3"

            default:
                throw JsonRpcException.ApplicationError($"Error while attempting to write: {result.Status}");     // "Unreachable"
            }
        }
示例#2
0
        private void Discover(JObject parameters)
        {
            var major = parameters["majorDeviceClass"]?.ToObject <BluetoothMajorClass>();
            var minor = parameters["minorDeviceClass"]?.ToObject <BluetoothMinorClass>();

            if (major == null || minor == null)
            {
                throw JsonRpcException.InvalidParams("majorDeviceClass and minorDeviceClass required");
            }

            var deviceClass = BluetoothClassOfDevice.FromParts(major.Value, minor.Value,
                                                               BluetoothServiceCapabilities.None);
            var selector = BluetoothDevice.GetDeviceSelectorFromClassOfDevice(deviceClass);

            try
            {
                _watcher = DeviceInformation.CreateWatcher(selector, new List <String>
                {
                    SignalStrengthPropertyName,
                    IsPresentPropertyName,
                    BluetoothAddressPropertyName
                });
                _watcher.Added += PeripheralDiscovered;
                _watcher.EnumerationCompleted += EnumerationCompleted;
                _watcher.Updated += PeripheralUpdated;
                _watcher.Stopped += EnumerationStopped;
                _watcher.Start();
            }
            catch (ArgumentException)
            {
                throw JsonRpcException.ApplicationError("Failed to create device watcher");
            }
        }
示例#3
0
        static SerialPort sp = null; //串口对象

        protected override async Task DidReceiveCall(string method, JObject parameters,
                                                     Func <JToken, JsonRpcException, Task> completion)
        {
            switch (method)
            {
            case "connect":
                await completion(await Connect(parameters), null);

                break;

            case "disconnect":
                await completion(await DisConnect(parameters), null);

                break;

            case "send":
                await completion(await SendMessage(parameters), null);

                break;;

            case "read":
                await completion(await ReadMessage(parameters), null);

                break;

            default:
                throw JsonRpcException.MethodNotFound(method);
            }
        }
        /// <summary>
        /// Handle a client request
        /// </summary>
        /// <param name="method">The name of the method called by the client</param>
        /// <param name="parameters">The parameters passed by the client</param>
        /// <param name="completion">The completion handler to be called with the result</param>
        protected override async Task DidReceiveCall(string method, JObject parameters,
                                                     Func <JToken, JsonRpcException, Task> completion)
        {
            switch (method)
            {
            case "discover":
                if (m_peripheralData != null)
                {
                    SendRemoteRequest("didDiscoverPeripheral", m_peripheralData);
                }
                else
                {
                    Discover(parameters);
                }
                await completion(null, null);

                break;

            case "connect":
                await Connect(parameters);
                await completion(null, null);

                break;

            case "write":
                await completion(await Write(parameters), null);

                break;

            case "read":
                await completion(await Read(parameters), null);

                break;

            case "startNotifications":
                await StartNotifications(parameters);
                await completion(null, null);

                break;

            case "stopNotifications":
                await StopNotifications(parameters);
                await completion(null, null);

                break;

            case "pingMe":
                await completion("willPing", null);

                SendRemoteRequest("ping", null, (result, error) =>
                {
                    Debug.Print($"Got result from ping: {result}");
                    return(Task.CompletedTask);
                });
                break;

            default:
                throw JsonRpcException.MethodNotFound(method);
            }
        }
示例#5
0
        protected async Task SendErrorNotification(JsonRpcException error)
        {
            try
            {
                var message     = MakeResponse(null, null, error);
                var messageText = JsonConvert.SerializeObject(message);

                _socketLock.Wait();
                try
                {
                    await _webSocket.Send(messageText);
                }
                finally
                {
                    _socketLock.Release();
                }
            }
            catch (Exception e)
            {
                // This probably means the WebSocket is closed, causing Send to throw. Since SendErrorNotification
                // is often called in response to an exception -- possibly one that caused the WebSocket to close --
                // reporting this secondary exception won't help anyone and may itself cause a crash. Instead, print
                // this secondary exception and move on...
                Debug.Print($"Error serializing or sending error notification: {e}");
                Debug.Print($"Original error notification was: {error}");
            }
        }
示例#6
0
        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);
        }
示例#7
0
        protected override async Task DidReceiveCall(string method, JObject parameters,
                                                     Func <JToken, JsonRpcException, Task> completion)
        {
            switch (method)
            {
            case "discover":
                Discover(parameters);
                await completion(null, null);

                break;

            case "connect":
                if (_watcher != null && _watcher.Status == DeviceWatcherStatus.Started)
                {
                    _watcher.Stop();
                }
                await Connect(parameters);
                await completion(null, null);

                break;

            case "send":
                await completion(await SendMessage(parameters), null);

                break;

            default:
                throw JsonRpcException.MethodNotFound(method);
            }
        }
示例#8
0
        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();
            }
        }
示例#9
0
        /// <summary>
        /// Handle the client's request to read the value of a particular service characteristic.
        /// </summary>
        /// <param name="parameters">
        /// The IDs of the service & characteristic, an optional encoding to be used in the response, and an optional
        /// flag to request notification of future changes to this characteristic's value.
        /// </param>
        /// <returns>
        /// The current value as a JSON object with a "message" property and optional "encoding" property
        /// </returns>
        private async Task <JToken> Read(JObject parameters)
        {
            var endpoint = await GetEndpoint("read request", parameters, GattHelpers.BlockListStatus.ExcludeReads);

            //Console.Write("-->" +endpoint.AttributeHandle);
            var encoding = parameters.TryGetValue("encoding", out var encodingToken)
                ? encodingToken?.ToObject <string>() // possibly null and that's OK
                : "base64";
            var startNotifications = parameters["startNotifications"]?.ToObject <bool>() ?? false;

            var readResult = await endpoint.ReadValueAsync(BluetoothCacheMode.Uncached);

            if (startNotifications)
            {
                await StartNotifications(endpoint, encoding);
            }

            switch (readResult.Status)
            {
            case GattCommunicationStatus.Success:
                // Calling ToArray() on a buffer of length 0 throws an ArgumentException
                var resultBytes = readResult.Value.Length > 0 ? readResult.Value.ToArray() : new byte[0];
                return(EncodingHelpers.EncodeBuffer(resultBytes, encoding));

            case GattCommunicationStatus.Unreachable:
                throw JsonRpcException.ApplicationError("destination unreachable");

            default:
                throw JsonRpcException.ApplicationError($"unknown result from read: {readResult.Status}");
            }
        }
示例#10
0
        internal BLEDataFilter(JToken dataFilter)
        {
            var filterObject = (JObject)dataFilter;

            JToken token;

            if (filterObject.TryGetValue("dataPrefix", out token))
            {
                dataPrefix = token.ToObject <List <byte> >();
            }
            else
            {
                dataPrefix = new List <byte>();
            }

            if (filterObject.TryGetValue("mask", out token))
            {
                mask = token.ToObject <List <byte> >();
            }
            else
            {
                mask = Enumerable.Repeat <byte>(0xFF, dataPrefix.Count).ToList();
            }

            if (dataPrefix.Count != mask.Count)
            {
                throw JsonRpcException.InvalidParams(
                          $"length of data prefix ({dataPrefix.Count}) does not match length of mask ({mask.Count})");
            }
        }
示例#11
0
        /// <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;
        }
示例#12
0
        private static async void ListenForMessages(string id)
        {
            try
            {
                DataReader _socketReader = s_readerMap[id];
                DataWriter _socketWriter = s_writerMap[id];

                while (true)
                {
                    await _socketReader.LoadAsync(sizeof(UInt16));

                    var messageSize = _socketReader.ReadUInt16();
                    var headerBytes = BitConverter.GetBytes(messageSize);

                    var messageBytes = new byte[messageSize];
                    await _socketReader.LoadAsync(messageSize);

                    _socketReader.ReadBytes(messageBytes);

                    var totalBytes = new byte[headerBytes.Length + messageSize];
                    Array.Copy(headerBytes, totalBytes, headerBytes.Length);
                    Array.Copy(messageBytes, 0, totalBytes, headerBytes.Length, messageSize);

                    var parameters = EncodingHelpers.EncodeBuffer(totalBytes, "base64");
                    s_currentSession.SendRemoteRequest("didReceiveMessage", parameters);
                }
            }
            catch (Exception e)
            {
                await s_currentSession.SendErrorNotification(JsonRpcException.ApplicationError("Peripheral connection closed"));

                Debug.Print($"Closing connection to peripheral: {e.Message}");
                //Dispose();
            }
        }
示例#13
0
        /// <summary>
        /// Resolve a Web Bluetooth GATT "name" to a canonical UUID, using an assigned numbers table if necessary.
        /// See <a href="https://webbluetoothcg.github.io/web-bluetooth/#resolveuuidname">here</a> for more info.
        /// </summary>
        /// <param name="nameToken">A short UUID in integer form, a full UUID, or the name of an assigned number</param>
        /// <param name="assignedNumbersTable">The table of assigned numbers to resolve integer names</param>
        /// <returns>The UUID associated with the token. Throws if not possible.</returns>
        public static Guid ResolveUuidName(JToken nameToken, IReadOnlyDictionary <string, short> assignedNumbersTable)
        {
            if (nameToken.Type == JTokenType.Integer)
            {
                return(CanonicalUuid(nameToken.ToObject <int>()));
            }

            var name = nameToken.ToObject <string>();

            // Web Bluetooth demands an exact match to this regex but the .NET Guid constructor is more permissive.
            // See https://webbluetoothcg.github.io/web-bluetooth/#valid-uuid
            var validGuidRegex = new Regex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$");

            if (validGuidRegex.IsMatch(name))
            {
                return(new Guid(name));
            }

            // TODO: does Windows / .NET really have no built-in call for this?
            if (assignedNumbersTable.TryGetValue(name, out var id))
            {
                return(CanonicalUuid(id));
            }

            throw JsonRpcException.InvalidParams($"unknown or invalid GATT name: {nameToken}");
        }
示例#14
0
        /// <summary>
        /// Encode `data` using `encoding`, either into `destination` or a new JSON object.
        /// </summary>
        /// <param name="data">The data to encode</param>
        /// <param name="encoding">The type of encoding to use, or null to "encode" as a Unicode string</param>
        /// <param name="destination">
        /// The optional object to encode into.
        /// If not null, the "message" and "encoding" properties will be adjusted as necessary.
        /// If null, a new object will be created with "message" and (possibly) "encoding" properties.
        /// </param>
        /// <returns>The object to which the encoded message was written, regardless of source</returns>
        public static JObject EncodeBuffer(byte[] data, string encoding, JObject destination = null)
        {
            if (destination == null)
            {
                destination = new JObject();
            }

            switch (encoding)
            {
            case "base64":
                destination["encoding"] = encoding;
                destination["message"]  = Convert.ToBase64String(data);
                break;

            case null:
                destination.Remove("encoding");
                destination["message"] = Encoding.UTF8.GetString(data);
                break;

            default:
                throw JsonRpcException.InvalidParams($"unsupported encoding: {encoding}");
            }

            return(destination);
        }
示例#15
0
 private JObject MakeResponse(JToken responseId, JToken result, JsonRpcException error)
 {
     return(new JObject(
                new JProperty("jsonrpc", "2.0"),
                new JProperty("id", responseId),
                error == null ?
                new JProperty("result", result) :
                new JProperty("error", JObject.FromObject(error))
                ));
 }
示例#16
0
        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");
                }
            }
            s_currentSession = this;
            if (s_connectMap.ContainsKey(id))
            {
                // ListenForMessages(id);
                return;
            }

            var services = await bluetoothDevice.GetRfcommServicesForIdAsync(RfcommServiceId.SerialPort,
                                                                             BluetoothCacheMode.Uncached);

            if (services.Services.Count > 0)
            {
                StreamSocket _connectedSocket = new StreamSocket();
                await _connectedSocket.ConnectAsync(services.Services[0].ConnectionHostName,
                                                    services.Services[0].ConnectionServiceName);

                DataWriter _socketWriter = new DataWriter(_connectedSocket.OutputStream);
                DataReader _socketReader = new DataReader(_connectedSocket.InputStream)
                {
                    ByteOrder = ByteOrder.LittleEndian
                };
                s_readerMap.Add(id, _socketReader);
                s_writerMap.Add(id, _socketWriter);
                s_connectMap.Add(id, _connectedSocket);
                ListenForMessages(id);
            }
            else
            {
                throw JsonRpcException.ApplicationError("Cannot read services from peripheral");
            }
        }
示例#17
0
        /// <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();
        }
示例#18
0
        /// <summary>
        /// Decode the "message" property of `jsonBuffer` into bytes.
        /// If the buffer has an `encoding` property, use that method. Otherwise, assume the message is Unicode text.
        /// </summary>
        /// <param name="jsonBuffer">
        /// A JSON object containing a "message" property and optionally an "encoding" property.
        /// </param>
        /// <returns>An array of bytes containing the decoded data</returns>
        public static byte[] DecodeBuffer(JObject jsonBuffer)
        {
            var message  = jsonBuffer["message"].ToObject <string>();
            var encoding = jsonBuffer["encoding"]?.ToObject <string>();

            switch (encoding)
            {
            case "base64":     // "message" is encoded with Base64
                return(Convert.FromBase64String(message));

            case null:     // "message" is a Unicode string with no additional encoding
                return(Encoding.UTF8.GetBytes(message));

            default:
                throw JsonRpcException.InvalidParams($"unsupported encoding: {encoding}");
            }
        }
示例#19
0
        private async Task StartNotifications(GattCharacteristic endpoint, string encoding)
        {
            if (!_notifyCharacteristics.Contains(endpoint))
            {
                endpoint.ValueChanged += OnValueChanged;
                var notificationRequestResult = await endpoint.WriteClientCharacteristicConfigurationDescriptorAsync(
                    GattClientCharacteristicConfigurationDescriptorValue.Notify);

                if (notificationRequestResult != GattCommunicationStatus.Success)
                {
                    endpoint.ValueChanged -= OnValueChanged;
                    throw JsonRpcException.ApplicationError(
                              $"could not start notifications: {notificationRequestResult}");
                }
                _notifyCharacteristics.Add(endpoint);
            }
        }
示例#20
0
        // See https://webbluetoothcg.github.io/web-bluetooth/#bluetoothlescanfilterinit-canonicalizing
        internal BLEScanFilter(JToken filter)
        {
            var filterObject = (JObject)filter;

            JToken token;

            if (filterObject.TryGetValue("name", out token))
            {
                Name = token.ToString();
            }

            if (filterObject.TryGetValue("namePrefix", out token))
            {
                NamePrefix = token.ToString();
            }

            if (filterObject.TryGetValue("services", out token))
            {
                var serviceArray = (JArray)token;
                RequiredServices = new HashSet <Guid>(serviceArray.Select(GattHelpers.GetServiceUuid));
                if (RequiredServices.Count < 1)
                {
                    throw JsonRpcException.InvalidParams($"filter contains empty or invalid services list: {filter}");
                }
            }

            if (filterObject.TryGetValue("manufacturerData", out token))
            {
                ManufacturerData = new Dictionary <int, BLEDataFilter>();
                var manufacturerData = (JObject)token;
                foreach (var kv in manufacturerData)
                {
                    var manufacturerId = int.Parse(kv.Key);
                    var dataFilter     = new BLEDataFilter(kv.Value);
                    ManufacturerData.Add(manufacturerId, dataFilter);
                }
            }

            if (filterObject.TryGetValue("serviceData", out token))
            {
                throw JsonRpcException.ApplicationError("filtering on serviceData is not currently supported");
            }
        }
示例#21
0
        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);
        }
示例#22
0
        /// <summary>
        /// Handle the client's request to write a value to a particular service characteristic.
        /// </summary>
        /// <param name="parameters">
        /// The IDs of the service & characteristic along with the message and optionally the message encoding.
        /// </param>
        /// <returns>The number of decoded bytes written</returns>
        private async Task <JToken> Write(JObject parameters)
        {
            var buffer   = EncodingHelpers.DecodeBuffer(parameters);
            var endpoint = await GetEndpoint("write request", parameters, GattHelpers.BlockListStatus.ExcludeWrites);

            var withResponse = (parameters["withResponse"]?.ToObject <bool>() ?? false) ||
                               !endpoint.CharacteristicProperties.HasFlag(GattCharacteristicProperties.WriteWithoutResponse);

            var result = await endpoint.WriteValueAsync(buffer.AsBuffer(),
                                                        withResponse?GattWriteOption.WriteWithResponse : GattWriteOption.WriteWithoutResponse);

            switch (result)
            {
            case GattCommunicationStatus.Success:
                return(buffer.Length);

            case GattCommunicationStatus.Unreachable:
                throw JsonRpcException.ApplicationError("destination unreachable");

            default:
                throw JsonRpcException.ApplicationError($"unknown result from write: {result}");
            }
        }
示例#23
0
        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);
            }
        }
示例#24
0
        // See https://webbluetoothcg.github.io/web-bluetooth/#bluetoothlescanfilterinit-canonicalizing
        internal BLEScanFilter(JToken filter)
        {
            var filterObject = (JObject)filter;

            JToken token;

            if (filterObject.TryGetValue("name", out token))
            {
                Name = token.ToString();
            }

            if (filterObject.TryGetValue("namePrefix", out token))
            {
                NamePrefix = token.ToString();
            }

            if (filterObject.TryGetValue("services", out token))
            {
                var serviceArray = (JArray)token;
                RequiredServices = new HashSet <Guid>(serviceArray.Select(GattHelpers.GetServiceUuid));
                if (RequiredServices.Count < 1)
                {
                    throw JsonRpcException.InvalidParams($"filter contains empty or invalid services list: {filter}");
                }
            }

            if (filterObject.TryGetValue("manufacturerData", out token))
            {
                throw JsonRpcException.ApplicationError("filtering on manufacturerData is not currently supported");
            }

            if (filterObject.TryGetValue("serviceData", out token))
            {
                throw JsonRpcException.ApplicationError("filtering on serviceData is not currently supported");
            }
        }
示例#25
0
        // Override this to handle received RPC requests & notifications.
        // Call this method with `await super.DidReceiveCall(...)` to implement default calls like `getVersion`.
        // Call the completion handler when done with a request:
        // - pass your call's "return value" (or null) as `result` on success
        // - pass an instance of `JsonRpcException` for `error` on failure
        // You may also throw a `JsonRpcException` (or any other `Exception`) to signal failure.
        // Exceptions are caught even when thrown in an `async` method after `await`:
        // http://www.interact-sw.co.uk/iangblog/2010/11/01/csharp5-async-exceptions
        protected virtual async Task DidReceiveCall(string method, JObject parameters, CompletionHandler completion)
        {
            switch (method)
            {
            case "pingMe":
                await completion("willPing", null);

                SendRemoteRequest("ping", null, (result, error) =>
                {
                    Debug.Print($"Got result from ping: {result}");
                    return(Task.CompletedTask);
                });
                break;

            case "getVersion":
                await completion(GetVersion(), null);

                break;

            default:
                // unrecognized method
                throw JsonRpcException.MethodNotFound(method);
            }
        }
示例#26
0
        /// <summary>
        /// Fetch the characteristic referred to in the endpointInfo object and perform access verification.
        /// </summary>
        /// <param name="errorContext">
        /// A string to include in error reporting, if an error is encountered
        /// </param>
        /// <param name="endpointInfo">
        /// A JSON object which may contain a 'serviceId' property and a 'characteristicId' property
        /// </param>
        /// <param name="checkFlag">
        /// Check if this flag is set for this service or characteristic in the block list. If so, throw.
        /// </param>
        /// <returns>
        /// The specified GATT service characteristic, if it can be resolved and all checks pass.
        /// Otherwise, a JSON-RPC exception is thrown indicating what went wrong.
        /// </returns>
        private async Task <GattCharacteristic> GetEndpoint(string errorContext, JObject endpointInfo,
                                                            GattHelpers.BlockListStatus checkFlag)
        {
            GattDeviceService service;
            Guid?serviceId;

            if (_peripheral.ConnectionStatus != BluetoothConnectionStatus.Connected)
            {
                throw JsonRpcException.ApplicationError($"Peripheral is not connected for {errorContext}");
            }

            if (endpointInfo.TryGetValue("serviceId", out var serviceToken))
            {
                serviceId = GattHelpers.GetServiceUuid(serviceToken);
                service   = _services?.FirstOrDefault(s => s.Uuid == serviceId);
            }
            else
            {
                service   = _services?.FirstOrDefault(); // could in theory be null
                serviceId = service?.Uuid;
            }

            if (!serviceId.HasValue)
            {
                throw JsonRpcException.InvalidParams($"Could not determine service UUID for {errorContext}");
            }

            //if (_allowedServices?.Contains(serviceId.Value) != true)
            //{
            //    throw JsonRpcException.InvalidParams($"attempt to access unexpected service: {serviceId}");
            //}

            var blockStatus = GattHelpers.GetBlockListStatus(serviceId.Value);

            if (blockStatus.HasFlag(checkFlag))
            {
                throw JsonRpcException.InvalidParams($"service is block-listed with {blockStatus}: {serviceId}");
            }

            if (service == null)
            {
                throw JsonRpcException.InvalidParams($"could not find service {serviceId}");
            }

            GattCharacteristic characteristic;
            Guid?characteristicId;

            if (endpointInfo.TryGetValue("characteristicId", out var characteristicToken))
            {
                characteristic   = null; // we will attempt to collect this below
                characteristicId = GattHelpers.GetCharacteristicUuid(characteristicToken);
            }
            else
            {
                if (!_cachedServiceCharacteristics.TryGetValue(service.Uuid, out var characteristics))
                {
                    var characteristicsResult = await service.GetCharacteristicsAsync(BluetoothCacheMode.Uncached);

                    if (characteristicsResult.Status != GattCommunicationStatus.Success)
                    {
                        throw JsonRpcException.ApplicationError(
                                  $"failed to collect characteristics from service: {characteristicsResult.Status}");
                    }
                    characteristics = characteristicsResult.Characteristics;
                    _cachedServiceCharacteristics.Add(service.Uuid, characteristics);
                }

                characteristic   = characteristics.FirstOrDefault(); // could in theory be null
                characteristicId = characteristic?.Uuid;
            }

            if (!characteristicId.HasValue)
            {
                throw JsonRpcException.InvalidParams($"Could not determine characteristic UUID for {errorContext}");
            }

            blockStatus = GattHelpers.GetBlockListStatus(characteristicId.Value);
            if (blockStatus.HasFlag(checkFlag))
            {
                throw JsonRpcException.InvalidParams(
                          $"characteristic is block-listed with {blockStatus}: {characteristicId}");
            }

            // collect the characteristic if we didn't do so above
            if (characteristic == null &&
                !_cachedCharacteristics.TryGetValue(characteristicId.Value, out characteristic))
            {
                var characteristicsResult =
                    await service.GetCharacteristicsForUuidAsync(characteristicId.Value, BluetoothCacheMode.Uncached);

                if (characteristicsResult.Status != GattCommunicationStatus.Success)
                {
                    throw JsonRpcException.ApplicationError(
                              $"failed to collect characteristics from service: {characteristicsResult.Status}");
                }

                if (characteristicsResult.Characteristics.Count < 1)
                {
                    throw JsonRpcException.InvalidParams(
                              $"could not find characteristic {characteristicId} on service {serviceId}");
                }

                // TODO: why is this a list?
                characteristic = characteristicsResult.Characteristics[0];
                _cachedCharacteristics.Add(characteristicId.Value, characteristic);
            }

            try
            {
                // Unfortunately there's no direct way to test if the peripheral object has been disposed. The
                // `connectionState` property still indicates that the peripheral is connected in some cases, for
                // example when Bluetooth is turned off in Bluetooth settings / Control Panel. However, trying to
                // access the `Service` property of the `Characteristic` will throw an `ObjectDisposedException` in
                // this case, so that's the hack being used here to check for a disposed peripheral.
                var tempDisposalProbe = characteristic.Service;
            }
            catch (ObjectDisposedException e)
            {
                // This could mean that Bluetooth was turned off or the computer resumed from sleep
                throw JsonRpcException.ApplicationError($"Peripheral is disposed for {errorContext}");
            }

            return(characteristic);
        }
示例#27
0
        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);
            }
        }
示例#28
0
        /// <summary>
        /// Fetch the characteristic referred to in the endpointInfo object and perform access verification.
        /// </summary>
        /// <param name="errorContext">
        /// A string to include in error reporting, if an error is encountered
        /// </param>
        /// <param name="endpointInfo">
        /// A JSON object which may contain a 'serviceId' property and a 'characteristicId' property
        /// </param>
        /// <param name="checkFlag">
        /// Check if this flag is set for this service or characteristic in the block list. If so, throw.
        /// </param>
        /// <returns>
        /// The specified GATT service characteristic, if it can be resolved and all checks pass.
        /// Otherwise, a JSON-RPC exception is thrown indicating what went wrong.
        /// </returns>
        private async Task <GattCharacteristic> GetEndpoint(string errorContext, JObject endpointInfo,
                                                            GattHelpers.BlockListStatus checkFlag)
        {
            GattDeviceService service;
            Guid?serviceId;

            if (endpointInfo.TryGetValue("serviceId", out var serviceToken))
            {
                serviceId = GattHelpers.GetServiceUuid(serviceToken);
                service   = _services?.FirstOrDefault(s => s.Uuid == serviceId);
            }
            else
            {
                service   = _services?.FirstOrDefault(); // could in theory be null
                serviceId = service?.Uuid;
            }

            if (!serviceId.HasValue)
            {
                throw JsonRpcException.InvalidParams($"Could not determine service UUID for {errorContext}");
            }

            if (_allowedServices?.Contains(serviceId.Value) != true)
            {
                throw JsonRpcException.InvalidParams($"attempt to access unexpected service: {serviceId}");
            }

            var blockStatus = GattHelpers.GetBlockListStatus(serviceId.Value);

            if (blockStatus.HasFlag(checkFlag))
            {
                throw JsonRpcException.InvalidParams($"service is block-listed with {blockStatus}: {serviceId}");
            }

            if (service == null)
            {
                throw JsonRpcException.InvalidParams($"could not find service {serviceId}");
            }

            GattCharacteristic characteristic;
            Guid?characteristicId;

            if (endpointInfo.TryGetValue("characteristicId", out var characteristicToken))
            {
                characteristic   = null; // we will attempt to collect this below
                characteristicId = GattHelpers.GetCharacteristicUuid(characteristicToken);
            }
            else
            {
                if (!_cachedServiceCharacteristics.TryGetValue(service.Uuid, out var characteristics))
                {
                    var characteristicsResult = await service.GetCharacteristicsAsync(BluetoothCacheMode.Uncached);

                    if (characteristicsResult.Status != GattCommunicationStatus.Success)
                    {
                        throw JsonRpcException.ApplicationError(
                                  $"failed to collect characteristics from service: {characteristicsResult.Status}");
                    }
                    characteristics = characteristicsResult.Characteristics;
                    _cachedServiceCharacteristics.Add(service.Uuid, characteristics);
                }

                characteristic   = characteristics.FirstOrDefault(); // could in theory be null
                characteristicId = characteristic?.Uuid;
            }

            if (!characteristicId.HasValue)
            {
                throw JsonRpcException.InvalidParams($"Could not determine characteristic UUID for {errorContext}");
            }

            blockStatus = GattHelpers.GetBlockListStatus(characteristicId.Value);
            if (blockStatus.HasFlag(checkFlag))
            {
                throw JsonRpcException.InvalidParams(
                          $"characteristic is block-listed with {blockStatus}: {characteristicId}");
            }

            // collect the characteristic if we didn't do so above
            if (characteristic == null &&
                !_cachedCharacteristics.TryGetValue(characteristicId.Value, out characteristic))
            {
                var characteristicsResult =
                    await service.GetCharacteristicsForUuidAsync(characteristicId.Value, BluetoothCacheMode.Uncached);

                if (characteristicsResult.Status != GattCommunicationStatus.Success)
                {
                    throw JsonRpcException.ApplicationError(
                              $"failed to collect characteristics from service: {characteristicsResult.Status}");
                }

                if (characteristicsResult.Characteristics.Count < 1)
                {
                    throw JsonRpcException.InvalidParams(
                              $"could not find characteristic {characteristicId} on service {serviceId}");
                }

                // TODO: why is this a list?
                characteristic = characteristicsResult.Characteristics[0];
                _cachedCharacteristics.Add(characteristicId.Value, characteristic);
            }

            return(characteristic);
        }
示例#29
0
        /// <summary>
        /// Handle a client request
        /// </summary>
        /// <param name="method">The name of the method called by the client</param>
        /// <param name="parameters">The parameters passed by the client</param>
        /// <param name="completion">The completion handler to be called with the result</param>
        protected override async Task DidReceiveCall(string method, JObject parameters,
                                                     Func <JToken, JsonRpcException, Task> completion)
        {
            switch (method)
            {
            case "discover":
                if (m_peripheralData != null)
                {
                    SendRemoteRequest("didDiscoverPeripheral", m_peripheralData);
                    firstconnect = false;
                }
                else
                {
                    Discover(parameters);
                    firstconnect = true;
                }
                await completion(null, null);

                break;

            case "connect":
                if (_services != null)
                {
                    foreach (var service in _services)
                    {
                        try
                        {
                            service.Dispose();
                        }
                        catch
                        {
                            // ignore: probably the peripheral is gone
                        }
                    }
                    _services = null;
                    _peripheral.Dispose();
                }
                _cachedCharacteristics.Clear();
                //if (firstconnect)

                JObject ret = null;

                try
                {
                    await Connect(parameters);

                    ret = new JObject
                    {
                        new JProperty("status", new JValue("OK"))
                    };
                }
                catch (Exception e)
                {
                    ret = new JObject
                    {
                        new JProperty("status", new JValue("Error")),
                        new JProperty("message", new JValue(e.Message))
                    };
                }
                SendRemoteRequest("connect", ret);
                await completion(null, null);

                break;

            case "write":
                await completion(await Write(parameters), null);

                break;

            case "read":
                await completion(await Read(parameters), null);

                break;

            case "startNotifications":
                //if (firstconnect)
                await StartNotifications(parameters);
                await completion(null, null);

                break;

            case "stopNotifications":
                await StopNotifications(parameters);
                await completion(null, null);

                break;

            case "pingMe":
                await completion("willPing", null);

                SendRemoteRequest("ping", null, (result, error) =>
                {
                    Debug.Print($"Got result from ping: {result}");
                    return(Task.CompletedTask);
                });
                break;

            default:
                throw JsonRpcException.MethodNotFound(method);
            }
        }