In this getting started guide we’ll use the Pytrack from Pycom to detect our location on Earth using the onboard GPS module and use the accelerometer to control a 3D model on the PC. I put a WiPy onto my Pytrack, connected it to the PC and used Device Manager to find out the COM port number allocated to the Pytrack.
We've covered the basics of the Pytrack in our Overview. There's also an Overview and Getting Started tutorial for the WiPy which applies to any other Pycom microcontroller module. If I've missed anything in this tutorial, please read back and see if I've covered it before.
Looking at the Pycom documentation’s index we see section 4. Pysense & Pytrack. Great! Unfortunately, I found the 4.2.1 Updating Firmware process a bit of a pain. I’ve broken that out as a separate tutorial. Having done a firmware update on the Pytrack, it's on to 4.2.3 Installing Libraries. The documentation says to download the Pycom libraries from Github. I've assumed you've downloaded them to your Downloads folder and unzipped the file.
Having a look through the downloaded files it seems we can just open Downloads\pycom-libraries-master\pycom-libraries-master\pytrack in Atom and run that. You might need to hardware reset your device before the Upload will work. Opening the main.py file and hitting Run starts the code and gives its output in the REPL panel. But my GPS wasn’t seeing anything.
Connecting on COM7... >>> No file open to run >>> Running \\mediapc\Shared (ce)\Content\Content2\Chris\Pycom\Pycom Pytrack\pytrack\main.py >>> >>> RTC Set from NTP to UTC: (1970, 1, 1, 0, 2, 44, 64755, None) Adjusted from UTC to EST timezone (1970, 1, 1, 2, 2, 44, 3, 1) (None, None) - (1970, 1, 1, 0, 3, 14, 118264, None) - 2548784 (None, None) - (1970, 1, 1, 0, 3, 44, 357110, None) - 2548752 (None, None) - (1970, 1, 1, 0, 4, 14, 462182, None) - 2548736 (None, None) - (1970, 1, 1, 0, 4, 44, 567196, None) - 2548672 (None, None) - (1970, 1, 1, 0, 5, 14, 674154, None) - 2548720 (None, None) - (1970, 1, 1, 0, 5, 44, 780187, None) - 2548592 (None, None) - (1970, 1, 1, 0, 6, 14, 890026, None) - 2548784 (None, None) - (1970, 1, 1, 0, 6, 44, 996056, None) - 2548512 (None, None) - (1970, 1, 1, 0, 7, 15, 234999, None) - 2548800 (None, None) - (1970, 1, 1, 0, 7, 45, 475047, None) - 2548864
The problem, I suspect, is not the code but the building I’m in. The GPS receiver needs a clear view of a large patch of sky. I’m basically in a Faraday cage given the grilles on the windows and the steel roof. With a little repositioning, the GPS finally locked on.
>>> >>> RTC Set from NTP to UTC: (1970, 1, 1, 0, 11, 2, 609778, None) Adjusted from UTC to EST timezone (1970, 1, 1, 2, 11, 2, 3, 1) (None, None) - (1970, 1, 1, 0, 11, 32, 691103, None) - 2548528 (None, None) - (1970, 1, 1, 0, 12, 2, 815440, None) - 2548832 (None, None) - (1970, 1, 1, 0, 12, 32, 932370, None) - 2548544 (None, None) - (1970, 1, 1, 0, 13, 3, 54021, None) - 2548528 (None, None) - (1970, 1, 1, 0, 13, 33, 178150, None) - 2548576 (None, None) - (1970, 1, 1, 0, 14, 3, 303090, None) - 2548784 (None, None) - (1970, 1, 1, 0, 14, 33, 426014, None) - 2548784 (-32.93984, 151.7185) - (1970, 1, 1, 0, 15, 1, 817086, None) - 2547856 (-32.93984, 151.7185) - (1970, 1, 1, 0, 15, 2, 731828, None) - 2548384 (-32.93984, 151.7185) - (1970, 1, 1, 0, 15, 3, 817346, None) - 2548400 (-32.93984, 151.7185) - (1970, 1, 1, 0, 15, 5, 9380, None) - 2548368 (-32.93984, 151.7185) - (1970, 1, 1, 0, 15, 6, 62453, None) – 2548400
Don’t you love how the time setting failed and the program carried on regardless? That’s something to fix! When I take the latitude/longitude and drop them into Google Maps it’s almost perfect. So, yes, the GPS works well. Interestingly we now have two ways to set the real-time clock (RTC) on the WiPy: use NTP via the Internet, or use GPS.
I’m a bit disappointed the GPS will only give a latitude and longitude. I’ve spent far too long poring over and parsing data from GPS devices. They usually output the raw data in NMEA format and there’s much more to know (including the time!) than just lat/long. Opening the L76GNSS.py file in our pytrack project we see the full code for this library. There is a
_read() function, but it starts with an underscore. I’m not familiar with that notation. Ah, the official Pycom documentation says:
“Private” instance variables that cannot be accessed except from inside an object don’t exist in Python. However, there is a convention that is followed by most Python code: a name prefixed with an underscore (e.g. _spam) should be treated as a non-public part of the API (whether it is a function, a method or a data member). It should be considered an implementation detail and subject to change without notice.
So technically we shouldn’t access the raw GPS data using
l76._read() because it should be treated as hidden and private. Here’s the whole function though:
self.reg = self.i2c.readfrom(GPS_I2CADDR, 64)
We can see that all it really does is read 64 bytes from the GPS device using the i2c bus. If we want to read all the data the GPS can provide we only need find another Python class that reads the text in NMEA format and parses that to output gps.datetime, gps.resolution, gps.velocity, gps.altitude etcetera. Have a look at some of the data a GPS can provide.
Visualising Accelerometer Data
We can utilize other code from Pycom to visualize the accelerometer data coming from a Pytrack or Pysense. First, we need to have Processing installed. What’s Processing? Arduino is based on Processing so if you’ve used Arduino this will feel very familiar. The only difference is we’re going to use it for writing Python, not Arduino (C/C++) code. We can’t just use Atom because we’re using a feature of Processing that allows us to quickly draw in 3D.
Installing Processing and Python Mode
- Go to https://processing.org/download/ and download the applicable app. It’s free.
- When you have the complete download file, unzip it in Downloads
- There’s no installer (setup.exe), Processing just runs when you run the processing.exe file.
- Since we might accidentally delete Processing if we leave it in our Downloads folder, we should move it to somewhere logical, like C:\Program Files\
- Once there, open the C:\Program Files\processing-3.3.7-windows64\processing-3.3.7 folder and drag the processing.exe file to the Start menu or Task Bar
- Start Processing using the shortcut you just created.
- From the Tools menu, select Add Tool…
- Switch tabs to the Modes tab.
- Type python in the search box.
- Click on Python Mode for Processing 3
- Click the Install button at the bottom of the window.
- Wait for Downloading and Installing to complete. I have found the Modes screen doesn’t show the installation is finished, so I click one of the other tabs and click back on Tools. You should get a green tick next to Python Mode for Processing 3
- Close Contribution Manager.
- Now at the top right, you should be able to change the development language from Java to Python.
Trying the Code
The code for the Pycom device isn’t quite ready to use out of the box. We need to:
- Create a new folder, let’s call it device, under Downloads\pycom-libraries-master\pycom-libraries-master\examples\pytrack_pysense_accelerometer
- We can now move the main.py file (from one level back) into the new device folder.
- Within the new device folder, we need to have a copy of the libraries for the Pysense or Pytrack that we’re using.
- Copy into your new device folder, the lib folder from either:
- Downloads\pycom-libraries-master\pycom-libraries-master\pytrack, or
Now we can open the project in the Atom IDE.
- From the File menu, select Open Folder
- Navigate to Downloads\pycom-libraries-master\pycom-libraries-master\examples\pytrack_pysense_accelerometer\device and click Select Folder.
You’ll see the Project panel shows the files in the project. Click on the main.py file to see what’s happening. The code is written for the Pytrack. If you’re using the Pysense instead, edit two lines in your main.py as shown below:
from LIS2HH12 import LIS2HH12
from pytrack import Pysense # Changed from Pytrack
py = Pysense() # Changed from Pytrack
acc = LIS2HH12()
pitch = acc.pitch()
roll = acc.roll()
Now we want to ensure the Atom IDE is connecting to the Pycom controller on a serial port, not over WiFi. Check Device Manager if you need to find your COM port number. Click on Settings between the REPL (bottom) panel and the code editor, click Global Settings. Ensure the Device Address is set to the correct COM port. Close Settings and connect to the device. Use the hardware reset button if you need to.
Do an Upload to get the project files onto the device. When the upload completes, the device will do a soft reboot and run the main.py file. You should get an error saying “no module named ‘pycoproc’. To fix this we need to grab the pycoproc.py file from Downloads\pycom-libraries-master\pycom-libraries-master\lib\pycoproc and copy it into the lib folder in our project. Hit that Upload one more time! Alright. Data is flowing. That’s what we need right now.
We can now open the visualization project in Processing.
- Run Processing from your Start menu
- From the File menu, select Open
- Navigate to Downloads\pycom-libraries-master\pycom-libraries-master\examples\pytrack_pysense_accelerometer\visualiser and open visualizer.pyde
We need to fix the second line of code. The line starting
SERIAL_DEVICE… should have the name of our COM port in it. When I amend this line, my program starts:
Now we need to realise that we have two IDEs both configured to use the same COM port. Only one can have control at any time so each time we jump from Atom to Processing and back again we need to make sure we disconnect and reconnect the COM port.
Since we were just in Atom getting the data stream to work, we should go there now and Disconnect. Jumping back to Processing we can hit the “Play” button above the code editor. The bottom part of Processing should say “Connected to COM3” and a second window should appear, a black square with a red bar across it.
We know what should happen, don’t we? The red bar is part of a 3D model that should rotate around as we rotate the Pytrack/Pysense about its pitch and yaw axes. How well does it work? Not at all initially. On my machine, I need to hold the device at an angle and wait a while for the visualizer to catch up. After that, I can see it’s trying to work in real time, but it’s laggy. What’s going on?
Stop the visualizer in Processing. Let’s look at the code. In Processing we must have a
setup() and a
setup() is called once, then
draw(). Every time the
draw() function ends, Processing starts it again from the top. Arduino’s
loop() functions work the same way. When writing in Python they are declared using the
def keyword as you can see. So
setup() gets things ready to run, then
draw() runs over and over.
It seems to me that each time I run the code for the visualizer I have to wait for something to “catch up”. I have the suspicion that there’s a backlog of data already at the COM port when we connect to it. But there’s code to handle that. In
setup() we see code to connect to a serial port and calls it
my_port. After the for-else statement, there are these two lines, with a helpful comment:
# Clear the serial buffer and consume first
# line incase we missed the start of it
So when the program starts, it should empty the backlog of data and start working in real time. But is this working? Let’s find out. In the
draw() function, under
if line != None: we’ll add the line:
The Processing documentation says
Serial.available() will give back an integer which is the count of bytes available at the Serial port. Running this code I get a stream of numbers printed in the Processing IDE. They start at about 64,000 and head downward. When the numbers finally get near to zero the 3D visualizer “catches up” and starts to show the actual orientation of the device. It’s laggy, but it works.
I’ve tried several ways to fix this error. In the end the thing that worked was implementing a delay. After the serial device is created with
my_port = Serial(this, dev, 115200) we need to implement the tiniest delay before we run into the
my_port.clear() line. Immediately above
my_port.clear() I added the line:
A whole millisecond of delay! So this line would work I also had to add an import command for the time library:
Now every time I start the visualizer the printed
my_port.available() value is nearly zero and the 3D model moves a lot more responsively. Nice!