From 4ddb2acdbc98f0115222c72835fb18cb2d7e6c89 Mon Sep 17 00:00:00 2001 From: Emil Lenngren Date: Thu, 28 Jul 2016 13:11:46 +0200 Subject: [PATCH] Added C# client library + GUI sample --- README.md | 3 +- .../FliclibDotNetClient.sln | 32 + .../csharp/FliclibDotNetClient/global.json | 6 + .../src/FliclibDotNetClient/Bdaddr.cs | 108 +++ .../ButtonConnectionChannel.cs | 238 ++++++ .../src/FliclibDotNetClient/ButtonScanner.cs | 59 ++ .../src/FliclibDotNetClient/Enums.cs | 83 ++ .../src/FliclibDotNetClient/FlicClient.cs | 761 ++++++++++++++++++ .../FliclibDotNetClient.xproj | 21 + .../FliclibDotNetClient.xproj.user | 6 + .../src/FliclibDotNetClient/Packets.cs | 426 ++++++++++ .../Properties/AssemblyInfo.cs | 23 + .../RaiseEventExtension.cs | 22 + .../src/FliclibDotNetClient/ScanWizard.cs | 92 +++ .../FliclibDotNetClient/SocketAwaitable.cs | 78 ++ .../src/FliclibDotNetClient/project.json | 12 + .../src/FliclibDotNetClient/project.lock.json | 14 + clientlib/csharp/GUISample/FlicLibTest.sln | 22 + .../csharp/GUISample/FlicLibTest/App.config | 6 + .../FlicLibTest/FlicButtonControl.Designer.cs | 97 +++ .../FlicLibTest/FlicButtonControl.cs | 33 + .../FlicLibTest/FlicButtonControl.resx | 120 +++ .../GUISample/FlicLibTest/FlicLibTest.csproj | 111 +++ .../FlicLibTest/MainForm.Designer.cs | 175 ++++ .../csharp/GUISample/FlicLibTest/MainForm.cs | 187 +++++ .../GUISample/FlicLibTest/MainForm.resx | 120 +++ .../csharp/GUISample/FlicLibTest/Program.cs | 22 + .../FlicLibTest/Properties/AssemblyInfo.cs | 36 + .../Properties/Resources.Designer.cs | 63 ++ .../FlicLibTest/Properties/Resources.resx | 117 +++ .../Properties/Settings.Designer.cs | 26 + .../FlicLibTest/Properties/Settings.settings | 7 + 32 files changed, 3125 insertions(+), 1 deletion(-) create mode 100644 clientlib/csharp/FliclibDotNetClient/FliclibDotNetClient.sln create mode 100644 clientlib/csharp/FliclibDotNetClient/global.json create mode 100644 clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/Bdaddr.cs create mode 100644 clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/ButtonConnectionChannel.cs create mode 100644 clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/ButtonScanner.cs create mode 100644 clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/Enums.cs create mode 100644 clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/FlicClient.cs create mode 100644 clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/FliclibDotNetClient.xproj create mode 100644 clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/FliclibDotNetClient.xproj.user create mode 100644 clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/Packets.cs create mode 100644 clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/Properties/AssemblyInfo.cs create mode 100644 clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/RaiseEventExtension.cs create mode 100644 clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/ScanWizard.cs create mode 100644 clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/SocketAwaitable.cs create mode 100644 clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/project.json create mode 100644 clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/project.lock.json create mode 100644 clientlib/csharp/GUISample/FlicLibTest.sln create mode 100644 clientlib/csharp/GUISample/FlicLibTest/App.config create mode 100644 clientlib/csharp/GUISample/FlicLibTest/FlicButtonControl.Designer.cs create mode 100644 clientlib/csharp/GUISample/FlicLibTest/FlicButtonControl.cs create mode 100644 clientlib/csharp/GUISample/FlicLibTest/FlicButtonControl.resx create mode 100644 clientlib/csharp/GUISample/FlicLibTest/FlicLibTest.csproj create mode 100644 clientlib/csharp/GUISample/FlicLibTest/MainForm.Designer.cs create mode 100644 clientlib/csharp/GUISample/FlicLibTest/MainForm.cs create mode 100644 clientlib/csharp/GUISample/FlicLibTest/MainForm.resx create mode 100644 clientlib/csharp/GUISample/FlicLibTest/Program.cs create mode 100644 clientlib/csharp/GUISample/FlicLibTest/Properties/AssemblyInfo.cs create mode 100644 clientlib/csharp/GUISample/FlicLibTest/Properties/Resources.Designer.cs create mode 100644 clientlib/csharp/GUISample/FlicLibTest/Properties/Resources.resx create mode 100644 clientlib/csharp/GUISample/FlicLibTest/Properties/Settings.Designer.cs create mode 100644 clientlib/csharp/GUISample/FlicLibTest/Properties/Settings.settings diff --git a/README.md b/README.md index d30a7f6..c7fce36 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ This library is built on top of the HCI_CHANNEL_USER capability of the Linux ker * `clientlib/python` - A library for python 3.3 or higher, very similar to the Java library. Some example programs included. * `clientlib/websocket` - A websocket proxy and a demo client in html/javascript which you can use to scan and connect buttons. * `clientlib/nodejs` - A library for nodejs and examples. +* `clientlib/csharp` - A library for C# with a GUI example that uses Windows Forms. * `simpleclient` - A simple command line client with source code that can be used to test the protocol. * `client_protocol_packets.h` - C/C++ structs for all packets that can be included in a C/C++ program. @@ -45,7 +46,7 @@ All Bluetooth controllers with support for Bluetooth 4.0 and Bluetooth Low Energ - Supports 10 concurrent connections and in total 128 pending connections. Can be a bit buggy sometimes, like dropping and duplicating BLE packets. Also sometimes "forgets" to disconnect a BLE link when instructed to. Should however work ok in most cases. **Sena Technologies Parani-UD100-G03 (Cambridge Silicon Radio, Bluetooth 4.0)** -- Supports 5 concurrent connections and in total 25 pending connections. This one is recommended if you need large range. Other budget bluetooth dongles seem to have very short range. +- Supports 5 concurrent connections and in total 25 pending connections. This one is recommended if you need large range. Other budget bluetooth dongles seem to have very short range. Note that the BLE part of the Microsoft Windows driver for this controller currently does not work, in case you are using https://github.com/50ButtonsEach/fliclib-windows. ## Quick start ### Packages diff --git a/clientlib/csharp/FliclibDotNetClient/FliclibDotNetClient.sln b/clientlib/csharp/FliclibDotNetClient/FliclibDotNetClient.sln new file mode 100644 index 0000000..407d9ba --- /dev/null +++ b/clientlib/csharp/FliclibDotNetClient/FliclibDotNetClient.sln @@ -0,0 +1,32 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25123.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{05FE72CA-02BF-441E-BFF0-B789DE873CC0}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{1A914936-F6A0-46D2-B245-C21E47780A66}" + ProjectSection(SolutionItems) = preProject + global.json = global.json + EndProjectSection +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "FliclibDotNetClient", "src\FliclibDotNetClient\FliclibDotNetClient.xproj", "{3115FEF5-C233-42EB-A2C7-181279F60A4C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3115FEF5-C233-42EB-A2C7-181279F60A4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3115FEF5-C233-42EB-A2C7-181279F60A4C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3115FEF5-C233-42EB-A2C7-181279F60A4C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3115FEF5-C233-42EB-A2C7-181279F60A4C}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {3115FEF5-C233-42EB-A2C7-181279F60A4C} = {05FE72CA-02BF-441E-BFF0-B789DE873CC0} + EndGlobalSection +EndGlobal diff --git a/clientlib/csharp/FliclibDotNetClient/global.json b/clientlib/csharp/FliclibDotNetClient/global.json new file mode 100644 index 0000000..24b79b1 --- /dev/null +++ b/clientlib/csharp/FliclibDotNetClient/global.json @@ -0,0 +1,6 @@ +{ + "projects": [ "src", "test" ], + "sdk": { + "version": "1.0.0-rc1-update2" + } +} diff --git a/clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/Bdaddr.cs b/clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/Bdaddr.cs new file mode 100644 index 0000000..574b94b --- /dev/null +++ b/clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/Bdaddr.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace FliclibDotNetClient +{ + /// + /// Represents a Bluetooth device address + /// + public sealed class Bdaddr + { + private readonly byte[] _bytes; + + /// + /// Construct a Bdaddr from a string of the form "xx:xx:xx:xx:xx:xx". + /// + /// Bluetooth device address + public Bdaddr(string addr) + { + _bytes = new byte[6]; + _bytes[5] = Convert.ToByte(addr.Substring(0, 2), 16); + _bytes[4] = Convert.ToByte(addr.Substring(3, 2), 16); + _bytes[3] = Convert.ToByte(addr.Substring(6, 2), 16); + _bytes[2] = Convert.ToByte(addr.Substring(9, 2), 16); + _bytes[1] = Convert.ToByte(addr.Substring(12, 2), 16); + _bytes[0] = Convert.ToByte(addr.Substring(15, 2), 16); + } + + internal Bdaddr(BinaryReader reader) + { + _bytes = reader.ReadBytes(6); + if (_bytes.Length != 6) + { + throw new EndOfStreamException(); + } + } + + internal void WriteBytes(BinaryWriter writer) + { + writer.Write(_bytes); + } + + /// + /// The string representation of a Bluetooth device address (xx:xx:xx:xx:xx:xx) + /// + /// A string + public override string ToString() + { + return String.Format("{0:x2}:{1:x2}:{2:x2}:{3:x2}:{4:x2}:{5:x2}", _bytes[5], _bytes[4], _bytes[3], _bytes[2], _bytes[1], _bytes[0]); + } + + /// + /// Gets a hash code + /// + /// Hash code + public override int GetHashCode() + { + return _bytes[0] ^ (_bytes[1] << 8) ^ (_bytes[2] << 16) ^ (_bytes[3] << 24) ^ (_bytes[4] & 0xff) ^ (_bytes[5] << 8); + } + + /// + /// Equals + /// + /// Other object + /// Result + public override bool Equals(object obj) + { + var bdaddrObj = obj as Bdaddr; + if ((object)bdaddrObj == null) + { + return false; + } + if (ReferenceEquals(this, obj)) + { + return true; + } + return _bytes.SequenceEqual(((Bdaddr)obj)._bytes); + } + + /// + /// Equality check + /// + /// First Bdaddr + /// Second Bdaddr + /// Result + public static bool operator ==(Bdaddr a, Bdaddr b) + { + if ((object)a == null) + { + return (object)b == null; + } + return a.Equals(b); + } + + /// + /// Inequality check + /// + /// First Bdaddr + /// Second Bdaddr + /// Result + public static bool operator !=(Bdaddr a, Bdaddr b) + { + return !(a == b); + } + } +} diff --git a/clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/ButtonConnectionChannel.cs b/clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/ButtonConnectionChannel.cs new file mode 100644 index 0000000..a43d35c --- /dev/null +++ b/clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/ButtonConnectionChannel.cs @@ -0,0 +1,238 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace FliclibDotNetClient +{ + /// + /// CreateConnectionChannelResponseEventArgs + /// + public class CreateConnectionChannelResponseEventArgs : EventArgs + { + /// + /// Whether the request succeeded or not, and if not, what error. + /// + public CreateConnectionChannelError Error { get; internal set; } + + /// + /// The current connection status to this button. + /// This might be a non-disconnected status if there are already other active connection channels to this button. + /// + public ConnectionStatus ConnectionStatus { get; internal set; } + } + + /// + /// ConnectionChannelRemovedEventArgs + /// + public class ConnectionChannelRemovedEventArgs : EventArgs + { + /// + /// Reason for this connection channel being removed. + /// + public RemovedReason RemovedReason { get; internal set; } + } + + /// + /// ConnectionStatusChangedEventArgs + /// + public class ConnectionStatusChangedEventArgs : EventArgs + { + /// + /// New connection status + /// + public ConnectionStatus ConnectionStatus { get; internal set; } + + /// + /// If the connection status is Disconnected, this contains the reason. Otherwise this parameter is considered invalid. + /// + public DisconnectReason DisconnectReason { get; internal set; } + } + + /// + /// ButtonEventEventArgs + /// + public class ButtonEventEventArgs : EventArgs + { + /// + /// The possible click type values depend on the event type + /// + public ClickType ClickType { get; internal set; } + + /// + /// If this button event happened during the button was disconnected or not + /// + public bool WasQueued { get; internal set; } + + /// + /// If this button event happened during the button was disconnected, + /// this will be the number of seconds since that event happened (otherwise it will most likely be 0). + /// Depending on your application, you might want to discard too old events. + /// + public uint TimeDiff { get; internal set; } + } + + /// + /// A button connection channel. + /// + /// Register the events and add an instance of this class to a FlicClient with AddConnectionChannel. + /// + public class ButtonConnectionChannel + { + private static int _nextId = 0; + internal uint ConnId = (uint)Interlocked.Increment(ref _nextId); + + private LatencyMode _latencyMode; + private short _autoDisconnectTime; + internal FlicClient FlicClient; + + /// + /// Full Constructor with all options + /// + /// Bluetooth device address + /// Latency mode + /// Auto disconnect time + public ButtonConnectionChannel(Bdaddr bdAddr, LatencyMode latencyMode, short autoDisconnectTime) + { + if (bdAddr == null) + { + throw new ArgumentNullException(nameof(bdAddr)); + } + + BdAddr = bdAddr; + _latencyMode = latencyMode; + _autoDisconnectTime = autoDisconnectTime; + } + + /// + /// Constructor that uses default values of latency mode (normal latency) and auto disconnect time (disable auto disconnect mechanism) + /// + /// Bluetooth device address + public ButtonConnectionChannel(Bdaddr bdAddr) : this(bdAddr, LatencyMode.NormalLatency, 0x1ff) + { + + } + + /// + /// Gets the Bluetooth device address that is assigned to this connection channel + /// + public Bdaddr BdAddr { get; private set; } + + /// + /// Gets or sets the latency mode for this connection channel + /// + public LatencyMode LatencyMode + { + get + { + return _latencyMode; + } + set + { + if (_latencyMode != value) + { + _latencyMode = value; + UpdateMode(); + } + } + } + + /// + /// Gets or sets the auto disconnect time for this connection channel. The new value will be applied the next time the button connects. + /// + public short AutoDisconnectTime + { + get + { + return _autoDisconnectTime; + } + set + { + if (_autoDisconnectTime != value) + { + _autoDisconnectTime = value; + UpdateMode(); + } + } + } + + private void UpdateMode() + { + if (FlicClient != null) + { + FlicClient.SendPacket(new CmdChangeModeParameters { ConnId = ConnId, AutoDisconnectTime = _autoDisconnectTime, LatencyMode = _latencyMode }); + } + } + + /// + /// Event raised when the server has received the request to add this connection channel + /// + public event EventHandler CreateConnectionChannelResponse; + + /// + /// Event raised when the connection channel has been removed at the server + /// + public event EventHandler Removed; + + /// + /// Event raised when the connection status has changed + /// + public event EventHandler ConnectionStatusChanged; + + /// + /// Used to simply know when the button was pressed or released. + /// + public event EventHandler ButtonUpOrDown; + + /// + /// Used if you want to distinguish between click and hold. + /// + public event EventHandler ButtonClickOrHold; + + /// + /// Used if you want to distinguish between a single click and a double click. + /// + public event EventHandler ButtonSingleOrDoubleClick; + + /// + /// Used if you want to distinguish between a single click, a double click and a hold. + /// + public event EventHandler ButtonSingleOrDoubleClickOrHold; + + protected internal virtual void OnCreateConnectionChannelResponse(CreateConnectionChannelResponseEventArgs e) + { + CreateConnectionChannelResponse.RaiseEvent(this, e); + } + + protected internal virtual void OnRemoved(ConnectionChannelRemovedEventArgs e) + { + Removed.RaiseEvent(this, e); + } + + protected internal virtual void OnConnectionStatusChanged(ConnectionStatusChangedEventArgs e) + { + ConnectionStatusChanged.RaiseEvent(this, e); + } + + protected internal virtual void OnButtonUpOrDown(ButtonEventEventArgs e) + { + ButtonUpOrDown.RaiseEvent(this, e); + } + + protected internal virtual void OnButtonClickOrHold(ButtonEventEventArgs e) + { + ButtonClickOrHold.RaiseEvent(this, e); + } + + protected internal virtual void OnButtonSingleOrDoubleClick(ButtonEventEventArgs e) + { + ButtonSingleOrDoubleClick.RaiseEvent(this, e); + } + + protected internal virtual void OnButtonSingleOrDoubleClickOrHold(ButtonEventEventArgs e) + { + ButtonSingleOrDoubleClickOrHold.RaiseEvent(this, e); + } + } +} diff --git a/clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/ButtonScanner.cs b/clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/ButtonScanner.cs new file mode 100644 index 0000000..2c51366 --- /dev/null +++ b/clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/ButtonScanner.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace FliclibDotNetClient +{ + /// + /// AdvertisementPacketEventArgs + /// + public class AdvertisementPacketEventArgs : EventArgs + { + /// + /// Bluetooth device address + /// + public Bdaddr BdAddr { get; internal set; } + + /// + /// The advertised name + /// + public string Name { get; internal set; } + + /// + /// RSSI value (signal strength) + /// + public int Rssi { get; internal set; } + + /// + /// Whether the button is currently in private mode (does not accept connections from unknown clients) or not + /// + public bool IsPrivate { get; internal set; } + + /// + /// This button is already verified at the server + /// + public bool AlreadyVerified { get; internal set; } + } + + /// + /// A raw scanner class. + /// Add an instance of this class to a FlicClient with AddScanner and register the AdvertisementPacket event. + /// + public class ButtonScanner + { + private static int _nextId = 0; + internal uint ScanId = (uint)Interlocked.Increment(ref _nextId); + + /// + /// This event will be raised for every advertisement packet received + /// + public event EventHandler AdvertisementPacket; + + protected internal virtual void OnAdvertisementPacket(AdvertisementPacketEventArgs e) + { + AdvertisementPacket.RaiseEvent(this, e); + } + } +} diff --git a/clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/Enums.cs b/clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/Enums.cs new file mode 100644 index 0000000..09a9acf --- /dev/null +++ b/clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/Enums.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace FliclibDotNetClient +{ + public enum CreateConnectionChannelError : byte + { + NoError, + MaxPendingConnectionsReached + }; + + public enum ConnectionStatus : byte + { + Disconnected, + Connected, + Ready + }; + + public enum DisconnectReason : byte + { + Unspecified, + ConnectionEstablishmentFailed, + TimedOut, + BondingKeysMismatch + }; + + public enum RemovedReason : byte + { + RemovedByThisClient, + ForceDisconnectedByThisClient, + ForceDisconnectedByOtherClient, + + ButtonIsPrivate, + VerifyTimeout, + InternetBackendError, + InvalidData, + + CouldntLoadDevice + }; + + public enum ClickType : byte + { + ButtonDown, + ButtonUp, + ButtonClick, + ButtonSingleClick, + ButtonDoubleClick, + ButtonHold + }; + + public enum BdAddrType : byte + { + PublicBdAddrType, + RandomBdAddrType + }; + + public enum LatencyMode : byte + { + NormalLatency, + LowLatency, + HighLatency + }; + + public enum ScanWizardResult : byte + { + WizardSuccess, + WizardCancelledByUser, + WizardFailedTimeout, + WizardButtonIsPrivate, + WizardBluetoothUnavailable, + WizardInternetBackendError, + WizardInvalidData + }; + + public enum BluetoothControllerState : byte + { + Detached, + Resetting, + Attached + }; +} diff --git a/clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/FlicClient.cs b/clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/FlicClient.cs new file mode 100644 index 0000000..a1fbb48 --- /dev/null +++ b/clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/FlicClient.cs @@ -0,0 +1,761 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; + +namespace FliclibDotNetClient +{ + /// + /// Callback for GetInfo + /// + /// Bluetooth controller state + /// The Bluetooth controller's device address + /// The type of the Bluetooth controller's device address + /// The maximum number of pending connections (0 if unknown) + /// The maximum number of concurrently connected buttons (-1 if unknown) + /// Number of buttons that have at least one active connection channel + /// Maximum number of connections is currently reached (only sent by Linux server implementation) + /// An array of verified buttons + public delegate void GetInfoResponseCallback(BluetoothControllerState bluetoothControllerState, Bdaddr myBdAddr, + BdAddrType myBdAddrType, byte maxPendingConnections, + short maxConcurrentlyConnectedButtons, byte currentPendingConnections, + bool currentlyNoSpaceForNewConnection, + Bdaddr[] verifiedButtons); + + /// + /// Callback for GetButtonUUID + /// + /// The Bluetooth device address for the request + /// The UUID of the button. Will be null if the button was not verified bufore. + public delegate void GetButtonUUIDResponseCallback(Bdaddr bdAddr, string uuid); + + /// + /// NewVerifiedButtonEventArgs + /// + public class NewVerifiedButtonEventArgs : EventArgs + { + /// + /// Bluetooth device address for new verified button + /// + public Bdaddr BdAddr { get; internal set; } + } + + /// + /// SpaceForNewConnectionEventArgs + /// + public class SpaceForNewConnectionEventArgs : EventArgs + { + /// + /// The number of max concurrently connected buttons + /// + public byte MaxConcurrentlyConnectedButtons { get; internal set; } + } + + /// + /// BluetoothControllerStateChangeEventArgs + /// + public class BluetoothControllerStateChangeEventArgs : EventArgs + { + /// + /// The new state of the Bluetooth controller + /// + public BluetoothControllerState State { get; internal set; } + } + + /// + /// Flic client class + /// + /// For overview of the protocol and more detailed documentation, see the protocol documentation. + /// + /// Create and connect a client to a server with Create or CreateAsync. + /// Then call HandleEvents to start the event loop. + /// + public sealed class FlicClient : IDisposable + { + private TcpClient _tcpClient; + private Socket _socket; + + private int _handleEventsThreadId; + + private readonly List _readList = new List(); + private readonly byte[] _lengthReadBuf = new byte[2]; + + private int _hasPendingWrite; + private readonly SocketAsyncEventArgs _socketWriteEventArgs; + private readonly ConcurrentQueue _outgoingPackets = new ConcurrentQueue(); + + private readonly ConcurrentDictionary _scanners = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _connectionChannels = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _scanWizards = new ConcurrentDictionary(); + private readonly ConcurrentQueue _getInfoResponseCallbackQueue = new ConcurrentQueue(); + private readonly Queue _getButtonUUIDResponseCallbackQueue = new Queue(); + private readonly SortedDictionary _timers = new SortedDictionary(); + + /// + /// Raised when a new button is verified at the server (initiated by any client) + /// + public event EventHandler NewVerifiedButton; + + /// + /// Raised when the Bluetooth controller status changed, for example when it is plugged or unplugged or for any other reason becomes available / unavailable. + /// During the controller is Detached, no scan events or button events will be received. + /// + public event EventHandler BluetoothControllerStateChange; + + /// + /// This event will be raised when the maximum number of concurrent connections has been reached (only sent by the Linux server implementation). + /// + public event EventHandler NoSpaceForNewConnection; + + /// + /// This event will be raised when the number of concurrent connections has decreased from the maximum by one (only sent by the Linux server implementation). + /// + public event EventHandler GotSpaceForNewConnection; + + private FlicClient() + { + _socketWriteEventArgs = new SocketAsyncEventArgs(); + _socketWriteEventArgs.Completed += SocketWriteEventArgsOnCompleted; + } + + /// + /// Connects to a server with default port 5551 + /// + /// Hostname or IP address + /// A connected FlicClient + /// If a connection couldn't be established + public static FlicClient Create(string host) + { + return Create(host, 5551); + } + + /// + /// Connects to a server + /// + /// Hostname or IP address + /// Port + /// A connected FlicClient + /// If a connection couldn't be established + public static FlicClient Create(string host, int port) + { + var tcpClient = new TcpClient(); + tcpClient.NoDelay = true; + tcpClient.Connect(host, port); + + var client = new FlicClient(); + client._tcpClient = tcpClient; + client._socket = tcpClient.Client; + return client; + } + + /// + /// Connects to a server + /// + /// Hostname or IP address + /// A connected FlicClient + /// If a connection couldn't be established + public static Task CreateAsync(string host) + { + return CreateAsync(host, 5551); + } + + /// + /// Connects to a server + /// + /// Hostname or IP address + /// Port + /// A connected FlicClient + /// If a connection couldn't be established + public static async Task CreateAsync(string host, int port) + { + var tcpClient = new TcpClient(); + tcpClient.NoDelay = true; + await tcpClient.ConnectAsync(host, port); + + var client = new FlicClient(); + client._tcpClient = tcpClient; + client._socket = tcpClient.Client; + return client; + } + + /// + /// Initiates a disconnection of the FlicClient. The HandleEvents method will return once the disconnection is complete. + /// + public void Disconnect() + { + try + { + _socket.Shutdown(SocketShutdown.Both); + } + catch (ObjectDisposedException) + { + // In case the Socket has already been closed + } + catch (SocketException) + { + // Some problem happened on the socket so just close it + Dispose(); + } + } + + /// + /// Disposes the client. + /// The socket will be closed. If you for some reason want to close the socket before you call HandleEvents, execute this. + /// Otherwise you should rather call Disconnect to make a more graceful disconnection. + /// + public void Dispose() + { + try + { + _tcpClient.Close(); + } + catch (Exception) + { + // ignored + } + } + + /// + /// Requests info from the server. + /// + /// Callback to be invoked when the response arrives + public void GetInfo(GetInfoResponseCallback callback) + { + if (callback == null) + { + throw new ArgumentNullException(nameof(callback)); + } + _getInfoResponseCallbackQueue.Enqueue(callback); + SendPacket(new CmdGetInfo()); + } + + /// + /// Requests the UUID for a button. + /// A null UUID will be sent to the callback if the button was not verified before. + /// + /// Bluetooth device address + /// Callback to be invoked when the response arrives + public void GetButtonUUID(Bdaddr bdAddr, GetButtonUUIDResponseCallback callback) + { + if (callback == null) + { + throw new ArgumentNullException(nameof(callback)); + } + + lock (_getButtonUUIDResponseCallbackQueue) + { + _getButtonUUIDResponseCallbackQueue.Enqueue(callback); + SendPacket(new CmdGetButtonUUID { BdAddr = bdAddr }); + } + } + + /// + /// Adds a raw scanner. + /// The AdvertisementPacket event will be raised on the scanner for each advertisement packet received. + /// The scanner must not already be added. + /// + /// A ButtonScanner + public void AddScanner(ButtonScanner buttonScanner) + { + if (buttonScanner == null) + { + throw new ArgumentNullException(nameof(buttonScanner)); + } + if (!_scanners.TryAdd(buttonScanner.ScanId, buttonScanner)) + { + throw new ArgumentException("Button scanner already added", nameof(buttonScanner)); + } + + SendPacket(new CmdCreateScanner { ScanId = buttonScanner.ScanId }); + } + + /// + /// Removes a raw scanner. + /// No further AdvertisementPacket events will be raised. + /// The scanner must be currently added. + /// + /// A ButtonScanner that was previously added + public void RemoveScanner(ButtonScanner buttonScanner) + { + if (buttonScanner == null) + { + throw new ArgumentNullException(nameof(buttonScanner)); + } + ButtonScanner buttonScannerPrev; + if (!_scanners.TryRemove(buttonScanner.ScanId, out buttonScannerPrev)) + { + throw new ArgumentException("Button scanner was not added", nameof(buttonScanner)); + } + + SendPacket(new CmdRemoveScanner { ScanId = buttonScanner.ScanId }); + } + + /// + /// Adds and starts a ScanWizard. + /// Events on the scan wizard will be raised as it makes progress. Eventually Completed will be raised. + /// The scan wizard must not currently be running. + /// + /// A ScanWizard + public void AddScanWizard(ScanWizard scanWizard) + { + if (scanWizard == null) + { + throw new ArgumentNullException(nameof(scanWizard)); + } + + if (!_scanWizards.TryAdd(scanWizard.ScanWizardId, scanWizard)) + { + throw new ArgumentException("Scan wizard already added"); + } + + SendPacket(new CmdCreateScanWizard { ScanWizardId = scanWizard.ScanWizardId }); + } + + /// + /// Cancels a ScanWizard. + /// The Completed event will be raised with status WizardCancelledByUser, if it already wasn't completed before the server received this command. + /// + /// A ScanWizard + public void CancelScanWizard(ScanWizard scanWizard) + { + if (scanWizard == null) + { + throw new ArgumentNullException(nameof(scanWizard)); + } + + SendPacket(new CmdCancelScanWizard { ScanWizardId = scanWizard.ScanWizardId }); + } + + /// + /// Adds a connection channel. + /// The CreateConnectionChannelResponse event will be raised with the response. + /// If the response was success, button events will be raised when the button is pressed. + /// + /// A ButtonConnectionChannel + public void AddConnectionChannel(ButtonConnectionChannel channel) + { + if (channel == null) + { + throw new ArgumentNullException(nameof(channel)); + } + if (!_connectionChannels.TryAdd(channel.ConnId, channel)) + { + throw new ArgumentException("Connection channel already added"); + } + + channel.FlicClient = this; + + SendPacket(new CmdCreateConnectionChannel { ConnId = channel.ConnId, BdAddr = channel.BdAddr, LatencyMode = channel.LatencyMode, AutoDisconnectTime = channel.AutoDisconnectTime }); + } + + /// + /// Removes a connection channel. + /// Button events will no longer be received after the server has received this command. + /// + /// A ButtonConnectionChannel + public void RemoveConnectionChannel(ButtonConnectionChannel channel) + { + if (channel == null) + { + throw new ArgumentNullException(nameof(channel)); + } + + SendPacket(new CmdRemoveConnectionChannel { ConnId = channel.ConnId }); + } + + /// + /// Forces disconnect of a button. + /// All connection channels among all clients the server has for this button will be removed. + /// + /// Bluetooth device address + public void ForceDisconnect(Bdaddr bdAddr) + { + if (bdAddr == null) + { + throw new ArgumentNullException(nameof(bdAddr)); + } + + SendPacket(new CmdForceDisconnect { BdAddr = bdAddr }); + } + + internal void SendPacket(CommandPacket packet) + { + if (!_socket.Connected) + { + return; + } + + var buf = packet.Construct(); + + if (Interlocked.CompareExchange(ref _hasPendingWrite, 1, 0) == 0) + { + // Don't execute SendAsync on non-threadpool threads since if such a thread exits before the operation completes, it is cancelled + if (Thread.CurrentThread.IsThreadPoolThread) + { + SendBufferHelper(buf); + } + else + { + ThreadPool.QueueUserWorkItem(arg => SendBufferHelper(buf)); + } + } + else + { + _outgoingPackets.Enqueue(buf); + } + } + + private void SendBufferHelper(byte[] buf) + { + _socketWriteEventArgs.SetBuffer(buf, 0, buf.Length); + bool completedSynchronously; + try + { + completedSynchronously = !_socket.SendAsync(_socketWriteEventArgs); + } + catch (ObjectDisposedException) + { + return; + } + catch (SocketException) + { + Disconnect(); + return; + } + if (completedSynchronously) + { + SocketWriteEventArgsOnCompleted(null, _socketWriteEventArgs); + } + } + + private void SocketWriteEventArgsOnCompleted(object sender, SocketAsyncEventArgs socketAsyncEventArgs) + { + if (socketAsyncEventArgs.SocketError == SocketError.Success) + { + byte[] nextBuffer; + if (_outgoingPackets.TryDequeue(out nextBuffer)) + { + SendBufferHelper(nextBuffer); + return; + } + + _hasPendingWrite = 0; + } + else + { + Disconnect(); + } + } + + /// + /// Schedules an action to be executed on the on the same thread that handles events. + /// Since only one event or callback is run concurrently, this might be useful to avoid race conditions in your code. + /// + /// Number of milliseconds to wait before the action should be run + /// An action + public void SetTimer(int timeoutMilliSeconds, Action action) + { + var pointInTime = Stopwatch.GetTimestamp() + (timeoutMilliSeconds * Stopwatch.Frequency / 1000); + lock (_timers) + { + while (_timers.ContainsKey(pointInTime)) + { + pointInTime++; + } + _timers.Add(pointInTime, action); + } + + if (Thread.CurrentThread.ManagedThreadId != _handleEventsThreadId) + { + // The only way to wake up the thread that waits for the socket to receive data is to make the server send something to us + SendPacket(new CmdPing { PingId = 0 }); + } + } + + /// + /// Runs an action on the same thread that handles events. + /// Since only one event or callback is run concurrently, this might be useful to avoid race conditions in your code. + /// If the current thread already is the handle events thread, it's run immediately. Otherwise it is scheduled to run immediately or after the current event handler finishes. + /// + /// An action + public void RunOnHandleEventsThread(Action action) + { + if (Thread.CurrentThread.ManagedThreadId == _handleEventsThreadId) + { + action(); + } + else + { + SetTimer(0, action); + } + } + + /// + /// Starts the event loop. + /// This must be called in order to receive events and callbacks. + /// The method will not return until the socket has been disconnected. + /// Once the socket disconnects (intentionally or unintentionally), this method will close the socket and return. + /// No more events or callbacks will be raised or called after this method has returned. + /// + public void HandleEvents() + { + _handleEventsThreadId = Thread.CurrentThread.ManagedThreadId; + try + { + while (_socket.Connected) + { + KeyValuePair firstTimer; + lock (_timers) + { + firstTimer = _timers.FirstOrDefault(); + } + long timeout = 0; + if (firstTimer.Key != 0) + { + timeout = 1000 * (firstTimer.Key - Stopwatch.GetTimestamp()) / Stopwatch.Frequency; + if (timeout <= 0) + { + lock (_timers) + { + _timers.Remove(firstTimer.Key); + } + firstTimer.Value(); + continue; + } + } + + if (_readList.Count == 0) + { + _readList.Add(_socket); + } + + byte[] pkt; + + try + { + Socket.Select(_readList, null, null, timeout == 0 ? -1 : Math.Max((int)timeout, 1000000) * 1000); + if (_readList.Count == 0) + { + continue; + } + + if (!_socket.Connected) + { + break; + } + + var received = _socket.Receive(_lengthReadBuf); + if (received == 0) + { + break; + } + if (received == 1) + { + received = _socket.Receive(_lengthReadBuf, 1, 1, SocketFlags.None); + if (received == 0) + { + break; + } + } + var len = _lengthReadBuf[0] | (_lengthReadBuf[1] << 8); + + if (len == 0) + { + continue; + } + + pkt = new byte[len]; + + var pos = 0; + while (pos < len) + { + var nbytes = _socket.Receive(pkt, pos, len - pos, SocketFlags.None); + if (nbytes == 0) + { + break; + } + pos += nbytes; + } + } + catch (ObjectDisposedException) + { + break; + } + catch (SocketException) + { + break; + } + DispatchPacket(pkt); + } + } + finally + { + Dispose(); + } + } + + private void DispatchPacket(byte[] packet) + { + int opcode = packet[0]; + switch (opcode) + { + case EventPacket.EVT_ADVERTISEMENT_PACKET_OPCODE: + { + var pkt = new EvtAdvertisementPacket(); + pkt.Parse(packet); + ButtonScanner scanner; + if (_scanners.TryGetValue(pkt.ScanId, out scanner)) + { + scanner.OnAdvertisementPacket(new AdvertisementPacketEventArgs { BdAddr = pkt.BdAddr, Name = pkt.Name, Rssi = pkt.Rssi, IsPrivate = pkt.IsPrivate, AlreadyVerified = pkt.AlreadyVerified }); + } + } + break; + case EventPacket.EVT_CREATE_CONNECTION_CHANNEL_RESPONSE_OPCODE: + { + var pkt = new EvtCreateConnectionChannelResponse(); + pkt.Parse(packet); + var channel =_connectionChannels[pkt.ConnId]; + if (pkt.Error != CreateConnectionChannelError.NoError) + { + _connectionChannels.TryRemove(channel.ConnId, out channel); + } + channel.OnCreateConnectionChannelResponse(new CreateConnectionChannelResponseEventArgs { Error = pkt.Error, ConnectionStatus = pkt.ConnectionStatus }); + } + break; + case EventPacket.EVT_CONNECTION_STATUS_CHANGED_OPCODE: + { + var pkt = new EvtConnectionStatusChanged(); + pkt.Parse(packet); + var channel = _connectionChannels[pkt.ConnId]; + channel.OnConnectionStatusChanged(new ConnectionStatusChangedEventArgs { ConnectionStatus = pkt.ConnectionStatus, DisconnectReason = pkt.DisconnectReason }); + } + break; + case EventPacket.EVT_CONNECTION_CHANNEL_REMOVED_OPCODE: + { + var pkt = new EvtConnectionChannelRemoved(); + pkt.Parse(packet); + ButtonConnectionChannel channel; + _connectionChannels.TryRemove(pkt.ConnId, out channel); + channel.OnRemoved(new ConnectionChannelRemovedEventArgs { RemovedReason = pkt.RemovedReason }); + } + break; + case EventPacket.EVT_BUTTON_UP_OR_DOWN_OPCODE: + case EventPacket.EVT_BUTTON_CLICK_OR_HOLD_OPCODE: + case EventPacket.EVT_BUTTON_SINGLE_OR_DOUBLE_CLICK_OPCODE: + case EventPacket.EVT_BUTTON_SINGLE_OR_DOUBLE_CLICK_OR_HOLD_OPCODE: + { + var pkt = new EvtButtonEvent(); + pkt.Parse(packet); + var channel = _connectionChannels[pkt.ConnId]; + var eventArgs = new ButtonEventEventArgs { ClickType = pkt.ClickType, WasQueued = pkt.WasQueued, TimeDiff = pkt.TimeDiff }; + switch (opcode) + { + case EventPacket.EVT_BUTTON_UP_OR_DOWN_OPCODE: + channel.OnButtonUpOrDown(eventArgs); + break; + case EventPacket.EVT_BUTTON_CLICK_OR_HOLD_OPCODE: + channel.OnButtonClickOrHold(eventArgs); + break; + case EventPacket.EVT_BUTTON_SINGLE_OR_DOUBLE_CLICK_OPCODE: + channel.OnButtonSingleOrDoubleClick(eventArgs); + break; + case EventPacket.EVT_BUTTON_SINGLE_OR_DOUBLE_CLICK_OR_HOLD_OPCODE: + channel.OnButtonSingleOrDoubleClickOrHold(eventArgs); + break; + } + } + break; + case EventPacket.EVT_NEW_VERIFIED_BUTTON_OPCODE: + { + var pkt = new EvtNewVerifiedButton(); + pkt.Parse(packet); + NewVerifiedButton.RaiseEvent(this, new NewVerifiedButtonEventArgs { BdAddr = pkt.BdAddr }); + } + break; + case EventPacket.EVT_GET_INFO_RESPONSE_OPCODE: + { + var pkt = new EvtGetInfoResponse(); + pkt.Parse(packet); + GetInfoResponseCallback callback; + _getInfoResponseCallbackQueue.TryDequeue(out callback); + callback(pkt.BluetoothControllerState, pkt.MyBdAddr, pkt.MyBdAddrType, pkt.MaxPendingConnections, pkt.MaxConcurrentlyConnectedButtons, pkt.CurrentPendingConnections, pkt.CurrentlyNoSpaceForNewConnection, pkt.BdAddrOfVerifiedButtons); + } + break; + case EventPacket.EVT_NO_SPACE_FOR_NEW_CONNECTION_OPCODE: + { + var pkt = new EvtNoSpaceForNewConnection(); + pkt.Parse(packet); + NoSpaceForNewConnection.RaiseEvent(this, new SpaceForNewConnectionEventArgs { MaxConcurrentlyConnectedButtons = pkt.MaxConcurrentlyConnectedButtons }); + } + break; + case EventPacket.EVT_GOT_SPACE_FOR_NEW_CONNECTION_OPCODE: + { + var pkt = new EvtGotSpaceForNewConnection(); + pkt.Parse(packet); + GotSpaceForNewConnection.RaiseEvent(this, new SpaceForNewConnectionEventArgs { MaxConcurrentlyConnectedButtons = pkt.MaxConcurrentlyConnectedButtons }); + } + break; + case EventPacket.EVT_BLUETOOTH_CONTROLLER_STATE_CHANGE_OPCODE: + { + var pkt = new EvtBluetoothControllerStateChange(); + pkt.Parse(packet); + BluetoothControllerStateChange.RaiseEvent(this, new BluetoothControllerStateChangeEventArgs { State = pkt.State }); + } + break; + case EventPacket.EVT_GET_BUTTON_UUID_RESPONSE_OPCODE: + { + var pkt = new EvtGetButtonUUIDResponse(); + pkt.Parse(packet); + GetButtonUUIDResponseCallback callback; + lock (_getButtonUUIDResponseCallbackQueue) + { + callback = _getButtonUUIDResponseCallbackQueue.Dequeue(); + } + callback(pkt.BdAddr, pkt.Uuid); + } + break; + case EventPacket.EVT_SCAN_WIZARD_FOUND_PRIVATE_BUTTON_OPCODE: + { + var pkt = new EvtScanWizardFoundPrivateButton(); + pkt.Parse(packet); + _scanWizards[pkt.ScanWizardId].OnFoundPrivateButton(); + } + break; + case EventPacket.EVT_SCAN_WIZARD_FOUND_PUBLIC_BUTTON_OPCODE: + { + var pkt = new EvtScanWizardFoundPublicButton(); + pkt.Parse(packet); + var wizard = _scanWizards[pkt.ScanWizardId]; + wizard.BdAddr = pkt.BdAddr; + wizard.Name = pkt.Name; + wizard.OnFoundPublicButton(new ScanWizardButtonInfoEventArgs { BdAddr = wizard.BdAddr, Name = wizard.Name }); + } + break; + case EventPacket.EVT_SCAN_WIZARD_BUTTON_CONNECTED_OPCODE: + { + var pkt = new EvtScanWizardButtonConnected(); + pkt.Parse(packet); + var wizard = _scanWizards[pkt.ScanWizardId]; + wizard.OnButtonConnected(new ScanWizardButtonInfoEventArgs { BdAddr = wizard.BdAddr, Name = wizard.Name }); + } + break; + case EventPacket.EVT_SCAN_WIZARD_COMPLETED_OPCODE: + { + var pkt = new EvtScanWizardCompleted(); + pkt.Parse(packet); + ScanWizard wizard; + _scanWizards.TryRemove(pkt.ScanWizardId, out wizard); + var eventArgs = new ScanWizardCompletedEventArgs { BdAddr = wizard.BdAddr, Name = wizard.Name, Result = pkt.Result }; + wizard.BdAddr = null; + wizard.Name = null; + wizard.OnCompleted(eventArgs); + } + break; + } + } + } +} diff --git a/clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/FliclibDotNetClient.xproj b/clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/FliclibDotNetClient.xproj new file mode 100644 index 0000000..a328ab8 --- /dev/null +++ b/clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/FliclibDotNetClient.xproj @@ -0,0 +1,21 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 3115fef5-c233-42eb-a2c7-181279f60a4c + FliclibDotNetClient + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\bin\$(MSBuildProjectName)\ + + + 2.0 + + + True + + + \ No newline at end of file diff --git a/clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/FliclibDotNetClient.xproj.user b/clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/FliclibDotNetClient.xproj.user new file mode 100644 index 0000000..c99c608 --- /dev/null +++ b/clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/FliclibDotNetClient.xproj.user @@ -0,0 +1,6 @@ + + + + Start + + \ No newline at end of file diff --git a/clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/Packets.cs b/clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/Packets.cs new file mode 100644 index 0000000..5cdbaa4 --- /dev/null +++ b/clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/Packets.cs @@ -0,0 +1,426 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.NetworkInformation; +using System.Threading.Tasks; +using System.Text; + +namespace FliclibDotNetClient +{ + internal abstract class CommandPacket + { + protected int Opcode; + + public byte[] Construct() + { + MemoryStream stream = new MemoryStream(); + Write(new BinaryWriter(stream)); + byte[] res = new byte[3 + stream.Length]; + res[0] = (byte)(1 + stream.Length); + res[1] = (byte)((1 + stream.Length) >> 8); + res[2] = (byte)Opcode; + Buffer.BlockCopy(stream.ToArray(), 0, res, 3, (int)stream.Length); + return res; + } + + protected abstract void Write(BinaryWriter writer); + } + + internal class CmdGetInfo : CommandPacket + { + protected override void Write(BinaryWriter writer) + { + Opcode = 0; + } + } + + internal class CmdCreateScanner : CommandPacket + { + internal uint ScanId; + + protected override void Write(BinaryWriter writer) + { + Opcode = 1; + writer.Write(ScanId); + } + } + + internal class CmdRemoveScanner : CommandPacket + { + internal uint ScanId; + + protected override void Write(BinaryWriter writer) + { + Opcode = 2; + writer.Write(ScanId); + } + } + + internal class CmdCreateConnectionChannel : CommandPacket + { + internal uint ConnId; + internal Bdaddr BdAddr; + internal LatencyMode LatencyMode; + internal short AutoDisconnectTime; + + protected override void Write(BinaryWriter writer) + { + Opcode = 3; + writer.Write(ConnId); + BdAddr.WriteBytes(writer); + writer.Write((byte)LatencyMode); + writer.Write(AutoDisconnectTime); + } + } + + internal class CmdRemoveConnectionChannel : CommandPacket + { + internal uint ConnId; + + protected override void Write(BinaryWriter writer) + { + Opcode = 4; + writer.Write(ConnId); + } + } + + internal class CmdForceDisconnect : CommandPacket + { + internal Bdaddr BdAddr; + + protected override void Write(BinaryWriter writer) + { + Opcode = 5; + BdAddr.WriteBytes(writer); + } + } + + internal class CmdChangeModeParameters : CommandPacket + { + internal uint ConnId; + internal LatencyMode LatencyMode; + internal short AutoDisconnectTime; + + protected override void Write(BinaryWriter writer) + { + Opcode = 6; + writer.Write(ConnId); + writer.Write((byte)LatencyMode); + writer.Write(AutoDisconnectTime); + } + } + + internal class CmdPing : CommandPacket + { + internal uint PingId; + + protected override void Write(BinaryWriter writer) + { + Opcode = 7; + writer.Write(PingId); + } + } + + internal class CmdGetButtonUUID : CommandPacket + { + internal Bdaddr BdAddr; + + protected override void Write(BinaryWriter writer) + { + Opcode = 8; + BdAddr.WriteBytes(writer); + } + } + + internal class CmdCreateScanWizard : CommandPacket + { + internal uint ScanWizardId; + + protected override void Write(BinaryWriter writer) + { + Opcode = 9; + writer.Write(ScanWizardId); + } + } + + internal class CmdCancelScanWizard : CommandPacket + { + internal uint ScanWizardId; + + protected override void Write(BinaryWriter writer) + { + Opcode = 10; + writer.Write(ScanWizardId); + } + } + + internal abstract class EventPacket + { + internal const int EVT_ADVERTISEMENT_PACKET_OPCODE = 0; + internal const int EVT_CREATE_CONNECTION_CHANNEL_RESPONSE_OPCODE = 1; + internal const int EVT_CONNECTION_STATUS_CHANGED_OPCODE = 2; + internal const int EVT_CONNECTION_CHANNEL_REMOVED_OPCODE = 3; + internal const int EVT_BUTTON_UP_OR_DOWN_OPCODE = 4; + internal const int EVT_BUTTON_CLICK_OR_HOLD_OPCODE = 5; + internal const int EVT_BUTTON_SINGLE_OR_DOUBLE_CLICK_OPCODE = 6; + internal const int EVT_BUTTON_SINGLE_OR_DOUBLE_CLICK_OR_HOLD_OPCODE = 7; + internal const int EVT_NEW_VERIFIED_BUTTON_OPCODE = 8; + internal const int EVT_GET_INFO_RESPONSE_OPCODE = 9; + internal const int EVT_NO_SPACE_FOR_NEW_CONNECTION_OPCODE = 10; + internal const int EVT_GOT_SPACE_FOR_NEW_CONNECTION_OPCODE = 11; + internal const int EVT_BLUETOOTH_CONTROLLER_STATE_CHANGE_OPCODE = 12; + internal const int EVT_PING_RESPONSE_OPCODE = 13; + internal const int EVT_GET_BUTTON_UUID_RESPONSE_OPCODE = 14; + internal const int EVT_SCAN_WIZARD_FOUND_PRIVATE_BUTTON_OPCODE = 15; + internal const int EVT_SCAN_WIZARD_FOUND_PUBLIC_BUTTON_OPCODE = 16; + internal const int EVT_SCAN_WIZARD_BUTTON_CONNECTED_OPCODE = 17; + internal const int EVT_SCAN_WIZARD_COMPLETED_OPCODE = 18; + + internal void Parse(byte[] arr) + { + var stream = new MemoryStream(arr); + stream.ReadByte(); + ParseInternal(new BinaryReader(stream)); + } + + protected abstract void ParseInternal(BinaryReader reader); + } + + internal class EvtAdvertisementPacket : EventPacket + { + internal uint ScanId; + internal Bdaddr BdAddr; + internal string Name; + internal int Rssi; + internal bool IsPrivate; + internal bool AlreadyVerified; + + protected override void ParseInternal(BinaryReader reader) + { + ScanId = reader.ReadUInt32(); + BdAddr = new Bdaddr(reader); + int nameLen = reader.ReadByte(); + var bytes = new byte[nameLen]; + for (var i = 0; i < nameLen; i++) + { + bytes[i] = reader.ReadByte(); + } + for (var i = nameLen; i < 16; i++) + { + reader.ReadByte(); + } + Name = Encoding.UTF8.GetString(bytes); + Rssi = reader.ReadSByte(); + IsPrivate = reader.ReadBoolean(); + AlreadyVerified = reader.ReadBoolean(); + } + } + + internal class EvtCreateConnectionChannelResponse : EventPacket + { + internal uint ConnId; + internal CreateConnectionChannelError Error; + internal ConnectionStatus ConnectionStatus; + + protected override void ParseInternal(BinaryReader reader) + { + ConnId = reader.ReadUInt32(); + Error = (CreateConnectionChannelError)reader.ReadByte(); + ConnectionStatus = (ConnectionStatus)reader.ReadByte(); + } + } + + internal class EvtConnectionStatusChanged : EventPacket + { + internal uint ConnId; + internal ConnectionStatus ConnectionStatus; + internal DisconnectReason DisconnectReason; + + protected override void ParseInternal(BinaryReader reader) + { + ConnId = reader.ReadUInt32(); + ConnectionStatus = (ConnectionStatus)reader.ReadByte(); + DisconnectReason = (DisconnectReason)reader.ReadByte(); + } + } + + internal class EvtConnectionChannelRemoved : EventPacket + { + internal uint ConnId; + internal RemovedReason RemovedReason; + + protected override void ParseInternal(BinaryReader reader) + { + ConnId = reader.ReadUInt32(); + RemovedReason = (RemovedReason)reader.ReadByte(); + } + } + + internal class EvtButtonEvent : EventPacket + { + internal uint ConnId; + internal ClickType ClickType; + internal bool WasQueued; + internal uint TimeDiff; + + protected override void ParseInternal(BinaryReader reader) + { + ConnId = reader.ReadUInt32(); + ClickType = (ClickType)reader.ReadByte(); + WasQueued = reader.ReadBoolean(); + TimeDiff = reader.ReadUInt32(); + } + } + + internal class EvtNewVerifiedButton : EventPacket + { + internal Bdaddr BdAddr; + + protected override void ParseInternal(BinaryReader reader) + { + BdAddr = new Bdaddr(reader); + } + } + + internal class EvtGetInfoResponse : EventPacket + { + internal BluetoothControllerState BluetoothControllerState; + internal Bdaddr MyBdAddr; + internal BdAddrType MyBdAddrType; + internal byte MaxPendingConnections; + internal short MaxConcurrentlyConnectedButtons; + internal byte CurrentPendingConnections; + internal bool CurrentlyNoSpaceForNewConnection; + internal Bdaddr[] BdAddrOfVerifiedButtons; + + protected override void ParseInternal(BinaryReader reader) + { + BluetoothControllerState = (BluetoothControllerState)reader.ReadByte(); + MyBdAddr = new Bdaddr(reader); + MyBdAddrType = (BdAddrType)reader.ReadByte(); + MaxPendingConnections = reader.ReadByte(); + MaxConcurrentlyConnectedButtons = reader.ReadInt16(); + CurrentPendingConnections = reader.ReadByte(); + CurrentlyNoSpaceForNewConnection = reader.ReadBoolean(); + var nbVerifiedButtons = reader.ReadUInt16(); + BdAddrOfVerifiedButtons = new Bdaddr[nbVerifiedButtons]; + for (var i = 0; i < nbVerifiedButtons; i++) + { + BdAddrOfVerifiedButtons[i] = new Bdaddr(reader); + } + } + } + + internal class EvtNoSpaceForNewConnection : EventPacket + { + internal byte MaxConcurrentlyConnectedButtons; + + protected override void ParseInternal(BinaryReader reader) + { + MaxConcurrentlyConnectedButtons = reader.ReadByte(); + } + } + + internal class EvtGotSpaceForNewConnection : EventPacket + { + internal byte MaxConcurrentlyConnectedButtons; + + protected override void ParseInternal(BinaryReader reader) + { + MaxConcurrentlyConnectedButtons = reader.ReadByte(); + } + } + + internal class EvtBluetoothControllerStateChange : EventPacket + { + internal BluetoothControllerState State; + + protected override void ParseInternal(BinaryReader reader) + { + State = (BluetoothControllerState)reader.ReadByte(); + } + } + + internal class EvtGetButtonUUIDResponse : EventPacket + { + internal Bdaddr BdAddr; + internal string Uuid; + + protected override void ParseInternal(BinaryReader reader) + { + BdAddr = new Bdaddr(reader); + var uuidBytes = reader.ReadBytes(16); + if (uuidBytes.Length != 16) + { + throw new EndOfStreamException(); + } + var sb = new StringBuilder(32); + for (var i = 0; i < 16; i++) + { + sb.Append(string.Format("{0:x2}", uuidBytes[i])); + } + Uuid = sb.ToString(); + if (Uuid == "00000000000000000000000000000000") + { + Uuid = null; + } + } + } + + internal class EvtScanWizardFoundPrivateButton : EventPacket + { + internal uint ScanWizardId; + + protected override void ParseInternal(BinaryReader reader) + { + ScanWizardId = reader.ReadUInt32(); + } + } + + internal class EvtScanWizardFoundPublicButton : EventPacket + { + internal uint ScanWizardId; + internal Bdaddr BdAddr; + internal string Name; + + protected override void ParseInternal(BinaryReader reader) + { + ScanWizardId = reader.ReadUInt32(); + BdAddr = new Bdaddr(reader); + int nameLen = reader.ReadByte(); + var bytes = new byte[nameLen]; + for (var i = 0; i < nameLen; i++) + { + bytes[i] = reader.ReadByte(); + } + for (var i = nameLen; i < 16; i++) + { + reader.ReadByte(); + } + Name = Encoding.UTF8.GetString(bytes); + } + } + + internal class EvtScanWizardButtonConnected : EventPacket + { + internal uint ScanWizardId; + + protected override void ParseInternal(BinaryReader reader) + { + ScanWizardId = reader.ReadUInt32(); + } + } + + internal class EvtScanWizardCompleted : EventPacket + { + internal uint ScanWizardId; + internal ScanWizardResult Result; + + protected override void ParseInternal(BinaryReader reader) + { + ScanWizardId = reader.ReadUInt32(); + Result = (ScanWizardResult)reader.ReadByte(); + } + } +} diff --git a/clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/Properties/AssemblyInfo.cs b/clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..1113b26 --- /dev/null +++ b/clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/Properties/AssemblyInfo.cs @@ -0,0 +1,23 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("FliclibDotNetClient")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("FliclibDotNetClient")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("3115fef5-c233-42eb-a2c7-181279f60a4c")] diff --git a/clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/RaiseEventExtension.cs b/clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/RaiseEventExtension.cs new file mode 100644 index 0000000..1ac4967 --- /dev/null +++ b/clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/RaiseEventExtension.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace FliclibDotNetClient +{ + internal static class EventExtensions + { + internal static void RaiseEvent(this EventHandler @event, object sender, EventArgs e) + { + if (@event != null) + @event(sender, e); + } + internal static void RaiseEvent(this EventHandler @event, object sender, T e) + where T : EventArgs + { + if (@event != null) + @event(sender, e); + } + } +} diff --git a/clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/ScanWizard.cs b/clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/ScanWizard.cs new file mode 100644 index 0000000..6f0d6b1 --- /dev/null +++ b/clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/ScanWizard.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace FliclibDotNetClient +{ + /// + /// Information about found button + /// + public class ScanWizardButtonInfoEventArgs : EventArgs + { + /// + /// Bluetooth device address + /// + public Bdaddr BdAddr { get; internal set; } + + /// + /// Advertised name + /// + public string Name { get; internal set; } + } + + /// + /// Information about result of scan wizard + /// + public class ScanWizardCompletedEventArgs : ScanWizardButtonInfoEventArgs + { + /// + /// The result. + /// If WizardSuccess, a new button has been found and the NewVerifiedButton event will be raised on the FlicClient. + /// + public ScanWizardResult Result { get; internal set; } + } + + /// + /// A high level scan wizard. + /// This class should be used when you want to add a new button. + /// Register the events and add an instance of this class to a FlicClient with AddScanWizard. + /// + public class ScanWizard + { + private static int _nextId = 0; + internal uint ScanWizardId = (uint)Interlocked.Increment(ref _nextId); + + internal Bdaddr BdAddr; + internal string Name; + + /// + /// Called at most once when a private button has been found. That means the user should press the Flic button for 7 seconds in order to make it public. + /// + public event EventHandler FoundPrivateButton; + + /// + /// Called at most once when a public button has been found. The server will now attempt to connect to the button. + /// When this event has been received the FoundPrivateButton event will not be raised. + /// + public event EventHandler FoundPublicButton; + + /// + /// Called at most once when a public button has connected. The server will now attempt to pair to the button. + /// When this event has been received the FoundPrivateButton or FoundPublicButton will not be raised. + /// + public event EventHandler ButtonConnected; + + /// + /// Called when the scan wizard has completed for any reason. + /// + public event EventHandler Completed; + + protected internal virtual void OnFoundPrivateButton() + { + FoundPrivateButton.RaiseEvent(this, EventArgs.Empty); + } + + protected internal virtual void OnFoundPublicButton(ScanWizardButtonInfoEventArgs e) + { + FoundPublicButton.RaiseEvent(this, e); + } + + protected internal virtual void OnButtonConnected(ScanWizardButtonInfoEventArgs e) + { + ButtonConnected.RaiseEvent(this, e); + } + + protected internal virtual void OnCompleted(ScanWizardCompletedEventArgs e) + { + Completed.RaiseEvent(this, e); + } + } +} diff --git a/clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/SocketAwaitable.cs b/clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/SocketAwaitable.cs new file mode 100644 index 0000000..d3fa5c8 --- /dev/null +++ b/clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/SocketAwaitable.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Sockets; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +namespace FliclibDotNetClient +{ + internal sealed class SocketAwaitable : INotifyCompletion + { + private readonly static Action SENTINEL = () => { }; + + internal bool m_wasCompleted; + internal Action m_continuation; + internal SocketAsyncEventArgs m_eventArgs; + + public SocketAwaitable(SocketAsyncEventArgs eventArgs) + { + if (eventArgs == null) throw new ArgumentNullException("eventArgs"); + m_eventArgs = eventArgs; + eventArgs.Completed += delegate + { + var prev = m_continuation ?? Interlocked.CompareExchange( + ref m_continuation, SENTINEL, null); + if (prev != null) prev(); + }; + } + + internal void Reset() + { + m_wasCompleted = false; + m_continuation = null; + } + + public SocketAwaitable GetAwaiter() { return this; } + + public bool IsCompleted { get { return m_wasCompleted; } } + + public void OnCompleted(Action continuation) + { + if (m_continuation == SENTINEL || + Interlocked.CompareExchange( + ref m_continuation, continuation, null) == SENTINEL) + { + Task.Run(continuation); + } + } + + public void GetResult() + { + if (m_eventArgs.SocketError != SocketError.Success) + throw new SocketException((int)m_eventArgs.SocketError); + } + } + + internal static class SocketExtensions + { + public static SocketAwaitable ReceiveAsync(this Socket socket, + SocketAwaitable awaitable) + { + awaitable.Reset(); + if (!socket.ReceiveAsync(awaitable.m_eventArgs)) + awaitable.m_wasCompleted = true; + return awaitable; + } + + public static SocketAwaitable SendAsync(this Socket socket, + SocketAwaitable awaitable) + { + awaitable.Reset(); + if (!socket.SendAsync(awaitable.m_eventArgs)) + awaitable.m_wasCompleted = true; + return awaitable; + } + } +} diff --git a/clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/project.json b/clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/project.json new file mode 100644 index 0000000..021f9f2 --- /dev/null +++ b/clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/project.json @@ -0,0 +1,12 @@ +{ + "version": "1.0.0-*", + "description": "FliclibDotNetClient Class Library", + "authors": [ "Emil" ], + "tags": [ "" ], + "projectUrl": "", + "licenseUrl": "", + + "frameworks": { + "net451": { } + } +} diff --git a/clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/project.lock.json b/clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/project.lock.json new file mode 100644 index 0000000..bb934f4 --- /dev/null +++ b/clientlib/csharp/FliclibDotNetClient/src/FliclibDotNetClient/project.lock.json @@ -0,0 +1,14 @@ +{ + "locked": false, + "version": 2, + "targets": { + ".NETFramework,Version=v4.5.1": {}, + ".NETFramework,Version=v4.5.1/win7-x86": {}, + ".NETFramework,Version=v4.5.1/win7-x64": {} + }, + "libraries": {}, + "projectFileDependencyGroups": { + "": [], + ".NETFramework,Version=v4.5.1": [] + } +} \ No newline at end of file diff --git a/clientlib/csharp/GUISample/FlicLibTest.sln b/clientlib/csharp/GUISample/FlicLibTest.sln new file mode 100644 index 0000000..98d5089 --- /dev/null +++ b/clientlib/csharp/GUISample/FlicLibTest.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25123.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FlicLibTest", "FlicLibTest\FlicLibTest.csproj", "{DA13DDAD-5070-4552-B7B0-1A29FD73E0CB}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {DA13DDAD-5070-4552-B7B0-1A29FD73E0CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DA13DDAD-5070-4552-B7B0-1A29FD73E0CB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DA13DDAD-5070-4552-B7B0-1A29FD73E0CB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DA13DDAD-5070-4552-B7B0-1A29FD73E0CB}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/clientlib/csharp/GUISample/FlicLibTest/App.config b/clientlib/csharp/GUISample/FlicLibTest/App.config new file mode 100644 index 0000000..bae5d6d --- /dev/null +++ b/clientlib/csharp/GUISample/FlicLibTest/App.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/clientlib/csharp/GUISample/FlicLibTest/FlicButtonControl.Designer.cs b/clientlib/csharp/GUISample/FlicLibTest/FlicButtonControl.Designer.cs new file mode 100644 index 0000000..87c179a --- /dev/null +++ b/clientlib/csharp/GUISample/FlicLibTest/FlicButtonControl.Designer.cs @@ -0,0 +1,97 @@ +namespace FlicLibTest +{ + partial class FlicButtonControl + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.lblBdAddr = new System.Windows.Forms.Label(); + this.lblStatus = new System.Windows.Forms.Label(); + this.btnListen = new System.Windows.Forms.Button(); + this.pictureBox = new System.Windows.Forms.PictureBox(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox)).BeginInit(); + this.SuspendLayout(); + // + // lblBdAddr + // + this.lblBdAddr.AutoSize = true; + this.lblBdAddr.Location = new System.Drawing.Point(4, 9); + this.lblBdAddr.Name = "lblBdAddr"; + this.lblBdAddr.Size = new System.Drawing.Size(94, 13); + this.lblBdAddr.TabIndex = 0; + this.lblBdAddr.Text = "00:00:00:00:00:00"; + // + // lblStatus + // + this.lblStatus.AutoSize = true; + this.lblStatus.Location = new System.Drawing.Point(4, 22); + this.lblStatus.Name = "lblStatus"; + this.lblStatus.Size = new System.Drawing.Size(73, 13); + this.lblStatus.TabIndex = 1; + this.lblStatus.Text = "Disconnected"; + // + // btnListen + // + this.btnListen.Location = new System.Drawing.Point(7, 38); + this.btnListen.Name = "btnListen"; + this.btnListen.Size = new System.Drawing.Size(75, 23); + this.btnListen.TabIndex = 2; + this.btnListen.Text = "Listen"; + this.btnListen.UseVisualStyleBackColor = true; + // + // pictureBox + // + this.pictureBox.BackColor = System.Drawing.Color.Red; + this.pictureBox.Location = new System.Drawing.Point(105, 9); + this.pictureBox.Name = "pictureBox"; + this.pictureBox.Size = new System.Drawing.Size(100, 50); + this.pictureBox.TabIndex = 3; + this.pictureBox.TabStop = false; + // + // FlicButtonControl + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(this.pictureBox); + this.Controls.Add(this.btnListen); + this.Controls.Add(this.lblStatus); + this.Controls.Add(this.lblBdAddr); + this.Name = "FlicButtonControl"; + this.Size = new System.Drawing.Size(217, 70); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox)).EndInit(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + public System.Windows.Forms.Label lblBdAddr; + public System.Windows.Forms.Label lblStatus; + public System.Windows.Forms.Button btnListen; + public System.Windows.Forms.PictureBox pictureBox; + } +} diff --git a/clientlib/csharp/GUISample/FlicLibTest/FlicButtonControl.cs b/clientlib/csharp/GUISample/FlicLibTest/FlicButtonControl.cs new file mode 100644 index 0000000..2d04c49 --- /dev/null +++ b/clientlib/csharp/GUISample/FlicLibTest/FlicButtonControl.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Data; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using FliclibDotNetClient; + +namespace FlicLibTest +{ + public partial class FlicButtonControl : UserControl + { + public bool Listens + { + get; + set; + } + + public ButtonConnectionChannel Channel + { + get; + set; + } + + public FlicButtonControl() + { + InitializeComponent(); + } + } +} diff --git a/clientlib/csharp/GUISample/FlicLibTest/FlicButtonControl.resx b/clientlib/csharp/GUISample/FlicLibTest/FlicButtonControl.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/clientlib/csharp/GUISample/FlicLibTest/FlicButtonControl.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/clientlib/csharp/GUISample/FlicLibTest/FlicLibTest.csproj b/clientlib/csharp/GUISample/FlicLibTest/FlicLibTest.csproj new file mode 100644 index 0000000..ee43b8a --- /dev/null +++ b/clientlib/csharp/GUISample/FlicLibTest/FlicLibTest.csproj @@ -0,0 +1,111 @@ + + + + + Debug + AnyCPU + {DA13DDAD-5070-4552-B7B0-1A29FD73E0CB} + WinExe + Properties + FlicLibTest + FlicLibTest + v4.6.1 + 512 + true + + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + false + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + false + + + + + + + + ..\..\FliclibDotNetClient\artifacts\bin\FliclibDotNetClient\Debug\net451\FliclibDotNetClient.dll + + + + + + + + + + + + + + + + UserControl + + + FlicButtonControl.cs + + + Form + + + MainForm.cs + + + + + FlicButtonControl.cs + + + MainForm.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + True + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + + \ No newline at end of file diff --git a/clientlib/csharp/GUISample/FlicLibTest/MainForm.Designer.cs b/clientlib/csharp/GUISample/FlicLibTest/MainForm.Designer.cs new file mode 100644 index 0000000..1ef7dfe --- /dev/null +++ b/clientlib/csharp/GUISample/FlicLibTest/MainForm.Designer.cs @@ -0,0 +1,175 @@ +namespace FlicLibTest +{ + partial class MainForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.buttonsList = new System.Windows.Forms.FlowLayoutPanel(); + this.lblConnectionStatus = new System.Windows.Forms.Label(); + this.lblBluetoothStatus = new System.Windows.Forms.Label(); + this.txtServer = new System.Windows.Forms.TextBox(); + this.lblServer = new System.Windows.Forms.Label(); + this.lblPort = new System.Windows.Forms.Label(); + this.txtPort = new System.Windows.Forms.TextBox(); + this.btnConnectDisconnect = new System.Windows.Forms.Button(); + this.btnAddNewFlic = new System.Windows.Forms.Button(); + this.lblScanWizardStatus = new System.Windows.Forms.Label(); + this.SuspendLayout(); + // + // buttonsList + // + this.buttonsList.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left))); + this.buttonsList.AutoScroll = true; + this.buttonsList.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + this.buttonsList.FlowDirection = System.Windows.Forms.FlowDirection.TopDown; + this.buttonsList.Location = new System.Drawing.Point(13, 13); + this.buttonsList.Name = "buttonsList"; + this.buttonsList.Size = new System.Drawing.Size(225, 358); + this.buttonsList.TabIndex = 0; + this.buttonsList.WrapContents = false; + // + // lblConnectionStatus + // + this.lblConnectionStatus.AutoSize = true; + this.lblConnectionStatus.Location = new System.Drawing.Point(274, 13); + this.lblConnectionStatus.Name = "lblConnectionStatus"; + this.lblConnectionStatus.Size = new System.Drawing.Size(164, 13); + this.lblConnectionStatus.TabIndex = 1; + this.lblConnectionStatus.Text = "Connection status: Disconnected"; + // + // lblBluetoothStatus + // + this.lblBluetoothStatus.AutoSize = true; + this.lblBluetoothStatus.Location = new System.Drawing.Point(273, 26); + this.lblBluetoothStatus.Name = "lblBluetoothStatus"; + this.lblBluetoothStatus.Size = new System.Drawing.Size(132, 13); + this.lblBluetoothStatus.TabIndex = 2; + this.lblBluetoothStatus.Text = "Bluetooth controller status:"; + // + // txtServer + // + this.txtServer.Location = new System.Drawing.Point(276, 101); + this.txtServer.Name = "txtServer"; + this.txtServer.Size = new System.Drawing.Size(100, 20); + this.txtServer.TabIndex = 3; + this.txtServer.Text = "localhost"; + // + // lblServer + // + this.lblServer.AutoSize = true; + this.lblServer.Location = new System.Drawing.Point(273, 85); + this.lblServer.Name = "lblServer"; + this.lblServer.Size = new System.Drawing.Size(41, 13); + this.lblServer.TabIndex = 4; + this.lblServer.Text = "Server:"; + // + // lblPort + // + this.lblPort.AutoSize = true; + this.lblPort.Location = new System.Drawing.Point(273, 124); + this.lblPort.Name = "lblPort"; + this.lblPort.Size = new System.Drawing.Size(29, 13); + this.lblPort.TabIndex = 5; + this.lblPort.Text = "Port:"; + // + // txtPort + // + this.txtPort.Location = new System.Drawing.Point(276, 140); + this.txtPort.Name = "txtPort"; + this.txtPort.Size = new System.Drawing.Size(100, 20); + this.txtPort.TabIndex = 6; + this.txtPort.Text = "5551"; + // + // btnConnectDisconnect + // + this.btnConnectDisconnect.Location = new System.Drawing.Point(275, 167); + this.btnConnectDisconnect.Name = "btnConnectDisconnect"; + this.btnConnectDisconnect.Size = new System.Drawing.Size(101, 23); + this.btnConnectDisconnect.TabIndex = 7; + this.btnConnectDisconnect.Text = "Connect"; + this.btnConnectDisconnect.UseVisualStyleBackColor = true; + this.btnConnectDisconnect.Click += new System.EventHandler(this.btnConnectDisconnect_Click); + // + // btnAddNewFlic + // + this.btnAddNewFlic.Enabled = false; + this.btnAddNewFlic.Location = new System.Drawing.Point(277, 226); + this.btnAddNewFlic.Name = "btnAddNewFlic"; + this.btnAddNewFlic.Size = new System.Drawing.Size(99, 23); + this.btnAddNewFlic.TabIndex = 8; + this.btnAddNewFlic.Text = "Add new Flic"; + this.btnAddNewFlic.UseVisualStyleBackColor = true; + this.btnAddNewFlic.Click += new System.EventHandler(this.btnAddNewFlic_Click); + // + // lblScanWizardStatus + // + this.lblScanWizardStatus.AutoSize = true; + this.lblScanWizardStatus.Location = new System.Drawing.Point(277, 256); + this.lblScanWizardStatus.Name = "lblScanWizardStatus"; + this.lblScanWizardStatus.Size = new System.Drawing.Size(105, 13); + this.lblScanWizardStatus.TabIndex = 9; + this.lblScanWizardStatus.Text = "lblScanWizardStatus"; + // + // MainForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(636, 383); + this.Controls.Add(this.lblScanWizardStatus); + this.Controls.Add(this.btnAddNewFlic); + this.Controls.Add(this.btnConnectDisconnect); + this.Controls.Add(this.txtPort); + this.Controls.Add(this.lblPort); + this.Controls.Add(this.lblServer); + this.Controls.Add(this.txtServer); + this.Controls.Add(this.lblBluetoothStatus); + this.Controls.Add(this.lblConnectionStatus); + this.Controls.Add(this.buttonsList); + this.Name = "MainForm"; + this.Text = "Flic Sample"; + this.Load += new System.EventHandler(this.Form1_Load); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.FlowLayoutPanel buttonsList; + private System.Windows.Forms.Label lblConnectionStatus; + private System.Windows.Forms.Label lblBluetoothStatus; + private System.Windows.Forms.TextBox txtServer; + private System.Windows.Forms.Label lblServer; + private System.Windows.Forms.Label lblPort; + private System.Windows.Forms.TextBox txtPort; + private System.Windows.Forms.Button btnConnectDisconnect; + private System.Windows.Forms.Button btnAddNewFlic; + private System.Windows.Forms.Label lblScanWizardStatus; + } +} + diff --git a/clientlib/csharp/GUISample/FlicLibTest/MainForm.cs b/clientlib/csharp/GUISample/FlicLibTest/MainForm.cs new file mode 100644 index 0000000..a9ee9df --- /dev/null +++ b/clientlib/csharp/GUISample/FlicLibTest/MainForm.cs @@ -0,0 +1,187 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using FliclibDotNetClient; + +/* + * Example client of FlicLib. + * + * Consists of a GUI where a user can scan for new buttons as well as connect and see button up/down events. + * + * Note that the Invoke((MethodInvoker) delegate { ... }) calls are made in order to run code on the UI thread which is needed to update the GUI. + */ + +namespace FlicLibTest +{ + public partial class MainForm : Form + { + private FlicClient _flicClient; + private ScanWizard _currentScanWizard; + + public MainForm() + { + InitializeComponent(); + } + + private void Form1_Load(object sender, EventArgs e) + { + lblScanWizardStatus.Text = ""; + } + + private async void btnConnectDisconnect_Click(object sender, EventArgs e) + { + if (_flicClient == null) + { + btnConnectDisconnect.Enabled = false; + try + { + _flicClient = await FlicClient.CreateAsync(txtServer.Text); + } catch (Exception ex) + { + MessageBox.Show("Connect failed: " + ex.Message); + btnConnectDisconnect.Enabled = true; + return; + } + + btnConnectDisconnect.Text = "Disconnect"; + btnConnectDisconnect.Enabled = true; + + btnAddNewFlic.Text = "Add new Flic"; + btnAddNewFlic.Enabled = true; + + _flicClient.BluetoothControllerStateChange += (o, args) => Invoke((MethodInvoker)delegate + { + lblBluetoothStatus.Text = "Bluetooth controller status: " + args.State.ToString(); + }); + + _flicClient.NewVerifiedButton += (o, args) => Invoke((MethodInvoker)delegate + { + GotButton(args.BdAddr); + }); + + _flicClient.GetInfo((bluetoothControllerState, myBdAddr, myBdAddrType, maxPendingConnections, maxConcurrentlyConnectedButtons, currentPendingConnections, currentlyNoSpaceForNewConnection, verifiedButtons) => Invoke((MethodInvoker)delegate + { + lblBluetoothStatus.Text = "Bluetooth controller status: " + bluetoothControllerState.ToString(); + + foreach (var bdAddr in verifiedButtons) + { + GotButton(bdAddr); + } + })); + + await Task.Run(() => _flicClient.HandleEvents()); + + // HandleEvents returns when the socket has disconnected for any reason + + buttonsList.Controls.Clear(); + btnAddNewFlic.Enabled = false; + + _flicClient = null; + lblConnectionStatus.Text = "Connection status: Disconnected"; + lblBluetoothStatus.Text = "Bluetooth controller status:"; + btnConnectDisconnect.Text = "Connect"; + btnConnectDisconnect.Enabled = true; + + _currentScanWizard = null; + lblScanWizardStatus.Text = ""; + } + else + { + _flicClient.Disconnect(); + btnConnectDisconnect.Enabled = false; + } + } + + private void GotButton(Bdaddr bdAddr) + { + var control = new FlicButtonControl(); + control.lblBdAddr.Text = bdAddr.ToString(); + control.btnListen.Click += (o, args) => + { + if (!control.Listens) + { + control.Listens = true; + control.btnListen.Text = "Stop"; + + control.Channel = new ButtonConnectionChannel(bdAddr); + control.Channel.CreateConnectionChannelResponse += (sender1, eventArgs) => Invoke((MethodInvoker)delegate + { + if (eventArgs.Error != CreateConnectionChannelError.NoError) + { + control.Listens = false; + control.btnListen.Text = "Listen"; + } + else + { + control.lblStatus.Text = eventArgs.ConnectionStatus.ToString(); + } + }); + control.Channel.Removed += (sender1, eventArgs) => Invoke((MethodInvoker)delegate + { + control.lblStatus.Text = "Disconnected"; + control.Listens = false; + control.btnListen.Text = "Listen"; + }); + control.Channel.ConnectionStatusChanged += (sender1, eventArgs) => Invoke((MethodInvoker)delegate + { + control.lblStatus.Text = eventArgs.ConnectionStatus.ToString(); + }); + control.Channel.ButtonUpOrDown += (sender1, eventArgs) => Invoke((MethodInvoker)delegate + { + control.pictureBox.BackColor = eventArgs.ClickType == ClickType.ButtonDown ? Color.LimeGreen : Color.Red; + }); + _flicClient.AddConnectionChannel(control.Channel); + } + else + { + _flicClient.RemoveConnectionChannel(control.Channel); + } + }; + buttonsList.Controls.Add(control); + } + + private void btnAddNewFlic_Click(object sender, EventArgs e) + { + if (_currentScanWizard == null) + { + lblScanWizardStatus.Text = "Press your Flic button"; + + var scanWizard = new ScanWizard(); + scanWizard.FoundPrivateButton += (o, args) => Invoke((MethodInvoker)delegate + { + lblScanWizardStatus.Text = "Hold down your Flic button for 7 seconds"; + }); + scanWizard.FoundPublicButton += (o, args) => Invoke((MethodInvoker)delegate + { + lblScanWizardStatus.Text = "Found button " + args.BdAddr.ToString() + ", now connecting..."; + }); + scanWizard.ButtonConnected += (o, args) => Invoke((MethodInvoker)delegate + { + lblScanWizardStatus.Text = "Connected to " + args.BdAddr.ToString() + ", now pairing..."; + }); + scanWizard.Completed += (o, args) => Invoke((MethodInvoker)delegate + { + lblScanWizardStatus.Text = "Result: " + args.Result; + _currentScanWizard = null; + btnAddNewFlic.Text = "Add new Flic"; + }); + + _flicClient.AddScanWizard(scanWizard); + + _currentScanWizard = scanWizard; + btnAddNewFlic.Text = "Cancel"; + } + else + { + _flicClient.CancelScanWizard(_currentScanWizard); + } + } + } +} diff --git a/clientlib/csharp/GUISample/FlicLibTest/MainForm.resx b/clientlib/csharp/GUISample/FlicLibTest/MainForm.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/clientlib/csharp/GUISample/FlicLibTest/MainForm.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/clientlib/csharp/GUISample/FlicLibTest/Program.cs b/clientlib/csharp/GUISample/FlicLibTest/Program.cs new file mode 100644 index 0000000..fdb9b89 --- /dev/null +++ b/clientlib/csharp/GUISample/FlicLibTest/Program.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace FlicLibTest +{ + static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new MainForm()); + } + } +} diff --git a/clientlib/csharp/GUISample/FlicLibTest/Properties/AssemblyInfo.cs b/clientlib/csharp/GUISample/FlicLibTest/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..9afce73 --- /dev/null +++ b/clientlib/csharp/GUISample/FlicLibTest/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("FlicLibTest")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("FlicLibTest")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("da13ddad-5070-4552-b7b0-1a29fd73e0cb")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/clientlib/csharp/GUISample/FlicLibTest/Properties/Resources.Designer.cs b/clientlib/csharp/GUISample/FlicLibTest/Properties/Resources.Designer.cs new file mode 100644 index 0000000..61e7a41 --- /dev/null +++ b/clientlib/csharp/GUISample/FlicLibTest/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace FlicLibTest.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("FlicLibTest.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/clientlib/csharp/GUISample/FlicLibTest/Properties/Resources.resx b/clientlib/csharp/GUISample/FlicLibTest/Properties/Resources.resx new file mode 100644 index 0000000..af7dbeb --- /dev/null +++ b/clientlib/csharp/GUISample/FlicLibTest/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/clientlib/csharp/GUISample/FlicLibTest/Properties/Settings.Designer.cs b/clientlib/csharp/GUISample/FlicLibTest/Properties/Settings.Designer.cs new file mode 100644 index 0000000..2d2ae33 --- /dev/null +++ b/clientlib/csharp/GUISample/FlicLibTest/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace FlicLibTest.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "12.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/clientlib/csharp/GUISample/FlicLibTest/Properties/Settings.settings b/clientlib/csharp/GUISample/FlicLibTest/Properties/Settings.settings new file mode 100644 index 0000000..3964565 --- /dev/null +++ b/clientlib/csharp/GUISample/FlicLibTest/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + +