MQTTNet: How to change value in topic for active client? - c#

I have a device that has a built-in mqtt client that subscribes to a broker server and displays topic 0 or 1;
0 - disabled;
1 - enabled;
MQTT Broker (ASP.NET WEB API) .Net 6
BUILDER
var optionBuilder = new MqttServerOptionsBuilder()
.WithDefaultEndpoint()
.WithDefaultCommunicationTimeout(TimeSpan.FromMilliseconds(5000))
.Build();
builder.Services
.AddHostedMqttServer(optionBuilder)
.AddMqttConnectionHandler()
.AddConnections()
.AddMqttTcpServerAdapter();
builder.Services.AddMqttConnectionHandler();
builder.Services.AddMqttWebSocketServerAdapter();
APP
app.UseMqttServer(server =>
{
});
After connecting the device to the server, I want to see the status of this client on the server and send a parameter to change the topic attribute.
In version 3.. - I used the IMqttServer interface
private readonly IMqttServer _mqttServer;
public MqttBrokerService(IMqttServer mqttServer)
{
_mqttServer = mqttServer ?? throw new ArgumentNullException(nameof(mqttServer));
}
public Task<IList<IMqttClientStatus>> GetClientStatusAsync()
{
return _mqttServer.GetClientStatusAsync();
}
public Task<IList<IMqttSessionStatus>> GetSessionStatusAsync()
{
return _mqttServer.GetSessionStatusAsync();
}
public Task ClearRetainedApplicationMessagesAsync()
{
return _mqttServer.ClearRetainedApplicationMessagesAsync();
}
public Task<IList<MqttApplicationMessage>> GetRetainedApplicationMessagesAsync()
{
return _mqttServer.GetRetainedApplicationMessagesAsync();
}
public Task<MqttClientPublishResult> PublishAsync(MqttApplicationMessage applicationMessage)
{
if (applicationMessage == null)
{
throw new ArgumentNullException(nameof(applicationMessage));
}
return _mqttServer.PublishAsync(applicationMessage);
}
But in version 4.. - this interface was removed and now I don't understand how I can build messages for the client and get detailed statistics.
there is MQTTnet.Extensions.ManagedClient, but I still could not connect to the active session of my client.
var options = new ManagedMqttClientOptionsBuilder()
.WithAutoReconnectDelay(TimeSpan.FromSeconds(5))
.WithClientOptions(new MqttClientOptionsBuilder()
.WithClientId("Client1")
.WithTcpServer("192.168.1.1")
.WithTls().Build())
.Build();
var mqttClient = new MqttFactory().CreateManagedMqttClient();
await mqttClient.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("my/topic").Build());
await mqttClient.StartAsync(options);
I will be very grateful for your help

Related

Where and how should I initialize service which is used for sending requests like HttpClient?

I am using Google Vision API and its C# library.
I want to understand where should I initialize ImageAnnotatorClient and how should I register GoogleVisionApiService.
ImageAnnotatorClient is used for sending images to the Google Vision API, like this _imageAnnotatorClient.DetectSafeSearchAsync(image)
First way - register service as Singleton and initialize ImageAnnotatorClient in the constructor
public class GoogleVisionApiService : IGoogleVisionApiService
{
private readonly IGoogleCredentialFactory _googleCredentialFactory;
private readonly ImageAnnotatorClient _imageAnnotatorClient;
public GoogleVisionApiService(IGoogleCredentialFactory googleCredentialFactory)
{
_googleCredentialFactory = googleCredentialFactory;
_imageAnnotatorClient = InitializeClient();
}
private ImageAnnotatorClient InitializeClient()
{
var googleCredential = _googleCredentialFactory.GetGoogleCredentialAsync().Result;
var credential = googleCredential.UnderlyingCredential as ServiceAccountCredential;
var imageAnnotatorClientBuilder = new ImageAnnotatorClientBuilder
{
Credential = credential
};
var imageAnnotatorClient = imageAnnotatorClientBuilder.Build();
return imageAnnotatorClient;
}
public async Task<SafeSearchAnnotation> GetSafeSearchAnnotationAsync(string imageBase64)
{
var image = Image.FromBytes(Convert.FromBase64String(requestImage));
var labels = await _imageAnnotatorClient.DetectSafeSearchAsync(image);
return labels;
}
}
Second way is to register service as Transient and initialize client every time service is called
public class GoogleVisionApiService : IGoogleVisionApiService
{
private readonly IGoogleCredentialFactory _googleCredentialFactory;
public GoogleVisionApiService(IGoogleCredentialFactory googleCredentialFactory)
{
_googleCredentialFactory = googleCredentialFactory;
}
public async Task<SafeSearchAnnotation> GetSafeSearchAnnotationAsync(string imageBase64)
{
var googleCredential = await _googleCredentialFactory.GetGoogleCredentialAsync();
var credential = googleCredential.UnderlyingCredential as ServiceAccountCredential;
var imageAnnotatorClientBuilder = new ImageAnnotatorClientBuilder
{
Credential = credential
};
var imageAnnotatorClient = await imageAnnotatorClientBuilder.BuildAsync();
var image = Image.FromBytes(Convert.FromBase64String(requestImage));
var labels = await _imageAnnotatorClient.DetectSafeSearchAsync(image);
return labels;
}
}
In general, it's best to create a single RPC client if you can.
In your specific case, I'd suggest using the (relatively new) DI-friendly extension methods in the Google client libraries. Then your GoogleVisionApiService can just accept an ImageAnnotatorClient.
// Wherever you're configuring services
services
// Make sure everything uses the right credential
.AddSingleton<GoogleCredential>(provider => /* code to fetch credential */)
// Note: this is for .NET 6 and .NET Core 3.1. Without this line, the client builder
// will use the "right" adapter for the environment, but won't be able to hook gRPC
// logging up with the DI's logging configuration
.AddGrpcNetClientAdapter()
.AddImageAnnotatorClient();
See the client lifecycle docs for more information.

SignalR C# Client Hubconnection.On not fired

I have a ASPNet.Core WebApi, with signalR. I have angular app, that consumes the webAPI, and I want to replace it with a Blazor Webassembly app. I have a problem with signalR in the Blazor app.
I create a hubconnection, set it up, and when the server sends data, the Hubconnection.On method is not invoked. Here's my code:
protected override async Task OnInitializedAsync()
{
_hubConnection = new HubConnectionBuilder()
.WithUrl("https://localhost:45299/hubs/accounthub", cfg =>
{
cfg.SkipNegotiation = true;
cfg.AccessTokenProvider = () => Task.FromResult(token);
cfg.Transports = HttpTransportType.WebSockets;
})
.Build();
_hubConnection.On<IEnumerable<AccountResponse>>("accountschanged", (accounts) =>
{
foreach(var account in accounts)
{
Console.WriteLine(account.Name);
}
});
await _hubConnection.StartAsync();
}
In the network tab, I see that the connection is ok, I receive new data, but the method in hubconnection.On doesn't get fired. I double checked the method name, and it is the same. In the angular app it works fine and as data gets send from the server, I don't there's any problem with the server code.
I use Fluxor for state management, and I fire an action in the 'On' method, I just replaced is with a single Console.WriteLine for simplicity.
Edit: Added server code, and the message received
Here's the server code, 'AccountsChanged' is called when an account is changed:
public class AccountHub : Hub, IAccountHub
{
private readonly IHubContext<AccountHub> _accHub;
private readonly IAggregateMapper _mapper;
public AccountHub(IHubContext<AccountHub> accHub, IAggregateMapper mapper)
{
_accHub = accHub;
_mapper = mapper;
}
public async Task AccountsChanged(Guid userId, IEnumerable<Account> accounts)
{
var mapped = _mapper.MapAll<Account, AccountResponse>(accounts);
await _accHub.Clients.User(userId.ToString()).SendAsync("accountschanged", mapped);
}
}
And here's the message I receive (I make a request from Postman), copied from the network tab (I removed additional properties of accounts to keep it simple):
{
"type":1,
"target":"accountschanged",
"arguments":[
[
{
"id":1,
"name":"bank account 1"
},
{
"id":2,
"name":"wallet 1"
}
]
]
}
I finally found the problem. It was about serializing the received json message. I had to add .AddJsonProtocol(), and set it up, here is the final code:
_hubConnection = new HubConnectionBuilder()
.WithUrl("http://localhost:59225/hubs/accounthub", cfg =>
{
cfg.SkipNegotiation = true;
cfg.Transports = HttpTransportType.WebSockets;
cfg.AccessTokenProvider = () => Task.FromResult(token);
})
.AddJsonProtocol(cfg =>
{
var jsonOptions = new System.Text.Json.JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
};
jsonOptions.Converters.Add(new JsonStringEnumConverter());
cfg.PayloadSerializerOptions = jsonOptions;
})
.Build();
I find it strange that I didn't get any error message btw.

Conversation always restarts in Directline BOT channel websocket, how to keep it flowing?

I have built an app that needs to connect to a Bot DirectLine - websockets channel to interact in conversations via LUIS and sms with Twilio.
To make the bot talk to the app I wrote a mvc controller that relays messages.
I am not sure this approach is correct, I made it up from some samples.
It works, but the main problem is that my code seems to always start a new conversation when a message is received from the client, so the context is not maintained.
How can I keep the conversation flowing and not restarting at every message?
I mean, the steps should be, for example:
Bot: Hello, what's your name?
User: Carl
Bot: Pleased to meet you Carl!
instead I get:
Bot: Hello, what's your name?
User: Carl
Bot: Sorry, I can't help you with that.
like the conversation is restarted from scratch.
Here is my controller code (the Twilio webhook is set to https://mySmsMVCapp.azurewebsites.net/smsapp/):
public class smsappController : TwilioController
{
private static string directLineSecret = ConfigurationManager.AppSettings["DirectLineSecret"];
private static string botId = ConfigurationManager.AppSettings["BotId"];
const string accountSid = "obfuscated";
const string authToken = "obfuscated";
private static string fromUser = "DirectLineSampleClientUser";
private string SMSreply = "";
public async Task<TwiMLResult> Index(SmsRequest incomingMessage)
{
// Obtain a token using the Direct Line secret
var tokenResponse = await new DirectLineClient(directLineSecret).Tokens.GenerateTokenForNewConversationAsync();
// Use token to create conversation
var directLineClient = new DirectLineClient(tokenResponse.Token);
var conversation = await directLineClient.Conversations.StartConversationAsync();
using (var webSocketClient = new WebSocket(conversation.StreamUrl))
{
webSocketClient.OnMessage += WebSocketClient_OnMessage;
// You have to specify TLS version to 1.2 or connection will be failed in handshake.
webSocketClient.SslConfiguration.EnabledSslProtocols = System.Security.Authentication.SslProtocols.Tls12;
webSocketClient.Connect();
while (true)
{
string input = incomingMessage.Body;
if (!string.IsNullOrEmpty(input))
{
if (input.ToLower() == "exit")
{
break;
}
else
{
if (input.Length > 0)
{
Activity userMessage = new Activity
{
From = new ChannelAccount(fromUser),
Text = input,
Type = ActivityTypes.Message
};
await directLineClient.Conversations.PostActivityAsync(conversation.ConversationId, userMessage);
//break;
if (!string.IsNullOrEmpty(SMSreply))
{
var messagingResponse = new MessagingResponse();
var message = messagingResponse.AddChild("Message");
message.AddText(SMSreply); //send text
SMSreply = string.Empty;
return TwiML(messagingResponse);
}
}
}
}
}
}
return null;
}
private void WebSocketClient_OnMessage(object sender, MessageEventArgs e)
{
// Occasionally, the Direct Line service sends an empty message as a liveness ping. Ignore these messages.
if (!string.IsNullOrWhiteSpace(e.Data))
{
var activitySet = JsonConvert.DeserializeObject<ActivitySet>(e.Data);
var activities = from x in activitySet.Activities
where x.From.Id == botId
select x;
foreach (Activity activity in activities)
{
if (!string.IsNullOrEmpty(activity.Text))
{
SMSreply = activity.Text;
}
}
}
}
}
The issue was actually I wasn't saving and retrieving conversationID.
For the moment I am testing using a static variable to store the value.
Then I reconnect to the conversation with it and the conversation with the bot keeps in context.

Why isn't Azure SignalR sending the messages published by the Azure Functions?

I have deployed a set of Azure Functions (v2).
I want to have an iOS application being notified when something happens on the server side so I created an Azure SignalR Service, configured as Serverless.
I have created and deployed a new negotiate Azure Function, as explained in the documentation, that publishes subscription information on a service bus queue:
[FunctionName("negotiate")]
public async Task<SignalRConnectionInfo> Negotiate(
[HttpTrigger(AuthorizationLevel.Anonymous)]
HttpRequest httpRequest,
ClaimsPrincipal claimsPrincipal,
[SignalRConnectionInfo(HubName = "updates", UserId = "{headers.x-ms-client-principal-id}")]
SignalRConnectionInfo connectionInfo)
{
try
{
// Uses the name identifier for now.
Claim nameIdentifierClaim = claimsPrincipal.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier);
string userId = nameIdentifierClaim.Value;
foreach (Guid groupId in claimsPrincipal.GetGroups())
{
var subscription = new SynchronizationSubscriptionContract {UserId = userId, GroupId = groupId};
await m_serviceBus.SendAsync(JsonConvert.SerializeObject(subscription));
}
return connectionInfo;
}
catch (Exception exc)
{
//
}
}
The Service Bus is bound to an Azure Function that adds the user to the list of groups:
[ServiceBusAccount(Constants.Configuration.ServiceBusConnectionString)]
[FunctionName("subscribe")]
public async Task AddToGroupAsync(
[ServiceBusTrigger("synchronization-subscriptions")] string requestMessage,
[SignalR(HubName = "updates")] IAsyncCollector<SignalRGroupAction> signalRGroupActions)
{
var synchronizationSubscriptionContract = JsonConvert.DeserializeObject<SynchronizationSubscriptionContract>(requestMessage);
string groupName = synchronizationSubscriptionContract.GroupId;
var addGroupAction = new SignalRGroupAction
{
UserId = synchronizationSubscriptionContract.UserId,
GroupName = groupName,
Action = GroupAction.Add
};
await signalRGroupActions.AddAsync(addGroupAction);
}
So far so good.
Whenever one of my existing Azure Functions needs to publish a message on the SignalR channel, it sends a message on a dedicated service bus queue that is bound to another Azure Function. The Azure Function then grabs the message and sends it to the Azure SignalR Service:
[ServiceBusAccount(Constants.Configuration.ServiceBusConnectionString)]
[FunctionName(Api.Functions.Synchronization.UpdateSynchronizationInfo)]
public async Task Run(
[ServiceBusTrigger("synchronization-requests")] string requestMessage,
[SignalR(HubName = "updates")]
IAsyncCollector<SignalRMessage> signalRMessages
)
{
var requestContract = JsonConvert.DeserializeObject<SynchronizationUpdateRequestContract>(requestMessage);
var request = m_mapper.Map<SynchronizationUpdateRequest>(requestContract);
// Do more stuff.
string groupName = request.GroupId;
var updateMessage = new SignalRMessage
{
GroupName = groupName,
Target = "notify",
Arguments = new string[] {}
};
await signalRMessages.AddAsync(updateMessage);
}
Based on the logs that I added at different places in the Azure Functions, I see no errors and everything seemed to be called properly. However, I am not seeing any messages on signalR: the message count stays at 0 even if the connection count increases. My application does not received any messages.
Question
Why aren't the messages being sent to the Azure SignalR? What am I missing?
Wow.
The message seems to be dropped if the Arguments property is an empty list.
When I changed to Message instance to the following, it immediately worked:
var updateMessage = new SignalRMessage
{
GroupName = groupName,
Target = "notify",
Arguments = new[] { "Lulz" }
};

Lambda Function using c# cannot invoke external HTTPS APIs

I am trying to invoke External APIs from AWS lambda function written in c#. The Lamda function is deployed in No VPC mode. I am calling this function from Alexa skill. The code works fine for an http request, but its not working for https.
The below code works when I use http://www.google.com.
But, if I replace http with https, then I get the error in the cloud watch saying:
"Process exited before completing request."
Even the log written in catch is not getting logged in cloud watch.
public class Function
{
public const string INVOCATION_NAME = "bingo";
public async Task<SkillResponse> FunctionHandler(SkillRequest input, ILambdaContext context)
{
var requestType = input.GetRequestType();
if (requestType == typeof(IntentRequest))
{
string response = "";
IntentRequest request = input.Request as IntentRequest;
response += $"About {request.Intent.Slots["carmodel"].Value}";
try
{
using (var httpClient = new HttpClient())
{
Console.WriteLine("Trying to access internet");
//var resp=httpClient.GetAsync("http://www.google.com").Result // this works perfect!
var resp = httpClient.GetAsync("https://www.google.com").Result; // this throws error
Console.WriteLine("Call was successful");
}
}
catch (Exception ex)
{
Console.WriteLine("Exception from main function " + ex.Message);
Console.WriteLine(ex.InnerException.Message);
Console.WriteLine(ex.StackTrace);
}
return MakeSkillResponse(response, true);
}
else
{
return MakeSkillResponse(
$"I don't know how to handle this intent. Please say something like Alexa, ask {INVOCATION_NAME} about Tesla.",
true);
}
}
private SkillResponse MakeSkillResponse(string outputSpeech, bool shouldEndSession,
string repromptText = "Just say, tell me about car models to learn more. To exit, say, exit.")
{
var response = new ResponseBody
{
ShouldEndSession = shouldEndSession,
OutputSpeech = new PlainTextOutputSpeech { Text = outputSpeech }
};
if (repromptText != null)
{
response.Reprompt = new Reprompt() { OutputSpeech = new PlainTextOutputSpeech() { Text = repromptText } };
}
var skillResponse = new SkillResponse
{
Response = response,
Version = "1.0"
};
return skillResponse;
}
}
The issue was resolved by updating the library version.
System.Net.Http v4.3.4 was not completely compatible with dotnet core v1.
So outbound http calls were working but not https calls. Changing the version of System.net.http resolved the issue.

Categories

Resources