Windows default photo viewer showing that the location infromation is saved in the EXIF information of the photo
I found a good Instructable, GeoTagging With a Standalone GPS Unit & GeoSetter : 6 Steps – Instructables, to walk through geotagging photos with a standalone GPS device and the GeoSetter application. As a result of reading through the 14 year old Instructable, I landed on a somewhat standard CSV format that GPSBabel understands. As a result, the C++ code was updated in the GitHub repository, richteel/gps_tracker: GPS Tracker and data logger (github.com) to use the ‘Universal csv with field structure in the first line’ format. The first line of the CSV file contains the following headers: utc_d,utc_t,lat,lon,alt,head,speed.
GPSBabel screen showing the ‘Universal csv with field structure in the first line’ format selected
GPS Logger
The GPS Logger that I’m discussing, was one I designed and built. The GPS Logger is Open Source, with the case, PCB, schematic, and code are included in the GitHub repository. The code, which is currently being used is the C++ code in the folder, Arduino/gps_tracker. (The CircuitPython code may be updated shortly as well, but for now, it is best to use the C++ code.) GPSBabel is used to translate the CSV file from the GPS Logger into a GPX file, that GeoSetter may be used to geotag photos.
First a note on the time setting on the camera. Previously, I would set the date and time to the local date time, however I frequently would forget to update when using the camera, which was problematic when switching between daylight saving time and standard time. I decided this time to simply set to UTC. I’m not certain if this will be a good solution, but we shall see.
The first step is to translate the GPS Logger CSV file into a GPX file, using GPSBabel.
The next steps are done using GeoSetter. My camera produces JPG files, so I have only set the values for JPEG.
Once the settings have been edited, then it is possible to load the GPX file with GPS data and geotag files in a folder. Start by selecting all the photos in the photos panel to the left.
GeoSetter Application with all photos in the photos panel selected
To geotag the selected photos, go to the menu and select, Edit > Synchronize with GPS Data Files…
GeoSetter Menu – Edit > Synchronize with GPS Data Files…
A dialog will open, with options for synchonizing the photos and GPS information. I have selected the “Synchronize with a Directory containing Data Files:” option. Not sure why, but I needed to uncheck the “Request Time Zone by using Webservice” option for the geotagging to work.
GeoSetter – Synchronize with GPS Data Files dialog window
A dialog showing the number of matched files is displayed. If no matches are found, then you will be prompted to try again or cancel. In this example, all 20 images were matched with GPS data, so the “Yes” option is selected.
GeoSetter – Synchronize with GPS Data Files confirmation dialog
An additional confirmation dialog will be displayed asking if you wish to save the assigned track(s) to the current directory. Select the desired option.
The photos will be geotagged and the positions will now be displayed on the map.
GeoSetter showing a map with the locations of the photos
The photos are not saved at this point. You will need to select Edit > Save Changes, from the menu to write the EXIF information to the photos.
Once the photos have been saved, you may view the photos in Windows Photo Viewer to show that the location information is saved with the photos.
Windows default photo viewer showing that the location infromation is saved in the EXIF information of the photo
Alternatively, you may look at the file properties to see the EXIF information.
Anousavary or Anosavari Monument in Vientiane, Laos
I’m on vacation/holiday this month, but before I left, I paid the GPS Logger project a visit and rewrote the code in C++, using the Arduino IDE and FreeRTOS to make it a bit more responsive to user input. The result is a nearly polished project that I’m very happy with, but I plan to reorganize the GitHub, project files, at richteel/gps_tracker: GPS Tracker and data logger (github.com), and provide a few different patterns for implementing such a project. While I like using FreeRTOS, it is not for everyone, particularly those just getting started with microcontroller projects. I also wish to revisit CircuitPython and see about implementing the FreeRTOS version of Python. I believe the CircuitPython implementation is still an alpha version so I may not be able to do anything with it yet, but may give it a go.
Here are some photos of the GPS Logger in action.
Screen 1 of 6 – Main TimeScreen 2 of 6 – Location InformationScreen 3 of 6 – GPS InformationScreen 4 of 6 – BatteryScreen 5 of 6 – SD Card InformationScreen 6 of 6 – Memory
Why is the GPS Logger needed when photos on the phone are Geotagged or the phone is capable of this function?
While this functionality may be available with apps on the phone, it is not always possible to get tracks off of the phone easily to use with an application such as GeoSetter for photos taken with a dedicated camera, such as the Sony DSC-WX500. I want to be able to use the data gathered from the logger and geotag photos from cameras, which do not have a GPS. I also want to create maps, showing the tracks that we took, while on vacation. For both of these reasons, a dedicated GPS Logger is a good option. The file format of the current version is a CSV file, which may or may not work with the GeoSetter application, but I should be able to convert the data into a format that will be usable.
Another month has gone by and I am still working on the Speech Timer project. I was able to develop a solution with CircuitPython but was not happy with the end product. Perhaps it would be possible to get to a more stable version but even with all the RAM available on the Raspberry Pi Pico W, I would still run into issues with the heap becoming fragmented and not having enough free space to allocate additional space to server web pages reliably. I tried many of the tips to resolve the issue including calling garbage collection periodically. These changes helped the code run a bit longer but still ran into issues. I decided to rewrite the code in C++, using the Arduino IDE and wanted to be able to debug my code on the Raspberry Pi Pico while it was running as I ran into an issue when I changed the configuration to disable serial debugging output by not calling Serial.begin().
I made a Picoprobe and attempted to debug my code, but things were not as straight forward as they could have been, which resulted in many hours waisted.
I had followed the instructions in the getting-started-with-pico.pdf guide and used the Windows Pico SDK, but could not get debugging to work in Visual Code or the Arduino IDE. I was nearly ready to setup another PC with Linux and give that a go as I had seen folks saw that just works with no issues. I’m running Windows, which I assumed was part of the issue. I saw another Windows user who was very frustrated as well and stating that it seemed they were the only ones testing/using this in Windows. They eventually got it working although they could not figure out the root cause. Unfortunately I too got it working and have no clear indication of what the root issue was but I have an idea that there is an issue with the Windows setup scripts that needs to be addressed. If someone has Visual Code and/or Arduino IDE installed before installing the SDK, they will have issues. This may not be the fault of the SDK, it could be with the Visual Code and Arduino IDE setup but I suspect it is indeed the Pico SDK setup on Windows that is the issue.
Out of frustration, I reinstalled the Windows Pico SDK and checked Visual Code and the Arduino IDE. Magically, Visual Studio code worked but the Arduino IDE still failed. I then uninstalled the Arduino IDE, then restarted the PC. Once the PC restarted, I deleted the C:\Users\<user id>\AppData\Local\Arduino15 and C:\Program Files\Arduino IDE folders to clear out anything that was already configured. I then reinstalled the Arduino IDE and tried again and the Arduino IDE worked too.
There really is a problem with the Pico SDK but it will be difficult to fix unless someone can figure out what changed from the problem install and the fixed install. Unfortunately, I did not capture anything before so I cannot tell what went wrong.
In the end, once debugging was working, I was able to step through my code and found the issue in about 2 minutes. Too bad the debugging fiasco caused several days of wasted time trying to get the debugging working.
Screen Capture of the PyPortal Event Countdown Clock
Some big updates to the software for the modified PyPortal Event Countdown Clock. The UI was changed and several features added. It also seems to be more stable as it does not lock-up randomly. Let’s get into the changes.
Header and Footer status bars display information
Header
Temperature
Touch to change between Fahrenheit and Celsius
Time
Touch to change between 24 hour and 12 hour formats
Network Strength
Footer
Brightness
Touching the left side of the footer, dims the display backlight
Touching the right side of the footer, increases the brightness of the display backlight
Touching the middle of the footer, returns to auto brightness of the display backlight
Event of the number of events
Change the event displayed by touching the left of the display to show previous event or the right side to advance to the next event.
If an event has passed more than a day ago, they will be removed from the list of events and no longer displayed
The events do not need to be in order in the JSON file. They are sorted when they are loaded from the SD Card with the soonest event being displayed first.
Demo of the PyPortal Event Countdown Clock Mods
Noticed that the temperature was several degrees too high. This was due to the ADT7410 being too close to devices generating heat. I removed the ADT7410 and placed it on a small board and added a PH2 JST connector so it may be plugged into the I2C socket. This resulted in much more usable temperature reading.
ADT7410 Temperature Sensor moved to a separate board.
Adafruit PyPortal Titano
I just purchased an Adafruit PyPortal Titano, Product ID: 4444, to test out the software and see about allowing the software to be used on different sized screens. The Titano, has a 480×320 screen verses 320×240 of the PyPortal, Product ID: 4116.
The PyPortal Titano that I received had CircuitPython 5 installed, so the first order of business was to install CircuitPython 8. I started by going to the PyPortal CircuitPython Guide on Learn.Adafruit.
Double click the reset button on the back of the Titano board and make certain the RGB LED turns green. If is is not green, try again.
Copy the file to the PORTALBOOT drive that shows on your PC NOTE: Do not be concerned that the preinstalled demo no longer works. The library files are different for different versions.
Download the CircuitPython 8 library files from https://circuitpython.org/libraries NOTE: This step is optional as the required libraries are in the GitHub project files
Unzip the files and copy the files in the PyPortal folder to the PyPortal Titano
The PyPortal Titano will restart and the images will change between “Connecting to the Internet” and “Failed to Connect”. The images will not take up the full screen due to the difference in resolution. (Until the code is modified.) The reason it is not connecting is we need add files to an SD Card and put that into the PyPortal Titano.
Copy the files in the SD_Card folder, from GitHub, and paste them into the root of a micro SD Card.
While the SD Card is still in you PC, double click on the index.html file to open the configuration utility.
You may choose to load the config.json file on the card or start creating a new file from scratch by adding new events.
Once you are satisfied with the events, click on the “Secrets” tab and enter your Wi-Fi settings, Timezone, and Adafruit IO Account information.
Once done, click the “Save JSON File” button and save it to the SD Card as config.json.
Remove the card from the PC and insert it into the PyPortal Titano.
Click the reset button on the back of the PyPortal Titano.
If everything was done correctly, you can anticipate that the events will be displayed on the screen
The application is in the upper left corner of the screen with space to the right and bottom.
You may notice from the image of the PyPortal Titano screen, the temperature is not displayed as the PyPortal Titano does not have the ADT7410 Temperature Sensor.
The next step will be to see how to detect the screen size or make it a setting in the config.json file.
I have implemented some of the items on my to do list from last week’s post and tested out a few more ideas. I have also published version 0.1 to GitHub.
I want to provide a few details on version 0.1 of the code and what is being worked for version 1.0. The two releases are very different with 1.0 being the one that I recommend you use once it is available.
Version 0.1 does automatically change the backlight brightness but it is not too dramatic therefore not very useful in my opinion. I’m disappointed by the way that turned out but plan to address it in version 1.0 with automatic and manual setting of the backlight.
Version 0.1 also freezes randomly, however I think it is a memory issue. I plan to address this by calling the CircuitPython garbage collection and/or implementing a watchdog timer. Not certain at the moment, which direction I will take but suspect that I will implement both.
Version 0.1 will also load all events, even those in the past so it is not very useful. Version 1.0 will address this by not loading past events.
The next version, 1.0, will have a different screen layout. As stated above, I wanted a way to manually control the backlighting so I have added icons at the bottom of the screen with touch target areas for increasing, decreasing, and automatic setting of the screen backlight. In order to add this as an option, I have created a 20 pixel high footer on the screen for the icons and to display brightness information. I may also display when the time is being updated as the touch interface is non-responsive during an update as it is a blocking call.
A 20 pixel header will also be included to display the time, the current event of the number of events, and a network strength indicator. The header and footer take away 40 pixels from the screen height but I think it will be worth it.
Hopefully I will finish this new version shortly and will have it available on GitHub. As you may have guessed, I will be on vacation/holiday for two weeks in about two weeks so there will not be any posts while I am away, unless it is about the vacation and I have stable enough internet to make some posts.
I recently purchased an Adafruit PyPortal and looked at using John Park’s project PyPortal Event Countdown Clock project. It was fairly easy to get it up an running but switching from CircuitPython 4 to 8, proved to cause a bit of a challenge, not with the code, but with the libraries. Some were moved from one file to multiple files in one folder but eventually, I got it all sorted out.
I plan to use the PyPortal on an upcoming cruise, which lead me to some ideas of how to change the code to make it a bit more useful and easier to change events. Below is a list of some of the ideas to be implemented.
(Implemented) Allow for multiple events to be tracked
(Implemented) Use touch to change which event is being displayed
(Implemented) Add title and subtitle to the display
Load the events and event images from an SD Card
Use a JSON file
Create a webpage to easily modify the JSON
If possible, add the WiFi information to the config file
Add a clock option and easily switch between countdown and clock
Dim or turn off the backlight if the room is dark
Ignore events that have passed (Date is not today but in the past)
The first step was to add multiple events and add touch capability to allow moving back and forth through the events. Adding multiple events was not difficult. A new class files was added for event objects and a list was created to hold them. Next was to add touch and split the screen in half with a 20 pixel dead zone in the middle so that touching the left side would take you to the previous event and touching the right side would move to the next event. It was also necessary to add a variable to track the last index so we would know when to load the selected event.
Touch zones on PyPortal 320×240 screen
event.py
import time
class event:
def __init__(self, title, subtitle, year, month, day, hour, minute, imageCountDown, imageEventDay, forecolor=0xF0C810):
self.title = title
self.subtitle = subtitle
self.forecolor = forecolor
self.year = year
self.month = month
self.day = day
self.hour = hour
self.minute = minute
self.date = time.struct_time((year, month, day,
hour, minute, 0, # we don't track seconds
-1, -1, False)) # we dont know day of week/year or DST
self.imageCountDown = imageCountDown
self.imageEventDay = imageEventDay
code.py
# SPDX-FileCopyrightText: 2019 Limor Fried for Adafruit Industries
#
# SPDX-License-Identifier: MIT
"""
This example will figure out the current local time using the internet, and
then draw out a countdown clock until an event occurs!
Once the event is happening, a new graphic is shown
"""
import time
import board
import busio
from adafruit_pyportal import PyPortal
from adafruit_bitmap_font import bitmap_font
from adafruit_display_text.label import Label
from analogio import AnalogIn
import adafruit_adt7410
import adafruit_touchscreen
from event import event
events = []
events.append(event("Day 1", "Barcelona, Spain",
2023, 5, 29, 00, 00,
"barcelona_background.bmp", "barcelona_event.bmp"))
events.append(event("Day 2", "Fun Day At Sea",
2023, 5, 30, 00, 00,
"funday_background.bmp", "funday_event.bmp"))
events.append(event("Day 3", "Málaga, Spain",
2023, 5, 31, 00, 00,
"countdown_background.bmp", "countdown_event.bmp"))
events.append(event("Day 4", "Gibraltar (UK)",
2023, 6, 1, 00, 00,
"countdown_background.bmp", "countdown_event.bmp"))
events.append(event("Day 5", "Lisbon, Portugal",
2023, 6, 2, 00, 00,
"countdown_background.bmp", "countdown_event.bmp"))
events.append(event("Day 6", "Fun Day At Sea",
2023, 6, 3, 00, 00,
"funday_background.bmp", "funday_event.bmp"))
events.append(event("Day 7", "Fun Day At Sea",
2023, 6, 4, 00, 00,
"funday_background.bmp", "funday_event.bmp"))
events.append(event("Day 8", "Fun Day At Sea",
2023, 6, 5, 00, 00,
"funday_background.bmp", "funday_event.bmp"))
events.append(event("Day 9", "Fun Day At Sea",
2023, 6, 6, 00, 00,
"funday_background.bmp", "funday_event.bmp"))
events.append(event("Day 10", "Fun Day At Sea",
2023, 6, 7, 00, 00,
"funday_background.bmp", "funday_event.bmp"))
events.append(event("Day 11", "Fun Day At Sea",
2023, 6, 8, 00, 00,
"funday_background.bmp", "funday_event.bmp"))
events.append(event("Day 12", "Fun Day At Sea",
2023, 6, 9, 00, 00,
"funday_background.bmp", "funday_event.bmp"))
events.append(event("Day 13", "Fun Day At Sea",
2023, 6, 10, 00, 00,
"funday_background.bmp", "funday_event.bmp"))
events.append(event("Day 14", "Fun Day At Sea",
2023, 6, 11, 00, 00,
"funday_background.bmp", "funday_event.bmp"))
events.append(event("Day 15", "Fun Day At Sea",
2023, 6, 12, 00, 00,
"funday_background.bmp", "funday_event.bmp"))
events.append(event("Day 16", "New York, New York",
2023, 6, 13, 00, 00,
"nyc_background.bmp", "nyc_event.bmp"))
# determine the current working directory
# needed so we know where to find files
cwd = ("/"+__file__).rsplit('/', 1)[0]
# Initialize the pyportal object and let us know what data to fetch and where
# to display it
pyportal = PyPortal(status_neopixel=board.NEOPIXEL,
default_bg=cwd+"/coming_soon.bmp")
big_font = bitmap_font.load_font(cwd+"/fonts/Helvetica-Bold-36.bdf")
big_font.load_glyphs(b'0123456789') # pre-load glyphs for fast printing
title_font = bitmap_font.load_font(cwd+"/fonts/Arial-36.bdf")
subtitle_font = bitmap_font.load_font(cwd+"/fonts/Arial-20.bdf")
text_color = 0xFFFFFF
days_position = (big_font, 8, 207)
hours_position = (big_font, 110, 207)
minutes_position = (big_font, 220, 207)
title_position = (title_font, 8, 140)
subtitle_position = (subtitle_font, 8, 175)
text_areas = []
for pos in (days_position, hours_position, minutes_position, title_position, subtitle_position):
textarea = Label(pos[0])
textarea.x = pos[1]
textarea.y = pos[2]
textarea.color = text_color
pyportal.splash.append(textarea)
text_areas.append(textarea)
refresh_time = None
# Touchscreen setup
# ------Rotate 270:
screen_width = 240
screen_height = 320
ts = adafruit_touchscreen.Touchscreen(board.TOUCH_XL, board.TOUCH_XR,
board.TOUCH_YD, board.TOUCH_YU,
calibration=(
(5200, 59000), (5800, 57000)),
size=(screen_width, screen_height))
# ------------- Inputs and Outputs Setup ------------- #
light_sensor = AnalogIn(board.LIGHT)
try:
# attempt to init. the temperature sensor
i2c_bus = busio.I2C(board.SCL, board.SDA)
adt = adafruit_adt7410.ADT7410(i2c_bus, address=0x48)
adt.high_resolution = True
except ValueError:
# Did not find ADT7410. Probably running on Titano or Pynt
adt = None
event_index = 0
last_event_index = -1
while True:
touch = ts.touch_point
light = light_sensor.value
if touch:
# print(f"Touched x={ts.touch_point[0]}, y={ts.touch_point[1]}")
if touch[0] < 150:
event_index = event_index - 1
if event_index < 0:
event_index = len(events) - 1
elif touch[0] > 170:
event_index = event_index + 1
if event_index > len(events) - 1:
event_index = 0
if event_index != last_event_index:
pyportal.set_background(cwd + "/" + events[event_index].imageCountDown)
text_areas[3].color = events[event_index].forecolor
text_areas[3].text = events[event_index].title
text_areas[4].color = events[event_index].forecolor
text_areas[4].text = events[event_index].subtitle
last_event_index = event_index
# only query the online time once per hour (and on first run)
if (not refresh_time) or (time.monotonic() - refresh_time) > 3600:
try:
print("Getting time from internet!")
pyportal.get_local_time(False)
refresh_time = time.monotonic()
except RuntimeError as e:
print("Some error occured, retrying! -", e)
continue
now = time.localtime()
print("Current time:", now)
remaining = time.mktime(events[event_index].date) - time.mktime(now)
print("Time remaining (s):", remaining)
if remaining < 0:
# oh, its event time!
pyportal.set_background(cwd + "/" + events[event_index].imageEventDay)
while True: # that's all folks
pass
secs_remaining = remaining % 60
remaining //= 60
mins_remaining = remaining % 60
remaining //= 60
hours_remaining = remaining % 24
remaining //= 24
days_remaining = remaining
print("%d days, %d hours, %d minutes and %s seconds" %
(days_remaining, hours_remaining, mins_remaining, secs_remaining))
text_areas[0].text = '{:>2}'.format(days_remaining) # set days textarea
text_areas[1].text = '{:>2}'.format(hours_remaining) # set hours textarea
text_areas[2].text = '{:>2}'.format(mins_remaining) # set minutes textarea
# update every 10 seconds
# time.sleep(10)
You must be logged in to post a comment.