My Skyboy application is a niche product, but I think it's pretty cool! (Let's be honest, I'm a little biased. I did write it for myself, after all.) It's a Python web app using the Streamlit framework.
Initially, there wasn't been a way to preview what Skyboy can do with FPV drone flight telemetry data. While the file upload function works, it hasn't been generalized enough (probably) to handle telemetry logs from other users. Having demo data available allows users to preview the app's functionality, which is particularly useful for anyone who doesn't have telemetry logs of their own.
My first task was to decide where to store the demo telemetry file. I could have packaged it up into the app's Docker container, but I wanted to start integrating other AWS services and so I uploaded the file to an S3 bucket.
I played around with trying to create a signed URL to grant the application access to the file, but was unable to make that work. My suspicion is that it had something to do with the file's access permissions. After several attempts, I backed up and decided to go a simpler route. I created an IAM user whose only permissions are for read access to that S3 bucket, and stored that user's credentials as Secrets on GitHub so they could be injected into the application when its image is built.
From there, I wrote a simple function that creates an S3 client using boto3 and downloads the file to memory. I coded a 'Demo' button and set it to run the download function, and made sure the visualization section of the code's logic checked whether or not the button had been clicked.
So far, so good: clicking the button downloaded the file, and the rest of the code read the file and produced its usual visualizations. Now, let's see those numbers in imperial units...
-selects the appropriate checkbox-
... wait a second. Where did all the fancy metrics and charts go?
With an uploaded telemetry file, selecting that checkbox converts the units and displays feet, miles per hour, and so on. But with the demo data, the application reset itself to its initial appearance. Confused, I attempted to solve this bug by rearranging some of my logic, but after a few increasingly frustrating attempts, I shut my computer down and walked away for the night.
The next morning, I re-engaged with this problem by going back through Streamlit's documentation. As I read the section about session state, it dawned on me that this feature was exactly what I needed.
Streamlit applications re-run themselves from the top every time some input (like a button, or checkbox, or menu) changes. I knew about this behavior, but hadn't experienced any issues with it until attempting to implement demo mode. When I looked carefully at my code, I found the source of the bug: I had conditional logic checking to see if the Demo button had been clicked. Naturally, if some other input had changed, the app would re-run, but within that subsequent run, the button would not have been clicked and the app would not load the demo data. Therefore, I needed a way for the app to know if the demo mode had been activated that persisted between runs.
I created a
demomode variable and set it initially to
if 'demomode' not in st.session_state: st.session_state.demomode = False
Clicking the Demo button changes this variable's value to
True, which then persists between runs as long as the application's browser tab remains open. The app's conditionals reference this persistent variable to determine if the demo telemetry should be read or loaded from the cache:
if not uploaded_file and not st.session_state.demomode: # display initial markdown with text and images
if st.session_state.demomode or (uploaded_file is not None): # read and/or load data from the appropriate source and # call the visualization functions
With the application checking session state to see if demo mode has been activated, the other sidebar inputs to manipulate the visualizations work as expected!
Some lessons learned from implementing a demo mode in Skyboy:
- the benefit of reading and re-reading documentation;
- the value of being able to persistently store information in an application's state;
- and the power of walking away and creating processing time when faced with a difficult challenge.
Check out the Skyboy GitHub repository here!