Lighthouse update: Touchscreen interface
Primary tabs
Interface Design Needs
What I was trying to accomplish with this interface is to have a complete solution for running and tweaking the operation of the lighthouse, without the need to plug the micro into a laptop. I’m not fooling myself to think I’ll get this perfect, and I know it won’t cover troubleshooting, but the basic operation of the lighthouse needs certain ‘unplugged’ features.
- There will be a power switch on the case, but once the power is on, the interface needs to come up on its own
- It needs to display basic up-to-date weather information
- I’d like to have indicators showing when the beacon and the foghorn are ON
- I’d like to be able to turn the beacon and the foghorn ON and OFF manually
- Display time of local sunset, as well as the time the beacon will come on and turn off each night
- Controls to change the timing of beacon ON and OFF each day (relative to local sunset)
- Controls to turn the sound ON and OFF
- A display of network connection status
- A method to handle daylight saving time
- Displays of basic troubleshooting data, namely touch coordinates, and 5 minute timing counter
- For aesthetics, I’d like to display a photo, with some associated hidden easter egg
Overview of the Display Interface
Here’s a diagram of the basic elements of the lighthouse interface. This screen comes up as soon as the M4 board boots up.
I started by setting a series of standard colors:
BLACK = color565(0, 0, 0)
LTGRAY = color565(242, 242, 242)
RED = color565(255, 0, 0)
LTRED = color565(255, 230, 230)
GREEN = color565(0, 255, 0)
LTGREEN = color565(230, 255, 230)
BLUE = color565(153, 204, 255)
YELLOW = color565(255, 255, 0)
CYAN = color565(0, 255, 255)
MAGENTA = color565(255, 0, 255)
WHITE = color565(255, 255, 255)
ORANGE = color565(255, 100, 5)
SOMECOLOR = color565(37, 180, 193)
Then painting the basic screen and background areas:
# ----------------------Draw main window and status bar backgrounds
display.fill(BLACK)
time.sleep(0.100)
display.rect(10, 10, 460, 250, WHITE)
display.fill_rect(10, 240, 460, 20, WHITE)
display.rect(10, 240, 460, 20, RED)
display.line(220,240,220,260,BLACK)
display.line(352,240,352,260,BLACK)
# --------------------Draw main window and status bar backgrounds – END
And then, populating the basic labels for weather data, along with the initial values:
# --------------------Populating Initial display
display.fill_rect(100, 40, 180, 20, BLACK)
display.txt_trans(WHITE)
display.txt_size(0)
display.txt_set_cursor(20, 20)
display.txt_write("Temp: %4.1f F" % weather_temp_F)
display.txt_set_cursor(135, 20)
display.txt_write("Humidity: %d%%" % weather_humid)
display.txt_set_cursor(20, 40)
display.txt_write("Weather:")
display.txt_trans(BLUE)
display.txt_set_cursor(97, 41)
display.txt_write(weather_description)
display.txt_trans(WHITE)
display.txt_set_cursor(20, 60)
display.txt_write("Visibility: %d meters" % weather_vis)
display.txt_set_cursor(20, 80)
display.txt_write("Wind speed: %4.1f mph" % weather_windspeed)
One thing to keep in mind is that I’m an unpracticed coder, so I tend to stick with primitives and basic language syntax, so I will spend time building a time/date readout when, you know, it turns out there are text string display controls that are much more nuanced. It took me about two weeks of tweaking the interface to figure out some of the relatively simple formatting features of python.
That was largely to get the time and sunset time to display correctly. Even with that, you’ll notice in my sample screen that I sometimes have residual text from previous values *showing through*. (Note the ‘8:33:35 PMM’ value)
There was a function call in the training to print out a bitmap image to the screen. It reads the file line by line and prints it out. It’s slow, so I just have a static image I converted of a lighthouse to the proper resolutions. This is the function:
def draw(self, disp, x=0, y=0):
With a lighthouse image I grabbed somewhere off the internet, it’s stored as a .bmp file, and loaded into the code using:
bitmap = BMP("/lighthouse-sm.bmp")
bitmap.draw(display, 314, 16)
Finally, I drew the beacon, foghorn, sound, daylight savings, and beacon ON/OFF settings boxes:
display.fill_rect(20, 130, 80, 20, LTGRAY) # Horn indicator
display.rect(20, 100, 80, 30, GREEN)
display.txt_size(0)
display.txt_trans(BLACK)
display.txt_set_cursor(32, 131)
display.txt_write("Foghorn")
display.fill_rect(118, 130, 80, 20, LTGRAY) # Lighthouse light indicator
display.rect(118, 100, 80, 30, RED)
display.txt_size(0)
display.txt_trans(BLACK)
display.txt_set_cursor(134, 131)
display.txt_write("Beacon")
display.fill_rect(210, 130, 80, 20, LTGRAY) # Sound ON/OFF indicator
display.fill_rect(210, 100, 80, 30, LTGREEN)
display.rect(210, 100, 80, 30, BLACK)
display.txt_trans(BLACK)
display.txt_set_cursor(230, 131)
display.txt_write("Sound")
display.txt_size(1)
display.txt_set_cursor(236, 97)
display.txt_write("ON")
display.circle(295,25,9,WHITE) # Wifi trouble indicator
display.rect(450, 221, 16, 16, WHITE) # DST On indicator box
display.txt_color(WHITE, BLACK)
display.txt_set_cursor(412, 221)
display.txt_size(0)
display.txt_write("DST:")
if DST_ACTIVE:
display.fill_circle(458,229,5,RED)
else:
display.fill_rect(451, 222, 14, 14, LTGRAY)
# ---------------------------------------------------------- Beacon ON and OFF Time set boxes
display.rect(20,189, 280,48,WHITE)
display.txt_set_cursor(23,190)
display.txt_write("Beacon: ON (sunset+5minutes) OFF ")
display.fill_rect(24,209,65,25, WHITE) # minutes after sunset for beaconON
display.fill_rect(170,209,65,25, WHITE) # minutes after sunset for beaconOFF
display.rect(24,209,65,25, RED) # minutes after sunset for beaconON
display.rect(170,209,65,25, RED)
display.rect(94,208,28,28, WHITE) # subtract 5 minutes from ON time button
display.rect(124,208,28,28,WHITE) # add 5 minutes to ON time button
display.rect(238,208,28,28, WHITE) # subtract 5 minutes from OFF time button
display.rect(268,208,28,28,WHITE) # add 5 minutes to OFF time button
display.txt_size(1)
display.txt_trans(WHITE)
time.sleep(0.1)
display.txt_set_cursor(99,204)
display.txt_write("-")
display.txt_set_cursor(130,204)
time.sleep(0.1)
display.txt_write("+")
display.txt_set_cursor(243,204)
time.sleep(0.1)
display.txt_write("-")
display.txt_set_cursor(274,204)
time.sleep(0.1)
display.txt_write("+")
# ---------------------------------------------------------- Beacon ON and OFF Time set boxes - END
The main, millisecond loop, is testing for touches anywhere on the screen, and then checking to see if any of those touches are on the beacon, foghorn, sound, DST, or timing ON/OFF boxes. (Note: I also check to see if anyone touches the lighthouse picture and do something fun. My 12 year old son loves that one.)
The inner loop also updates the clock in the status bar at the bottom of the screen, and the loop and counter numbers in the status bar. If a touch is detected, the coordinates are converted to the screen dimensions and printed to the middle of the status bar.
I had to hack a bit of debounce, using a touchflag variable to throw out multiple touches in a row on this inner loop.
The outer loop runs approximately every five minutes. I tweaked the count of the inner loop as I worked. As I added more and more features to the inner loop, I spent more and more time doing stuff and the inner loop took longer and longer. Originally, I was counting to 3000 on the inner loop; but I ended up only counting to 2200. I suppose I could have used the RTC to escape from the inner loop at exactly five minutes, but honestly, it didn’t matter that much that it was five minutes. Close was good enough, and the hassle of checking the RTC constantly would have probably started slowing things down excessively.
Each time through the outer loop, I increment a weatherLoopCount counter.
And he outer loop is primarily used to called the OpenWeatherAPI to update the weather values. After that, I check to see if the weather meets a condition to turn on the beacon, turn on the lights in the lighthouse, and turn on the foghorn. This is also where I set which sound effect I want to play. With two sound boards, I use one dedicated to play the foghorn sound whenever the weather is foggy, snowy, or has moderate to heavy intensity rain. (It’s actually sounding right now, as we weather the outer bands of hurricane Idalia.
The second sound board is used for what I think of as atmospherics or background sounds. I adjust those based on the weather conditions. More wind and I play a windy Norwegian harbor sound. A thunderstorm prompts a heavy waves and thunder background sound. Most other weather conditions just play gentle lapping waves and a light wind. It’s really awesome.
I also update the weather details displayed on the screen in the outer loop:
# -------------------------------5 Minute - Update Weather details
display.txt_color(BLACK, WHITE)
display.txt_size(0)
display.txt_set_cursor(361, 241)
display.txt_write("Loop:")
display.txt_set_cursor(402, 241)
display.txt_write(str(weatherLoopCount))
display.txt_color(WHITE, BLACK)
display.txt_set_cursor(20, 20)
display.txt_write("Temp: %4.1f F" % weather_temp_F)
display.txt_set_cursor(135, 20)
display.txt_write("Humidity: %d %%" % weather_humid)
display.txt_set_cursor(20, 40)
display.txt_write("Weather:")
display.txt_trans(WHITE)
display.txt_set_cursor(97, 41)
There are tons of little tweaks to the interface, but I’ll let you discover those on your own. I fill in ‘HORN’ and ‘LIGHT’ in the indicator boxes, and update the timing in the sunset timer boxes as part of the outer loop. (I do detect calls for the beacon and the horn in the inner loop with a yellow CALL display in those boxes, but I don’t actually change the behavior of the lighthouse until the five minute timer kicks off.) It’s a slow moving weather indicator.
I currently have this sitting in my basement office, and it is sometimes startling during the day to have the foghorn go off and I realize we’ve got a rainstorm passing through. And in this summer of heat, it is useful to be able to just look over at the screen and see the current temperature and humidity outside.