I'm attempting to communicate between windows and iOS using Bluetooth.
On the iOS side I'm publishing advertisements and on the windows side I'm "watching" for advertisements.
currently upon receiving the advertisements that my iOS device is publishing, these advertisements are... empty. No services, no ManufacturerData, DataSections list contains 1 DataSection with the Data of length 1 (?) local name is null, there's basically nothing in this advertisement packet of any use.
When I call BluetoothLEDevice.FromBluetoothAddressAsync(bluetooth address) I get an object, but with everything empty, no services, an element not found exception when I call GetGattService, DeviceInformation is null... nothing useful as far as I can see.
I don't know if there's something wrong with the advertisement I'm publishing on the iOS side, or if it's how I'm treating it on the Windows side.
Here is the iOS code:
import Foundation
import CoreBluetooth
class BluetoothController : NSObject, CBPeripheralManagerDelegate {
var delegate : BluetoothControllerDelegate?
var manager : CBPeripheralManager?
init(_ delegate : BluetoothControllerDelegate) {
super.init()
manager = CBPeripheralManager(delegate: self, queue: nil)
self.delegate = delegate
self.delegate?.statusUpdate("init")
}
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager){
delegate?.statusUpdate("Perepheral did update state : \(peripheral.state)")
if(peripheral.state == CBManagerState.poweredOn){
addServices()
}
}
func addServices(){
let service = CBMutableService(type: CBUUID.init(string: "e1fa36b4-9700-414b-a4e0-382a3e249e56"), primary: true)
// service.
manager?.add(service)
}
func peripheralManager(_ peripheral: CBPeripheralManager, didAdd service: CBService, error: Error?){
delegate?.statusUpdate("Service was added : \(service.uuid)")
manager?.startAdvertising(getAdvertisementData(service))
}
func peripheralManagerDidStartAdvertising(_ peripheral: CBPeripheralManager, error: Error?){
delegate?.statusUpdate("peripheralManagerDidStartAdvertising : \(peripheral.isAdvertising) : \(error.debugDescription)")
}
private func getAdvertisementData(_ service : CBService) -> Dictionary<String, Any>{
var data : Dictionary<String, Any> = Dictionary<String, Any>()
data["CBAdvertisementDataLocalNameKey"] = "e1fa36b4-9700-414b-a4e0-382a3e249e56"
data["CBAdvertisementDataServiceUUIDsKey"] = [service.uuid]
return data
}
}
And here is the Windows C# code (a simple console app for the moment) :
class Program
{
static void Main(string[] args)
{
BluetoothLEAdvertisementWatcher watcher = new BluetoothLEAdvertisementWatcher();
watcher.ScanningMode = BluetoothLEScanningMode.Active;
watcher.Received += Watcher_Received;
DeviceWatcher devWatcher = DeviceInformation.CreateWatcher();
devWatcher.Added += DevWatcher_Added;
devWatcher.Updated += DevWatcher_Updated;
watcher.Start();
devWatcher.Start();
while (true)
{
}
}
private static void DevWatcher_Updated(DeviceWatcher sender, DeviceInformationUpdate args)
{
Console.Write("\nUpdated\n");
}
private static void DevWatcher_Added(DeviceWatcher sender, DeviceInformation args)
{
// Console.Write("Added : "+args.Name+"\n");
}
static List<ulong> tried = new List<ulong>();
private static async void Watcher_Received(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementReceivedEventArgs args)
{
if (tried.Contains(args.BluetoothAddress)) {
return;
}
tried.Add(args.BluetoothAddress);
Console.Write("Device found =================================================");
Console.Write("\nDataSections: " + args.Advertisement.DataSections[0].Data);
//BluetoothLEDevice device =
BluetoothLEDevice device = await BluetoothLEDevice.FromBluetoothAddressAsync(args.BluetoothAddress);
try
{
Console.Write("\n GattServices: " + device.GetGattService(Guid.Parse("e1fa36b4-9700-414b-a4e0-382a3e249e56")).Uuid);
}catch(Exception e)
{
Console.Write(e.ToString());
}
if(device.DeviceInformation == null)
{
Console.Write("DeviceInformation null");
return;
}
if(device.DeviceInformation.Pairing == null)
{
Console.Write("Pairing null");
return;
}
var res = await device.DeviceInformation.Pairing.PairAsync(DevicePairingProtectionLevel.None);
Console.Write("Pair complete (?) ========================================"+res.Status);
}
}
Sorry it's a bit messy...
Thank you in advance for your help, I'm also trying it in the other direction (Windows advertising and iOS watching) it's not going well either, here's another question I've posted on this subject if interested: Listening to and accepting a BLE Connect request in windows
The advertisements are passive so you should change the code from active to passive such as:
watcher.ScanningMode = BluetoothLEScanningMode.Passive;
public sealed partial class MainPage : Page
{
Windows.Devices.Bluetooth.Advertisement.BluetoothLEAdvertisementWatcher advertisementWatcher;
public MainPage()
{
this.InitializeComponent();
advertisementWatcher = new Windows.Devices.Bluetooth.Advertisement.BluetoothLEAdvertisementWatcher();
advertisementWatcher.ScanningMode = Windows.Devices.Bluetooth.Advertisement.BluetoothLEScanningMode.Passive;
advertisementWatcher.Received += AdvertisementWatcher_Received;
advertisementWatcher.Start();
}
private void AdvertisementWatcher_Received(Windows.Devices.Bluetooth.Advertisement.BluetoothLEAdvertisementWatcher sender, Windows.Devices.Bluetooth.Advertisement.BluetoothLEAdvertisementReceivedEventArgs args)
{
System.Diagnostics.Debug.WriteLine("Address: {0:X}", args.BluetoothAddress);
}
}
With just the .Received callback you should start seeing data coming such as:
Address: A45E60F3E896 Address: 4BF5661A4EF2 Address: 549F476B5F37 Address: 4CB6DF3019A9 Address: 4BF5661A4EF2 Address: 106070EB42B1
Related
Sorry, if this is a stupid question but I don't find any useful information in the internet.
Has anyone ever tried to implement the observer pattern in C# using gRPC as communication?
If yes, please show me the link.
Many thanks in advance and best regards.
I have implemented a client convenience class wrapper to turn server streaming calls into regular events for a project I am working. Not sure if this is what you are after. Here is a simple gRPC server that just publishes the time as a string once every second.
syntax = "proto3";
package SimpleTime;
service SimpleTimeService
{
rpc MonitorTime(EmptyRequest) returns (stream TimeResponse);
}
message EmptyRequest{}
message TimeResponse
{
string time = 1;
}
The server implementation, which just loops once a second returning the string representation of the current time until canceled, is as follows
public override async Task MonitorTime(EmptyRequest request, IServerStreamWriter<TimeResponse> responseStream, ServerCallContext context)
{
try
{
while (!context.CancellationToken.IsCancellationRequested)
{
var response = new TimeResponse
{
Time = DateTime.Now.ToString()
};
await responseStream.WriteAsync(response);
await Task.Delay(1000);
}
}
catch (Exception)
{
Console.WriteLine("Exception on Server");
}
}
For the client, I created a class that contains the gRPC client and exposes the results of the server streaming MonitorTime call as a plain ole .net event.
public class SimpleTimeEventClient
{
private SimpleTime.SimpleTimeService.SimpleTimeServiceClient mClient = null;
private CancellationTokenSource mCancellationTokenSource = null;
private Task mMonitorTask = null;
public event EventHandler<string> OnTimeReceived;
public SimpleTimeEventClient()
{
Channel channel = new Channel("127.0.0.1:50051", ChannelCredentials.Insecure);
mClient = new SimpleTime.SimpleTimeService.SimpleTimeServiceClient(channel);
}
public void Startup()
{
mCancellationTokenSource = new CancellationTokenSource();
mMonitorTask = Task.Run(() => MonitorTimeServer(mCancellationTokenSource.Token));
}
public void Shutdown()
{
mCancellationTokenSource.Cancel();
mMonitorTask.Wait(10000);
}
private async Task MonitorTimeServer(CancellationToken token)
{
try
{
using (var call = mClient.MonitorTime(new SimpleTime.EmptyRequest()))
{
while(await call.ResponseStream.MoveNext(token))
{
var timeResult = call.ResponseStream.Current;
OnTimeReceived?.Invoke(this, timeResult.Time);
}
}
}
catch(Exception e)
{
Console.WriteLine($"Exception encountered in MonitorTimeServer:{e.Message}");
}
}
}
Now create the client and subscribe to the event.
static void Main(string[] args)
{
SimpleTimeEventClient client = new SimpleTimeEventClient();
client.OnTimeReceived += OnTimeReceivedEventHandler;
client.Startup();
Console.WriteLine("Press any key to exit");
Console.ReadKey();
client.Shutdown();
}
private static void OnTimeReceivedEventHandler(object sender, string e)
{
Console.WriteLine($"Time: {e}");
}
Which when run produces
I have left out a lot of error checking and such to make the example smaller. One thing I have done is for gRPC interfaces with many server streaming calls that may or may not be of interest to call clients, is to implement the event accessor (add,remove) to only call the server side streaming method if there is a client that has subscribed to the wrapped event. Hope this is helpful
I'm working on a really simple app running as RFC Server. I installed SAP Netweaver 7.5 trial version. Here is the code to start the server :
private void button2_Click(object sender, EventArgs e)
{
RfcDestinationManager.RegisterDestinationConfiguration(new RfcDestinationConfig());
RfcServerManager.RegisterServerConfiguration(new RfcServerConfig());
Type[] handlers = new Type[1] { typeof(StfcConnectionStaticImpl) };
RfcServer server = RfcServerManager.GetServer("PRD_REG_SERVER", handlers);
server.Start();
}
Then the handling class :
class StfcConnectionStaticImpl
{
// The annotation binds the function (name) to its implementation
[RfcServerFunction(Name = "STFC_CONNECTION")]
public static void StfcConnection(RfcServerContext serverContext, IRfcFunction function)
{
Console.WriteLine("System Attributes: " + serverContext.SystemAttributes.ToString());
function.SetValue("ECHOTEXT", function.GetString("REQUTEXT"));
function.SetValue("RESPTEXT", "NCO3: Hello world.");
}
}
next the Rfc destination config class :
public class RfcDestinationConfig : IDestinationConfiguration
{
public event RfcDestinationManager.ConfigurationChangeHandler ConfigurationChanged;
public bool ChangeEventsSupported()
{
return false;
}
public RfcConfigParameters GetParameters(string destinationName)
{
if ("PRD_000".Equals(destinationName))
{
RfcConfigParameters parms = new RfcConfigParameters();
parms.Add(RfcConfigParameters.AppServerHost, "172.18.3.22");
parms.Add(RfcConfigParameters.SystemNumber, "00");
parms.Add(RfcConfigParameters.User, "developer");
parms.Add(RfcConfigParameters.Password, "Appl1ance");
parms.Add(RfcConfigParameters.Client, "001");
parms.Add(RfcConfigParameters.Language, "EN");
parms.Add(RfcConfigParameters.PoolSize, "5");
parms.Add(RfcConfigParameters.MaxPoolSize, "10");
parms.Add(RfcConfigParameters.IdleTimeout, "600");
return parms;
}
else return null;
}
}
and last the Rfc server class :
public class RfcServerConfig : IServerConfiguration
{
public event RfcServerManager.ConfigurationChangeHandler ConfigurationChanged;
public bool ChangeEventsSupported()
{
return false;
}
public RfcConfigParameters GetParameters(string serverName)
{
if ("PRD_REG_SERVER".Equals(serverName))
{
RfcConfigParameters parms = new RfcConfigParameters();
parms.Add(RfcConfigParameters.GatewayHost, "172.18.3.22");
parms.Add(RfcConfigParameters.GatewayService, "sapgw00");
parms.Add(RfcConfigParameters.ProgramID, "DOT_NET_SERVER");
parms.Add(RfcConfigParameters.RepositoryDestination, "PRD_000");
parms.Add(RfcConfigParameters.RegistrationCount, "5");
return parms;
}
else return null;
}
}
The server start perfectly and my registration in the Gateway is ok.
SAP GATEWAY (SMGW)
The problem coming from Logged-on Client because I only see the external client and not the registered program :
Logged-on Client
My question is why I don't get the Program as Registered Server ?
Is there some SAP configuration to do to allow Program registration ?
If someone can't help me to solve this out, it will be great.
Thank you,
Ronan
How do I write a method that resolves the hostname which is entered as a parameter that returns an IP Address?
I searched for a way to do this and both of the sites I found have similar solutions
WP7 Mango - How to get an IP address for a given hostname
https://social.msdn.microsoft.com/Forums/en-US/5c07b344-be5b-4358-beb1-abea581ca2bb/how-to-resolve-a-hostname-to-an-ip-address-in-windows-phone-8?forum=wpdevelop
public void DnsLookup(string hostname)
{
var endpoint = new DnsEndPoint(hostname, 0);
DeviceNetworkInformation.ResolveHostNameAsync(endpoint, OnNameResolved, null);
}
private void OnNameResolved(NameResolutionResult result)
{
IPEndPoint[] endpoints = result.IPEndPoints;
// Do something with your endpoints
}
I am having trouble using the soluitons.
I can't change the return type of the OnNameResolved and the ResolveHostNameAsync requires a NameResolutionCallback.
So how do I make a method that returns the IP Adress?
Given the limited capabilities by the .NET Framework here, you have to write an asynchronous approach here:
public static class NetworkHelper
{
public event EventHandler<DnsLookupCompletedEventArgs> DnsLookupCompleted;
public void DnsLookupAsync(string hostname)
{
var endpoint = new DnsEndPoint(hostname, 0);
DeviceNetworkInformation.ResolveHostNameAsync(endpoint, OnNameResolved, null);
}
private void OnNameResolved(NameResolutionResult result)
{
IPEndPoint[] endpoints = result.IPEndPoints;
var args = new DnsLookupCompletedEventArgs(endpoints);
if (DnsLookupCompleted != null)
DnsLookupCompleted(this, args);
}
}
Whereas DnsLookupCompletedEventArgs would look like this, so you can handle the endpoints later on:
public class DnsLookupCompletedEventArgs : EventArgs
{
public IPEndPoint[] Endpoints { get; private set; }
public DnsLookupCompletedEventArgs(IPEndPoint[] endpoints)
{
Endpoints = endpoints;
}
}
So basically I am trying to be able to show custom messages to the Burn UI during a custom action. (In this case show progress from DISM running in the background. This is the code I tried:
public static class CAExtensions
{
public static void SendMessage(this Session session, string message)
{
var record = new Record();
record.SetString(0, message);
session.Message(InstallMessage.Info, record);
}
}
This in my custom action I do this:
session.SendMessage("Message goes here");
I subscribe to the ExecuteMsiMessage event:
model.BootstrapperApplication.ExecuteMsiMessage += HandleMessage;
private void HandleMessage(object sender, ExecuteMsiMessageEventArgs e)
{
Installer.Dispatcher.Invoke((Action) (() =>
{
var rawMessage = string.Empty;
var app = model.GetAppData(e.PackageId);
if (app != null)
{
rawMessage = app.Item1.DisplayName + ": ";
}
rawMessage += e.Message;
InstallMessage = rawMessage;
}));
}
InstallMessage is bound to a Label in the UI. This shows all the standard messages, but not the ones I send in my custom actions.
Any idea what I am doing wrong?
The main issue is that Info level messages don't get passed to the ExecuteMsiMessage event. From my testing, I found that Warning was the only level that reliable were passed through.
I implemented this by adding an additional flag to the message so I could tell which messages were mine and which were real warning messages (which I didn't want to show in the UI). However, I don't show that here for simplicity:
In the CustomAction:
public virtual void SendMessage(string message, string[] data)
{
var fields = new List<object>(data);
using (var record = new Record(fields.ToArray()) { FormatString = message })
{
_session.Message(InstallMessage.Warning, record);
}
}
In the Bootstrapper:
private void EventProviderOnExecuteMsiMessage(object sender, ExecuteMsiMessageEventArgs executeMsiMessageEventArgs)
{
if (executeMsiMessageEventArgs.MessageType == InstallMessage.Warning)
{
var theActualMessage = executeMsiMessageEventArgs.Data.ToList();
//do something with theActualMessage here...
}
}
I've been driving myself nuts trying to resolve this issue so really hoping someone has some insight.
I have a console application which runs/hosts my signalR server.
I have already successfully connected to it using a web(javascript) client and a windows forms client with no trouble at all.
BUT for the life of me I cannot get a silverlight client to connect to it. Initially I was getting a
'System.Security.SecurityException' occurred in Microsoft.Threading.Tasks error
on
await Connection.Start();
I managed to fix that by force sending the clientaccesspolicy file using code i found on a random thread.
THREAD
However the connection still never establishes. The status goes thru connecting, disconnected, connection closed.
I am at my wits end as to why this won't work. Any input is appreciated. Code below.
MainPage.xaml.cs
public partial class MainPage : UserControl
{
private SignalRClient client;
public MainPage()
{
InitializeComponent();
dataGrid1.ItemsSource = new ItemsCollection();
client = new SignalRClient();
client.RunAsync();
Debug.WriteLine("Init Done");
}
}
-
SignalRClient.cs
public class SignalRClient
{
private HubConnection Connection { get; set; }
private IHubProxy HubProxy { get; set; }
const string url = "http://localhost:8080/";
public SignalRClient()
{
}
public async void RunAsync()
{
Connection = new HubConnection(url, useDefaultUrl: true);
Connection.Closed += Connection_Closed;
Connection.StateChanged += ConnectionDidSomething;
HubProxy = Connection.CreateHubProxy("TickerHub");
HubProxy.On<string>("receiveAllData", data => Debug.WriteLine("RECDATA={0}", data));
try
{
await Connection.Start();
}
catch (HttpClientException e)
{
Debug.WriteLine("Unable to connect to server.1 {0}", e.Message);
return;
}
catch (HttpRequestException e)
{
Debug.WriteLine("Unable to connect to server.2 {0}", e.Message);
return;
}
}
-
Server
class Program
{
static void Main(string[] args)
{
string url = "http://localhost:8080/";
using (WebApp.Start(url))
{
Console.WriteLine("SignalR server running on {0}", url);
Console.ReadLine();
}
Console.ReadLine();
}
}
class Startup
{
public void Configuration(IAppBuilder app)
{
Console.WriteLine("Configuration");
//Tried this approach too
/*app.Map("/signalr", map =>
{
map.UseCors(CorsOptions.AllowAll);
var hubConfiguration = new HubConfiguration
{
EnableJSONP = true
};
map.RunSignalR(hubConfiguration);
});*/
app.UseCors(CorsOptions.AllowAll);
app.MapSignalR<ClientAccessPolicyConnection>("/clientaccesspolicy.xml");
}
}
-
TickerHub.cs
public class TickerHub : Hub
{
public override Task OnConnected()
{
string connectionID = Context.ConnectionId;
Console.WriteLine("New Connection:" + connectionID);
InitNewClient(connectionID);
return base.OnConnected();
}
//send all data to newly connected client
public void InitNewClient(string connectionID)
{
}
//client requested all data
public void GetAllData()
{
Console.WriteLine("Get Data Triggered");
Clients.All.receiveAllData("TESTING123");
}
}
I figured it out! Hopefully this helps someone in the future.
Its quite simple. This is what you need to have in your startup class configuration method.
Below that is the code required to send the clientaccesspolicy.xml.
class Startup
{
public void Configuration(IAppBuilder app)
{
// Branch the pipeline here for requests that start with "/signalr"
app.Map("/signalr", map =>
{
// Setup the CORS middleware to run before SignalR.
// By default this will allow all origins. You can
// configure the set of origins and/or http verbs by
// providing a cors options with a different policy.
map.UseCors(CorsOptions.AllowAll);
var hubConfiguration = new HubConfiguration
{
// You can enable JSONP by uncommenting line below.
// JSONP requests are insecure but some older browsers (and some
// versions of IE) require JSONP to work cross domain
EnableJSONP = true
};
// Run the SignalR pipeline. We're not using MapSignalR
// since this branch already runs under the "/signalr"
// path.
map.RunSignalR(hubConfiguration);
});
app.UseCors(CorsOptions.AllowAll);
app.MapSignalR<ClientAccessPolicyConnection>("/clientaccesspolicy.xml");
}
}
-
public class ClientAccessPolicyConnection : PersistentConnection
{
public override Task ProcessRequest(Microsoft.AspNet.SignalR.Hosting.HostContext context)
{
string[] urlArray = context.Request.Url.ToString().Split('/');
string path = urlArray[urlArray.Length - 1];
if (path.Equals("clientaccesspolicy.xml", StringComparison.InvariantCultureIgnoreCase))
{
//Convert policy to byteArray
var array = Encoding.UTF8.GetBytes(ClientAccessPolicy);
var segment = new ArraySegment<byte>(array);
//Write response
context.Response.ContentType = "text/xml";
context.Response.Write(segment);
//Return empty task to escape from SignalR's default Connection/Transport checks.
return EmptyTask;
}
return EmptyTask;
}
private static readonly Task EmptyTask = MakeTask<object>(null);
public static Task<T> MakeTask<T>(T value)
{
var tcs = new TaskCompletionSource<T>();
tcs.SetResult(value);
return tcs.Task;
}
public static readonly string ClientAccessPolicy =
"<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+ "<access-policy>"
+ "<cross-domain-access>"
+ "<policy>"
+ "<allow-from http-request-headers=\"*\">"
+ "<domain uri=\"*\"/>"
+ "</allow-from>"
+ "<grant-to>"
+ "<resource path=\"/\" include-subpaths=\"true\"/>"
+ "</grant-to>"
+ "</policy>"
+ "</cross-domain-access>"
+ "</access-policy>";
}