A tutorial on using the PointPerfect L-band service as an RTK correction source for AgOpenGPS

Prerequisites for this solution to work (alternatives to some of these items may work, but it is up to you to test them out!):

  • simpleRTK2B (sold by digikey or ardusimple)
  • A high quality sma to tnc cable such is this one https://www.digikey.ca/en/products/detail/siretta-ltd/asmzg500a058l13/6096481
  • a Calibrated survey GNSS Tripleband+Lband antenna(IP67) or equivalent (sold by ardusimple, digikey, mouser etc.)
  • u.fl to sma cable
  • PointPerfect L-Band Corrections Receiver NEO-D9S (sold by ardusimple)
  • A personal computer with the latest AGOPENGPS and AGIO software installed as well as a working ethernet port.
  • Teensy 4.1
  • AOG 4.1 firmware (Autosteer_gps_teensy_v4_1.ino)
  • Arduino IDE 1.8.19
  • AiO 4.1/4.2 PCB or equivalent
  • You live/work in a geographic area which offers the PointPerfect service (please refer to the official ublox site for details)
  • You have been given permission by ublox to use the service
  • A clear unobstructed view of the southern sky
  • a thingstream account with a subscription to the pointperfect L-band service
  • 1.32 SingleAntennaRover.txt (firmware for F9p. Further modifications for enabling lband service must be made to the f9p by using the ucenter program. See PointPerfect L-band Configuration for instructions on that.)
  • The SparkFun u-blox GNSS Arduino Library - v3 installed via the Arduino IDE
    https://github.com/sparkfun/SparkFun_u-blox_GNSS_v3
  1. In the same directory as the Autosteer_gps_teensy_v4_1.ino file, create a new header file called secrets.h and insert the following code to the file:
// You can set the information below after signing up with the u-blox Thingstream portal 
// and adding a new New PointPerfect Thing (L-Band or L-Band + IP)
// https://portal.thingstream.io/app/location-services/things
// In the new PointPerfect Thing, you go to the credentials tab and copy and paste the IP Dynamic Keys here.
//
// The keys are valid from a particular GPS Week Number and Time of Week.
// Looking at the credentials tab, the current key expires 23:59 Feb 11th 2022.
// This means the next key is valid _from_ Midnight Feb 12th 2022.
// That is GPS Week 2196. The GPS Time of Week in seconds is 518400.
// Working backwards, the current key became valid exactly 4 weeks earlier (Midnight Jan 15th 2022).
//
// See: https://www.labsat.co.uk/index.php/en/gps-time-calculator
//
// The keys are given as: 32 hexadecimal digits = 128 bits = 16 Bytes
//
// The next example shows how to retrieve the keys using ESP32 WiFi and MQTT.
// You can cut and paste the keys and GPS week/time-of-week from that example into here.

const uint8_t currentKeyLengthBytes =   16; 
const char currentDynamicKey[] =        "<ADD YOUR L-Band or L-Band + IP DYNAMIC KEY HERE>";
const uint16_t currentKeyGPSWeek =      2254; // Update this when you add new keys
const uint32_t currentKeyGPSToW =       0;

const uint8_t nextKeyLengthBytes =      16; 
const char nextDynamicKey[] =           "<ADD YOUR L-Band or L-Band + IP DYNAMIC KEY HERE>";
const uint16_t nextKeyGPSWeek =         2258; // Update this when you add new keys
const uint32_t nextKeyGPSToW =          0;

Add your secret credentials to the appropriate places in the file and then save the file. The steps on getting these credentials is beyond the scope of this tutorial. Please browse the ublox documentation for more information on how to acquire the keys.

  1. Add the following 3 statements somewhere at the top but after the first initial comments of the Autosteer_gps_teensy_v4_1.ino file
#include "secrets.h"
#include <SparkFun_u-blox_GNSS_v3.h>
SFE_UBLOX_GNSS_SERIAL myGNSS;

  1. Within the same file under the Serial port comment, add the following definition:
#define mySerial Serial7
  1. To avoid any errors during compilation, make sure to change the name of ubxPacket struct definition to something unique so that it does not conflict with the ubxPacket struct definition originating from the Sparkfun GNSS v3 header file. I renamed it to ubxPacket2, but you can rename it anything you’d like:
struct ubxPacket2
{
  uint8_t cls;
  uint8_t id;
  uint16_t len; //Length of the payload. Does not include cls, id, or checksum bytes
  uint16_t counter; //Keeps track of number of overall bytes received. Some responses are larger than 255 bytes.
  uint16_t startingSpot; //The counter value needed to go past before we begin recording into payload array
  uint8_t *payload; // We will allocate RAM for the payload if/when needed.
  uint8_t checksumA; //Given to us from module. Checked against the rolling calculated A/B checksums.
  uint8_t checksumB;
    
  ////sfe_ublox_packet_validity_e valid;       //Goes from NOT_DEFINED to VALID or NOT_VALID when checksum is checked
  ////sfe_ublox_packet_validity_e classAndIDmatch; // Goes from NOT_DEFINED to VALID or NOT_VALID when the Class and ID match the requestedClass and requestedID
};
  1. MAKE SURE to find all instances of ubxPacket in Autosteer_gps_teensy_v4_1.ino and replace it with the unique name you gave the ubxPacket struct in step 4.

  2. Add following code just below the delay(10) line which is just after Serial.begin(baudAOG) in the Autosteer_gps_teensy_v4_1.ino file:

 mySerial.begin(460800);
   delay(5000);
    Serial.println("F9P LBand Key Insertion");
  while (myGNSS.begin(mySerial) == false) //Connect to the u-blox module using Serial
  {
    Serial.println(F("u-blox GNSS module not detected. Please check wiring."));
    delay(10);
  }
  Serial.println(F("u-blox GNSS module connected"));
  uint8_t ok = myGNSS.setDynamicSPARTNKeys(currentKeyLengthBytes, currentKeyGPSWeek, currentKeyGPSToW, currentDynamicKey,nextKeyLengthBytes, nextKeyGPSWeek, nextKeyGPSToW, nextDynamicKey);
  Serial.print(F("GNSS: configuration "));
  Serial.println(OK(ok));
  delay(100);
  myGNSS.end();
  1. Once you have made the code changes above, save the file, re-compile the code and finally upload it to the teensy 4.1.

  2. Plug in all of your expensive hardware to the AiO 4.1/4.2 board and then apply power to the AiO 4.1/4.2 baord.

  3. If everything is configured properly, you should eventually see an RTK fix in under a minute using the AGOPENGPS UI on your personal computer connected to the system via ethernet cable. I can confirm this is working on my own system.

IMPORTANT TO NOTE: the dynamic keys expire every 28 days. You are given two dynamic keys, so this fix should work unimpeded for a maximum time of 56 days. After day 56 you will have to edit the secrets.h with your newly updated dynamic keys. This should be a sufficient time window for anyone using it for agricultural purposes.

Lastly, keep in mind this is just a quick hack to integrating this service into the AgOpenGPS ecosystem. By no means is this the best fix, but it should allow someone who is more skilled at programming and has a better understanding of the code base to come up with something that might be worth incorporating to the official repository. Changes to the UI for dynamic key management would be a nice addition as well!

Hope this helps!

Have a happy new year everyone!

Blockquote

2 Likes

Pleased to see you got this one sorted out, sorry for not responding to your last messages.

A couple of us worked on this for a while and got it running on an R-Pi, but it all became too much of a faff for a subscription service so we abandoned it and moved back to a base / rover model using radios and a fixed reference point. It takes just a couple of minutes to deploy a radio base station which achieves the same effect and saves ÂŁ360 a year. No need to spend ÂŁ150 on a D9S board and a ÂŁ200 L-Band antenna (BTW, these can be picked up for ÂŁ70 from digikey, etc).

Yeah getting the l-band correction service to play nice with AOG was much simpler than I thought. We are just evaluating the PointPerfect service for the next year to see how it performs for agricultural use.

I am aware of the cost savings of using my own base station and I did some research on those, but our issue is that the tallest point on our property would be on the top of our grain leg. I am unsure as to whether having the base station on top of the leg would be stable enough to get a consistent reference point during a really windy day in the spring. The other issue would be the added attenuation coming in through the tnc to sma cable due to the length between the antenna and base station inside grain room. Using cellular and NTRIP server is not really reliable enough for us due to a few cellular dead zones in the fields. The only option would be to use 900 mhz radios. I have some LoRa modules and a gateway lying around but they can very limited in terms of bandwidth.

I went with that more expensive D9S module because It fits the footprint of my f9p from ardusimple. I have to work within the confines of one of those Hammond enclosures so it would be too much finicky business getting one of those cheaper modules to fit within the same enclosure as the AIO 4.1 std board.

@bluerabbit, what radios do you recommend when communicating with a base station?

We are just using the below which are pretty of much plug and play, just needs the right config dumping to the RTK2B boards:

They work well enough just so long as you have LoS. You shouldn’t need a massive pigtail between the device and antenna if you wanted to put one of these on a silo, it would be better to just run a powered USB extender to the device from a battery. Get a 12V 36ah lifepo4 battery with an inverter and it will run for a week.

I mount ours on a tripod in the field and run it off a 30,000mah 5V USB charger, runs for a good few days off one of those.

A small solar panel would power one of these.

some news for you.

That should make life easier since NTRIP is already supported

The only way I got my teensy to work was using the AOGConfigOMatic so is there any way I can do this using that, if not do you know any way of using l-band because if I try to upload the code using arduino ide a bunch of errors show up.

You’d have to use Arduino IDE. If you tell us what the errors are, maybe someone will know how to fix them.

C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer_gps_teensy_v4.ino:695:6: error: variable or field ‘calcChecksum’ declared void
695 | void calcChecksum(ubxPacket *msg)
| ^~~~~~~~~~~~
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer_gps_teensy_v4.ino:695:19: error: ‘ubxPacket’ was not declared in this scope
695 | void calcChecksum(ubxPacket *msg)
| ^~~~~~~~~
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer_gps_teensy_v4.ino:695:30: error: ‘msg’ was not declared in this scope
695 | void calcChecksum(ubxPacket msg)
| ^~~
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer.ino: In function ‘void autosteerSetup()’:
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer.ino:222:5: error: ‘Autosteer_running’ was not declared in this scope
222 | Autosteer_running = false;
| ^~~~~~~~~~~~~~~~~
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer.ino:235:20: error: ‘networkAddress’ was not declared in this scope
235 | EEPROM.put(60, networkAddress);
| ^~~~~~~~~~~~~~
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer.ino:241:20: error: ‘networkAddress’ was not declared in this scope
241 | EEPROM.get(60, networkAddress);
| ^~~~~~~~~~~~~~
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer.ino:247:7: error: ‘Autosteer_running’ was not declared in this scope
247 | if (Autosteer_running)
| ^~~~~~~~~~~~~~~~~
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer.ino:251:18: error: ‘AUTOSTEER_ACTIVE_LED’ was not declared in this scope
251 | digitalWrite(AUTOSTEER_ACTIVE_LED, 0);
| ^~~~~~~~~~~~~~~~~~~~
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer.ino:251:18: note: the macro ‘AUTOSTEER_ACTIVE_LED’ had not yet been defined
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer_gps_teensy_v4.ino:76: note: it was later defined here
76 | #define AUTOSTEER_ACTIVE_LED 12 //Green
|
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer.ino:252:18: error: ‘AUTOSTEER_STANDBY_LED’ was not declared in this scope
252 | digitalWrite(AUTOSTEER_STANDBY_LED, 1);
| ^~~~~~~~~~~~~~~~~~~~~
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer.ino:252:18: note: the macro ‘AUTOSTEER_STANDBY_LED’ had not yet been defined
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer_gps_teensy_v4.ino:75: note: it was later defined here
75 | #define AUTOSTEER_STANDBY_LED 11 //Red
|
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer.ino: In function ‘void autosteerLoop()’:
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer.ino:443:20: error: ‘AUTOSTEER_ACTIVE_LED’ was not declared in this scope
443 | digitalWrite(AUTOSTEER_ACTIVE_LED, 1);
| ^~~~~~~~~~~~~~~~~~~~
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer.ino:443:20: note: the macro ‘AUTOSTEER_ACTIVE_LED’ had not yet been defined
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer_gps_teensy_v4.ino:76: note: it was later defined here
76 | #define AUTOSTEER_ACTIVE_LED 12 //Green
|
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer.ino:444:20: error: ‘AUTOSTEER_STANDBY_LED’ was not declared in this scope
444 | digitalWrite(AUTOSTEER_STANDBY_LED, 0);
| ^~~~~~~~~~~~~~~~~~~~~
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer.ino:444:20: note: the macro ‘AUTOSTEER_STANDBY_LED’ had not yet been defined
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer_gps_teensy_v4.ino:75: note: it was later defined here
75 | #define AUTOSTEER_STANDBY_LED 11 //Red
|
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer.ino:467:21: error: ‘AUTOSTEER_STANDBY_LED’ was not declared in this scope
467 | digitalWrite (AUTOSTEER_STANDBY_LED, 1);
| ^~~~~~~~~~~~~~~~~~~~~
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer.ino:467:21: note: the macro ‘AUTOSTEER_STANDBY_LED’ had not yet been defined
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer_gps_teensy_v4.ino:75: note: it was later defined here
75 | #define AUTOSTEER_STANDBY_LED 11 //Red
|
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer.ino:468:21: error: ‘AUTOSTEER_ACTIVE_LED’ was not declared in this scope
468 | digitalWrite (AUTOSTEER_ACTIVE_LED, 0);
| ^~~~~~~~~~~~~~~~~~~~
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer.ino:468:21: note: the macro ‘AUTOSTEER_ACTIVE_LED’ had not yet been defined
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer_gps_teensy_v4.ino:76: note: it was later defined here
76 | #define AUTOSTEER_ACTIVE_LED 12 //Green
|
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer.ino:478:11: error: ‘speedPulseUpdateTimer’ was not declared in this scope
478 | if (speedPulseUpdateTimer > 200) // 100 (10hz) seems to cause tone lock ups occasionally
| ^~~~~~~~~~~~~~~~~~~~~
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer.ino:489:20: error: ‘velocityPWM_Pin’ was not declared in this scope
489 | tone(velocityPWM_Pin, uint16_t(speedPulse));
| ^~~~~~~~~~~~~~~
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer.ino:492:22: error: ‘velocityPWM_Pin’ was not declared in this scope
492 | noTone(velocityPWM_Pin);
| ^~~~~~~~~~~~~~~
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer.ino:498:14: error: ‘velocityPWM_Pin’ was not declared in this scope
498 | noTone(velocityPWM_Pin);
| ^~~~~~~~~~~~~~~
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer.ino: In function ‘void ReceiveUdp()’:
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer.ino:522:10: error: ‘Ethernet_running’ was not declared in this scope
522 | if (!Ethernet_running)
| ^~~~~~~~~~~~~~~~
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer.ino:527:20: error: ‘Eth_udpAutoSteer’ was not declared in this scope
527 | uint16_t len = Eth_udpAutoSteer.parsePacket();
| ^~~~~~~~~~~~~~~~
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer.ino:542:48: error: ‘Autosteer_running’ was not declared in this scope
542 | if (autoSteerUdpData[3] == 0xFE && Autosteer_running) //254
| ^~~~~~~~~~~~~~~~~
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer.ino:605:51: error: ‘Eth_ipDestination’ was not declared in this scope
605 | SendUdp(PGN_253, sizeof(PGN_253), Eth_ipDestination, portDestination);
| ^~~~~~~~~~~~~~~~~
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer.ino:605:70: error: ‘portDestination’ was not declared in this scope
605 | SendUdp(PGN_253, sizeof(PGN_253), Eth_ipDestination, portDestination);
| ^~~~~~~~~~~~~~~
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer.ino:715:73: error: ‘Eth_ipDestination’ was not declared in this scope
715 | SendUdp(helloFromAutoSteer, sizeof(helloFromAutoSteer), Eth_ipDestination, portDestination);
| ^~~~~~~~~~~~~~~~~
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer.ino:715:92: error: ‘portDestination’ was not declared in this scope
715 | SendUdp(helloFromAutoSteer, sizeof(helloFromAutoSteer), Eth_ipDestination, portDestination);
| ^~~~~~~~~~~~~~~
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer.ino:717:20: error: ‘useBNO08x’ was not declared in this scope
717 | if(useBNO08x || useCMPS)
| ^~~~~~~~~
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer.ino:717:33: error: ‘useCMPS’ was not declared in this scope
717 | if(useBNO08x || useCMPS)
| ^~~~~~~
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer.ino:719:62: error: ‘Eth_ipDestination’ was not declared in this scope
719 | SendUdp(helloFromIMU, sizeof(helloFromIMU), Eth_ipDestination, portDestination);
| ^~~~~~~~~~~~~~~~~
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer.ino:719:81: error: ‘portDestination’ was not declared in this scope
719 | SendUdp(helloFromIMU, sizeof(helloFromIMU), Eth_ipDestination, portDestination);
| ^~~~~~~~~~~~~~~
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer.ino:728:15: error: ‘networkAddress’ was not declared in this scope
728 | networkAddress.ipOne = autoSteerUdpData[7];
| ^~~~~~~~~~~~~~
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer.ino:747:55: error: ‘Eth_myip’ was not declared in this scope
747 | uint8_t scanReply[] = { 128, 129, Eth_myip[3], 203, 7,
| ^~~~~~~~
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer.ino: In function 'void SendUdp(uint8_t
, uint8_t, IPAddress, uint16_t)':
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer.ino:774:3: error: ‘Eth_udpAutoSteer’ was not declared in this scope
774 | Eth_udpAutoSteer.beginPacket(dip, dport);
| ^~~~~~~~~~~~~~~~
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer_gps_teensy_v4.ino: In function ‘void setup()’:
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer_gps_teensy_v4.ino:247:14: error: ‘class HardwareSerial’ has no member named ‘addMemoryForRead’
247 | SerialGPS->addMemoryForRead(GPSrxbuffer, serial_buffer_size);
| ^~~~~~~~~~~~~~~~
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer_gps_teensy_v4.ino:248:14: error: ‘class HardwareSerial’ has no member named ‘addMemoryForWrite’
248 | SerialGPS->addMemoryForWrite(GPStxbuffer, serial_buffer_size);
| ^~~~~~~~~~~~~~~~~
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer_gps_teensy_v4.ino:256:15: error: ‘class HardwareSerial’ has no member named ‘addMemoryForRead’
256 | SerialGPS2->addMemoryForRead(GPS2rxbuffer, serial_buffer_size);
| ^~~~~~~~~~~~~~~~
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer_gps_teensy_v4.ino:257:15: error: ‘class HardwareSerial’ has no member named ‘addMemoryForWrite’
257 | SerialGPS2->addMemoryForWrite(GPS2txbuffer, serial_buffer_size);
| ^~~~~~~~~~~~~~~~~
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer_gps_teensy_v4.ino: In function ‘void loop()’:
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer_gps_teensy_v4.ino:412:29: error: too many arguments to function ‘bool calcChecksum()’
412 | calcChecksum(&packetCfg);
| ^~
C:\Users\baiat\OneDrive\Documents\autosteer\Autosteer\Autosteer_gps_teensy_v4.ino:679:6: note: declared here
679 | bool calcChecksum()
| ^
~~~~~~~~~

exit status 1

Compilation error: variable or field ‘calcChecksum’ declared void

But if i just upload the decryption key in u-center whats the difference compared to this code?

The difference is that every time you enter the decryption key into ucenter you will only be able to use the l band correction service until the system shuts down. So you will have to end up always re entering the key every time you power up the system which is not very convenient. With the solution I have shared above, the key is hardcoded into the arduino code so you will only have to modify the code with the new key every 56 days if the key actually gets updated on the ublox server. Make sure to make all changes I have mentioned above and make sure to save all code changes and recompile before uploading code to teensy through the arduino ide. I do not use the aog configomatic so unfortunately I cannot assist on that side of things. Hope this helps.

1 Like

That sounds inconvenient, I wonder why they even have a decryption key that changes once a month since other correction services don’t, I guess I’ll try to fix my errors and hope that it works, also do you know if I can program my teensy trough the lan cable from the v4 AIO?

I do not know of any way of programming the teensy other than the traditional usb cable. Maybe other people on this forum might know of other ways though.

There is a thread on OTA updating on the PJRC site. OTA through Ethernet with Teensy 4.1 | Teensy Forum

The alpha firmware for the upcoming AIO v5.0 board has incorporated this method as well.