private void DeviceRemovedHandler(object aObj, EventArgs aEvent) { if ((aObj as ButtplugDevice) == null) { _bpLogger.Error("Got DeviceRemoved message from an object that is not a ButtplugDevice."); return; } var device = (ButtplugDevice)aObj; // The device itself will fire the remove event, so look it up in the dictionary and translate that for clients. var entry = (from x in _devices where x.Value.Identifier == device.Identifier select x).ToList(); if (!entry.Any()) { _bpLogger.Error("Got DeviceRemoved Event from object that is not in devices dictionary"); } if (entry.Count > 1) { _bpLogger.Error("Device being removed has multiple entries in device dictionary."); } foreach (var pair in entry.ToList()) { pair.Value.DeviceRemoved -= DeviceRemovedHandler; _bpLogger.Info($"Device removed: {pair.Key} - {pair.Value.Name}"); DeviceMessageReceived?.Invoke(this, new MessageReceivedEventArgs(new DeviceRemoved(pair.Key))); } }
public UWPBluetoothDeviceInterface( [NotNull] IButtplugLogManager aLogManager, [NotNull] IBluetoothDeviceInfo aInfo, [NotNull] BluetoothLEDevice aDevice, [NotNull] GattCharacteristic[] aChars) { _bpLogger = aLogManager.GetLogger(GetType()); _bleDevice = aDevice; if (aInfo.Characteristics.Count > 0) { foreach (var item in aInfo.Characteristics) { var c = (from x in aChars where x.Uuid == item.Value select x).ToArray(); if (c.Length != 1) { var err = $"Cannot find characteristic ${item.Value} for device {Name}"; _bpLogger.Error(err); throw new Exception(err); } if (_indexedChars == null) { _indexedChars = new Dictionary <uint, GattCharacteristic>(); } _indexedChars.Add(item.Key, c[0]); } } else { foreach (var c in aChars) { if (c.CharacteristicProperties.HasFlag(GattCharacteristicProperties.Read) || c.CharacteristicProperties.HasFlag(GattCharacteristicProperties.Notify) || c.CharacteristicProperties.HasFlag(GattCharacteristicProperties.Indicate)) { _rxChar = c; } else if (c.CharacteristicProperties.HasFlag(GattCharacteristicProperties.WriteWithoutResponse) || c.CharacteristicProperties.HasFlag(GattCharacteristicProperties.Write)) { _txChar = c; } } } if (_rxChar == null && _txChar == null && _indexedChars == null) { var err = $"No characteristics to connect to for device {Name}"; _bpLogger.Error(err); throw new Exception(err); } _bleDevice.ConnectionStatusChanged += ConnectionStatusChangedHandler; }
public async Task <IButtplugDevice> CreateDeviceAsync([NotNull] BluetoothLEDevice aDevice) { // GetGattServicesForUuidAsync is 15063 only var services = await aDevice.GetGattServicesAsync(BluetoothCacheMode.Cached); List <Guid> uuids = new List <Guid>(); foreach (var s in services.Services) { _bpLogger.Trace($"Found service UUID: {s.Uuid} ({aDevice.Name})"); uuids.Add(s.Uuid); } var srvs = (from x in services.Services from y in _deviceInfo.Services where x.Uuid == y select x).ToArray(); if (srvs.Length != 1) { // Somehow we've gotten multiple services back, something we don't currently support. _bpLogger.Error($"Found {srvs.Length} services for {aDevice.Name}, which is more/less than 1. Please fix this in the bluetooth definition."); return(null); } var service = srvs[0]; var chrResult = await service.GetCharacteristicsAsync(); if (chrResult.Status != GattCommunicationStatus.Success) { _bpLogger.Error($"Cannot connect to service {service.Uuid} of {aDevice.Name}."); return(null); } foreach (var s in chrResult.Characteristics) { _bpLogger.Trace($"Found characteristics UUID: {s.Uuid} ({aDevice.Name})"); } var chrs = chrResult.Characteristics.ToArray(); // If there aren't any characteristics by this point, something has gone wrong. if (!chrs.Any()) { _bpLogger.Error($"Cannot find characteristics for service {service.Uuid} of {aDevice.Name}."); return(null); } var bleInterface = new UWPBluetoothDeviceInterface(_buttplugLogManager, _deviceInfo, aDevice, chrs); var device = _deviceInfo.CreateDevice(_buttplugLogManager, bleInterface); // If initialization fails, don't actually send the message back. Just return null, we'll // have the info in the logs. return(await device.InitializeAsync().ConfigureAwait(false) is Ok ? device : null); }
public async Task ShutdownAsync(CancellationToken aToken = default(CancellationToken)) { // Don't disconnect devices on shutdown, as they won't actually close. // Uncomment this once we figure out how to close bluetooth devices. // _deviceManager.RemoveAllDevices(); var msg = await _deviceManager.SendMessageAsync(new StopAllDevices(), aToken).ConfigureAwait(false); if (msg is Error error) { _bpLogger.Error("An error occured while stopping devices on shutdown."); _bpLogger.Error(error.ErrorMessage); } _deviceManager.StopScanning(); _deviceManager.DeviceMessageReceived -= DeviceMessageReceivedHandler; _deviceManager.ScanningFinished -= ScanningFinishedHandler; BpLogManager.LogMessageReceived -= LogMessageReceivedHandler; }
public async Task Shutdown() { // Don't disconnect devices on shutdown, as they won't actually close. // Uncomment this once we figure out how to close bluetooth devices. // _deviceManager.RemoveAllDevices(); var msg = await _deviceManager.SendMessage(new StopAllDevices()); if (msg is Error) { _bpLogger.Error("An error occured while stopping devices on shutdown."); _bpLogger.Error((msg as Error).ErrorMessage); } _deviceManager.StopScanning(); _deviceManager.DeviceMessageReceived -= DeviceMessageReceivedHandler; _deviceManager.ScanningFinished -= ScanningFinishedHandler; BpLogManager.LogMessageReceived -= LogMessageReceivedHandler; }
public async Task SubscribeToUpdates() { if (_rxChar != null && _rxChar.CharacteristicProperties.HasFlag(GattCharacteristicProperties.Notify)) { GattCommunicationStatus status = await _rxChar.WriteClientCharacteristicConfigurationDescriptorAsync( GattClientCharacteristicConfigurationDescriptorValue.Notify); if (status == GattCommunicationStatus.Success) { // Server has been informed of clients interest. _rxChar.ValueChanged += BluetoothNotifyReceivedHandler; } else { _bpLogger.Error($"Cannot subscribe to BLE updates from {Name}: Failed Request"); } } else { _bpLogger.Error($"Cannot subscribe to BLE updates from {Name}: No Rx characteristic found."); } }
public async Task <ButtplugMessage> WriteValue(uint aMsgId, Guid aCharacteristic, byte[] aValue, bool aWriteOption = false) { if (!(_currentTask is null)) { _currentTask.Cancel(); _bpLogger.Error("Cancelling device transfer in progress for new transfer."); } var chrs = from x in _gattCharacteristics where x.Uuid == aCharacteristic select x; var gattCharacteristics = chrs.ToArray(); if (!gattCharacteristics.Any()) { return(_bpLogger.LogErrorMsg(aMsgId, Error.ErrorClass.ERROR_DEVICE, $"Requested characteristic {aCharacteristic} not found")); } else if (gattCharacteristics.Length > 1) { _bpLogger.Warn($"Multiple gattCharacteristics for {aCharacteristic} found"); } var gattCharacteristic = gattCharacteristics[0]; _currentTask = gattCharacteristic.WriteValueAsync(aValue.AsBuffer(), aWriteOption ? GattWriteOption.WriteWithResponse : GattWriteOption.WriteWithoutResponse); try { var status = await _currentTask; _currentTask = null; if (status != GattCommunicationStatus.Success) { return(_bpLogger.LogErrorMsg(aMsgId, Error.ErrorClass.ERROR_DEVICE, $"GattCommunication Error: {status}")); } } catch (InvalidOperationException e) { // This exception will be thrown if the bluetooth device disconnects in the middle of a transfer. return(_bpLogger.LogErrorMsg(aMsgId, Error.ErrorClass.ERROR_DEVICE, $"GattCommunication Error: {e.Message}")); } return(new Ok(aMsgId)); }
public async Task SubscribeToUpdatesAsync(uint aIndex) { if (_indexedChars == null) { _bpLogger.Error("SubscribeToUpdates using indexed characteristics called with no indexed characteristics available"); return; } if (!_indexedChars.ContainsKey(aIndex)) { _bpLogger.Error("SubscribeToUpdates using indexed characteristics called with invalid index"); return; } await SubscribeToUpdatesAsync(_indexedChars[aIndex]).ConfigureAwait(false); }
/// <summary> /// Serializes a single <see cref="ButtplugMessage"/> object into a JSON string for a specified version of the schema. /// </summary> /// <param name="aMsg"><see cref="ButtplugMessage"/> object</param> /// <param name="clientSchemaVersion">Target schema version</param> /// <returns>JSON string representing a B******g message</returns> public string Serialize([NotNull] ButtplugMessage aMsg, uint clientSchemaVersion) { // Warning: Any log messages in this function must be localOnly. They will possibly recurse. // Support downgrading messages var tmp = aMsg; while (tmp == null || tmp.SchemaVersion > clientSchemaVersion) { if (tmp?.PreviousType == null) { if (aMsg.Id == ButtplugConsts.SystemMsgId) { // There's no previous version of this system message _bpLogger?.Warn($"No messages serialized!"); return(null); } var err = new Error($"No backwards compatible version for message #{aMsg.GetType().Name}!", ErrorClass.ERROR_MSG, aMsg.Id); var eo = new JObject(new JProperty(err.GetType().Name, JObject.FromObject(err))); var ea = new JArray(eo); _bpLogger?.Error(err.ErrorMessage, true); _bpLogger?.Trace($"Message serialized to: {ea.ToString(Formatting.None)}", true); return(ea.ToString(Formatting.None)); } tmp = (ButtplugMessage)aMsg.PreviousType.GetConstructor( new Type[] { tmp.GetType() })?.Invoke(new object[] { tmp }); } var o = new JObject(new JProperty(aMsg.GetType().Name, JObject.FromObject(tmp))); var a = new JArray(o); _bpLogger?.Trace($"Message serialized to: {a.ToString(Formatting.None)}", true); return(a.ToString(Formatting.None)); }
public async Task <ButtplugMessage> WriteValue(uint aMsgId, uint aCharacteristicIndex, byte[] aValue, bool aWriteOption = false) { if (!(_currentTask is null)) { _currentTask.Cancel(); _bpLogger.Error("Cancelling device transfer in progress for new transfer."); } var gattCharacteristic = _gattCharacteristics[aCharacteristicIndex]; if (gattCharacteristic == null) { return(_bpLogger.LogErrorMsg(aMsgId, Error.ErrorClass.ERROR_DEVICE, $"Requested character {aCharacteristicIndex} out of range")); } _currentTask = gattCharacteristic.WriteValueAsync(aValue.AsBuffer(), aWriteOption ? GattWriteOption.WriteWithResponse : GattWriteOption.WriteWithoutResponse); try { var status = await _currentTask; _currentTask = null; if (status != GattCommunicationStatus.Success) { return(_bpLogger.LogErrorMsg(aMsgId, Error.ErrorClass.ERROR_DEVICE, $"GattCommunication Error: {status}")); } } catch (InvalidOperationException e) { // This exception will be thrown if the bluetooth device disconnects in the middle of a transfer. return(_bpLogger.LogErrorMsg(aMsgId, Error.ErrorClass.ERROR_DEVICE, $"GattCommunication Error: {e.Message}")); } return(new Ok(aMsgId)); }
private async Task HandleConnectionAsync(WebSocket ws, CancellationToken cancellation) { if (!_connections.IsEmpty) { try { ws.WriteString(new ButtplugJsonMessageParser(_logManager).Serialize(_logger.LogErrorMsg( ButtplugConsts.SystemMsgId, ErrorClass.ERROR_INIT, "WebSocketServer already in use!"), 0)); ws.Close(); } catch { // noop } finally { ws.Dispose(); } return; } var remoteId = ws.RemoteEndpoint.ToString(); _connections.AddOrUpdate(remoteId, ws, (oldWs, newWs) => newWs); ConnectionAccepted?.Invoke(this, new ConnectionEventArgs(remoteId)); var b******g = _factory.GetServer(); EventHandler <MessageReceivedEventArgs> msgReceived = (aObject, aEvent) => { var msg = b******g.Serialize(aEvent.Message); if (msg == null) { return; } try { if (ws != null && ws.IsConnected) { ws.WriteString(msg); } if (aEvent.Message is Error && (aEvent.Message as Error).ErrorCode == Error.ErrorClass.ERROR_PING && ws != null && ws.IsConnected) { ws.Close(); } } catch (WebSocketException e) { // Probably means we're repling to a message we recieved just before shutdown. _logger.Error(e.Message, true); } }; b******g.MessageReceived += msgReceived; EventHandler <MessageReceivedEventArgs> clientConnected = (aObject, aEvent) => { var msg = aEvent.Message as RequestServerInfo; var clientName = msg?.ClientName ?? "Unknown client"; ConnectionUpdated?.Invoke(this, new ConnectionEventArgs(remoteId, clientName)); }; b******g.ClientConnected += clientConnected; try { while (ws.IsConnected && !cancellation.IsCancellationRequested) { var msg = await ws.ReadStringAsync(cancellation).ConfigureAwait(false); if (msg != null) { var respMsgs = await b******g.SendMessage(msg); var respMsg = b******g.Serialize(respMsgs); if (respMsg == null) { continue; } await ws.WriteStringAsync(respMsg, cancellation); foreach (var m in respMsgs) { if (m is Error && (m as Error).ErrorCode == Error.ErrorClass.ERROR_PING && ws != null && ws.IsConnected) { ws.Close(); } } } } } catch (Exception e) { _logger.Error(e.Message, true); try { ws.Close(); } catch { // noop } } finally { b******g.MessageReceived -= msgReceived; await b******g.Shutdown(); b******g = null; ws.Dispose(); _connections.TryRemove(remoteId, out _); ConnectionClosed?.Invoke(this, new ConnectionEventArgs(remoteId)); } }
/// <inheritdoc /> /// <summary> /// Creates a ButtplugException. /// </summary> /// <param name="aLogger">Logger to log exception error message through (gives type context for the message).</param> /// <param name="aMessage">Exception message.</param> /// <param name="aClass">Exception class, based on B******g Error Message Classes. (https://b******g-spec.docs.b******g.io/status.html#error)</param> /// <param name="aId">Message ID for the resulting B******g Error Message.</param> /// <param name="aInner">Optional inner exception.</param> public ButtplugException([NotNull] IButtplugLog aLogger, string aMessage, Error.ErrorClass aClass = Error.ErrorClass.ERROR_UNKNOWN, uint aId = ButtplugConsts.SystemMsgId, Exception aInner = null) : this(aMessage, aClass, aId, aInner) { ButtplugUtils.ArgumentNotNull(aLogger, nameof(aLogger)); aLogger.Error(aMessage); }
public static ButtplugException FromError(IButtplugLog aLogger, Error aMsg) { ButtplugUtils.ArgumentNotNull(aLogger, nameof(aLogger)); aLogger.Error(aMsg.ErrorMessage); return(FromError(aMsg)); }
/// <summary> /// Logs an <see cref="Error"/> B******g message as an error level log message. /// </summary> /// <param name="logger">Logger to use for message</param> /// <param name="error">Error B******g message</param> public static void LogErrorMsg(this IButtplugLog logger, Error error) { logger.Error(error.ErrorMessage); }
public async Task RunServerSession() { try { var readTask = _ws.ReadStringAsync(_linkedCancelSource.Token); var writeTask = _outgoingMessages.OutputAvailableAsync(_linkedCancelSource.Token); while (_ws.IsConnected && !_linkedCancelSource.IsCancellationRequested) { var msgTasks = new Task[] { readTask, writeTask, }; var completedTaskIndex = Task.WaitAny(msgTasks); if (completedTaskIndex == 0) { var incomingMsg = await((Task <string>)msgTasks[0]).ConfigureAwait(false); if (incomingMsg != null) { ButtplugMessage[] msg; try { msg = await _server.SendMessageAsync(incomingMsg).ConfigureAwait(false); } catch (ButtplugException ex) { msg = new ButtplugMessage[] { ex.ButtplugErrorMessage }; } await QueueMessage(msg).ConfigureAwait(false); } readTask = _ws.ReadStringAsync(_linkedCancelSource.Token); } else { try { _outgoingMessages.TryReceiveAll(out var msgs); var outMsgs = msgs.Aggregate(string.Empty, (current, msg) => current + msg); if (_ws != null && _ws.IsConnected) { await _ws.WriteStringAsync(outMsgs, _linkedCancelSource.Token).ConfigureAwait(false); } writeTask = _outgoingMessages.OutputAvailableAsync(_linkedCancelSource.Token); } catch (WebSocketException e) { // Probably means we're replying to a message we received just before shutdown. _logger.Error(e.Message, true); } } } } catch (Exception e) { _logger.Error(e.Message, true); } finally { await ShutdownSession().ConfigureAwait(false); } }
private async Task ConnectionAccepter(string aPipeName, CancellationToken aToken) { while (!aToken.IsCancellationRequested) { var pipeServer = new NamedPipeServerStream(aPipeName, PipeDirection.InOut, 10, PipeTransmissionMode.Message, PipeOptions.Asynchronous); await pipeServer.WaitForConnectionAsync(aToken).ConfigureAwait(false); if (!pipeServer.IsConnected) { continue; } var aServer = pipeServer; ConnectionAccepted?.Invoke(this, new IPCConnectionEventArgs()); var buttplugServer = _serverFactory(); void MsgReceived(object aObject, MessageReceivedEventArgs aEvent) { var msg = buttplugServer.Serialize(aEvent.Message); if (msg == null) { return; } try { if (aServer != null && aServer.IsConnected) { var output = Encoding.UTF8.GetBytes(msg); aServer.WriteAsync(output, 0, output.Length, aToken); } if (aEvent.Message is Error && (aEvent.Message as Error).ErrorCode == Error.ErrorClass.ERROR_PING && aServer != null && aServer.IsConnected) { aServer.Close(); } } catch (WebSocketException e) { // Probably means we're replying to a message we received just before shutdown. _logger.Error(e.Message, true); } } buttplugServer.MessageReceived += MsgReceived; void ClientConnected(object aObject, EventArgs aUnused) { ConnectionUpdated?.Invoke(this, new IPCConnectionEventArgs(buttplugServer.ClientName)); } buttplugServer.ClientConnected += ClientConnected; try { _connections.Enqueue(aServer); while (!aToken.IsCancellationRequested && aServer.IsConnected) { var buffer = new byte[4096]; var msg = string.Empty; var len = -1; while (len < 0 || (len == buffer.Length && buffer[4095] != '\0')) { try { len = await aServer.ReadAsync(buffer, 0, buffer.Length, aToken).ConfigureAwait(false); if (len > 0) { msg += Encoding.UTF8.GetString(buffer, 0, len); } } catch { // no-op? } } if (msg.Length > 0) { ButtplugMessage[] respMsgs; try { respMsgs = await buttplugServer.SendMessageAsync(msg).ConfigureAwait(false); } catch (ButtplugException e) { respMsgs = new ButtplugMessage[] { e.ButtplugErrorMessage }; } var respMsg = buttplugServer.Serialize(respMsgs); if (respMsg == null) { continue; } var output = Encoding.UTF8.GetBytes(respMsg); await aServer.WriteAsync(output, 0, output.Length, aToken).ConfigureAwait(false); foreach (var m in respMsgs) { if (m is Error && (m as Error).ErrorCode == Error.ErrorClass.ERROR_PING && aServer.IsConnected) { aServer.Close(); } } } } } catch (Exception e) { _logger.Error(e.Message, true); try { aServer.Close(); } catch { // noop } } finally { buttplugServer.MessageReceived -= MsgReceived; await buttplugServer.ShutdownAsync().ConfigureAwait(false); buttplugServer = null; _connections.TryDequeue(out var stashed); while (stashed != aServer && _connections.Any()) { _connections.Enqueue(stashed); _connections.TryDequeue(out stashed); } aServer.Close(); aServer.Dispose(); aServer = null; ConnectionClosed?.Invoke(this, new IPCConnectionEventArgs()); } } }
public static Error LogErrorMsg(this IButtplugLog logger, uint aId, Error.ErrorClass aCode, string aMsg) { logger.Error(aMsg); return(new Error(aMsg, aCode, aId)); }