After experiments with artificial intelligence, I decided to go with an old navigation way — over GPS.
At first, the simple GPS-USB dongle was bought.
Then I set it up based on an article from Adafruit. Although the paper is six years old, there are only a few details changed:
- The dongle was recognized as /dev/ttyACM0 instead of /dev/ttyUSB0.
- Linux service for GPS (aka gpsd) did not find the dongle initially. It turned out it is necessary to modify the /etc/default/gpsd:
- GPS package for Python-3 had not been installed by default. Had to do it manually.
Tank with the GPS dongle:
Then I stuck for a long time because the device was not providing any coordinates. It was not broken — there some information generated like version, vendor, settings and so on. A lot of information, but the coordinates.
At first, I was thought there is a defect. I had an old SD-card from previous experiments where such dongle was working on Raspbian of 2017. When I loaded with that card — everything worked fine: GPS started blinking green and delivered the coordinates.
I had a brainstorm over a week, compared all the configs, suggested the new Raspbian is incompatible and prepared to compile gpsd from the sources in debug mode.
Fortunately, I read a topic on Raspberry support forum where someone recommended to bring the robot outside to let it find satellites. The whole process could take up to 30 minutes.
I already did it, but without any success. Probably the time was to short or sky too cloudy. But that day was clear and sunny and I was finally rewarded.
A deeper search discovered the satellites info loaded and cached in the device firmware and Raspbian has no control over this process.
Sorry, there’s no easy way to do these things through GPSD yet. The reason is that there is no consistent way to make GPS receivers report this information.
Many don’t ship it at all. Others (including some but not all devices shipping SiRF binary packets) ship it occasionally in SUBFRAME information, but you have to know exactly how to grovel through the SUBFRAME fields to get it and the documentation of those in IS-GPS-200E (the over-the-air protocol used by GPS satellites) is extremely obscure. Still others report varying subsets of almanac/ephemeris/pseudorange data in reasonably straightforward ways, but in vendor-proprietary sentences that are extremely specific to individual receiver types, poorly documented or undocumented, and often needing to be activated by control sequences that are equally specific and even worse documented.
The source is here.
Python code to read data from gpsd is very simple:
import gps gs = gps.gps("localhost", "2947") gs.stream(gps.WATCH_ENABLE | gps.WATCH_NEWSTYLE) for i in range(0,10): report = gs.next() print (report)
I control the tank with a phone via an Android app. Then it is a good idea to draw maps for navigation. Google Maps is the most obvious choice for that purpose. They provide complete documentation and Android Studio can create a blank maps project. I merged the maps code into my tank app and it worked perfectly.
Google requires to register an API key to work with Maps API. It is free (at least now) and simple.
Then I extended REST interface of the tank to get GPS-coordinates. The app requests the coordinates and draws the tank on the map. Looks good.
Next step is to build a route.
Google offers Direction API for routes creation. Directions — is a web service, accepting coordinates of 2 points and returning a lot of information about the route with waypoints, addresses, description and so on. Actually, I need only coordinates.
So the whole process is:
- tap on the screen
- get coordinates of the tapped point
- send the coordinates into directions
- receive the route
- pull waypoints
- draw waypoints coordinates on the map.
The tank is ready to navigate:
At this moment tank receives a command with the first point of the route.
There is a problem — the tank does not know its direction initially. It would be so easy if there were a compass… But this configuration does not carry a compass.
Then I made a workaround. The tank moves for a few seconds, asking for GPS data at the beginning and end of this movement. Having 2 points it is easy to calculate the bearing:
def azimuth(pos1, pos2): lat1 = toRadians(pos1["lat"]) lon1 = toRadians(pos1["lon"]) lat2 = toRadians(pos2["lat"]) lon2 = toRadians(pos2["lon"]) dlat = lat2 - lat1 dlon = lon2 - lon1 x = math.sin(dlon) * math.cos(lat2) y = math.cos(lat1)*math.sin(lat2) - math.sin(lat1)*math.cos(lat2) * math.cos(dlon) return math.atan2(x, y)
But it was not the end of the problems. Turned out, sometimes GPS returns data with error and this error could be so big that coordinates and the bearing show upside down direction.
The only way to deal with it — use GPS field track. This field contains an angle from true north. Sometimes this field is not populated and then we have to rely on the calculated bearing.
After the bearing found the tank makes a turn to the target point and moves for a few seconds. Making turns without a compass is not a trivial task as well. Anyway, new bearing measured and the course corrected with new data. This process continues until the tank is close enough to the target.
Navigation with only raw GPS data is not a good solution. A compass is required to do routes reliable way.
Also, there are some technics to smooth GPS error. Kalman filter is widely used, I am going to apply it in the future versions.