Jump to page: 1 2
Thread overview
September 25
Hi,

I have a problem to solve that may be solved using fibers. I have no previous experience with fibers. We are working on a variable-rate weeder. A camera is installed in front of a tractor. A flame weeder is connected to the behind of a tractor. Doing image processing (on RP3), we determine a weed density rate and send a PWM signal to the LPG valve to adjust the intensity of the flame. It is working well under lab conditions so far. However, my control signal has to be continuous with a delayed time shift. Because the tractor is moving with a ground-speed, and the flame applicator will reach the scene seen by the camera after about 1.5 seconds (I cannot change the location of the camera for some bad and mandatory design decisions). My pseudo-code is like:

int main(){
    ...

    while(true){

        int pwmval = getFlameIntensityViaImageProcessing();

        sendPWMSignalToValfe(pwmval); // I need this streamed ctrl signal to the valfe with a delayed time shift

        // a possible solution:
        // enum afterNmilliseconds = 1500;

        // schedulePWMSignalToValve(pwmval, afterNmilliseconds );

        ...
    }
    ...
}

How can I implement schedulePWMSignalToValve(pwmval, afterNmilliseconds ) using fibers?

Thanks in advance.
September 25
On Friday, 25 September 2020 at 11:58:53 UTC, Ferhat Kurtulmuş wrote:
> Hi,
>
> I have a problem to solve that may be solved using fibers. I have no previous experience with fibers. We are working on a variable-rate weeder. A camera is installed in front of a tractor. A flame weeder is connected to the behind of a tractor. Doing image processing (on RP3), we determine a weed density rate and send a PWM signal to the LPG valve to adjust the intensity of the flame. It is working well under lab conditions so far. However, my control signal has to be continuous with a delayed time shift. Because the tractor is moving with a ground-speed, and the flame applicator will reach the scene seen by the camera after about 1.5 seconds (I cannot change the location of the camera for some bad and mandatory design decisions). My pseudo-code is like:
>
> [...]

A naive implementation would be to store 1500 ms worth of data with the pwm values, like a buffer. I guess memory is not a problem if you're using a RP3? Then just loop through and yield depending on your sample rate (you didn't say what it was)
September 25
On Friday, 25 September 2020 at 12:43:41 UTC, Imperatorn wrote:
> On Friday, 25 September 2020 at 11:58:53 UTC, Ferhat Kurtulmuş wrote:
>> [...]
>
> A naive implementation would be to store 1500 ms worth of data with the pwm values, like a buffer. I guess memory is not a problem if you're using a RP3? Then just loop through and yield depending on your sample rate (you didn't say what it was)

I didn't measure the entire sample rate yet, but I can say image processing (the most costly process) can be done with ~15 FPS.
September 25
On Friday, 25 September 2020 at 11:58:53 UTC, Ferhat Kurtulmuş wrote:
> int main(){
>     ...
>
>     while(true){
>
>         int pwmval = getFlameIntensityViaImageProcessing();
>
>         sendPWMSignalToValfe(pwmval); // I need this streamed ctrl signal to the valfe with a delayed time shift
>
>         // a possible solution:
>         // enum afterNmilliseconds = 1500;
>
>         // schedulePWMSignalToValve(pwmval, afterNmilliseconds );
>
>         ...
>     }
>     ...
> }
>
> How can I implement schedulePWMSignalToValve(pwmval, afterNmilliseconds ) using fibers?
>
> Thanks in advance.

No need for fibers per se.

You can run 2 threads. One that produces {time: now + 1500.msecs, value: getFlameIntensityViaImageProcessing} objects and one that consumes those and basically waits until each's msg.time < now and then sendPWMSignalToValfe(msg.value). You would basically rely on std.concurrency's MessageBox to do the queuing. Although you could do that manually as well.

Could also run it on 1 thread if you don't mind there being a jitter of however long getFlameIntensityViaImageProcessing takes, but you will need a queue.
September 25
On Friday, 25 September 2020 at 13:08:16 UTC, Sebastiaan Koppe wrote:
> On Friday, 25 September 2020 at 11:58:53 UTC, Ferhat Kurtulmuş
>> How can I implement schedulePWMSignalToValve(pwmval, afterNmilliseconds ) using fibers?
>
> No need for fibers per se.

Can also use https://code.dlang.org/packages/timingwheels
September 25
On Friday, 25 September 2020 at 13:08:16 UTC, Sebastiaan Koppe wrote:
> On Friday, 25 September 2020 at 11:58:53 UTC, Ferhat Kurtulmuş wrote:
>> [...]
>
> No need for fibers per se.
>
> You can run 2 threads. One that produces {time: now + 1500.msecs, value: getFlameIntensityViaImageProcessing} objects and one that consumes those and basically waits until each's msg.time < now and then sendPWMSignalToValfe(msg.value). You would basically rely on std.concurrency's MessageBox to do the queuing. Although you could do that manually as well.
>
> Could also run it on 1 thread if you don't mind there being a jitter of however long getFlameIntensityViaImageProcessing takes, but you will need a queue.

That was the first thing I thought. A FIFO queue. I just wanted to not reinvent the wheel. So, you guys say go for regular threads not fibers. Thank you.
September 25
On Friday, 25 September 2020 at 13:13:50 UTC, Sebastiaan Koppe wrote:
> On Friday, 25 September 2020 at 13:08:16 UTC, Sebastiaan Koppe wrote:
>> On Friday, 25 September 2020 at 11:58:53 UTC, Ferhat Kurtulmuş
>>> How can I implement schedulePWMSignalToValve(pwmval, afterNmilliseconds ) using fibers?
>>
>> No need for fibers per se.
>
> Can also use https://code.dlang.org/packages/timingwheels

I will take a look at that also, thanks.
September 25
On 9/25/20 9:16 AM, Ferhat Kurtulmuş wrote:
> On Friday, 25 September 2020 at 13:08:16 UTC, Sebastiaan Koppe wrote:
>> On Friday, 25 September 2020 at 11:58:53 UTC, Ferhat Kurtulmuş wrote:
>>> [...]
>>
>> No need for fibers per se.
>>
>> You can run 2 threads. One that produces {time: now + 1500.msecs, value: getFlameIntensityViaImageProcessing} objects and one that consumes those and basically waits until each's msg.time < now and then sendPWMSignalToValfe(msg.value). You would basically rely on std.concurrency's MessageBox to do the queuing. Although you could do that manually as well.
>>
>> Could also run it on 1 thread if you don't mind there being a jitter of however long getFlameIntensityViaImageProcessing takes, but you will need a queue.
> 
> That was the first thing I thought. A FIFO queue. I just wanted to not reinvent the wheel. So, you guys say go for regular threads not fibers. Thank you.

Whether a fiber is a valid solution or not depends on that getFlameItensityViaImageProcessing function.

If that is actually code running in your process, a fiber is going to block any other fibers during that operation. So threads are the way to go here.

fibers work great if you can yield the fiber when waiting on something *external* to complete (like for instance, i/o). But if the fiber *is* the thing you are waiting on, it's not going to be able to execute anything else until that is done.

Given the rate and the number of concurrent tasks, I'd say threads.

-Steve
September 27
On Friday, 25 September 2020 at 13:37:09 UTC, Steven Schveighoffer wrote:

> Given the rate and the number of concurrent tasks, I'd say threads.
>
> -Steve

Here is my testable and minimal code using 1 extra thread. Thank you all!

import core.thread;

import std.stdio;
import std.concurrency;
import std.container.dlist;
import std.datetime;
import std.datetime.systime;

__gshared DList!Entry queue;
__gshared bool shouldRun = true;

struct Entry {
    SysTime st;
    int val;
}

void main() {

    spawn(&worker);

    while (true) {
        int v;

        "enter your value: ".write; // getFlameIntensityViaImageProcessing()
        readf(" %d", &v);

        if(v==0){
            shouldRun = false;
            break;
        }


        queue.insertFront(Entry(Clock.currTime + 1500.msecs, v));
    }

    writeln("main is done.");
}

void worker() {
    while(shouldRun){
        auto r = queue[];
        if(!r.empty && queue.back.st < Clock.currTime){
            writeln(queue.back); // consume the value sendPWMSignalToValfe(pwmval)
            queue.popLastOf(r);
        }
    }
}
September 27
On 9/27/20 3:06 AM, Ferhat Kurtulmuş wrote:

> __gshared DList!Entry queue;
> __gshared bool shouldRun = true;

Have you considered passing messages with std.concurrency.send() and std.concurrency.receive() and friends? You wouldn't need 'queue' because all of your threads already have mail boxes to send messages to each other.

> void worker() {
>      while(shouldRun){
>          auto r = queue[];
>          if(!r.empty && queue.back.st < Clock.currTime){
>              writeln(queue.back); // consume the value
> sendPWMSignalToValfe(pwmval)
>              queue.popLastOf(r);
>          }
>      }
> }

It's not clear whether it's only in your test code but busy-waiting like that will make your CPU very warm. :) Since requests cannot pass each other, your worker thread should have something like the following in that loop:

import core.thread;

  Thread.sleep(duration);

Depending on how accurate the operating system honors your sleep requests (e.g. is it real-time?), you may want to sleep less than 'duration' and then busy-wait the rest of the duration. Similar to the difference between spinForce() and yieldForce() of std.parallelism (I understand that your solution should not involve std.parallelism):

  https://dlang.org/phobos/std_parallelism.html#.Task.spinForce

As an improvement when defining durations, you don't need to "hide" units in comments:

        // enum afterNmilliseconds = 1500;

        // Instead:
        enum after = 1500.msecs;

msecs and friends are defined in core.time:

  https://dlang.org/phobos/core_time.html#.dur

Ali


« First   ‹ Prev
1 2