I have found a lot of information on the Backgroundworker updating a progress bar and I have written numerous versions of this code. But none of the versions has updated the progress bar DURING the time my upgrade application is running. Here is one of the DoWork handler versions that I have used:
void worker_DoWork(object sender, DoWorkEventArgs e)
{
updater.updater();
int percents = 0;
// Progress bar
int total = 57;
for (int i = 0; i <= total; i++)
{
System.Threading.Thread.Sleep(100);
percents = (i * 100) / total;
bw.ReportProgress(percents, i);
}
If I run updater (my app) before ReportProgress (as shown), updater runs completely and then the progress bar updates from 0 to 100%. If I place updater after the ReportProgress call, the progress bar runs and THEN updater runs. If I replace the Thread.Sleep line with updater, it runs at the 0% interval of the progress bar.
Is it actually possible to have the progress bar update while a long-running app is executing in the backgroundworker? That is what the MSDN page for backgroundworker claims, but what they actually show is it running a series of short processes (Sleep) and not one long process. Most of the examples I have found on line use this format, making no reference to a longer running process that is not segmented into the ReportProgress section.
I would love to know if the backgroundworker is capable of doing this or is this a job for some other threading-type solution.
Thanks!
After seeing Tim's answer below, I attempted to implement an EventArg and Handler for the progress bar progress.
public class FWupdater
{
public string comPort;
public int percentage;
public State state;
public string path;
public const int ACK = 0x79;
public const int NACK = 0x1F;
public class PBProgressEventArgs : EventArgs
{
private int prog;
public int progress
{
set { prog = value; }
get { return this.prog; }
}
}
public class PBProgress
{
public event PBProgressHandler Progress;
public delegate void PBProgressHandler(PBProgress p, PBProgressEventArgs e);
public void Start()
{
if (Progress != null)
{
PBProgressEventArgs progressUpdate = new PBProgressEventArgs();
progressUpdate.progress = 0;
Progress(this, progressUpdate);
}
}
}
And then create an instance in the main program so that the backgroundworker could see it.
PBProgress progUpdater = new PBProgress();
But I can't get the backgroundworker to see the progress percentage from the DoWork method.
Including the updater code.
public void updater()
{
// Create a new SerialPort object.
SerialPort _serialPort;
_serialPort = new SerialPort(comPort, 115200, Parity.Even, 8, StopBits.One);
// for state machine
bool _continue = true;
try
{
_serialPort.Open();
if (_serialPort.IsOpen)
{
Console.WriteLine("");
Console.WriteLine("Serial Port is Open");
Console.WriteLine("");
}
else
{
MessageBox.Show("Serial Port is not open. Choose another port.");
}
}
catch (UnauthorizedAccessException ex)
{
MessageBox.Show(ex.Message);
}
catch (ArgumentOutOfRangeException ex)
{
MessageBox.Show(ex.Message);
}
catch (ArgumentException ex)
{
MessageBox.Show(ex.Message);
}
catch (IOException ex)
{
MessageBox.Show(ex.Message);
}
catch (InvalidOperationException ex)
{
MessageBox.Show(ex.Message);
}
// Move through states until upgrade is complete
while (_continue)
{
switch (state)
{
case State.NORMAL:
// Beginning state for instance of upgrader
break;
case State.WAITING_TO_UPGRADE:
SetUpComm( _serialPort);
state = State.ERASING_FIRMWARE;
break;
case State.ERASING_FIRMWARE:
EraseFlashMemory(_serialPort);
state = State.UPGRADING_FIRMWARE;
break;
case State.UPGRADING_FIRMWARE:
WriteNewAppToFlash(_serialPort);
state = State.UPGRADE_COMPLETE;
break;
case State.UPGRADE_COMPLETE:
JumpToNewApp(_serialPort);
_continue = false;
_serialPort.Close();
break;
default:
break;
} // end SWITCH (state)
} // end WHILE (_continue) - main loop
} // end public void updater()
//
// ---- METHODS -------------------
public void SetUpComm(SerialPort _serialPort)
{
int byte_read = 0x00;
var sevenF = new byte[] { 0x7F };
// Send 0x55 and 0xAA to peripheral input to execute SwitchToBootloader()
var byte1 = new byte[] { 0x55 };
var byte2 = new byte[] { 0xAA };
_serialPort.Write(byte1, 0, 1);
_serialPort.Write(byte2, 0, 1);
// If in bootloader mode, where the boot pins on the board are set,
// the device will be looking to receive 0x7F to establish contact with the host.
// In this case, the bytes to trigger boot load from inside the firmware will be
// ignored and the following 0x7F will serve to trigger comm set-up .
// Wait for acknowledge byte from USART
while (byte_read != ACK)
{
// Write "7F" to start communicating with Bootloader
_serialPort.Write(sevenF, 0, 1);
Thread.Sleep(100);
// read ACK byte after parameters set and bootloader running
byte_read = _serialPort.ReadByte();
}
}
public void EraseFlashMemory(SerialPort _serialPort)
{
int byte_read = 0;
var ff = new byte[] { 0xFF };
Console.WriteLine("Erasing flash memory...");
Console.WriteLine("");
/* NOTE: the ERASE COMMAND is not supported by this device, use EXTENDED ERASE */
// Send 0x44 and 0xBB (extended erase memory command), see AN3155
var exeraseMem = new byte[] { 0x44 };
var bb = new byte[] { 0xBB };
_serialPort.Write(exeraseMem, 0, 1);
_serialPort.Write(bb, 0, 1);
// Receive ACK byte
byte_read = _serialPort.ReadByte();
if (byte_read == NACK)
{
//Console.WriteLine("NACK received for ERASE MEMORY start");
//Console.WriteLine("");
}
//// end sending EXTENDED ERASE COMMAND
//---------------------------------------
// Global erase (send 0xFFFF, and 0x00)
//---------------------------------------
//var globalErase = new byte[] { 0x00 };
//_serialPort.Write(ff, 0, 1);
//_serialPort.Write(ff, 0, 1);
//_serialPort.Write(globalErase, 0, 1);
// Erase all but the first page (16k)
// send number of pages to erase, msb first [11 pages, leaving page 0]
// *ALERT* send 10 pages (N) to erase 11, for some reason it erases N + 1, whatever...
var num_pages_msb = new byte[] { 0x00 };
var num_pages_lsb = new byte[] { 0x0A };
_serialPort.Write(num_pages_msb, 0, 1);
_serialPort.Write(num_pages_lsb, 0, 1);
// send page numbers, 2 bytes each, msb first
// PAGE 1
var page01_msb = new byte[] { 0x00 };
var page01_lsb = new byte[] { 0x01 };
_serialPort.Write(page01_msb, 0, 1); // 0
_serialPort.Write(page01_lsb, 0, 1); // 1
// PAGE 2
var page02_lsb = new byte[] { 0x02 };
_serialPort.Write(page01_msb, 0, 1); // 0
_serialPort.Write(page02_lsb, 0, 1); // 2
// PAGE 3
var page03_lsb = new byte[] { 0x03 };
_serialPort.Write(page01_msb, 0, 1); // 0
_serialPort.Write(page03_lsb, 0, 1); // 3
// PAGE 4
var page04_lsb = new byte[] { 0x04 };
_serialPort.Write(page01_msb, 0, 1); // 0
_serialPort.Write(page04_lsb, 0, 1); // 4
// PAGE 5
var page05_lsb = new byte[] { 0x05 };
_serialPort.Write(page01_msb, 0, 1); // 0
_serialPort.Write(page05_lsb, 0, 1); // 5
// PAGE 6
var page06_lsb = new byte[] { 0x06 };
_serialPort.Write(page01_msb, 0, 1); // 0
_serialPort.Write(page06_lsb, 0, 1); // 6
// PAGE 7
var page07_lsb = new byte[] { 0x07 };
_serialPort.Write(page01_msb, 0, 1); // 0
_serialPort.Write(page07_lsb, 0, 1); // 7
// PAGE 8
var page08_lsb = new byte[] { 0x08 };
_serialPort.Write(page01_msb, 0, 1); // 0
_serialPort.Write(page08_lsb, 0, 1); // 8
// PAGE 9
var page09_lsb = new byte[] { 0x09 };
_serialPort.Write(page01_msb, 0, 1); // 0
_serialPort.Write(page09_lsb, 0, 1); // 9
// PAGE 10
var page10_msb = new byte[] { 0x01 }; // 1
var page10_lsb = new byte[] { 0x00 }; // 0
_serialPort.Write(page10_msb, 0, 1);
_serialPort.Write(page10_lsb, 0, 1);
// PAGE 11
_serialPort.Write(page10_msb, 0, 1); // 1
_serialPort.Write(page01_lsb, 0, 1); // 1
// checksum = A
_serialPort.Write(num_pages_lsb, 0, 1);
// Receive ACK byte
byte_read = _serialPort.ReadByte();
bw.ReportProgress(20);
if (byte_read == NACK)
{
//Console.WriteLine("NACK received for ERASE MEMORY completed");
//Console.WriteLine("");
}
}
// -- end EXTENDED ERASE MEMORY --------------------------------------------------
public void WriteNewAppToFlash(SerialPort _serialPort)
{
// For testing
int blockCount = 0;
int byte_read = 0;
long checksum = 0;
var ff = new byte[] { 0xFF };
// ------------------------------------------------------------------------------
// -------- WRITE MEMORY --------------------------------------------------------
// ------------------------------------------------------------------------------
// for Address
int baseAddress = 0x08008000;
int offset = 0;
// for string from HEX file
string line;
string[] lineBuffer = new string[16];
int lineCount = 0;
int length;
int type;
int hexChecksum = 0;
bool sendAddress = true;
int counter = 0; // Counting the number of lines in the file
int byteCounter = 0; // Counting nmumber of bytes in the current block
// Create byte array with 256 bytes
byte[] buffer256 = new byte[256];
// Read the file and process one line at a time
System.IO.StreamReader file = new System.IO.StreamReader(path);
while ((line = file.ReadLine()) != null)
{
// Store line into a line buffer. This will allow reprocessing of all lines
// in a block if there is an error sending a block of 256 bytes below
if( line[8] == '0')
{
lineBuffer[lineCount++] = line;
}
// Send WRITE COMMAND and the next address every 256 bytes
if (sendAddress == true)
{
/*
-------------------------------------------------------------------------------------------------------
SEND WRITE COMMAND
-----------------------------------------------------------------------------------------------------*/
do
{
// Send WRITE command - 0x31 and 0xCE
var writeMem = new byte[] { 0x31 };
var ce = new byte[] { 0xCE };
_serialPort.Write(writeMem, 0, 1);
_serialPort.Write(ce, 0, 1);
// Receive ACK byte
byte_read = _serialPort.ReadByte();
} while (byte_read != ACK);
// -- end SEND 0x31 and 0xCE and wait for ACK -----------------------------------------
/*
-------------------------------------------------------------------------------------------------------
SEND CURRENT ADDRESS AND CHECKSUM TO FLASH MEMORY
-----------------------------------------------------------------------------------------------------*/
Byte[] currentAddr = BitConverter.GetBytes(baseAddress + offset);
// Increment offset by 0x100 (256 bytes)
offset = offset + 0x00000100;
// Reset Checksum and XOR address
checksum = 0;
foreach (byte b in currentAddr)
{
checksum ^= b;
}
Byte[] cksum = BitConverter.GetBytes(checksum);
// Send address, MSB first, LSB last
_serialPort.Write(currentAddr, 3, 1);
_serialPort.Write(currentAddr, 2, 1);
_serialPort.Write(currentAddr, 1, 1);
_serialPort.Write(currentAddr, 0, 1);
// Send checksum of address bytes
_serialPort.Write(cksum, 0, 1);
// Receive ACK byte
byte_read = _serialPort.ReadByte();
if (byte_read == NACK)
{
// Handle
}
// -- end addr or increment ---------------------------------------------------------
sendAddress = false;
// Send number of bytes, always 256, the last group will be padded with 0xFF
_serialPort.Write(ff, 0, 1);
} // end IF for WRITE COMMAND and ADDRESS
/* FIRST CHARACTER in HEX FILE
The colon indicates the start of a "record"
Remove colon from beginning of string */
line = line.Substring(1, line.Length - 1);
// Create byte array from string for whole line from HEX file
var bytes = GetBytesFromByteString(line).ToArray();
// Identify RECORD TYPE of HEX line [byte 4]
type = bytes[3];
/* Next TWO CHARACTERS 00-data 03-start segment address
in HEX FILE are 01-EOF 04-extended linear address
the record type: 02-extended segment address 05-start linear address */
// BLOCK WRITE TO MEMORY
if (type == 0)
{
// Length of line is stored at byte 0, in this case 0x10, or 16 bytes of data
length = bytes[0];
// Add data from current line to buffer of 256 bytes
for (int i = 0; i < length; i++)
{
// Stuff all bytes from line into buffer of 256 bytes
buffer256[byteCounter++] = bytes[4 + i];
// Add byte to checksum
hexChecksum ^= bytes[4 + i];
}
// When buffer is full, send block of 256 bytes and checksum, reset variables for next block
if (byteCounter >= 255)
{
// Convert checksum to a byte value
hexChecksum = hexChecksum ^ 0xFF;
byte csByte = Convert.ToByte(hexChecksum);
Byte[] csByte_arr = BitConverter.GetBytes(csByte);
// Send byte array
_serialPort.Write(buffer256, 0, 256);
// For testing
// Console.WriteLine("block number [{0}]", ++blockCount);
//send checksum
_serialPort.Write(csByte_arr, 0, 1);
//Receive ACK byte
byte_read = _serialPort.ReadByte();
Console.WriteLine("block/ACK = [{0}] | {1}", ++blockCount, byte_read);
while (byte_read != ACK)
{
Array.Clear(buffer256, 0, buffer256.Length);
hexChecksum = 0;
lineCount = 0;
// reprocess the previous 16 lines stored in the line buffer
for ( int j = 0; j < 16; j++ )
{
line = lineBuffer[j];
line = line.Substring(1, line.Length - 1);
var bytesLocal = GetBytesFromByteString(line).ToArray();
length = bytesLocal[0];
for (int i = 0; i < length; i++)
{
buffer256[byteCounter++] = bytesLocal[4 + i];
hexChecksum ^= bytesLocal[4 + i];
}
}
// Convert checksum to a byte value
hexChecksum = hexChecksum ^ 0xFF;
byte csByteLocal = Convert.ToByte(hexChecksum);
Byte[] csByte_arrLocal = BitConverter.GetBytes(csByteLocal);
// Send byte array
_serialPort.Write(buffer256, 0, 256);
//send checksum
_serialPort.Write(csByte_arrLocal, 0, 1);
//Receive ACK byte
byte_read = _serialPort.ReadByte();
Console.WriteLine("block/ACK = [{0}] | {1}", ++blockCount, byte_read);
}
// Clear buffer, reset byte count, clear checksum, set flag to send write cmd/send new addr
Array.Clear(buffer256, 0, buffer256.Length);
byteCounter = 0;
hexChecksum = 0;
lineCount = 0;
sendAddress = true;
}
} // end BLOCK WRITE TO MEMORY
else if (type == 1) // Marker for end of file
{
while (byteCounter != 0)
{
// Add 0xFF to the remaining bytes in this last block of 256
buffer256[byteCounter++] = 0xFF;
// Add byte to checksum
hexChecksum ^= 0xFF;
if (byteCounter >= 255)
{
byteCounter = 0;
// Convert checksum to a byte value
hexChecksum = hexChecksum ^ 0xFF;
byte csByte = Convert.ToByte(hexChecksum);
Byte[] csByte_arr = BitConverter.GetBytes(csByte);
// Send byte array
_serialPort.Write(buffer256, 0, 256);
// For testing
// Console.WriteLine("block number [{0}]", ++blockCount);
//send checksum
_serialPort.Write(csByte_arr, 0, 1);
//Receive ACK byte
byte_read = _serialPort.ReadByte();
Console.WriteLine("block/ACK = [{0}] | {1}", ++blockCount, byte_read);
if (byte_read == NACK)
{
// ??
}
}
}
}
// end ELSE if TYPE == 1
counter++;
} // end WHILE loop for loading hex file
file.Close();
// For testing
// Console.WriteLine("File is closed.");
// System.Console.WriteLine("There were {0} lines.", counter);
// Console.WriteLine("");
// -- end WRITE MEMORY ------------------------------------------------------
} // end WriteNewAppToFlash
private void handleAppSerialError(IOException exc)
{
throw new NotImplementedException();
}
private void raiseAppSerialDataEvent(byte[] received)
{
throw new NotImplementedException();
}
public void JumpToNewApp(SerialPort _serialPort)
{
int byte_read = 0;
long checksum = 0;
var ff = new byte[] { 0xFF };
int baseAddress = 0x08000000;
// Jumps to flash memory 0x08000000, where the sector 0 code will perform a normal startup
// Send 0x21 ( GO ) and complement 0xDE
var go = new byte[] { 0x21 };
var de = new byte[] { 0xDE };
while (byte_read != 0x79)
{
_serialPort.Write(go, 0, 1);
_serialPort.Write(de, 0, 1);
// Receive ACK byte
byte_read = _serialPort.ReadByte();
if (byte_read == NACK)
{
//Console.WriteLine("NACK received for GO COMMAND start");
//Console.WriteLine("");
}
}
// -- end SEND GO COMMAND and wait for ACK -----------------------------------------
Byte[] startAddr = BitConverter.GetBytes(baseAddress);
// Reset Checksum and XOR address
checksum = 0;
foreach (byte b in startAddr)
{
checksum ^= b;
}
Byte[] cheksum = BitConverter.GetBytes(checksum);
// Send first byte (msb) of address
_serialPort.Write(startAddr, 3, 1);
// Send second byte of address
_serialPort.Write(startAddr, 2, 1);
// Send third byte of address
_serialPort.Write(startAddr, 1, 1);
// Send last byte (lsb) of address
_serialPort.Write(startAddr, 0, 1);
_serialPort.Write(cheksum, 0, 1);
Thread.Sleep(20);
// Receive ACK byte
byte_read = _serialPort.ReadByte();
} // end JUMPTONEWAPP
// Converts a string to a byte array
public static IEnumerable<byte> GetBytesFromByteString(string str)
{
for (int index = 0; index < str.Length; index += 2)
{
yield return Convert.ToByte(str.Substring(index, 2), 16);
}
}
protected void AssertOpenPort()
{
// if( !IsOpen )
// throw new InvalidOperationException("Serial Port is not open");
}
} // end public class FWupdater
If you are looking for real progress, then your updater will need to raise progress as it goes. You can raise events out of updater, and subscribe to them from within worker_DoWork, and use ReportProgress to marshal it back to the UI thread for progress report:
void worker_DoWork(object sender, DoWorkEventArgs e)
{
updater.Progress += updater_Progress;
try {
updater.updater();
} finally {
updater.Progress -= updater_Progress;
}
}
void updater_Progress(object sender, ProgressEvents evt) {
worker.ReportProgress(evt.Percent);
}
This of course requires you to create a Progress event in your Updater class and to invoke that event as your updater method does its work.
BackgroundWorker does two things for you:
Lets you run a task in a background thread so your UI thread stays responsive
Lets you easily marshal progress from the background thread to the UI thread without having to use Form.Invoke.
The DoWork event fires in a background thread. Everything in that event handler happens in order, like normal code-- while your UI thread happily continues operating. If you want fake progress, you would do the progress updating with a timer callback from the UI thread, while the BackgroundWorker runs your updater code in the background
The question was to get updates DURING the upgrade, so it makes sense to send out changes in percentage from the program doing the work you are trying to measure. The part I was missing was supplied by #mjwills - passing the BackgroundWorker as a parameter to the updater allowed me to call ReportProgress from the updater and increment the percentage value as I wished.
I used the BackgroundWorker (bw) set-up pretty much as shown in MSDN. Here are the methods for the bw, which I placed in my form class.
BackgroundWorker bw = new BackgroundWorker();
Then a button click event (shows end of method) when the client has the COM port and upgrade file selected, followed by the bw methods.
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
bw.WorkerReportsProgress = true;
bw.RunWorkerAsync();
pbar.Maximum = 100;
pbar.Minimum = 0;
pbar.Value = 0;
// Percentage will be added to the end of this line during the upgrade
updateMsg.Content = "Upgrade in progress... ";
}
}
void bw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker bw = sender as BackgroundWorker;
updater.updater(bw);
}
void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
pbar.Value = e.ProgressPercentage;
updateMsg.Content = String.Format("Upgrade in progress... {0} %", e.ProgressPercentage);
}
void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
updateMsg.Content = "Upgrade Complete. Exit window to proceed...";
}
On the updater() side, define a percentage variable:
public int percentage;
Add the BackgroundWorker as a parameter:
public void updater( BackgroundWorker bw ) { <code> }
Then call ReportProgress to update the ProgressPercentage event in the bw_ProgressChanged method. Start at 0% and increment the percent variable:
bw.ReportProgress(percentage += 5);
Later on, I change the update to single percents while writing many blocks of data to flash memory:
// update progress bar in backgroundWorker thread
if ( blockCount % 10 == 0)
{
bw.ReportProgress(percentage++);
}
I would like to thank everyone for their input and I hope this answer saves someone writing an extra thousand lines of code. I hope to receive feedback on this answer and I am still interested in alternative, better solutions.
You can solve it like this
BackgroundWorker worker;
public void Init()
{
worker = new BackgroundWorker();
worker.DoWork += Worker_DoWork;
worker.ProgressChanged += Worker_ProgressChanged;
worker.WorkerReportsProgress = true; // This is important
worker.RunWorkerAsync();
}
private void Worker_DoWork(object sender, DoWorkEventArgs e)
{
// Do your update progress here...
for (int i = 0; i <= 100; i++) // This simulates the update process
{
System.Threading.Thread.Sleep(100);
worker.ReportProgress(i); // Report progress from the background worker like this
}
}
private void Worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// Update the progress bar or other ui elements here...
// Use the e.ProgressPercentage
}
It is absolutely ok to run the background worker for a longer period of time. I have never experienced any problems with it, even when having one running at all time.
I'm a C# programmer and I know nothing about hardware protocol things.
today I received a document that is some protocols of a Lock hardware, like this:
lock command
start 0x80
board address 0x01-0xf
lock address 0x00-18
command 0x33
verify code XX
sample:
machine send 0x80 0x01 0x01 0x33 0xB2
if recieve 0x80 0x01 0x01 0x01 0x91 (means unlock)
if receive 0x80 0x01 0x01 0x00 0x80 (means locked)
All I want to know is, if C# can handle these commands? if can, where I can have a quick start, or what should I search on google?
Thanks.
Yes. C# can handle this. This is call polling. Basically, the idea is
send command, receive reply, substring information that you need (in your case, most probably the last 2 bytes) and perform some function on it. I'm not sure if you understand but I'll give you an example on something I've done previously but this is event where data is send to machine whenever event is triggered.
public enum Transaction
{
LOCK = 0x01,
UNLOCK
};
private static string getTransactionDescription(Transaction transaction, string data = "")
{
string result = "";
switch (transaction)
{
case Transaction.UNLOCK:
case Transaction.LOCK:
var slot = ByteOperation.reverse4ByteBitPattern(data.Substring(32, 64));
for (int i = 8 - 1; i >= 0; i--)
{
for (int j = 0; j < 8; j++)
{
if ((Convert.ToInt32(ByteOperation.ToggleEndian_4Bytes(slot.Substring(i * 8, 8)), 16) & (1 << j)) > 0)
{
if (!string.IsNullOrWhiteSpace(result))
{
result += ", ";
}
result += "Slot " + (((7 - i) * 8) + j + 1).ToString("D3");
}
}
}
break;
}
return result;
I'm working on Serialport. I'm facing a new problem that once I receive data my data are incomplete. How can I check if my data are complete then process them, and if not, don't process them?
Here are my data receive and my send function:
private void Send(byte[] cmd)
{
bResponse = new byte[0];
Write(cmd);
}
void comPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
int iCount = comPort.BytesToRead;
byte[] bBuffer = new byte[iCount];
comPort.Read(bBuffer, 0, iCount)
if (bBuffer.Length == 1 && bBuffer[0] == ACK)
Write(new byte[] { ENQ });
else if (bBuffer.Length == 1 && bBuffer[0] == NAK)
{
Debug.WriteLine("Incomplete Message detected!");
}
else
{
bResponse = bResponse.Concat(bBuffer).ToArray();
rResponse = Decode(bResponse);
Write(new byte[] { ACK });
}
}
I know my data are received in a few packages and I need to wait until the response is complete, but I don't know based on the code above. How should I check whether the data are complete to determine whether to wait? (P.S: The size of the received response varies.)
There is no built-in concept of completeness or packet size.
You'll have to append to a buffer until you see some recognizable end-of-packet pattern that you (or someone else) defined as part of the protocol specification. - And then probably time out after a while if you haven't seen what you are looking for.
Example of old project, notice the firstindex, lastindex, you put in a character to know the length, the start/end character is predefined and can be any character you choose, just be sure not to take any common characters
This is for tcp/ip, but same principle can be used for serialport
public void ReceiveMessage(IAsyncResult ar)
{
int bytesRead;
try
{
lock (client1.GetStream())
{
bytesRead = client1.GetStream().EndRead(ar);
}
string messageReceived = System.Text.Encoding.ASCII.GetString(data, 0, bytesRead);
received = messageReceived;
int firstindex = received.IndexOf((char)2);
int lastindex = received.IndexOf((char)3);
if (firstindex > 0 && lastindex > 0)
{
string first = received.Substring(firstindex, 1);
string last = received.Substring(lastindex, 1);
}
lock (client1.GetStream())
{
client1.GetStream().BeginRead(data, 0, System.Convert.ToInt32(client1.ReceiveBufferSize), ReceiveMessage, null);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
I have some code for you.
First, you implement the DataReceived Event (as you have done already). This event is only called when there is data to process. While I would not call it interrupt-based (as in "realtime capable") is is definitely not polling. Which is good.
Second: When the event is called you may have only one byte, but there may be more bytes. To capture each packet you need to implement an custom buffer.
Third: After you append one byte to your buffer, you check whether the buffer contains a valid packet. If so, process it. If not: Append another one. If no bytes are left, wait for the event to be called again.
In code it looks like this:
const BUFFERLENGTH = 17; // Bytes
byte[] buffer = new byte[BUFFERLENGTH];
private void COM_Port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
var port = (SerialPort)sender;
while (port.BytesToRead > 0)
{
var data = (byte)port.ReadByte();
Read(data);
}
}
private void Read(byte value)
{
// Append Byte to buffer
System.Buffer.BlockCopy(buffer, 1, buffer, 0, BUFFERLENGTH- 1);
buffer[BUFFERLENGTH - 1] = value;
// Check for valid Packet
if (IsValidPacket(buffer))
{
// Yeah! Gotcha :-)
// Now copy your Packet from the Buffer to your struct/whatever
}
}
private bool IsValidPacket(byte[] buffer)
{
// Todo: Check whether buffer contains a valid Packet.
// Some think like:
// return buffer != null && buffer[0] == 0xF4 && buffer[2] == buffer.length
throw new NotImplementedException();
}
Note that I did not "append the byte to the buffer". I discarded the first byte, shifted every byte by one position and inserted the new byte at the end. If a valid Packet was found I could just copy it in one block into a struct. So the buffer size is always constant and exactly as long as one packet.
This may not be the fastest code out there (because it's reading each byte separately) but it works well for me :-)
Oh, and remember to use Begininvoke() if you want to display that stuff in your GUI.
I have a form in which I am able to receive data and show it in a richtextbox, but what I need is to read the data that is coming continuously from serial port and decode accordingly.
For ex: I am receiving data in bytes in the format as 36 0 0 0 1 0 0...., 36 is used to indicate start of frame n rest are the data through which an event will be fired.
My code:
private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
// get number off bytes in buffer
Bytenumber = serialPort1.BytesToRead;
// read one byte from buffer
ByteToRead = serialPort1.ReadByte();
this.Invoke(new EventHandler(DoUpdate));
}
Above code is used to receive data and fire an event. The code for the event is as follows:
int w=0;
public void DoUpdate(object sender, System.EventArgs e)
{
byte[] t = new byte[Bytenumber];
for(int g=0; g<Bytenumber;g++)
{
t[g] = Convert.ToByte(ByteToRead);
}
w++;
// richTextBox1.Text += ByteToRead;
if (ByteToRead == 36)
{
for (int r = 0; r <= 73; r++)
{
if (ByteToRead == 0x01)
{
timer1.Start();
w++;
}
}
}
}
In the data received event handler I am looking for 36 (i.e., start of frame) once I get that I am looking for 1s from the buffer. The problem is when I get 36 (i.e., start of frame) the same data is retained in the if loop and tries to compare with 1 which will not be true # any case. All I need is to read the next bytes of data coming from the buffer once I get 36.
I can spot a few problems. A little code-review:
Bytenumber = serialPort1.BytesToRead;
ByteNumber is the Bytes-to-Read at this moment. It is not thread-safe to keep this in a member field.
ByteToRead = serialPort1.ReadByte();
This only reads 1 Byte. And then, on another thread:
byte[] t = new byte[Bytenumber]; // ByteNumber may have changed already
for(int g=0; g<Bytenumber;g++)
{
t[g] = Convert.ToByte(ByteToRead); // store the _same_ byte in all elements
}
What you should do (not complete code):
private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
// get number off bytes in buffer
int n = serialPort1.BytesToRead;
byte[] buffer = new byte[n];
// read one byte from buffer
int bytesToProcess = serialPort1.Read(buffer, 0, n);
this.Invoke(UpdateMethod, buffer, bytesToProcess);
}
But do search the internet for working code. I just made this up.
I'm receiving packets over COM port. Each packet begins with {0xFF, 0xFF} and ends with {0xFE, OxFE}. All received bytes are queued in Queue<byte> and after each void port_DataReceived(object sender, SerialDataReceivedEventArgs e) I'm processing that queue.
If any 0xFF or 0xFE shows up in packet, device add 0x00 after it.
How to extract each packet?
How to delete unnecessary 0x00 inside each packet that have header byte inside?
For the first issue I have:
void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
byte[] data = new byte[port.BytesToRead];
try
{
port.Read(data, 0, data.Length);
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
data.ToList().ForEach(newByte => receivedData.Enqueue(newByte));
processData();
}
private void processData()
{
// Determine if we have a "packet" in the queue
if (Enumerable.SequenceEqual(receivedData.Take(2), new List<byte> { 0xFF, 0xFF }))
{
// Beginning of new packet in the front of queue is ready!
if (Enumerable.SequenceEqual(receivedData.Skip(Math.Max(0, receivedData.Count() - 2)).Take(2), new List<byte> { 0xFE, 0xFE }))
{
List<byte> tempPacket = new List<byte>();
// Whole packet in the queue
while(receivedData.Count > 0)
tempPacket.Add(receivedData.Dequeue());
tempPacket.TrimExcess();
Packet pack = new Packet(tempPacket, PacketOrigin.Serial);
}
}
}
I'm trying to remove all 0x00 that are after any 0xFE and 0xFF that can be found inside Queue<byte> so far I came up with:
List<byte> unconvertedPacket = new List<byte> { 0xFF, OxFF, 0x00, 0x00,0x4D, 0xFA 0xFE, 0x00, 0x01, 0x00, 0x03, 0xFE, 0xFE}
int index = 0;
while (index != null)
{
unconvertedPacket.RemoveAt(index + 1);
index = unconvertedPacket.IndexOf(0xFE);
}
while (index != null)
{
unconvertedPacket.RemoveAt(index + 1);
index = unconvertedPacket.IndexOf(0xFF);
}
Does anyone have maybe any other solution/advice for doing it?
Try the following approach:
In the DataReceived event handler keep reading the incoming data and append it to a buffer (byte[]).
First you need to find the start marker ({0xFF, 0xFF}) in the buffer of received data. You need to determine the index of this marker within the buffer.
Once you have the start index, you need to keep appending incoming data to the buffer and check if the end marker (0xFE, 0xFE) has arrived. Capture the index of the end marker within the buffer.
Once you have the start and end index you can extract the packet between them. Nevermind about the extra 0x00 byte that gets added after it. You know the index of the start and end marker and their length (2). Just extract the array of bytes between them.
You need to create a search algorithm to suit this purpose. Both the needle and the haystack are an array of bytes (byte[]). You can use the Boyer-Moore string search algorithm for this purpose.
Here's a simple C# implementation of the Boyer-Moore algorithm which only implements the bad character rule. Read up on Wikipedia if you also want to implement the good suffix rule.
The algorithm is normally intended for strings, but I modified it to work with byte arrays. Tested this locally with an IP camera to extract the received JPEG images.
Check out the Wikipedia article for more information about it. It contains a full Java implementation which you can easily translate to C#.
public class BoyerMoore
{
public static int IndexOf(byte[] needle, byte[] haystack)
{
if (needle == null || needle.Length == 0)
return -1;
int[] charTable = CreateCharTable(needle);
for (int i = needle.Length - 1, j; i < haystack.Length;)
{
for (j = needle.Length - 1; needle[j] == haystack[i]; i--, j--)
{
if (j == 0)
return i;
}
i += charTable[haystack[i]];
}
return -1;
}
private static int[] CreateCharTable(byte[] needle)
{
const int ALPHABET_SIZE = 256;
var table = new int[ALPHABET_SIZE];
for (int i = 0; i < table.Length; i++)
{
table[i] = needle.Length;
}
for (int i = 0; i < needle.Length - 1; i++)
{
table[needle[i]] = needle.Length - 1 - i;
}
return table;
}
}
Example usage:
var haystack = new byte[]
{0xFF, 0xFF, 0x00, 0x00, 0x4D, 0xFA, 0xFE, 0x00, 0x01, 0x00, 0x03, 0xFE, 0xFE};
var startIndexOf = BoyerMoore.IndexOf(new byte[] {0xFF, 0xFF}, haystack);
var endIndexOf = BoyerMoore.IndexOf(new byte[] {0xFE, 0xFE}, haystack);
var packet = new byte[endIndexOf - 2 - startIndexOf];
for (int i = startIndexOf + 2, j = 0; i < endIndexOf - startIndexOf; i++, j++)
{
packet[j] = haystack[i];
}
Voila, the packet byte array contains 9 bytes in this example and only contains the bytes between the start and end marker. You can now trigger an event and pass the packet as an event arg for example.
Remark: Receiving the data from the COM port is a continuous event. You need to keep monitoring it. Keep appending the received data and keep checking for the start -and index markers, extract the packages...etc. Watch out that your buffer does not overflow. You need to implement some housekeeping there.
Hope it helps. Check out AForge implementation of an MJPEGStream for an example of continuously reading incoming data.
To recapitulate:
Declare an instance variable to store the received data (e.g. _buffer = new byte[4096]).
Append the incoming data to the buffer in the DataReceived event handler.
Search for the start marker. If found remember the start index in an instance variable.
If you already know the position of the start marker, then search for the index of the end marker.
When you find the end marker, extract the packet and fire an event. Use the packet as part of the event's EventArgs.
Wash, rinse, repeat.
You need to implement some housekeeping to make sure the buffer won't overflow (> 4096 bytes). For instance once you find a packet you can clean up the buffer up until the last received end marker.