/* MainWindow.xaml.cs/Open GoPro, Version 2.0 (C) Copyright 2021 GoPro, Inc. (http://gopro.com/OpenGoPro). */ /* This copyright was auto-generated on Wed, Sep 1, 2021 5:05:38 PM */ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Globalization; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using Windows.Devices.Bluetooth; using Windows.Devices.Bluetooth.GenericAttributeProfile; using Windows.Devices.Enumeration; using Windows.Media.MediaProperties; using Windows.Storage.Streams; namespace GoProCSharpSample { /// /// Interaction logic for MainWindow.xaml /// public partial class MainWindow : Window, INotifyPropertyChanged { public class GDeviceInformation { public GDeviceInformation(DeviceInformation inDeviceInformation, bool inPresent, bool inConnected) { DeviceInfo = inDeviceInformation; IsPresent = inPresent; IsConnected = inConnected; } public DeviceInformation DeviceInfo { get; set; } = null; public bool IsPresent { get; set; } = false; public bool IsConnected { get; set; } = false; public bool IsVisible { get { return IsPresent || IsConnected; } } private GDeviceInformation() { } } #region Binded Properties public ObservableCollection Devices { get; set; } = new ObservableCollection(); private bool mEncoding = false; public bool Encoding { get { return mEncoding; } set { mEncoding = value; if (this.PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs("Encoding")); } } } private int mBatterylevel = 0; public int BatteryLevel { get { return mBatterylevel; } set { mBatterylevel = value; if (this.PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs("BatteryLevel")); } } } private bool mWifiOn = false; public bool WifiOn { get { return mWifiOn; } set { mWifiOn = value; if (this.PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs("WifiOn")); } } } #endregion #region Bluetooth Device Members private BluetoothLEDevice mBLED = null; public GattCharacteristic mNotifyCmds = null; public GattCharacteristic mSendCmds = null; public GattCharacteristic mSetSettings = null; public GattCharacteristic mNotifySettings = null; public GattCharacteristic mSendQueries = null; public GattCharacteristic mNotifyQueryResp = null; public GattCharacteristic mReadAPName = null; public GattCharacteristic mReadAPPass = null; #endregion public event PropertyChangedEventHandler PropertyChanged; DeviceWatcher mDeviceWatcher = null; private readonly Dictionary mAllDevices = new Dictionary(); public MainWindow() { InitializeComponent(); WindowStartupLocation = WindowStartupLocation.CenterScreen; } #region Button Click Handlers private void BtnScanBLE_Click(object sender, RoutedEventArgs e) { string BLESelector = "System.Devices.Aep.ProtocolId:=\"{bb7bb05e-5972-42b5-94fc-76eaa7084d49}\""; DeviceInformationKind deviceInformationKind = DeviceInformationKind.AssociationEndpoint; string[] requiredProperties = { "System.Devices.Aep.Bluetooth.Le.IsConnectable", "System.Devices.Aep.IsConnected" }; mDeviceWatcher = DeviceInformation.CreateWatcher(BLESelector, requiredProperties, deviceInformationKind); mDeviceWatcher.Added += MDeviceWatcher_Added; ; mDeviceWatcher.Updated += MDeviceWatcher_Updated; ; mDeviceWatcher.Removed += MDeviceWatcher_Removed; ; mDeviceWatcher.EnumerationCompleted += MDeviceWatcher_EnumerationCompleted; ; mDeviceWatcher.Stopped += MDeviceWatcher_Stopped; ; this.txtStatusBar.Text = "Scanning for devices..."; mDeviceWatcher.Start(); } private async void BtnPair_Click(object sender, RoutedEventArgs e) { GDeviceInformation lDevice = (GDeviceInformation)lbDevices.SelectedItem; if (lDevice != null) { StatusOutput("Pairing started"); mBLED = await BluetoothLEDevice.FromIdAsync(lDevice.DeviceInfo.Id); mBLED.DeviceInformation.Pairing.Custom.PairingRequested += Custom_PairingRequested; if (mBLED.DeviceInformation.Pairing.CanPair) { DevicePairingProtectionLevel dppl = mBLED.DeviceInformation.Pairing.ProtectionLevel; DevicePairingResult dpr = await mBLED.DeviceInformation.Pairing.Custom.PairAsync(DevicePairingKinds.ConfirmOnly, dppl); StatusOutput("Pairing result = " + dpr.Status.ToString()); } else { StatusOutput("Pairing failed"); } } else { StatusOutput("Select a device"); } } private async void BtnConnect_Click(object sender, RoutedEventArgs e) { StatusOutput("Connecting..."); GDeviceInformation mDI = (GDeviceInformation)lbDevices.SelectedItem; if (mDI == null) { StatusOutput("No device selected"); return; } mBLED = await BluetoothLEDevice.FromIdAsync(mDI.DeviceInfo.Id); if(!mBLED.DeviceInformation.Pairing.IsPaired) { StatusOutput("Device not paired"); return; } GattDeviceServicesResult result = await mBLED.GetGattServicesAsync(); mBLED.ConnectionStatusChanged += MBLED_ConnectionStatusChanged; if (result.Status == GattCommunicationStatus.Success) { IReadOnlyList services = result.Services; foreach (GattDeviceService gatt in services) { GattCharacteristicsResult res = await gatt.GetCharacteristicsAsync(); if (res.Status == GattCommunicationStatus.Success) { IReadOnlyList characteristics = res.Characteristics; foreach (GattCharacteristic characteristic in characteristics) { GattCharacteristicProperties properties = characteristic.CharacteristicProperties; if (properties.HasFlag(GattCharacteristicProperties.Read)) { // This characteristic supports reading from it. } if (properties.HasFlag(GattCharacteristicProperties.Write)) { // This characteristic supports writing to it. } if (properties.HasFlag(GattCharacteristicProperties.Notify)) { // This characteristic supports subscribing to notifications. } if (characteristic.Uuid.ToString() == "b5f90002-aa8d-11e3-9046-0002a5d5c51b") { mReadAPName = characteristic; } if (characteristic.Uuid.ToString() == "b5f90003-aa8d-11e3-9046-0002a5d5c51b") { mReadAPPass = characteristic; } if (characteristic.Uuid.ToString() == "b5f90072-aa8d-11e3-9046-0002a5d5c51b") { mSendCmds = characteristic; } if (characteristic.Uuid.ToString() == "b5f90073-aa8d-11e3-9046-0002a5d5c51b") { mNotifyCmds = characteristic; GattCommunicationStatus status = await mNotifyCmds.WriteClientCharacteristicConfigurationDescriptorAsync(GattClientCharacteristicConfigurationDescriptorValue.Notify); if (status == GattCommunicationStatus.Success) { mNotifyCmds.ValueChanged += MNotifyCmds_ValueChanged; } else { //failure StatusOutput("Failed to attach notify cmd " + status); } } if (characteristic.Uuid.ToString() == "b5f90074-aa8d-11e3-9046-0002a5d5c51b") { mSetSettings = characteristic; } if (characteristic.Uuid.ToString() == "b5f90075-aa8d-11e3-9046-0002a5d5c51b") { mNotifySettings = characteristic; GattCommunicationStatus status = await mNotifySettings.WriteClientCharacteristicConfigurationDescriptorAsync(GattClientCharacteristicConfigurationDescriptorValue.Notify); if (status == GattCommunicationStatus.Success) { mNotifySettings.ValueChanged += MNotifySettings_ValueChanged; } else { //failure StatusOutput("Failed to attach notify settings " + status); } } if (characteristic.Uuid.ToString() == "b5f90076-aa8d-11e3-9046-0002a5d5c51b") { mSendQueries = characteristic; } if (characteristic.Uuid.ToString() == "b5f90077-aa8d-11e3-9046-0002a5d5c51b") { mNotifyQueryResp = characteristic; GattCommunicationStatus status = await mNotifyQueryResp.WriteClientCharacteristicConfigurationDescriptorAsync(GattClientCharacteristicConfigurationDescriptorValue.Notify); if (status == GattCommunicationStatus.Success) { mNotifyQueryResp.ValueChanged += MNotifyQueryResp_ValueChanged; if (mSendQueries != null) { //Register for settings and status updates DataWriter mm = new DataWriter(); mm.WriteBytes(new byte[] { 1, 0x52 }); GattCommunicationStatus gat = await mSendQueries.WriteValueAsync(mm.DetachBuffer()); mm = new DataWriter(); mm.WriteBytes(new byte[] { 1, 0x53 }); gat = await mSendQueries.WriteValueAsync(mm.DetachBuffer()); } else { StatusOutput("send queries was null!"); } } else { //failure StatusOutput("Failed to attach notify query " + status); } } } } } SetThirdPartySource(); } else if (result.Status == GattCommunicationStatus.Unreachable) { //couldn't find camera StatusOutput("Connection failed"); } } private async void BtnReadAPName_Click(object sender, RoutedEventArgs e) { if (mReadAPName != null) { GattReadResult res = await mReadAPName.ReadValueAsync(); if (res.Status == GattCommunicationStatus.Success) { DataReader dataReader = Windows.Storage.Streams.DataReader.FromBuffer(res.Value); string output = dataReader.ReadString(res.Value.Length); txtAPName.Text = output; } else { StatusOutput("Failed to read ap name"); } } else { StatusOutput("Not connected"); } } private async void BtnReadAPPass_Click(object sender, RoutedEventArgs e) { if (mReadAPPass != null) { GattReadResult res = await mReadAPPass.ReadValueAsync(); if (res.Status == GattCommunicationStatus.Success) { DataReader dataReader = Windows.Storage.Streams.DataReader.FromBuffer(res.Value); string output = dataReader.ReadString(res.Value.Length); txtAPPassword.Text = output; } else { StatusOutput("Failed to read password"); } } else { StatusOutput("Not connected"); } } private void BtnTurnWifiOn_Click(object sender, RoutedEventArgs e) { TogglefWifiAP(1); } private void BtnTurnWifiOff_Click(object sender, RoutedEventArgs e) { TogglefWifiAP(0); } private void BtnShutterOn_Click(object sender, RoutedEventArgs e) { ToggleShutter(1); } private void BtnShutterOff_Click(object sender, RoutedEventArgs e) { ToggleShutter(0); } #endregion #region Device Watcher Event Handlers private void MDeviceWatcher_Stopped(DeviceWatcher sender, object args) { Application.Current.Dispatcher.BeginInvoke(new Action(() => { this.txtStatusBar.Text = "Scan Stopped!"; })); } private void MDeviceWatcher_EnumerationCompleted(DeviceWatcher sender, object args) { Application.Current.Dispatcher.BeginInvoke(new Action(() => { this.txtStatusBar.Text = "Scan Complete"; })); } private void MDeviceWatcher_Removed(DeviceWatcher sender, DeviceInformationUpdate args) { for (int i = 0; i < Devices.Count; i++) { if (Devices[i].DeviceInfo.Id == args.Id) { Application.Current.Dispatcher.BeginInvoke(new Action(() => { Devices.RemoveAt(i); })); break; } } } private void MDeviceWatcher_Updated(DeviceWatcher sender, DeviceInformationUpdate args) { bool isPresent = false, isConnected = false, found = false; if (args.Properties.ContainsKey("System.Devices.Aep.Bluetooth.Le.IsConnectable")) { isPresent = (bool)args.Properties["System.Devices.Aep.Bluetooth.Le.IsConnectable"]; } if (args.Properties.ContainsKey("System.Devices.Aep.IsConnected")) { isConnected = (bool)args.Properties["System.Devices.Aep.IsConnected"]; } for (int i = 0; i < Devices.Count; i++) { if (Devices[i].DeviceInfo.Id == args.Id) { found = true; Application.Current.Dispatcher.BeginInvoke(new Action(() => { Devices[i].DeviceInfo.Update(args); Devices[i].IsPresent = isPresent; Devices[i].IsConnected = isConnected; })); break; } } if(!found && (isPresent || isConnected)) { if (mAllDevices.ContainsKey(args.Id)) { mAllDevices[args.Id].Update(args); Application.Current.Dispatcher.BeginInvoke(new Action(() => { Devices.Add(new GDeviceInformation(mAllDevices[args.Id], isPresent, isConnected)); })); } } } private void MDeviceWatcher_Added(DeviceWatcher sender, DeviceInformation args) { bool isPresent = false; bool isConnected = false; if (args.Properties.ContainsKey("System.Devices.Aep.Bluetooth.Le.IsConnectable")) { isPresent = (bool)args.Properties["System.Devices.Aep.Bluetooth.Le.IsConnectable"]; } if (args.Properties.ContainsKey("System.Devices.Aep.IsConnected")) { isConnected = (bool)args.Properties["System.Devices.Aep.IsConnected"]; } if (args.Name != "" && args.Name.Contains("GoPro")) { bool found = false; if (!mAllDevices.ContainsKey(args.Id)) { mAllDevices.Add(args.Id, args); } for (int i = 0; i < Devices.Count; i++) { if (Devices[i].DeviceInfo.Id == args.Id) { found = true; Application.Current.Dispatcher.BeginInvoke(new Action(() => { Devices[i].DeviceInfo = args; Devices[i].IsPresent = isPresent; Devices[i].IsConnected = isConnected; })); break; } } if (!found && (isPresent || isConnected)) { Application.Current.Dispatcher.BeginInvoke(new Action(() => { Devices.Add(new GDeviceInformation(args, isPresent, isConnected)); })); } } } #endregion #region BLE Device Handlers private void MBLED_ConnectionStatusChanged(BluetoothLEDevice sender, object args) { if (sender.ConnectionStatus == BluetoothConnectionStatus.Connected) StatusOutput("CONNECTED"); else StatusOutput("DISCONNECTED"); } private void Custom_PairingRequested(DeviceInformationCustomPairing sender, DevicePairingRequestedEventArgs args) { StatusOutput("Pairing request..."); args.Accept(); } #endregion #region Gatt Characteristic Notification Handlers private readonly List mBufQ = new List(); private int mExpectedLengthQ = 0; private void MNotifyQueryResp_ValueChanged(GattCharacteristic sender, GattValueChangedEventArgs args) { var reader = DataReader.FromBuffer(args.CharacteristicValue); byte[] myBytes = new byte[reader.UnconsumedBufferLength]; reader.ReadBytes(myBytes); int newLength = ReadBytesIntoBuffer(myBytes, mBufQ); if (newLength > 0) mExpectedLengthQ = newLength; if (mExpectedLengthQ == mBufQ.Count) { if ((mBufQ[0] == 0x53 || mBufQ[0] == 0x93) && mBufQ[1] == 0) { //status messages for (int k = 0; k < mBufQ.Count;) { if (mBufQ[k] == 10) { Encoding = mBufQ[k + 2] > 0; } if (mBufQ[k] == 70) { BatteryLevel = mBufQ[k + 2]; } if(mBufQ[k] == 69) { WifiOn = mBufQ[k + 2] == 1; } k += 2 + mBufQ[k + 1]; } } else { //Unhandled Query Message } mBufQ.Clear(); mExpectedLengthQ = 0; } } private readonly List mBufSet = new List(); private int mExpectedLengthSet = 0; private void MNotifySettings_ValueChanged(GattCharacteristic sender, GattValueChangedEventArgs args) { var reader = DataReader.FromBuffer(args.CharacteristicValue); byte[] myBytes = new byte[reader.UnconsumedBufferLength]; reader.ReadBytes(myBytes); int newLength = ReadBytesIntoBuffer(myBytes, mBufSet); if (newLength > 0) mExpectedLengthSet = newLength; if (mExpectedLengthSet == mBufSet.Count) { /* if (mBufSet[0] == 0xXX) { } */ mBufSet.Clear(); } } private readonly List mBufCmd = new List(); private int mExpectedLengthCmd = 0; private void MNotifyCmds_ValueChanged(GattCharacteristic sender, GattValueChangedEventArgs args) { var reader = DataReader.FromBuffer(args.CharacteristicValue); byte[] myBytes = new byte[reader.UnconsumedBufferLength]; reader.ReadBytes(myBytes); int newLength = ReadBytesIntoBuffer(myBytes, mBufCmd); if (newLength > 0) mExpectedLengthCmd = newLength; if (mExpectedLengthCmd == mBufCmd.Count) { /* if (mBufCmd[0] == 0xXX) { } */ mBufCmd.Clear(); } } #endregion #region Private Helper Functions private async void SetThirdPartySource() { DataWriter mm = new DataWriter(); mm.WriteBytes(new byte[] { 0x01, 0x50 }); GattCommunicationStatus res = GattCommunicationStatus.Unreachable; if (mSendCmds != null) { res = await mSendCmds.WriteValueAsync(mm.DetachBuffer()); } if (res != GattCommunicationStatus.Success && mSendCmds != null) { StatusOutput("Failed to set command source: " + res.ToString()); } } private void StatusOutput(string status) { Application.Current.Dispatcher.BeginInvoke(new Action(() => { this.txtStatusBar.Text = status; })); } private int ReadBytesIntoBuffer(byte[] bytes, List mBuf) { int returnLength = -1; int startbyte = 1; int theseBytes = bytes.Length; if ((bytes[0] & 32) > 0) { //extended 13 bit header startbyte = 2; int len = ((bytes[0] & 0xF) << 8) | bytes[1]; returnLength = len; } else if ((bytes[0] & 64) > 0) { //extended 16 bit header startbyte = 3; int len = (bytes[1] << 8) | bytes[2]; returnLength = len; } else if ((bytes[0] & 128) > 0) { //its a continuation packet } else { //8 bit header returnLength = bytes[0]; } for (int k = startbyte; k < theseBytes; k++) mBuf.Add(bytes[k]); return returnLength; } private async void TogglefWifiAP(int onOff) { DataWriter mm = new DataWriter(); mm.WriteBytes(new byte[] { 0x03, 0x17, 0x01, (byte)onOff }); GattCommunicationStatus res = GattCommunicationStatus.Unreachable; if (onOff != 1 && onOff != 0) { res = GattCommunicationStatus.AccessDenied; } else if (mSendCmds != null) { res = await mSendCmds.WriteValueAsync(mm.DetachBuffer()); } if (res != GattCommunicationStatus.Success) { StatusOutput("Failed to turn on wifi: " + res.ToString()); } } private async void ToggleShutter(int onOff) { DataWriter mm = new DataWriter(); mm.WriteBytes(new byte[] { 3, 1, 1, (byte)onOff }); GattCommunicationStatus res = GattCommunicationStatus.Unreachable; if (onOff != 1 && onOff != 0) { res = GattCommunicationStatus.AccessDenied; } else if (mSendCmds != null) { res = await mSendCmds.WriteValueAsync(mm.DetachBuffer()); } if (res != GattCommunicationStatus.Success) { StatusOutput("Failed to send shutter: " + res.ToString()); } } #endregion } public class BrushBoolColorConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (!(bool)value) { return new SolidColorBrush(Color.FromRgb(100, 100, 100)); } return new SolidColorBrush(Color.FromRgb(255, 100, 100)); } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } } }