The Combine Forum banner

AgOpenGPS

903K views 4K replies 204 participants last post by  escotch.ca 
#1 · (Edited)
Download it at......
https://github.com/farmerbriantee/AgOpenGPS

Well it seems have had some time on my hands with it snowing so here goes, the introduction of AgOpenGPS.

Have a peak! Its a bit like watching paint dry, but i think the application has promise - considering its free. AB line, up to 5 sections GPS control, fully configurable and it actually works extremely well. Can not fool the section control as it gives very good recognition of unapplied areas.

My simulator only runs at 1 hz so its jerky a bit and slower, but was the only way to make the video. Using a real antenna and at least 5 hz, smooth as butter.

Click in the youtube icon at the bottom of the video and watch in youtube, this window has much lower resolution

 
#524 · (Edited)
I'm completely rethinking how heading is determined. No patch is going to fix this problem - which technically is only a problem with a wiggly GPS signal, i have some ideas tho.

I really like all that has been done, the single section/multisection, space controlled mapping, individual look ahead of sections, smart speed and section reverse shutoff. The final piece to this puzzle is determining a stable heading.

Gonna take a bit to rewrite "Position.cs".

I have attached a simulator that can do noisy bouncy GPS. Just check the checkbox for noise and use as normal.
 

Attachments

#525 · (Edited)
I've eliminated all of my section going backwards skips. But still have some skips while going forwards, for the same reasons apparently. Sections slow down momentarily such that the forward look ahead is under 1, which rounds to zero.

One thing I don't understand is that the occasional skips I'm seeing are somewhat non-deterministic. Sometimes they occur and sometimes they don't, even though I'm replaying the same GPS data over and over again. I don't understand why the math (even floating point!) would come up with slightly different numbers for sectionLookAhead from run to run! It's really odd. Also occasionally in one part of my track where I cross what I've done, a section comes on when it shouldn't. It doesn't do it on every run but when it does it's always in the same place. Very odd. Again this is all with a single GPS data set, played back over and over.
 
#530 ·
I expect it should work as the software just needs a particular nmea sentence, it's not aware of which GNSS system is feeding it the position data. That system looks handy from the perspective that it's water proof and easy to plug into a computer but it doesn't look like it's able to receive a rtk correction signal and output a corrected position.
 
#531 · (Edited)
Some GPS units integrate position data from several constellations including Galileo, Glonass, and GPS. Anything that can emit NMEA sentences will work with AgOpenGPS. And actually probably with most precision farming hardware (mapping and prescriptions, not steering which is all proprietary).

EDIT: The receiver Digital Yacht receiver is one such receiver. The Emlid Reach system also receives Glonass signals which it uses to help get a fix identified faster, though I don't think the RTK calculations do anything with Glonass signals, but only GPS.
 
#532 · (Edited)
I think I'm having success with simply doing a moving average on the fixHeadingSection. Right now I'm averaging 4 headings, so there is lag, but it doesn't seem to be a huge problem. And any filtering, be it weighted averaging or kalman filtering does have lag as well. Perhaps the filtering could be adjusted based on speed. The slower you go, the more filtering you do. Or I was thinking that a smart filtering approach might work. If a new heading is outside the trend of the change in the last 4 or 5 headings, discount it. And actually that's probably describing a kalman filter. ha.

I think for my purposes the average works well enough to be a go for me.

I guess an alternative is to do the averages on the sectionLookAhead values, not the heading. I'll have to experiment and see if this is better. Or maybe a combination of techniques will be best.

Brian you probably already know this, but I learned a cool way to average headings tonight, that deals with crossing the 0/360 boundary. Goes like this:

ave_heading = Math.Atan2( sin_sum, cos_sum)

where sin sum is the sum of the sin() of the headings, and cos_sum is the sum of the cos() of the headings.
 
#533 · (Edited)
Taking a moving average of each section's sectionLookAhead over the last 3 values seems to have done the trick, even without heading filtering. I might turn on a small bit of heading filtering as well. Lag should be minimal and it won't affect how quickly the sections turn off or turn on.

Either way, for me at least, I think the skipping problems are gone or very much reduced. I'm confident it is ready for a test run for real in a few weeks on the sprayer.

I built a small moving average struct/class to abstract away the details. I can post it if you are interested. I'm using it to average the heading (with a special method) and also do normal averaging.
 
#534 ·
That sounds really cool. Haven't been on here much, but still working on it when i can.

I like the sum cos and sin averaging. I've taken a completely different direction on the whole fix position and heading. the noise generator certainly shows if a system works or not. so basically i've created an array of current and previous fix values easting and northings along with the distance between each fix and store that in an x,z,h vector. The biggest challenge has been the "crossing over" between going slow and more normal speeds. What i've done is just set a distance - say 2 meters - and add up all the individual fixes that make up to that 2 meters and use the last point that triggered that distance as the base of the heading along with the current point, those two points, using Atan2 to determine the new heading. This makes it completely speed independent. In settings you can set any distance you like, the longer that trigger distance, the smoother the ride as the farther the points are away, the less the noise makes a difference. So whether you're going 0.3 kmh or 33, once you pass 2 meters, that's the new heading.

What that also does is force the sections and section heading to just follow along and make blocks at that minimum distance. The old way that distance was either to short up to some speed, and then way to short initally once it crossed over that set speed. The new way, It may take 100 fixes at 0.5 km/h but no matter, it just keeps the same position till the next one triggers. Also since its running at the nmea frequency all the time, any section can turn on or off exactly when it should no matter the speed.

I'll make a video. There seems to be no way to get real time immediate solid heading without some averaging of some kind. But, a speed dependent average works well because as the speed goes up, the points requiring averaging drop dramatically so you get the best of both worlds.

We will have to combine what we both have been working on....

The new section heading calculations no longer need any distance or prevNorthing or prevEasting and the whole thing is done in 4 lines now

Code:
                {
                    t = (vehicle.toolTrailingHitchLength) / distanceCurrentStepFix;
                    toolEasting = hitchEasting + t * (hitchEasting - toolEasting);
                    toolNorthing = hitchNorthing + t * (hitchNorthing - toolNorthing);
                    fixHeadingSection = Math.Atan2(hitchEasting - toolEasting, hitchNorthing - toolNorthing);                    
                }
 
#535 ·
ITs strange, but here is the code of minimum distance calcs that are independent of speed, or nmea updates, and will work on 1,2,5,10 hz. Simply the higher the freq the shorter eachinterval, the longer the buffer.

Code:
           distanceCurrentStepFix = pn.Distance(pn.northing, pn.easting, stepFixPts[0].z, stepFixPts[0].x);
            fixStepDist = distanceCurrentStepFix;

            if (distanceCurrentStepFix <= minFixStepDist)
            {
                for (stepFixNumber = 0; stepFixNumber < numFixSteps; stepFixNumber++)
                {
                    fixStepDist += stepFixPts[stepFixNumber].h;
                    if (fixStepDist > minFixStepDist)
                    {
                        if (stepFixNumber < (numFixSteps-1) ) stepFixNumber++; 
                        isFixHolding = false;
                        break;
                    }
                    else isFixHolding = true;
                }
            }
            else stepFixNumber = 0;

            //if total distance is less then the addition of all the fixes, keep last one as reference
            if (isFixHolding)
            {
                if (isFixHoldLoaded == false)
                {
                    vHold = stepFixPts[(numFixSteps - 1)];
                    isFixHoldLoaded = true;
                }

                //cycle thru like normal
                for (int i = numFixSteps - 1; i > 0; i--) stepFixPts[i] = stepFixPts[i - 1];

                //fill in the latest distance and fix
                stepFixPts[0].h = pn.Distance(pn.northing, pn.easting, stepFixPts[0].z, stepFixPts[0].x);
                stepFixPts[0].x = pn.easting;
                stepFixPts[0].z = pn.northing;

                //reload the last position that was triggered.
                stepFixPts[(numFixSteps - 1)].h = pn.Distance(vHold.z, vHold.x, stepFixPts[(numFixSteps - 1)].z, stepFixPts[(numFixSteps - 1)].x);
                stepFixPts[(numFixSteps - 1)].x = vHold.x;
                stepFixPts[(numFixSteps - 1)].z = vHold.z;
            }
            
            else
            {
                //positions and headings 
                CalculatePositionHeading();

                isFixHoldLoaded = false;

                stepFixPts[(numFixSteps - 1)].h = 0;

                //To prevent drawing high numbers of triangles, determine and test before drawing vertex
                sectionTriggerDistance = pn.Distance(toolNorthing, toolEasting, prevSectionNorthing, prevSectionEasting);

                //section on off and points, contour points
                if (sectionTriggerDistance > sectionTriggerStepDistance)
                        AddSectionContourPathPoints();

                //calc distance travelled since last GPS fix
                distance = pn.Distance(pn.northing, pn.easting, prevNorthing[0], prevEasting[0]);
                totalDistance += distance; //distance tally                   
                userDistance += distance;//userDistance can be reset

                //save a copy of previous positions for cam heading of desired filtering or delay
                for (int x = numPrevs - 1; x > 0; x--) { prevNorthing[x] = prevNorthing[x - 1]; prevEasting[x] = prevEasting[x - 1]; }

               //most recent fixes
                prevEasting[0] = pn.easting; prevNorthing[0] = pn.northing;

                //load up history with valid data
                for (int i = numFixSteps - 1; i > 0; i--) stepFixPts[i] = stepFixPts[i - 1];

                stepFixPts[0].h = pn.Distance(pn.northing, pn.easting, stepFixPts[0].z, stepFixPts[0].x);

                stepFixPts[0].x = pn.easting;
                stepFixPts[0].z = pn.northing;
 
                prevToolNorthing = toolNorthing;
                prevToolEasting = toolEasting;
                prevFixHeadingSection = fixHeadingSection;
            }
 
#541 · (Edited)
Oh very nice. That looks perfect for the job if you need relay-like control. Be aware that that board is a low-side switch. In other words the load should be attached to the + side, and the - should go to ground. When the "switch" is closed, it connects your load to ground. At least if I read the data sheet correctly. Just as a data point, my Raven SmartBoom outputs function as 12V high-side switches. you can hang the load right off the signal wire.

Last year I designed the input circuit for my sprayer controller section control. It's basically just a set of NPN transistors that short the toggle switch signals to ground, simulating the closing of the switches. The signals are 5v, so really any base voltage over 5v will saturate the transistors. And according to my simulations at 123d.circuits.io, it should work very well indeed.

Yeah isolation... probably a good idea. Instead of an NPN transistor on the little interface adapter I made, an NPN optocoupler would have been better. I will investigate whether I can mod that board you linked to to suit my needs. I need to be able to handle an input signal from 5v to 12v which the board can't do without blowing the opto-isolator chip.

By the way, http://123d.circuits.io can save you a bit of money on blown electronics when you're a novice like me... Can even simulate basic arduino stuff. A/C circuits not so much, but I've had fairly complicated DC circuits work in the simulator (and worked on the real breadboard too!).
 
#542 ·
Been having some issues connecting a real receiver to AOG, what am I doing wrong?
Tried various permutations of NMEA settings but AOG refuses to parse the stream I am giving it.

An example sentence from my receiver: $GPRMC,114100.80,A,5249.1481977,N,00114.9800869,W,0.020,,310317,,*0A
I note that this is different from the stream generated by AgSim, with a few empty fields, but it should still be valid?

As an aside, what are your thoughts on supporting GNRMC (combined GNSS) sentences so people can use multiple constellations?
 
#544 ·
You probably have to tell windows that a comma is a period. It isn't region smart, yet.

In control panel select Region and language.

Additional Settings.

Under decimal symbol, change that to a decimal ( . ) from what is probably a comma ( , )

Give that a whirl

I will look at GNRMC for sure!
 
#545 · (Edited)
Didn't we talk about the regional parsing thing a while back?

This is an easy thing to fix. I think all you have to do is pass CultureInfo.InvariantCulture as the last argument to double.Parse(). That will force it to treat a "." as the decimal point. CultureInfo comes from the System.Globalization namespace. I just did a quick run through CNMEA.cs and added the CultureInfo.InvariantCulture flag to every double.Parse() and it worked great.
 

Attachments

#552 ·
The latest version uses all parsing with TryParse() instead of Parse(). It returns only true false, i'm not using the return value, as to success of parsing the intended data type rather than an exception. Also it uses invariant as the default. I'm assuming it should just work, although i haven't specifically tried it.
 
#547 · (Edited)
Can't see anything wrong with the NMEA string there. The missing fields should not cause AOG any issues (just double checked the parsing code). Maybe you could record some of the NMEA strings to a file that I can replay here to reproduce the problem? Just 20 or 30 seconds of NMEA strings would be enough.
 
#548 · (Edited)
I notice the time, the 153143.200 has a decimal after it. Hmm. I think that is the problem. I will modify my sim and have it spit out a double precision times instead of an integer. Me thinks it will not work either.

It should be a relatively easy fix!

I'm skiing tho - and that's a much bigger problem!! I will look at it tonight.

Edit...... It would seem the true heading is missing, position #8.

//$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A
// 0 1 2 3 4 5 6 7 8 9 10 11
// Time Lat Lon knots Ang Date MagV

Where:
RMC Recommended Minimum sentence C
123519 Fix taken at 12:35:19 UTC
A Status A=active or V=Void.
4807.038,N Latitude 48 deg 07.038' N
01131.000,E Longitude 11 deg 31.000' E
022.4 Speed over the ground in knots
084.4 Track angle in degrees True
230394 Date - 23rd of March 1994
003.1,W Magnetic Variation
*6A The checksum data, always begins with *

Is there a way to turn that on in your GPS? AgOpenGPS considers a missing true heading as an invalid sentence so it never gets parsed, just discarded as junk. It also makes sure latitude and and longitude aren't blank either.

So....... Should i have it so it ignores the true heading and accept the sentence anyways?

Let me know if you can't turn on true heading.
 
#549 ·
Hmm. I think it should be accepted as valid. The heading will just be unknown until you start moving (which is kind of the case anyway, even when heading is in the sentence it's fairly meaningless at first).

Also put in the CultureInfo.InvariantCulture in your double.Parse() calls and that should fix any problems with European locales.
 
#553 ·
I can easily test it in Linux. I can set locale from the command line before running it under wine. Just let me know when it hits git.

Now that things are getting stable, can you keep the exe version number the same so that the settings stick? Getting really good at re-entering my sprayer boom widths after each git pull. ;)
 
#554 · (Edited)
Ha ya, that versioning is a pain! You can also save your vehicle and just load it all again too. That is version insensitive, unless i make a big change in vehicles.

What is on Github, uses tryparse. Its been in there for a few versions.

Since we are on the culture thing, i would like some feedback on getting rid of inches in the setup of all options and making it all in centimeters. the display is adjustable to Imperial and Metric, but to have both for options is a complete programming nightmare. Since everything in the background is in metric, it certainly would make the code much more clearer to have all setup in metric.
 
This is an older thread, you may not receive a response, and could be reviving an old thread. Please consider creating a new thread.
Top