0

I'm new to C# WPF and I have a problem while running my program. My program freezes if my serial port (xbee on it) can't receive the required data in a specific time (Dispatcher).

So, I am trying to solve it by making a second window that will be actively waiting for a data to lessen the load of the main UI window. The problem I am experiencing is that I can't run the second window synchronously with my main window.

Any tips?

MAIN UI

public partial class Window5 : Window
{
    SerialPort Senport = new SerialPort("COM4", 9600, Parity.None, 8, StopBits.One);
    int flagger=0;
    int fflagger=0;
    DispatcherTimer timer1 = new DispatcherTimer(); //Feed Timer
    DispatcherTimer timer2 = new DispatcherTimer();//Disabler Timer during Feeding
    DispatcherTimer timer3 = new DispatcherTimer();//Disabler Timer during Drinking
    DispatcherTimer timer4 = new DispatcherTimer();//Disabler Timer during Cleaning
    DispatcherTimer timer5 = new DispatcherTimer();// Serial Port Data Receiver Timer
    DispatcherTimer timer6 = new DispatcherTimer();//
    DispatcherTimer timer7 = new DispatcherTimer();
    public Window5()
    {   
        InitializeComponent();
        new Window2();
        timer1.Interval = TimeSpan.FromSeconds(300);
        timer2.Interval = TimeSpan.FromSeconds(15);
        timer3.Interval = TimeSpan.FromSeconds(15);
        timer4.Interval = TimeSpan.FromSeconds(60);
    }
    void timer_Tick(object sender, EventArgs e)
    {
        FEED.IsEnabled=true;
        timer1.Stop();
    }

    void FEED_Click(object sender, RoutedEventArgs e)
    {
        fflagger=1;
        flagger=1;
        Sender();
        timer1.Start();
        timer2.Start();
        Disabler();
        MessageBox.Show("Feeds Dispensing is starting","Drinking Water Process",MessageBoxButton.OK,MessageBoxImage.Information);
        timer1.Tick +=timer_Tick;
        timer2.Tick +=Enabler;
        // Xbee Code Will be Here
    }
    void FEED2_Click(object sender, RoutedEventArgs e)
    {
        flagger=2;
        Sender();
        timer3.Start();
        Disabler();
        MessageBox.Show("Water Dispensing is starting", "Drinking Water Process",MessageBoxButton.OK,MessageBoxImage.Information);
        timer3.Tick +=Enabler;
        // Xbee Code Will be Here
    }
    void Clean_Click(object sender, RoutedEventArgs e)
    {
        //Window4 w4 = new Window4();
        flagger=3;
        Sender();
        timer4.Start();
        Disabler();
        MessageBox.Show("Cleaning Process is starting", "Cleaning Process",MessageBoxButton.OK,MessageBoxImage.Information);
        //w4.Show();
        timer4.Tick +=Enabler;
    }

    void CCTV_Click(object sender, RoutedEventArgs e)
    {
        Process.Start(@"C:\Program Files\ CMS 2.0\CMS");
    }


    public void Enabler(object sender, EventArgs e)
    {       
        if(flagger==1)
        {
            FEED2.IsEnabled=true;
            Clean.IsEnabled=true;
            timer2.Stop();
        }
        else if(flagger==2 && fflagger==0)
        {
            FEED.IsEnabled=true;
            FEED2.IsEnabled=true;
            Clean.IsEnabled=true;
            timer3.Stop();
        }
        else if(flagger==2 && fflagger==1)
        {
            FEED2.IsEnabled=true;
            Clean.IsEnabled=true;
            timer3.Stop();
        }
        else if(flagger==3 && fflagger==0)
        {
            FEED.IsEnabled=true;
            FEED2.IsEnabled=true;
            Clean.IsEnabled=true;
            timer4.Stop();
        }
        else
        {
            FEED2.IsEnabled=true;
            Clean.IsEnabled=true;
            timer4.Stop();
        }
    }

    // Function in disabling Buttons
    public void Disabler()
    {
        if(flagger == 1)
        {
            FEED.IsEnabled=false;
            FEED2.IsEnabled=false;
            Clean.IsEnabled=false;  
        }
        else if(flagger == 2)
        {
            Clean.IsEnabled=false;
            FEED.IsEnabled=false;
            FEED2.IsEnabled=false;
        }
        else
        {
            Clean.IsEnabled=false;
            FEED.IsEnabled=false;
            FEED2.IsEnabled=false;
        }           
    }

    //Function for Serial Port Sender
    public void Sender()
    {
        if(flagger == 1)
        {
            try
            {
                if (!(Senport.IsOpen == true)) Senport.Open();
                Senport.Write("AB");
            }
            catch {}
        }

        else if(flagger == 2)
        {
            try
            {
                if (!(Senport.IsOpen == true)) Senport.Open();
                Senport.Write("BC");
            }
            catch {}
        }
        else if(flagger == 3)
        {
            try
            {
                if (!(Senport.IsOpen == true)) Senport.Open();
                Senport.Write("CD");
            }
            catch {}
        }       
        Senport.Close();    
    }
}

Window 2

 public partial class Window2 : Window
 {
     SerialPort Senport = new SerialPort("COM4", 9600, Parity.None, 8,StopBits.One);
     DispatcherTimer timer1 = new DispatcherTimer(); //Feed Timer
     DispatcherTimer timer2 = new DispatcherTimer();//Disabler Timer during Feeding
     DispatcherTimer timer3 = new DispatcherTimer();//Disabler Timer during Drinking
     DispatcherTimer timer4 = new DispatcherTimer();//Disabler Timer during Cleaning
     DispatcherTimer timer5 = new DispatcherTimer();// Serial Port Data Receiver Timer
     DispatcherTimer timer6 = new DispatcherTimer();//
     DispatcherTimer timer7 = new DispatcherTimer();
     string rdata;
     string rdata1;
     public Window2()
     {
         InitializeComponent();
         Window5 WORK1 = new Window5();
         while(true)
         {

             if (!(Senport.IsOpen == true)) Senport.Open();
             rdata= Senport.ReadLine();
             rdata1.ToString();
             rdata1 = rdata;
             Senport.Close();

             if(rdata1 == "FEED")
             {
                 MessageBox.Show("Feeds already being dispense!", "Feeding Process",MessageBoxButton.OK,MessageBoxImage.Information);
             }

             if(rdata1 == "DRINK")
             {
                 MessageBox.Show("Drinkable water is dispense!", "Drinking Water Process",MessageBoxButton.OK,MessageBoxImage.Information);
             }  

             if(rdata1 == "CLEAN")
             {
                 MessageBox.Show("Cleaning the cage is done!", "Cleaning Process",MessageBoxButton.OK,MessageBoxImage.Information);
             }              
         }
     }
}
Luis Lavieri
  • 4,064
  • 6
  • 39
  • 69
  • 1
    I would use a BackGroundWorker instead of using a different window. – jdweng Jan 25 '17 at 17:48
  • 1
    @jdweng Or fire up a DispatchTimer that periodically checks the serial port to see if any new data has arrived... – Lynn Crumbling Jan 25 '17 at 18:47
  • Polling serial port with a time should never be used. Use an event instead. – jdweng Jan 25 '17 at 18:52
  • Create a program outside of WPF, say a console application but keep the code separated say in a separate class from the console code. Once that one is running, then try to move the operations into a WPF app. Your problem is you at doing work (reading the port) on the GUI thread and it is freezing the app. By going to a console app, you will be able to separate the work process from the graphical process. Once that is done, migrate back the work process only into a new WPF app. – ΩmegaMan Jan 25 '17 at 19:21
  • Your code contains a large amount of garbage. Half of your timers are never used, WORK1 is never used. The line "rdata1.ToString()" does nothing. Please clean up the code samples you post here, if only as a sign of respect. – Guillaume CR Jan 25 '17 at 20:40
  • @jdweng Im still new with this C# programming WPF. I want my program always active in receiving data from my Arduino (XBee module) – Rkp Conson Visda Jan 26 '17 at 03:59
  • Creating a 2nd window is not necessary. Creating a class with a backgroundworker accomplishes the same without the windows overhead. Running a backgroundworker Async creates a new process. Putting the backgroundworker in a separate window creates two new processes. – jdweng Jan 26 '17 at 10:15

1 Answers1

3

Event based programming is largely superior to timer-based and blocking code. There are many levels of event based programming, from synchronous to asynchronous to full-fledged TPL. In your case, I would recommend starting by thoroughly reading the SerialPort documentation, and in particular the DataReceived event.

Adding your second UI is a large amount of unnecessary complexity.

Here's a basic example of how you can use SerialPorts with a state machine:

public class SerialPortTests
{
    //Your states are pretty obscure to me, I'm making those up.
    private enum States
    {
        State1,
        State2,
        State3
    }

    private States _state = States.State1;
    private SerialPort _port = new SerialPort(/*Enter your port's config here*/);

    public SerialPortTests()
    {
        _port.DataReceived += dataReceived; //This is the important line
        _port.Open();
    }

    private void dataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        var sendingPort = (SerialPort)sender;
        var data = sendingPort.ReadExisting(); //Careful, you may have more than 1 line in data.
        var dataLines = data.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
        foreach (var line in dataLines)
        {
            verifyState(line);
            processLine(line);
        }
    }

    private void verifyState(string line)
    {
        //Your states are pretty obscure, I'm just making things up here.
        if (line == "FEED" && _state != States.State1)
        {
            //Handle the error if you can, or just throw to learn more about the problem in the stack trace.
            throw new ApplicationException("received FEED while in state " + _state);
        }
    }

    private void processLine(string line)
    {
        if (line == "FEED")
        {
            //Don't use MessageBox unless you really have to. Change a label's text or something.
            Console.WriteLine("Feeds already being dispense!");
            _state = States.State2;
        }
    }  
}

Essentially, the SerialPort class is capable of raising an event when it knows there is more data to read. You can subscribe to that event. In C#, that's called "handling" and it's done on this line:

_port.DataReceived += dataReceived; //This is the important line

This line means "everytime the port raises the DataReceived event, execute the private void dataReceived(...) function that I declared further down".

Your problem was that you used SerialPort.ReadLine() in a while(true) loop. That is almost always a bad idea. SerialPort.ReadLine() is a blocking call, as the documentation tells you. Your code will stop until it has read the NewLine characters from the COM port. If those characters don't come, your program will freeze forever. By using the "DataReceived" event, you are guaranteed that there is data to read, so even though I still call ReadExisting (which is also a blocking call) I already know that there will be data and that the line will execute and return very quickly. For most cases, it will be so quick that your UI will not freeze long enough for anyone to notice.

This is the lowest level event based programming. If you still see freezes, you have to use multi-threading, and that is very complex. Use it only if it's strictly necessary.

The rest of the code doesn't have anything to do with SerialPorts. It's there because you use timers to simulate a state machine, and that's also a bad idea. DataReceived will be fired when any data is received, regardless of the data, so it's important to keep track of your state to compare it with the incoming data. If you are expecting "FEED" (ie. your program is in State1, or StateFeed) and you receive "DRINK" then something went wrong and you have to do something about it. The least you can do when things go wrong is to throw an exception. Once you understand what went wrong, you can start adding code that handles exceptions gracefully.

Guillaume CR
  • 3,006
  • 1
  • 19
  • 31
  • its a nice code. but sadly I cant understand it all XD – Rkp Conson Visda Jan 26 '17 at 04:02
  • As what i told you, im still new with this programming langguage – Rkp Conson Visda Jan 26 '17 at 04:02
  • 1
    @RkpConsonVisda a line by line explanation is probably out of scope for StackOverflow. Try to go through each section, and then ask me specific questions about the parts you do not understand. I will gladly help you. – Guillaume CR Jan 27 '17 at 15:47
  • 1
    @RkpConsonVisda I expanded my answer a lot to give additional detail. Let me know if it's still unclear. – Guillaume CR Jan 27 '17 at 16:03
  • thank you for explaining this thoroughly, but I have to ask if I implement this sample code, is the Data receiving always on? and My Data sending will only active during when I click my buttons? – Rkp Conson Visda Jan 28 '17 at 06:20
  • can you help me in implementing the code youve given to my code? I just really need an active receiver that doesnt block or freeze my UI – Rkp Conson Visda Jan 30 '17 at 09:40
  • @RkpConsonVisda Yes Data Received is always ON. You can start and stop this behaviour by opening and closing the COM port. I've given you all you need to solve your problem, at this point you either need to apply my technique until you figure it out or find a programmer. – Guillaume CR Jan 31 '17 at 14:44
  • Ok I will try to understand this code. One more question, is this Class will be putted as #include ? or just simply put it inside the main window? – Rkp Conson Visda Jan 31 '17 at 15:24