Following my recent successful test of balloon-to-balloon communications, I had the idea of uploading messages from the ground to a balloon (via LoRa radio), and then have those messages displayed on the LED matrix of an “Astro Pi” Sense HAT, as will be used by Tim Peake during his stay on the International Space Station.
You may well be asking who is likely to see those messages, so here’s the plan:
- Launch a weather balloon before Tim flies to the ISS on Soyuz TMA-19M
- Encourage people to send messages to Tim Peake via tweets to a specific hashtag
- Those tweets will be automatically uploaded to the balloon via a radio link
- Tweets will then be scrolled on the Astro Pi LED matrix
- A video camera which will record the entire flight and the scrolling messages
- Download sense HAT data during the flight
- After the flight is recovered, selected camera footage will be uploaded to YouTube and sent to Tim.
There’s nothing particularly complicated in the above, but there are quite a few steps involved to make it work.
Astro Pi + Pi In The Sky + LoRa Board
For the balloon tracker to perform the above tasks, it needs an Astro Pi of course, plus a Pi In The Sky board for GPS and power, plus a LoRa board to allow for uploads from the ground. The first thing to do then was check to see if there are any I/O conflicts between the above.
The only conflicts were to do with one of the 2 LoRa modules on the LoRa board. Since we only need 1 LoRa module, we can just put that in the non-conflicting position (CE1).
So, I built a stack with a Pi B+ on the bottom, then a Pi In The Sky (PITS) board, LoRa board and finally an Astro Pi board which, of course, has to be on top otherwise the LED matrix won’t be visible!
The first part of the sequence is to search Twitter for messages to upload, and for this Iwrite a short Python script using the Tweepy library. The following function and main code show the process:
def SearchHashtag(HashTag, LastID, Count): c = tweepy.Cursor(api.search, since_id=LastID, q=HashTag).items(Count) for tweet in c: text = tweet.text.encode('ascii',errors='ignore').decode("utf-8") print(str(tweet.id) + " - @" + tweet.user.screen_name + ": " + text) if tweet.id > LastID: LastID = tweet.id return LastID LastID = 0 Count = 10 HashTag = '#AstroPiTest' while 1: LastID = SearchHashtag(HashTag, LastID, Count) print ("LastID = ", LastID) time.sleep(5)
The complete script reads each matching tweet:
and creates a separate text file for each such tweet:
@daveake: Just testing for a project ....
For the actual flight then will be more filtering, both programmatic and human, to ensure that only real/polite/sane messages are uplinked!
For the radio uplink I have modified my LoRa Gateway, which is normally tasked with receiving LoRa transmissions from balloons and then uploading the data (telemetry and/or images) to a web server. For this flight though we need it to be able to send messages up to the balloon. That’s a straightforward task, using GPS timing to decide when the balloon can transmit and the gateway should listen, and then when the gateway can transmit and the balloon should listen. For testing I’ve set a short period each minute for the uploads, but this may change for the flight (to allow more messages to be uploaded).
The gateway has to monitor a folder, looking for messages to upload. Those messages are produced by the Python script mentioned earlier, so the gateway just has to read the messages, and send them to the balloon. Sometimes tweets may come in faster than they can be uploaded, so the code will need to be able to cope with that.
Here’s a screenshot of the gateway uploading the above message:
Receiving Messages At The Tracker
For this I modified the PITS software so that it can accept and process these uplinked messages. To keep the PITS changes simple, all it does is receive the message:
and then create a small file containing the text to display:
Just testing for a project ....
Displaying The Message
This is done by a simple Python script that uses the Astro Pi library. The following snippet shows how it works:
def process_file(file): print ("File found: ", file) f = open(file, 'rt') line = f.read() f.close() os.remove(file) sense = SenseHat() sense.show_message(line) while 1: for file in os.listdir('.'): if fnmatch.fnmatch(file, '*.sms'): process_file(file) time.sleep(1) time.sleep(1)