Main Site Documentation

Gadgeteer.Timer problem?


#1

Hi,

I’ve received my Fez Spider starter kit a few days ago and I’m trying some things to see how everything works. The Fez Spider is running firmware 4.1.8.0, so that should be the latest version.

My question is about the code below: why does timer t1 work and timer t2 not?
It seems like there is a problem with Gadgeteer.Timer… or am I doing something wrong here?


using System;
using System.Collections;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Presentation;
using Microsoft.SPOT.Presentation.Controls;
using Microsoft.SPOT.Presentation.Media;
using Microsoft.SPOT.Touch;

using Gadgeteer.Networking;
using GT = Gadgeteer;
using GTM = Gadgeteer.Modules;
using Gadgeteer.Modules.GHIElectronics;

namespace GadgeteerTestTimer
{
    public partial class Program
    {
        // This method is run when the mainboard is powered up or reset.   
        void ProgramStarted()
        {
            Timer t1 = new Timer(new TimerCallback(timer_Tick), null, 100, 1000);
            
            GT.Timer t2 = new GT.Timer(1200,GT.Timer.BehaviorType.RunContinuously);
            t2.Tick += new GT.Timer.TickEventHandler(t2_Tick);
            t2.Start();
            
            // Use Debug.Print to show messages in Visual Studio's "Output" window during debugging.
            Debug.Print("Program Started");

            Thread.Sleep(Timeout.Infinite);
        }

        void t2_Tick(GT.Timer timer)
        {
            Debug.Print("Tick2");
        }

        void timer_Tick(object o)
        {
            Debug.Print("Tick1");
        }

    }
}


#2

No problem with Gadgeteer timers… :wink:

For events to work, you should not have a Thread.Sleep in your ProgramStarted method. After you do your initialization just return. ProgramStarted in not the same as main() in Gadgeteer.

Time t1 worked because it used a callback rather than an event.


#3

Think of ProgramStarted method as just another event handler. It gets called when the application is ready for you to set up your basic program operation. Once you’re done with the setup, you just return from this handler and the application continues running. Among other things, it starts up its message pump.

Since you put your main application thread to sleep in this initialization handler, the application never gets to continue onto starting the message pump. Therefore, your timer events never get handled.


#4

Aha, it works now.

In all the examples that I saw, there was either an infinite while loop or a Thread.Sleep, so I just assumed that was necessary.

Thank you!


#5

yeah, that’s the non-gadgeteer way (and the correct way if you use a non-Gadgeteer app for any of the other Fezzez with a MAIN() as the core of your application).


#6

My first experience with Gadgeteer and my first frustration. Event though i remembered about not locking the main method with Thread.Sleep() i spent over an hour figuring out why the new XBee driver does not work on Hydra and works on Cobra. Finally when i narrowed it down i found the bug. When developing on FEZ Cobra I was using pure SerialPort and reset events in order to get a blocking call (sending data and returning the response). When doing the same on Hydra using the Gadgeteer serial class you will not get the same result. Look at the code below. To check the difference uncomment one or the other serial port initialization.

using System.IO.Ports;
using System.Threading;
using Gadgeteer;
using Microsoft.SPOT;
using Serial = Gadgeteer.Interfaces.Serial;

namespace GadgeteerApp1
{
    public partial class Program
    {
        void ProgramStarted()
        {
            var dataReceivedFlag = new ManualResetEvent(false);
            var data = new byte[] { 1, 2, 3, 4 };
            var socket = Socket.GetSocket(4, true, null, null);
            
            var serial = new Serial(socket, 9600, Serial.SerialParity.None, Serial.SerialStopBits.One, 8, Serial.HardwareFlowControl.NotRequired, null);
            
            //var serial = new SerialPort("COM3", 9600);

            serial.Open();

            serial.DataReceived += (s, d) =>
            {
                Debug.Print("DataReceived event raised!");
                dataReceivedFlag.Set();
            };

            serial.Write(data, 0, data.Length);

            if (!dataReceivedFlag.WaitOne(1000, false))
                Debug.Print("Error - DataReceived event was not raised...");
        }
    }
}


#7

I forgot to add that the example above requires you to do a serial loopback on hydra socket nr 4. I used MakeBread module from ransomhall and connected pin 3 with 4.


#8
if (!dataReceivedFlag.WaitOne(1000, false))
                Debug.Print("Error - DataReceived event was not raised...");

This code blocks Gadgeteer event handling the same as Thread.Sleep(), so you will have a problem.

You must exit from ProgramStarted method for Gadgeteer event handling to work properly.


#9

@ Mike, you are correct. The problem is i was not thinking about serial events as something that can be blocked. So if i want to offer and API with blocking methods (as in this example, sending request and returning response) it’s impossible using the Gadgeteer serial class. Something like in Silverlight (at least the first versions) where you have to do all I/O calls async.


#10

If you want to use blocking API in Gadgeteer then you can use a thread.


#11

Even if I exit the ProgramStarted method and run the code in e.g. timer callback the result will be the same. The data received event of gadgeteer serial port runs on the main thread.

Ok, so how would you correct this example to make a blocking call ? I don’t know if this would work: create a thread that would create the serial object so that it’s data received events are not fired on main thread ??


#12

Currently the serial port will be closed when you leave the ProgramStarted method. You need to move the serial port variable out of the ProgramStarted method and make it a class variable. Also the same for the ManualReset object. Actually, I usually use an automatic reset event since there is only one thread waiting on it. Also, you will need to move your data buffer to a class variable.

I would then start a thread which would wait on the event.

When a receive event occurs, I would then store the data and set the event. That would wake up the thread waiting on the event.

BTW, What are you trying to implement? A producer/consumer pattern?


#13

I guess if you change the last lines to this it will work:

            var thread = new Thread(() =>
            {
                if (!dataReceivedFlag.WaitOne(1000, false))
                    Debug.Print("Error - DataReceived event was not raised...");
            });

            thread.Start();

I’m want the XBee API to behave the same on PC/.NET MF/Gadgeteer. It requires this to work e.g.:

var request = xbee.Send(“TEST”).To(address);
var response = request.GetResponse();

The code above is running inside some gadgeteer event so it blocks the main thread that also processes incoming data. GetResponse blocks on reset event until it gets a signal from an internal thread that parses the incoming data. The internal thread won’t receive eny data because the main thread is blocke. We get a deadlock. It would work with the same approach as in the first example - creating a thread and running the code inside a thread. I don’t think the end-user will remember. Also it looks bad…


#14

I would develop a class which presents the XBee interface(not c# interface). It would have a method for submitting a request. This request would contain the request data and a delegate reference(callback). When the XBee class finishes the request, it could send the results back via the callback.

Internally, to the XBee class, you could have the blocking thread.

You could get fancy and have a queue of outstanding requests, which would be handled one at a time by the thread.


#15

Your version would work but would narrow the API. Instead of getting the result back you would always have to provide a callback and free the main thread. Even if you provide the callback and after that do some other operations your response will not be received until you exit the event handler. Not too friendly if you asked me.

On the other hand there is no problem if we just use the .NET MF SerialPort class instead. I’m sure that people that will want to try the XBee driver will write something simple in the ProgramStarted method and will not want to use any callbacks. What do you think? I have to see if we are able to get the serial port name without creating the gadgeteer version of serial class…


#16

How about something a simple as a SendMessage() method and a MessageReceived event?

Gadgeteer was designed to be event driven, not using the classical main loop. I think the XBee design should conform to the Gadgeteer paradigm.


#17

If you have some free time please check out our current API. In my opinion it’s very flexible and should fit into gadeteer event driven model. I would very much like to discuss it with you in detail after you get a better understanding of how it works right now.

There is an easy way to get the serial port name from gadgeteer socket (_socket.SerialPortName :wink: ) so I think for now we will go with this solution.


#18

Just catching up on this topic… this is good stuff!

@ Gralin - can I assume you’ll be updating something in the near future? No rush, just don’t want to duplicate effort.

@ Mike - We certainly could use more folks on the project. Some other regulars promised to help, but so far that hasn’t happened


#19

@ ransomhall: I don’t expect any revolution but who knows :wink: I will for sure update the Gadgeteer serial part so it works. If you have any uncommited changed related to this project just push them and I will include them.


#20

@ Mike, I have analyzed the code and it won’t be hard to get the delegate arg passing or events so that the response doesn not block the main thread. I would like to keep the simple blocking solution as well as it works and is easy for beginners. There might be a problem though if someone uses them in Gadgeteer… oh so many problems :wink:

BTW I think we are a little off topic here :confused: