I'm trying to refactor some ultra-complex legacy code that sends data from a handheld device to an app running on a PC, to which the handheld device is connected.
There is a "conversation" that goes on between the two apps that follows a protocol; the server (the app running on the PC) responds based on what the client tells it, and vice versa. Actually, the "conversation" can be seen about two thirds of the way down here.
Anyway, my problem is: how can I let the client wait for the server to respond without interrupting it, or thinking it's not going to respond and failing to continue? This is what I have right now:
public class FileXferLegacy : IFileXfer
{
private SerialPort cereal;
private String lastDataReceived;
private String receivedData;
. . .
private void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
// This method will be called when there is data waiting in the port's buffer
try
{
receivedData += cereal.ReadLine();
lastDataReceived = receivedData;
ExceptionLoggingService.Instance.WriteLog(String.Format("Received {0} in FileXferLegacy.SendDataContentsAsXML", receivedData));
}
catch (Exception ex)
{
//MessageBox.Show(ex.Message);
}
}
#region IFileFetchSend Members
. . .
public void SendDataContentsAsXML(string destinationPath, string data)
{
byte[] stuff;
ExceptionLoggingService.Instance.WriteLog("Reached
FileXferLegacy.SendDataContentsAsXML");
cereal.Open();
stuff = System.Text.UTF8Encoding.UTF8.GetBytes("PING" + "\n");
cereal.Write(stuff, 0, stuff.Length);
if (lastDataReceived.Contains("PING")) // Expecting "PING|ACKNOWLEDGE|"
{
stuff =
System.Text.UTF8Encoding.UTF8.GetBytes("LOGIN|foo|000003|LOC_HOST|PPP_PEER|1.4.0.42|bar" + "\n");
// TODO: replace this test data with dynamic data
cereal.Write(stuff, 0, stuff.Length);
}
if (lastDataReceived.Contains("JOIN|LEVEL")) // Expecting something like "JOIN|LEVEL|1
SETTING|ALT_ID|FALSE"
{
stuff = System.Text.UTF8Encoding.UTF8.GetBytes("HHTCOMMAND|GETHHTSUPDATE|");
cereal.Write(stuff, 0, stuff.Length);
}
. . .
String lastResponse = lastDataReceived; // Expecting something like
"RESULT|FILECOMPLETE|INV_000003_whatever(not identical to what was sent earlier!).XML"
// Parse out and do something with the filename ("INV_000003_whatever(not identical to
what was sent earlier!).XML" above)
}
As you can see, the client/handheld sends a string; it then reads "lastDataReceived" which is assigned in the DataReceived method. But what if there has been a delay, and "lastDataReceived" is null? What do I need to do to force a delay (without going to an extreme that would cause the app to appear slothlike in its slowness)? Or what is the way this should be done, if I'm totally off base?
A typical approach is to use a reader thread that pulls bytes off the port with blocking reads (though it can be done with async notification instead) and, once detecting that an entire message has been delivered, it either:
Puts them into a blocking queue (with consumer blocking on calls to dequeue until either a msg is added or a timeout reached
or
Notifies a listener with an event that contains the message.
Which of those two depends a lot on the consumer of those messages. Your code above would benefit from #1, though if the consumer is the UI thread then you should look at #2.
The protocol seems to be half-duplex so rewriting it with synchronous calls to Write/Readline seems to be the simplest way to handle it.
Related
I am making a program that starts a child process and communicates via anonymous pipes. When I read from a pipe, the program hangs at the first ReadLine() call as seen in the following code method:
// Reads messages sent from module's process via anonymous pipe
internal string[] ReadPipe() {
try {
Log.Verbose("Checking read pipe");
// Check for sync message and return null if there is no message to receive
string pipeMessage = _pipeInReader.ReadLine(); // HANGS ON THIS LINE
if(pipeMessage == null || !pipeMessage.StartsWith("SYNC")) {
Log.Verbose("No message found in pipe");
return null;
}
// Return array of message lines
Log.Verbose("Received message from module {ModuleName}", _fileInfo.Name);
List<string> pipeMessageLines = new();
do {
pipeMessage = _pipeInReader.ReadLine();
pipeMessageLines.Add(pipeMessage);
Log.Debug(pipeMessage);
} while(pipeMessage != null && !pipeMessage.StartsWith("END"));
return pipeMessageLines.ToArray();
} catch(Exception e) {
Log.Error(e.ToString());
return null;
}
}
The code I am using to write to the pipe is the following:
// Sends a message to module's process via anonymous pipe
public static void WritePipe(string message) {
try {
Log.Verbose("Sending \"{Message}\" to kit pipe", message);
// Send sync message and wait for module process to receive it
Log.Verbose("Waiting for pipe drain");
_pipeOutWriter.Write("SYNC");
_pipeOut.WaitForPipeDrain();
// Send the specified message
Log.Verbose("Pipe drained. Sending message");
_pipeOutWriter.Write(message);
_pipeOutWriter.Write("END");
} catch(Exception e) {
Log.Error(e.ToString());
}
}
Why does it hang at that ReadLine() line?
Thanks in advance!
Without a proper minimal, reproducible example, it's impossible to say for sure. However, one glaring problem with your code is that when you write to the _pipeOutWriter object, you don't call Flush(). Assuming that's a TextWriter, by default it's going to buffer the data until the internal buffer is full, and not send anything to the underlying Stream until then.
By calling Flush(), you force it to flush its internal buffer and send the data right away.
If that does not address your question, please edit the question to improve it, making sure to provide a minimal, reproducible example, and any additional details about what you've tried so far to fix the problem and what specifically you need help with.
In my scenario there are one or two serial port. If only one port is configured, that port can be dedicated to a PLC or to a Scale.
The PLC communicate only when user push some button on the machine, the Scale instead constantly poll the measured that has weighted. If only one port is configured there are no issue.
When two port are configured the communication protocol change a bit, if user push the PLC's weight button PLC send a weight request. If that request is listened on DataReceived event the Scale port is opened and the polled value is readen.
Now i have to admit that the legacy code that i will paste here is not the best code possible, and that is bad that it use the same DataReceived event for both the ports; but that code had work for quite a lot (about six years). Recently the Scale has been changed and the program had stoped working properly (only in the two port configuration) raising a sistematic timeout exception. I have sniffed the traffic and nothing change with protocol or data format.
Here the problematic piece of code :
private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
lock (_myLockSerialPort)
{
string source = "No Data" ;
try
{
if (sender == null)
return;
SerialPort genericPort = (SerialPort)sender;
if (genericPort.Equals(ScalePort))
{
if (genericPort.BytesToRead > 0)
{
source = "Scale" ;
string datas = string.Empty;
datas = genericPort.ReadTo(_currentConfiguration.ReadToForPese);
ReadDataFromScale(genericPort, datas);
}
}
else
{
if (genericPort.BytesToRead > 0)
{
source = "PLC" ;
string datas = string.Empty;
datas = genericPort.ReadTo(_currentConfiguration.ReadToForPlc);
ReadDataFromPlc(genericPort, datas);
}
}
}
catch (TimeoutException timeoutException)
{
//Handle Timeout
}
catch (Exception err)
{
//Handle Other Errors
}
}
}
The question is if there is something in the ReadTo method or in the polling interval that can explain that behaviour.
I would underline that nothing in the data exchange format has changed, so the character used to terminate the communication are still in place (and present in the sniffed traffic).
NO, NO, NO!!! If you had a text file normally you would use ReadLine() method to read one line at a time. With ports or sockets data is coming at you at real time and the program is probably running faster than the data so you will not get a full line at one time. The serial uart in the PC has only a small read buffer (could be as small as 8 bytes). Windows is using timers to move the serial port data into a stream. Your program is reading the windows stream. So the actual data received is a combination of how fast the sender is sending data, the latency of the UART, the latency of Windows, and the latency of the Net Library. It is not a configuration issue. It is a real time issue.
I would recommend using two SerialPort classes, one for your scale and one for the PLC in which they would have their own DataReceived events. As others have suggested, you should append characters until you see your end character, then parse the string with your methods you wrote. Below is an example where Line Feed (LF) is my end character I'm looking for.
StringBuilder sbScale = new StringBuilder();
StringBuilder sbPLC = new StringBuilder();
char LF = (char)10;
private void serialPortScale_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
string Data = serialPortScale.ReadExisting();
foreach (char c in Data)
{
if (c == LF)
{
sbScale.Append(c);
CurrentLine = sbScale.ToString();
sbScale.Clear();
ReadDataFromScale(CurrentLine);
}
else
{
sbScale.Append(c);
}
}
}
private void serialPortPLC_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
string Data = serialPortPLC.ReadExisting();
foreach (char c in Data)
{
if (c == LF)
{
sbPLC.Append(c);
CurrentLine = sbPLC.ToString();
sbPLC.Clear();
ReadDataFromPlc(CurrentLine);
}
else
{
sbPLC.Append(c);
}
}
}
You could try to implement that into one DataReceived event, but it can be messy. I think have dedicated classes to each device is much cleaner and easier to read, especially if someone has to take over the code some day after you. Also I clear the string builder immediately, because the DataReceived event is threaded, so it can fire while you're already in there.
I have 1 exe which is nothing bit a Windows form which will continuously run in background and will watch my serial port and I have 1 event data receive event which fires as my serial port receive data.
As soon as I receive data in this event I will pass this data to another event handler which saves this data in database through web api method.
But data to my serial port will be coming frequently so I want to save this data to my database independently so that my database insert operation doesn't block my incoming serial port data.
This is my code:
void _serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)//Fires as my serial port receives data
{
int dataLength = _serialPort.BytesToRead;
byte[] data = new byte[dataLength];
int nbrDataRead = _serialPort.Read(data, 0, dataLength);
if (nbrDataRead == 0)
return;
// Send data to whom ever interested
if (NewSerialDataRecieved != null)
{
NewSerialDataRecieved(this, new SerialDataEventArgs(data)); //pass serial port data to new below event handler.
}
}
void _spManager_NewSerialDataRecieved(object sender, SerialDataEventArgs e) //I want this event handler to run independently so that database save operation doenst block incoming serial port data
{
if (this.InvokeRequired)
{
// Using this.Invoke causes deadlock when closing serial port, and BeginInvoke is good practice anyway.
this.BeginInvoke(new EventHandler<SerialDataEventArgs>(_spManager_NewSerialDataRecieved), new object[] { sender, e });
return;
}
//data is converted to text
string str = Encoding.ASCII.GetString(e.Data);
if (!string.IsNullOrEmpty(str))
{
//This is where i will save data to through my web api method.
RunAsync(str).Wait();
}
}
static async Task RunAsync(string data)
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:33396/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var content = new StringContent(data);
var response = await client.PostAsJsonAsync<StringContent>("api/Service/Post", content);//nothing happens after this line.
}
}
Web api controller:
public class MyController : ApiController
{
[HttpPost]
public HttpResponseMessage Post(HttpRequestMessage request)
{
var someText = request.Content.ReadAsStringAsync().Result;
return new HttpResponseMessage() { Content = new StringContent(someText) };
}
}
But here problem is:
var response = await client.PostAsJsonAsync<StringContent>("api/Service/Post", content);
Nothing happens after this line that is operation blocks on this line.
So can anybody guide me with this?
By independently we determined in the SO C# chat room that you really mean "Asynchronously".
Your solution is the code above, saving this data to a WebAPI endpoint so any solution to the problem needs to be in 2 parts ...
PART 1: The Client Part
On the client all we need to do is make the call asynchronously in order to free up the current thread to carry on receiving data on the incoming serial port, we can do that like so ...
// build the api client, you may want to keep this in a higher scope to avoid recreating on each message
var api = new HttpClient();
api.BaseAddress = new Uri(someConfigVariable);
// asynchronously make the call and handle the result
api.PostAsJsonAsync("api/My", str)
.ContinueWith(t => HandleResponseAsync(t.Result))
.Unwrap();
...
PART 2: The Server Part
Since you have web api i'm also going to assume you are using EF too, the common and "clean" way to do this, with all the extras stripped out (like model validation / error handling) might look something like this ...
// in your EF code you will have something like this ...
Public async Task<User> SaveUser(User userModel)
{
try
{
var newUser = await context.Users.AddAsync(userModel);
context.SavechangesAsync();
return newUser;
}
catch(Exception ex) {}
}
// and in your WebAPI controller something like this ...
HttpPost]
public async Task<HttpResponseMessage> Post(User newUser)
{
return Ok(await SaveUser(newUser));
}
...
Disclaimer:
The concepts involved here go much deeper and as I hinted above, much has been left out here like validation, error checking, ect but this is the core to getting your serial port data in to a database using the technologies I believe you are using.
Key things to read up on for anyone wanting to achieve this kind of thing might include: Tasks, Event Handling, WebAPI, EF, Async operations, streaming.
From what you describe it seems like you might want to have a setup like this:
1) your windows form listens for serial port
2) when new stuff comes to port your windows forms app saves it to some kind of a queue (msmq, for example)
3) you should have separate windows service that checks queue and as it finds new messages in a queue it sends request to web api
Best solution for this problem is to use ConcurrentQueue.
Just do search on google and you will get planty of samples.
ConcurrentQueue is thread safe and it support writing and reading from multiple threads.
So the component listening to the searal port can write data to the queue. And you can have 2 or more tasks running parallel which listening to this queue and update db as soon as it receives data.
Not sure if it's the problem, but you shouldn't block on async code. You are doing RunAsync(str).Wait(); and I believe that's the problem. Have a look at this blog post by Stephen Cleary:
http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html
Overview of Problem:
I need to connect to an IRC Server. Once connected, the program will send a message to the channel, and a response will occur over multiple lines back. I need to read these lines and store in a variable for later use. A special character at the end of the message (]) will define the end of the message over multiple lines. Once we have received this character, the IRC session should disconnect and processing should continue.
Situation:
I am using the Smartirc4net library. Calling irc.Disconnect() takes about 40 seconds to disconnect the session. Once we've received the ] character, the session should be disconnected, Listen() should not be blocking, and the rest of the program should continue to run.
Research:
I have found this: smartirc4net listens forever, can't exit thread, and I think it might be the same issue, however, I am unsure of what I need to do to resolve the problem.
Code:
public class IrcCommunicator
{
public IrcClient irc = new IrcClient();
string data;
public string Data { get { return data; } }
// this method we will use to analyse queries (also known as private messages)
public void OnQueryMessage(object sender, IrcEventArgs e)
{
data += e.Data.Message;
if (e.Data.Message.Contains("]"))
{
irc.Disconnect(); //THIS TAKES 40 SECONDS!!!
}
}
public void RunCommand()
{
irc.OnQueryMessage += new IrcEventHandler(OnQueryMessage);
string[] serverlist;
serverlist = new string[] { "127.0.0.1" };
int port = 6667;
string channel = "#test";
try
{
irc.Connect(serverlist, port);
}
catch (ConnectionException e)
{
// something went wrong, the reason will be shown
System.Console.WriteLine("couldn't connect! Reason: " + e.Message);
}
try
{
// here we logon and register our nickname and so on
irc.Login("test", "test");
// join the channel
irc.RfcJoin(channel);
irc.SendMessage(SendType.Message, "test", "!query");
// here we tell the IRC API to go into a receive mode, all events
// will be triggered by _this_ thread (main thread in this case)
// Listen() blocks by default, you can also use ListenOnce() if you
// need that does one IRC operation and then returns, so you need then
// an own loop
irc.Listen();
// when Listen() returns our IRC session is over, to be sure we call
// disconnect manually
irc.Disconnect();
}
catch (Exception e)
{
// this should not happen by just in case we handle it nicely
System.Console.WriteLine("Error occurred! Message: " + e.Message);
System.Console.WriteLine("Exception: " + e.StackTrace);
}
}
}
IrcBot bot = new IrcBot();
bot.RunCommand();
ViewBag.IRC = bot.Data;
As you can see, once this
Thank you for your time to look at this code and read my problem description. If you have any thoughts, or other suggestions, please let me know.
Mike
I was able to successfully disconnect straight away by calling RfcQuit() within OnQueryMessage(), before irc.Disconnect();
I am having some problems with BluetoothChat (that I beleive its the same code on bot Java and MonoForAndroid) example app. I have connected my Android to an microcontroller using a Bluetooth module. In case of sending messages (just raw bytes to microcontroller) it works just fine!
The microcontroller streams a constant serial message and I want to read that data. There is a class named MyHandler in BluetoothChat.cs app that has a code block like this:
case MESSAGE_READ:
byte[] readBuf = (byte[])msg.Obj;
// construct a string from the valid bytes in the buffer
var readMessage = new Java.Lang.String (readBuf, 0, msg.Arg1);
bluetoothChat.conversationArrayAdapter.Add(
bluetoothChat.connectedDeviceName + ": " + readMessage);
break;
So what I need to do is to process the incoming raw data and then change color of some buttons, So I made the follwing changes to the code above:
case MESSAGE_READ:
byte[] readBuf = (byte[])msg.Obj;
//I have just added this code and it blocks the UI
bluetoothChat.ProcessIncomingData(readBuff);
break;
And in the BluetootChat activity I have this method:
public void ProcessIncomingData(byte[] readBuf)
{
if (_logBox != null)
{
_logBox.Text += "\r\n"; //TextView
foreach (var b in readBuf)
{
_logBox.Text += (uint)b + " "; //Show the bytes as int value
}
}
}
`
But unfortunately the changes I made stops the UI and the app crashes after f short while.
Any ideas how can I do this neatly without freezing the UI?
You'll want to hand the work off to a background thread in order to keep the UI thread free to respond to input. I wrote up a post awhile back outlining some of the different methods available to you for doing background threads: Using Background Threads in Mono For Android Applications
One thing to be careful with when dealing with background threads is that if you want to make any changes to the UI, you must switch back to the UI thread. You can do this by using the RunOnUiThread() method.
Create a new thread for the process to take place in.
public static void threadProcess()
{
Thread thread = new Thread()
{
public void run()
{
// Process that will run in the thread
}
};
thread.start();
}