I've been experimenting with SignalR today and It's really neat. Basically what I wanted to achieve is the following:
As soon as a device connects it should send a message to the first one. If there are more devices than 1 connected I would like to send two messages. One to all except the last connected client. And one message to only the last connected client.
The code I've been using works perfect when I place it in a custom API controller and basically call the action, but that's not what I want.
I would like to send the messages as soon as a device connects within OnConnected without any user interaction, but when I place my code inside the OnConnected override it stops working. It doesn't send to the specific clients anymore (first connected and last connected).
I hope someone is able to help me out with this, because I've been banging my head for a few hours now.
public override System.Threading.Tasks.Task OnConnected()
{
UserHandler.ConnectedIds.Add(Context.ConnectionId, UserHandler.ConnectedIds.Count + 1);
int amountOfConnections = UserHandler.ConnectedIds.Count;
var lastConnection = UserHandler.ConnectedIds.OrderBy(x => x.Value).LastOrDefault();
var allExceptLast = UserHandler.ConnectedIds.Take(amountOfConnections - 1).Select(x => x.Key).ToList();
if (amountOfConnections == 1)
{
Clients.Client(UserHandler.ConnectedIds.First().Key).hello("Send to only(also first) one");
}
else
{
Clients.Clients(allExceptLast).hello("Send to everyone except last");
Clients.Client(lastConnection.Key).hello("Send to only the last one");
}
return base.OnConnected();
}
Unless I miss something from your question, the solution looks pretty simple to me, you just need to switch to using
Clients.Caller.hello("Send to only the last one");
instead of trying to understand yourself who's the last connected id. Same for the other ones, you can use:
Clients.Others.hello("Send to everyone except last");
You do not need all the logic you setup, those 2 lines do what you need, and they work inside OnConnected.
Thanks for all the help (upvoted you guys). Actually found the problem.. it was inside my client. I first subscribed to the 'hello' function and after that I started the HubConnection. As soon as I changed this order everything worked fine.
It worked with the following client code:
private async Task ConnectToSignalR()
{
var hubConnection = new HubConnection("url");
hubConnection.Headers["x-zumo-application"] = "clientapikey";
IHubProxy proxy = hubConnection.CreateHubProxy("ChatHub");
proxy.On<string>("hello", async (msg) =>
{
Console.WriteLine(msg);
});
await hubConnection.Start();
}
Since you haven't established a connection yet, trying to call your client .hello() function within OnConnected is not possible at this point. However, we can define a server hub method and immediately call that upon our connection .done callback. Then, in our new server method we can reallocate the logic you currently have in OnConnected.
This will change our setup quite a bit and introduce some additional steps, but observe the following example...
// WhateverHub
public override Task OnConnected()
{
return base.OnConnected()
}
public void AfterConnected()
{
// if(stuff) -- whatever if/else first user/last user logic
// {
Clients.Caller.hello("message")
// }
}
var proxy= $.connection.whateverHub;
proxy.client.hello = function(message) {
// last step in event chain
}
$.connection.hub.start().done(function () {
proxy.server.afterConnected() // call AfterConnected() on hub
});
So the basic idea here is to first
Connect => .done(function() { ... });
Call server.afterConnected()
Execute logic within that method
If we're satisfied with conditions call our .hello() client function
Note - this implementation is for a JavaScript client - but the same idea can be translated to a .net client. This is mostly an architectural issue.
well... you are returning a task... so i think that may be the issue...
you should first execute your code and then return the task... or put a ContinueWith... like...
public override Task OnConnected()
{
Task task = new Task(() =>
{
UserHandler.ConnectedIds.Add(Context.ConnectionId, UserHandler.ConnectedIds.Count + 1);
int amountOfConnections = UserHandler.ConnectedIds.Count;
var lastConnection = UserHandler.ConnectedIds.OrderBy(x => x.Value).LastOrDefault();
var allExceptLast = UserHandler.ConnectedIds.Take(amountOfConnections - 1).Select(x => x.Key).ToList();
if (amountOfConnections == 1)
{
Clients.Client(UserHandler.ConnectedIds.First().Key).hello("Send to only(also first) one");
}
else
{
Clients.Clients(allExceptLast).hello("Send to everyone except last");
Clients.Client(lastConnection.Key).hello("Send to only the last one");
}
});
task.ContinueWith(base.OnConnected());
return task;
}
I haven't tested that... its just a guess..
Related
I am trying to receive the JSON value from the Realtime Database of Firebase using Unity.
I do the following:
FirebaseDatabase.DefaultInstance
.GetReference("Leaders").OrderByChild("score").GetValueAsync().ContinueWith(task =>
{
if (task.IsFaulted)
{
Debug.LogError("error in reading LeaderBoard");
return;
}
else if (task.IsCompleted)
{
Debug.Log("Received values for Leaders.");
string JsonLeaderBaord = task.Result.GetRawJsonValue();
callback(JsonLeaderBaord);
}
}
});
Trying to Read the CallBack :
private string GetStoredHighScores()
{
private string JsonLeaderBoardResult;
DataBaseModel.Instance.RetriveLeaderBoard(result =>
{
JsonLeaderBoardResult = result; //gets the data
});
return JsonLeaderBoardResult; //returns Null since it doesn't wait for the result to come.
}
Question is how do i wait for the callback to return the value and afterwards return the value of the JsonLeaderBoardResult.
return JsonLeaderBoardResult; //returns Null since it doesn't wait
for the result to come.
The RetriveLeaderBoard function doesn't return immediately. You can either use coroutine to wait for it or return the JsonLeaderBoardResult result via Action. Using Action make more sense in your case.
Change the string return type to void then return the result through Action:
private void GetStoredHighScores(Action<string> callback)
{
string JsonLeaderBoardResult;
DataBaseModel.Instance.RetriveLeaderBoard(result =>
{
JsonLeaderBoardResult = result; //gets the data
if (callback != null)
callback(JsonLeaderBoardResult);
});
}
Usage:
GetStoredHighScores((result) =>
{
Debug.Log(result);
});
EDIT:
That is great, but still need to do some stuff after getting the
result in `GetStoredHighScores' outside the Action, otherwise i can
get an error like: get_transform can only be called from the main
thread.
You get this error because RetriveLeaderBoard is running from on another Thread. Grab UnityThread from this post then do the callback on the main Thread with UnityThread.executeInUpdate.
Your new code:
void Awake()
{
UnityThread.initUnityThread();
}
private void GetStoredHighScores(Action<string> callback)
{
string JsonLeaderBoardResult;
DataBaseModel.Instance.RetriveLeaderBoard(result =>
{
JsonLeaderBoardResult = result; //gets the data
UnityThread.executeInUpdate(() =>
{
if (callback != null)
callback(JsonLeaderBoardResult);
});
});
}
You're seeing a classical confusion with asynchronous APIs. Since loading data from Firebase may take some time, it happens asynchronously (on a separate thread). This allows your main code to continue, while the Firebase client is waiting for a response from the server. When the data is available, the Firebase client calls your callback method with that data, so that you can process it.
It's easiest to see what this does in your code by placing a few logging statements:
Debug.Log("Before starting to load data");
FirebaseDatabase.DefaultInstance
.GetReference("Leaders").OrderByChild("score").GetValueAsync().ContinueWith(task => {
Debug.Log("Got data");
}
});
Debug.Log("After starting to load data");
When you run this code, it prints:
Before starting to load data
After starting to load data
Got data
This is probably not the order in which you expected the output. Due to the asynchronous nature of the call to Firebase, the second log line gets printed last. That explains precisely why you're seeing an empty array when you return it: by that time the data hasn't been loaded from Firebase yet, and your ContinueWith hasn't executed yet.
The solution to this is always the same: since you can't return data that hasn't loaded yet, you have to implement a callback function. The code you shared does that twice already: once for the ContinueWith of Firebase itself and one in RetriveLeaderBoard.
Any code that needs to have the up to date leaderboard, will essentially have to call RetriveLeaderBoard and do any work with the leaderboard data in its callback. For example, if you want to print the leaderboard:
DataBaseModel.Instance.RetriveLeaderBoard(result => {
Debug.Log(result);
});
Let me start by saying this all works perfectly at the moment except for one thing - the notification update for the progress bar goes to all clients (as you would expect given that the code example I used sends to ...Clients.All).
All I want to do is send the notification back to the client that initiated the current call to the hub. That's it, nothing else. There is no concept of "logging in" on this website so there's no user identity stuff to work with.
My method is:
public void NotifyUpdates(decimal val)
{
var hubContext = GlobalHost.ConnectionManager.GetHubContext<EventsForceHub>();
if (hubContext != null)
{
//"updateProgress" is javascript event trigger name
await hubContext.Clients.All.updateProgress(val);
}
}
So back in my view I subscribe to "updateProgress" and it works fine - the progress bar updates as desired. But if another client happens to be connected to the hub, when one runs the async task and causes the NotifyUpdates method to run, then ALL connected clients see the taskbar update, which is a bit confusing for them!
In debug, if I inspect hubContext at runtime I can see a Clients property and that has a Connection property which has an Identity property with a unique GUID. Perfect! Just what I want to use. But... I cannot access it! If I try:
var currentConnection = hubContext.Clients.Connection;
...then I simply get a
"does not contain a definition for 'Connection'"
error, which I simply don't understand.
I've tried accessing Context.ConnectionId from the method too, but Context is null at that point so I'm a bit confused. The server method that uses NotifyUpdates to send information back to the client is called via a normal asp.net button, not via AJAX.
Clarification on structure
I think there is a degree of confusion here. It's a very simply webpage with an asp.net button control on it. The eventhandler for that button invokes an async method to return data from the server via a service call/repository.
Inside the async method, it has to process each returned data line and make three or four remote web api calls. On each loop I make a call back to the NotifyUpdates SignalR method shown above with a percentage complete number so this can update back to the client via an eventhandler for the method name specified (updateProgress as shown above). There could be dozens of data lines and each data line requires several Web API calls to a remote server to add data. This can take several seconds per iteration, hence me sending back the "update progress" to the client via that updateProgress method call.
NEW ANSWER
Based on your comments I made the following little test:
It will allow clients to connect to the hub with a clientName, and every client is listening to updates send to them. We will have a group defined for them to be able to notify them from the server side.
I made a dummy progress simulator class to throw some update values to the users.
The code:
Hub class:
public class EventsForceHub : Hub {
public static IHubContext hubContext = GlobalHost.ConnectionManager.GetHubContext<EventsForceHub>();
// allow users to join to hub and get s dedicated group/channel for them, so we can update them
public async Task JoinGroup(string clientName) {
string clientID = Context.ConnectionId;
ClientInfo.clients.Add(clientID, new MyAppClient(clientID, clientName));
await Groups.Add(clientID, clientName);
// this is just mockup to simulate progress events (this uis not needed in real application)
MockupProgressGenerator.DoJob(clientName, 0);
}
public static void NotifyUpdates(decimal val, string clientName) {
// update the given client on his group/channel
hubContext.Clients.Group(clientName).updateProgress(val);
}
}
Some little helper classes:
// client "storage"
public static class ClientInfo {
public static Dictionary<string, MyAppClient> clients = new Dictionary<string, MyAppClient>();
// .. further data and methods
}
// client type
public class MyAppClient {
public string Id { get; set; }
public string Name { get; set; }
// .. further prooerties and methods
public MyAppClient(string id, string name) {
Id = id;
Name = name;
}
}
// this a completely made up and dumb class to simulate slow process and give some simple progress events
public static class MockupProgressGenerator {
public static void DoJob(string clientName, int status) {
if (status < 100) {
Task.Delay(1000).ContinueWith(a =>
{
EventsForceHub.NotifyUpdates(status += 20, clientName);
DoJob(clientName, status);
});
}
}
}
Let's see two simple clients in JS:
$(function () {
var eventsForceHub = $.connection.eventsForceHub;
$.connection.hub.start().done(function () {
$('body').append("Joining with Name: Jerry");
eventsForceHub.server.joinGroup("Jerry");
});
eventsForceHub.client.updateProgress = function (val) {
// message received
$('body').append('<br>').append("New Progress message: " + val);
};
});
For simplicity, same code, with different params, I even put this in two different html pages and stated execution in slightly different timing.
$(function () {
var eventsForceHub = $.connection.eventsForceHub;
$.connection.hub.start().done(function () {
$('body').append("Joining with Name: Tom");
eventsForceHub.server.joinGroup("Tom");
});
eventsForceHub.client.updateProgress = function (val) {
// message received
$('body').append('<br>').append("New Progress message: " + val);
};
});
See it in action:
FIRST ANSWER
I made a small web application to verify your claim. You may create the following to be able to isolate the issue from other possible problems.
I created an empty Web Application and included SignalR.
This is the hub class:
public class EventsForceHub : Hub {
public void NotifyUpdates(decimal val) {
var hubContext = GlobalHost.ConnectionManager.GetHubContext<EventsForceHub>();
if (Context != null) {
string clientID = Context.ConnectionId; // <-- on debug: Ok has conn id.
object caller = Clients.Caller; // <-- on debug: Ok, not null
object caller2 = Clients.Client(clientID); // <-- on debug: Ok, not null
Clients.Caller.updateProgress(val); // Message sent
Clients.Client(clientID).updateProgress(val); // Message sent
}
if (hubContext != null) {
//"updateProgress" is javascript event trigger name
hubContext.Clients.All.updateProgress(val); // Message sent
}
}
}
This is the web page:
<script src="Scripts/jquery-1.10.2.min.js"></script>
<script src="Scripts/jquery.signalR-2.2.2.min.js"></script>
<script src="signalr/hubs"></script>
<script type="text/javascript">
$(function () {
var eventsForceHub = $.connection.eventsForceHub;
$.connection.hub.start().done(function () {
// send mock message on start
console.log("Sending mock message: " + 42);
eventsForceHub.server.notifyUpdates(42);
});
eventsForceHub.client.updateProgress = function (val) {
// message received
console.log("New Progress message: " + val);
};
});
</script>
Try to build an application as little as this to isolate the issue. I have not had any of the issues you mentioned.
For the sake of simplicity and using the debugger I took away the await and async.
Actually, SignalR will take care of that for you. You will get a new instance of your Hub class at every request, no need to force asynchrony into the methods.
Also, GlobalHost is defined as static which should be shared between instances of your Hub class. Using in an instance method does not seem like a very good idea. I think you want to use the Context and the Clients objects instead. However, while debugging we can verify that using GlobalHost also works.
Some debugger screenshots showing runtime values of callerId, Clients.Caller and Clients.Client(clientID):
Understanding SignalR better will help you a lot in achieving your goal.
Happy debugging!
If you want to send the notification back to the client
you should not call
hubContext.Clients.All.updateProgress(val);
instead try
accessing the current user's ConnectionId and use Clients.Client
hubContext.Clients.Client(Context.ConnectionId);
I just started in coding c#, and I am making a Bot for my discord server. I recently added a command !meme, where it would randomly pull from around 100 different pictures to send in the chat. The second I implemented this command, everyone just totally abused it, and it was at the point where there was like 15 pictures popping up in chat every 2 seconds because of the !meme spam.
I want to be able to add a 3 second delay before the command itself can be used again. I tried using Thread.Sleep(3000); and that did not work. Same for
await Task.Delay(3000); which I used inside of the async.
private void RegisterMemeCommand()
{
commands.CreateCommand("Meme")
.Do(async (e) =>
{
int RandomMeme = rng.Next(MemeList.Length);
string memetopost = MemeList[RandomMeme];
await e.Channel.SendFile(memetopost);
});
}
As I don't have complete code, from what you have up in the question, try this:
var lastSentOn=DateTime.MinValue;
commands.CreateCommand("Meme")
.Do(async (e) =>
{
if((DateTime.Now - lastSentOn).TotalSeconds > 3)
{
int RandomMeme = rng.Next(MemeList.Length);
string memetopost = MemeList[RandomMeme];
lastSentOn = DateTime.Now;
await e.Channel.SendFile(memetopost);
}
});
You may need to associate lastSentOn with some user id etc. A dictionary will be helpful in this scenario.
What is my issue?
When I say "Blink green" the drone will do exactly what I want. That works great.
When I say "Blink green" again, it will execute the code, but the drone doesn't give a response. The drone gives only a response in the first time. So every time I have to restart the program to make it work. Annoying...
What happens the second time?
It will do exactly the same, it's executing the code as well, but the drone doesn't give a response.
Here's the code:
private static void _speechRecognitionEngine_SpeechRecognized(object sender, SpeechRecognizedEventArgs e)
{
switch (e.Result.Text)
{
case "Blink green":
Task.Run((Action)blinkGreen);
break;
}
}
// ONLY WORKS THE FIRST TIME
// WHEN I SAY "BLINK GREEN" AGAIN, IT'S WILL EXECUTE THE
// CODE BUT THE DRONE DOESN'T GIVE A RESPONSE. ONLY THE
// FIRST TIME (SO I HAVE TO RESTART THE PROGRAM)
public static async void blinkGreen()
{
var func = Edge.Func(#"
var arDrone = require('ar-drone');
var client = arDrone.createClient();
return function(data, callback){
client.animateLeds('blinkGreen',5,2);
callback(null, data);
}
");
Console.WriteLine(await func("BLINK EXECUTED!"));
}
I think something is going on with:
var arDrone = require('ar-drone');
var client = arDrone.createClient();
I am allowed to create one client I guess. I have to create and reuse the client instance, so I can use the same client which would solve the problem. But I have no idea how to do that with the Edge.js module...
I hope someone could help me out with this problem.
Thanks,
Jamie
#Jay >
I am a little bit further, let's see what I did.
I have created one boolean to check if there's already one client and one variable to save the client.
static bool clientCreated = false;
static object currentClient;
In the beginning of the program I've created a check to get an existing or new client
// First check to get an existing or new client
currentClient = GetOrCreateClient();
I've created a method to return an existing or new client
public static object GetOrCreateClient()
{
// If there is already a client
if (clientCreated)
{
Console.WriteLine("There's already a client!");
}
// If there is no client
else
{
// Set boolean
clientCreated = true;
// Create a client
var newClient = Edge.Func(#"
var arDrone = require('ar-drone');
var client = arDrone.createClient();
return function(data, callback){
callback(null, data);
}
");
Console.WriteLine("Client created!");
// Return new client
return newClient;
}
// Return current client
return currentClient;
}
And the functionality method to do an action (animation of the drone) which should use the same client
public static async void blinkGreen()
{
// Get an existing or new client
var client = GetOrCreateClient();
// Execute the functionality
var func = Edge.Func(#"
var arDrone = require('ar-drone'); // ACTUALLY THIS ONE IS ALREADY DEFINED IN THE METHOD GetOrCreateClient()
var client = I WANT HERE THE CLIENT, BUT HOW?
return function(data, callback){
client.animateLeds('blinkGreen',5,2);
callback(null, data);
}
");
Console.WriteLine(await func("BLINK EXECUTED!"));
}
Now I am stuck on a few things, because is that the right way to save the client globally in an object? Because the method GetOrCreateClient() returns the following object:
`System.Func`2[System.Object,System.Threading.Tasks.Task`1[System.Object]]`
So my next question would be, how can I use the client object into the functionality?
The var client = GetOrCreateClient();
variable should placed into:
var func = Edge.Func(#"
var arDrone = require('ar-drone'); // ACTUALLY THIS ONE IS ALREADY DEFINED IN THE METHOD GetOrCreateClient()
var client = I WANT HERE THE CLIENT, BUT HOW?
The other way to disconnect the client would be solve the problem as well, but cost more overhead and latency indeed.
I honestly haven't used Edge.js but I can see your issue from the example your posting.
Why don't you break apart the Edge functionality into separate functions on the class.
E.g. define the edge functionality for creating the client and returning it, if the client is already created then return the existing client. Call this function GetOrCreateClient.
Then define the calls which need the client to take a client object so if you have multiple clients for multiple drones you can make each blink separately.
Finally interpret the voice commands and dispatch as necessary to the appropriate client for the drone using the GetOrCreateClient function to get the client for the correct drone.
I can write some code to help you with this but I would like to see how you get there from what I explained first rather than just writing it for you.
The other way would be to disconnect the client after receiving the response but this will cost more overhead and latency when issuing commands.
UPDATE
To share data between function calls I think you need to export the variable or function you want to be shared.
There may be an export function or a window or GLOBAL object which can be augmented with the variable you need to export.
As far as your current approach, you seen to want to define the logic in C#, since the logic is occurring in Javascript you can actually define GetOrCreateClient in edge.js function, pretty much like you have in C# but in Javascript.
'function GetOrCreateClient(){ return !this.client ? this.client=require ('ardone') : this.client; }'
This can be defined right before you handle the call to blinkGreen.
Then make client = GetOrCreateClient in the javascript, The first time it will create the client and the second time it will return the existing client.
Then make the call to blinkGreen as you need to do.
Finally, C# already has an Ardone library, unless you need Node it would make sense to me to keep Node out of the equation and just use c# alone for this.
I am not familiar with this library, but most NodeJS requires are cached, so only the portion inside the function will be run more than once. Try moving your animateLeds call inside the returned function, like so:
public static void blinkGreen()
{
var func = Edge.Func(#"
var arDrone = require('ar-drone');
var client = arDrone.createClient();
return function(data, callback){
client.animateLeds('blinkGreen',5,2);
callback(null, data);
}
");
}
I have an HTTP server written in C# based off the HttpListenerContext class. The server is for processing binary log files and converting them to text, and can take quite a long time to do the conversion. I would like to indicate progress back to the user, but I am unsure on the best way to do this. On the server side, in handling my HTTP request, I essentially have this function:
public async Task HandleRequest()
{
try
{
await ProcessRequest();
}
catch (HttpListenerException)
{
// Something happened to the http connection, don't try to send anything
}
catch (Exception e)
{
SendFailureResponse(500);
}
}
Currently, ProcessRequest() sends the HTML response when finished, but I would like to essentially add the IProgress interface to the function and somehow indicate that progress back to the Web client. What is the best way to do this?
One way of doing it would be to store progress on server side and periodically pull the information from client.
However, if you want the server to notify the client ( push ), then you will need to implement some kind of bi-directional communication between the server and client (I am currently using ASP.NET Web API and SignalR to achieve this at work).
Here is what I got I'll try to explain and I hope you notice its not FULL FULL complete, you'll have to understand the logic behind this and accept or not as a plausible option.
The Method: Set a custom object to store progress of your ongoing operations, make a global static list containing this metadata. Notice how I track them with Ids: I don't store that on DB, the natural act of instantiating the class will auto_increment their Id.
Then, you can add a new controller to respond the progress of a particular ongoing process.
Now that you have a controller to respond the progress of an ongoing process by Id, you can create a javascript timer to call it and update the DOM.
When creating your process, dont hold the htmlrequest until its over, open a background operation instead and just respond with the newly created ProgressTracker.Id, through that class/list you can keep track of the progress and reply accordingly.
As said in another answer, when an operation finishes you can send a push notification and the clientside javascript will interrupt the timers and proceed to the next view/result/page, or you can increment the looping timer to detect when its done and call the results from another controller. (this way you can avoid using push if needed.)
Here is the partial code:
public class ProgressTracker {
private static GlobalIdProvider = 0;
public int _id = ++GlobalIdProvider;
public int Id { get { return _id; } }
bool IsInProgress = false;
bool IsComplete = false;
float Progress;
public YourProgressObject Data;
}
public class GlobalStatic {
public static List<ProgressTracker> FooOperations = new List<ProgressTracker>();
}
public class SomeWebApiController {
[HttpGet]
[Authorize]
public HttpResponseMessage GetProgress(int Id) {
var query = (from a in GlobalStatic.FooOperations where a.Id==Id select a);
if(!query.Any()) {
return Request.CreateResponse(HttpStatusCode.NotFound, "No operation with this Id found.");
} else {
return Request.CreateResponse(HttpStatusCode.Ok, query.First());
}
}
}
// this is javascript
// ... Your code until it starts the process.
// You'll have to get the ProgressTracker Id from the server somehow.
var InProgress = true;
window.setTimeout(function(e) {
var xmlhttp = new XMLHttpRequest();
var url = "<myHostSomething>/SomeWebApiController/GetProgress?Id="+theId;
xmlhttp.setRequestHeader("Authentication","bearer "+localStorage.getItem("access_token"));
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
var data = JSON.parse(xmlhttp.responseText);
updateProgressBar(data);
}
}
xmlhttp.open("GET", url, true);
xmlhttp.send();
function updateProgressBar(data) {
document.getElementById("myProgressText").innerHTML = data.Progress;
}
}, 3000);
Disclaimer: If my javascript is shitty, pardon me but I'm too used to using jQuery and all this fancy stuff x_x