Introduction: Using the Raspberry Pi SenseHat: a Live ISS Tracker
Update: another function has been added: the next time ISS will fly over the location of the RPI.
The description of this function is added in Step 2.
--------------------------------------------------------------------------------------------------------------------------------
First of all my apologies for the poor quality of the video. I couldn’t find a way to capture the output of the lcd matrix decently and tried a lot of filters. It’s meant for a short display of the script parts, but doesn’t come near the live experience . You might get the picture by observing it through your eye lashes. ;-0)
The python script applies all of the SenseHat sensors. It contains the following functionality, which I will explain later on:
- A joystick-driven menu
- Live tracking of the International Space Station, using 2 API’s of ESA, displaying information about the latitude/longitude, country, time zone, visibility and in many cases a sprite representation of the country flag
- A level with two crossed visor lines to measure the horizontal position
- A display of environmental data: temperature, humidity, pressure, sea level and IMU data
It is a straight forward script, avoiding classes for example, to make it easy to follow for beginning programmers (like me).
The hardware I used :
- Pi B+ (Had one still laying around. It is fast enough, because the speed of displaying through the led matrix is the most slow part)
- Sense Hat board
- Wifi dongle
- SD card (including the rubber band to keep it in the broken slot)
- Pi case.
Some upfront remarks:
- The SenseHat board has been launched for the Astro Pi project (https://astro-pi.org/) . A very good educational and motivational project of ESA.
- The SenseHat disables further use of the GPIO pins. I found it rather disappointing for I expected the Sense Hat to complete the full E2E application of the Raspberry. With E2E I mean full interaction with the environment like with robotics: physical sensing => digital processing => physical (re-)action through actuators/motors or anything like that. (On the Astro Pi site you can find an instruction for building a flight case. In this instruction buttons are added to the Pi, but an explanation on the wiring of the SenseHat and which pins can be used for soldering, lacks.)
- The IMU handling is based upon a good library: github: richards-tech I couldn’t find a description of the sensor fusion algorithm used, but the Pitch and Roll angles seemed to be pretty accurate. Yaw returns the magnetic angle, so it returns a value even when the Pi is in complete resting position. The temperature sensor is way off (in my case at least 10 degrees Celsius). The documentation explains such as a result of the heat produced by the device.
So it looks like the main application use for the SenseHat is data logging. One can code some simple sprite based games as well. (Useful when grasping the basics of game development). Personally I’m not really interested in logging huge amounts of environmental data. The winning contribution of the kids of the Thirsk school inspired me more, so I enhanced their Idea towards a live tracker for the ISS.
Step 1: The Script
You can find the full script at: Bitbucket
First of all the libraries needed:
- SenseHat library. (I replaced the standard installed RTIMUlib by RTIMUlib2)
- os and sys command parsing like ending the script execution
- json for retrieving the data of the API calls in a Python dictionary
- urllib2 for accessing the URL’s through the web
- datetime and time for obvious reasons
- pygame for interpretation of the joystick events
- IssFlags a second Python script containing all the sprite definitions of the flags
The main section contains the statements to fetch the events (joystick movements) of the pygame queue. This part should always be in the main loop of the script. The joystick is a 5-way button (Up, Down, Left, Right, Press) that produces 10 different events (5 times KEYUP, 5 times KEYDOWN). When pushing the joystick, one produces 2 events (KEYUP and KEYDOWN) with the same keycode (Up, Down, Left, Right). I first cleaned the queue after reading a single event, but when I added the trackISS() function to the main loop, in some peculiar way, pygame handled both events before clearing the queue. For that reason the handle.event() function starts with a filter on event type. The rest speaks for itself.
trackISS() was added to the main loop to make the script a bit more lively: when no keys are used, the location of the ISS is displayed. The function is kept simple: get a json record from the API, enrich the data received through a call to a second API and display the results.
A description of the API’s and some examples on how to use them, can be found at: ISS API's
The data structure of the json records can also be easily retrieved by copying the url in a browser. (After all an API is just a ‘not-so-good-looking-url’.) A ‘try – except’ statement handles access to the API-url. This is done to prevent the script from crashing when the url is not accessible (caused by e.g. loss of wifi, a glitch from your provider or maintenance done at the API-server, etc.).
The first API returns a lot of data like solar coordinates, velocity, visibility and earth coordinates. To enrich this data with a country code and the current time zone, the latitude and longitude returned by the first API have to be added to the second URL by simple string concatenation.
The second API only returns data when the ISS is above land. Above sea the API returns the notorious ‘404 Page not found error’. So, after checking the availability of the url, the script checks for a HTTPError as well. When encountering a HTTPError, the location at sea is displayed. If data is returned, the country code is used to search the flag data.
The function showLevels() deals with the pitch and roll data. It contains a loop checking the orientation of the RPi and timing the idle position of the Raspberry. Every time the RPi is moved within the maximum idle time, the number of rows and collumns is calculated and displayed. The size of the lcd matrix (8x8) determines the resolution of the level (180 degr / 8). Since there’s no centre/middle of the matrix, the row/column to start with has to be determined every time, depending on the movement (nose or tail, left or right). It’s no sophisticated coding, just handling each requirement/constrain at a time.
The environmentals() function just calls for each sensor at a time. The coding for each sensor is taken straight out of the SenseHat examples (the folder is part of the install). Be aware that calling for the magnetometer disables the gyroscope and the accelerometer. You’ll have to switch those on every time using the ‘set_imu_config(bool, bool, bool)' statement.
So far for what I could get out of the SenseHat. Hopefully it helps people with the first steps using the board. I’m curious about other/new ideas for apllications. And of course feel free to push more flags into the IssFlag.py script.
Step 2: When Will ISS Fly Over Here?
This feature came across in another blog. While working on a answer, I thought it would be a nice feature for the ISS tracker since the main menu was still a bit empty. The script doesn’t use new functionality of the SenseHat. In fact it only displays output on the lcd matrix. I saved the script as a new release, so the first version is also still available. You can find the new script here: Release 1.0
For an answer on the question 'When is the next time ISS will fly over the location of the RPi?', we need to get the current location of the RPi (‘Where am I?’) and a projection of that location at the ISS flight scheme (‘When will ISS be here?’).
The Google Maps Geocoding API (for dynamic Geocoding) can produce location data upon many different kinds of requests. I used the mac address of the Wifi host to post for the location coordinates. Python provides the 'uuid-library' to get that mac address, so we have to import it first ('from uuid import getnode as get_mac'). The library returns the address as a 48 bit integer. Google expects a format with :’s and uppercase hexadecimal digits. The conversion can be made in a single line:
'MAC = ':'.join(("%012X" % mac)[i:i+2] for i in range(0, 12, 2))'
This blog wasn’t supposed to be about string handling in Python, so I’ll leave it here. The call to the Google api needs a specific format, so in the next lines the json-object and the header are created.
To use Google api’s, one need a specific API_Key. Those are personal keys; so I blanked out the keys in the script, but one can obtain a personal key at: GeoCoding For personal usage the keys are for free and are limited to 2.500 calls a day. (So don’t use it in a loop.)
If there’s a problem (like too many calls) the api’s will return a HTTPError. Since that error is a subclass of URLError and I didn’t need additional information I only used the URLError exception.
The response is a json record consisting of a concatenation of layered dictionaries. You’ll have to peel them off to get the coordinates we need.
So, to answer the question ‘Where am i?’, the script obtains the mac address and sends it in a post to a Google api. This is enough data to build up the next question, but I thought it nice, to display some readable info of the location. For that I used the returned coordinates for a post to a second Google api for reversed Geocoding. For this api you’ll need a second API_KEY and it'll return a huge json record. So the script peels of that structure to obtain a ‘formatted_address’. Mind you: the content of ‘formatted_address’ differs per situation! In my case it just returns City and Country, which was all I wanted, but if you’re in the US, you’ll probably receive Streetname, Housenumber and Zip code as well (which is a but too much for displaying at the lcd matrix).
Because I didn’t want to go into nesting several ‘try-except’ statements, I divided the calls into several Python functions and keep track of the flow by several Booleans (URLn_FOUND). Not exactly a contribution to the readability of the script, but I tried to keep it as simple and transparent as possible. Pro programmers surely will come up with better way’s, use classes, etc.
So, the next question (‘When will ISS fly over’) can be answered with a simple call to the api at ‘open-notify.org’. This api returns an array of date’s, times and durations. The duration is based upon the moments ISS will be at about 20 degrees above the horizon. I only used the first entry (the next ones will differ around 90 minutes each in time - lap time of ISS - and of course in duration).
Well, does this make Sense(hat)? I don’t know. In my opinion the SenseHat is in essence about environmental data and easy displaying on a lcd-matrix. My hobby is building (almost) autonomous robots. The SenseHat can produce (some) IMU data to use for example with a balancing robot. Because the GPIO pins are no longer accessible, communicating with servo’s, dc/stepper motors, lidar/ir/us-sensors, etc. have to be done through another microprocessor board. (Serial) Communication between boards brings in unnecessary latency. So for now I’ll stick to my current architecture: using microprocessors like Arduino to deal with the ‘firmware’ part (sensors, actuators/motors, including balancing/odometry) and have the RPi deal with more heavy logic and processing, like computer vision.
Nevertheless it was fun playing with the SenseHat and I’m really fond of the Astro Pi project.
Leaving the main menu in the script with one vacancy …… So, I’m truly interested in ideas!
Maybe …. A function to operate a flashlight, flashing ‘Greetings ISS crew’ in Morse every time the ISS passes over your house?