Showing posts with label Python. Show all posts
Showing posts with label Python. Show all posts

Monday, August 31, 2015

Adding 'Hack' monospace font functionality into Spyder IDE in Fedora Core 21

As of late, a lot of the code I have been hacking out for just about anything has been straight, 100% Python.  With that, I've also ditched 'vi' + plugins and have grown extremely comfortable (and probably complacent?) using Spyder IDE GUI.  I never wanted to be GUI dependent (again) but I am, so whatever.  I deal with it.

But, recently, I saw a post on arstechnica.com touting the coolness of Hack, an open-source monospace font marketed towards the code slingers of the world.

I undoubtedly get tired of using of the defacto DejaVu and Liberation monospace fonts and decided to give this a shot.  And quite honestly, even though it's subtle, I really like it.

Characters of note:  I don't notice the lowercase-L vs. the numeral 1 as much as I do the zero vs. uppercase-O and lowercase-I curvature.

Font sizes:  I think my favorite font size is 9px or 11px (11px especially if I've been staring at my monitor for more than 4 hour straight with contacts in to boot!)

The overall font character scheme also seems a bit more compressed as well, which is nice so I 'feel' like I'm getting more width real-estate.  Case in point:  take it for what it's worth.

Even though this is a less traditional and definitely 'non-distro' way to add an overall font to your OS, I think it gives most people the flexibility enough to give it a shot themselves with as minimal overhead as possible, especially if you want it 'long-term' and want to put it in the same place as your other distro fonts via package management without creating a distro package for it.

Here's how to make the 'Hack' monospace font accessible in Spyder IDE under Fedora (or any other RHEL based OS for that matter).

If you really want to get technical and overview-ish, I guess this would be a good way to hack-and-slash 'any' font you want to Spyder, monospace or not.

Download and Unzip 'Hack' Font from SourceFoundary

Go to Hack's SourceFoundary site and download the True-Type font .zip archive.

Once you have downloaded the 'Hack' fonts, simply unzip them wherever you want:

$ cd /path/to/hack/fonts/
$ unzip Hack-v2_010-ttf.zip
unzip Hack-v2_010-ttf.zip 
Archive:  Hack-v2_010-ttf.zip
  inflating: Hack-Bold.ttf           
  inflating: Hack-BoldOblique.ttf    
  inflating: Hack-Regular.ttf        
  inflating: Hack-RegularOblique.ttf
$

Add 'Hack' font into to your OS

Adding the Hack monospace font (this way) is pretty trivial.  The key point here is to know the top-level path where your Linux distro stashes it's fonts.  For a font like this that I really want to use long-term, I think this is a better solution than putting it in ~/.fonts/  or equivalent. I'm also going to assume you have proper privileges to do all these command(s) or know how to obtain it (e.g. NOT prefacing the sudo command to obvious root-level stuff)
  • Create a new directory for the 'Hack' font in your Linux distro's top-level system font path
  • $ mkdir /usr/share/fonts/hack
    
  • Copy fonts over
  • $ cp -a /path/to/hack/fonts/Hack-*.ttf /usr/share/fonts/hack/
    
  • Make sure permissions + ownership are sane on your font directory and fonts
  • $ chown -R root:root /usr/share/fonts/hack && chmod 755 /usr/share/fonts/hack
Changes to Spyder IDE

I'm using Spyder IDE v2.2.4, so YMMV if you're using another version.  I didn't go scout all this out, but it's pretty self-explanatory.

Spyder IDE has a default, hard-coded list of approve and available monospace fonts that are defacto standards.  All you have to do is alter the list data structure defining the list of monospace fonts in spyderlib.

To do this, you'll want to look for the following line of code in /usr/lib/python2.7/site-packages/spyderlib/config.py module:

MONOSPACE = ['Monospace', 'DejaVu Sans Mono', 'Consolas', 'Monaco',
                'Bitstream Vera Sans Mono', 'Andale Mono', 'Liberation Mono',
                'Courier New', 'Courier', 'monospace', 'Fixed', 'Terminal']

...and add in the 'Hack' fonts into the MONOSPACE list structure so it looks something like this:

MONOSPACE = ['Monospace', 'DejaVu Sans Mono', 'Consolas', 'Monaco',
               'Bitstream Vera Sans Mono', 'Andale Mono', 'Liberation Mono',
               'Courier New', 'Courier', 'monospace', 'Fixed', 'Terminal',
               'Hack-Regular', 'Hack-RegularOblique.ttf', 'Hack-Bold.ttf',
               'Hack-BoldOblique.ttf']

NOTE:  There are several 'config.py' modules in Spyder IDE, so if you're really confused at this point, just do a case-insensitive recursive grep (e.g.  grep -i -R "MONOSPACE" /path/to/spyderide/site-package/install) and find the right one to edit.

Restart Spyder IDE and Choose 'Hack' as your Font

If you still have Spyder IDE running, close it and re-open it.

Then on Sypder's top menu bar, you'll want to navigate to 'Tools > Preferences', then select the 'Editor' option on the left-hand side.  Now, if you select the 'font' drop-down menu, you should see 'Hack' available for your choosing:



You can now click 'Apply' and watch all those wonderful tabs of 'code' change over to using 'Hack'.

If you also want your Python console in Spyder to use this font as well, just select 'Console' and change it there, too.

Before-and-After of Hack vs. DejaVu Sans

Here's a couple of quick screenshots put in extra-large to show the differences of Hack vs. DejaVu Sans out-of-the-box Spyder IDE font:

DejaVu Sans - Normal - 9px

Hack - Normal - 9px


Now as much as I'd want to package this into an RPM for my own sake in case some update comes about, that would just be more work than it's worth.

Enjoy!

Saturday, January 05, 2013

Calculating NMEA sentence checksums with Python


Another feature I wanted to add to my NTP/GPS project I'm doing with my Raspberry Pi is to validate the NMEA sentences I'm reading in by checksum'ing the NMEA sentence and comparing it with the XOR checksum provided from the GPS receiver.

This is especially important if you plan on writing an application that is reading $GPGGA, $GPGLL or $GPRMC sentences for timekeeping purposes and want to validate the sentence you read for time is legit or not.


Background on NMEA Checksums


After reading some documentation on NMEA sentence types and, more importantly, about checksums, the process is actually quite easy and can be coded up fairly easily.

Here's the main blurb from the NMEA documentation about the checksum and how to calculate it:
Programs that read the data should only use the commas to determine the field boundaries and not depend on column positions. There is a provision for a checksum at the end of each sentence which may or may not be checked by the unit that reads the data. The checksum field consists of a '*' and two hex digits representing an 8 bit exclusive OR of all characters between, but not including, the '$' and '*'. A checksum is required on some sentences.
Not so hard, right? We need to exclusive OR (XOR) all of the characters (INCLUDING the commas) between the '$' and the '*'. 

Let's do it!


NMEA Sentence Breakdown


Here's a NMEA sentence example that I will use to show the XOR process on:

$GPGGA,174134.000,4345.9112,N,09643.8029,W,1,05,2.7,452.6,M,-27.1,M,,0000*60

The color breakdown is:

            Black: Two positional characters in the NMEA sentence we need to read our characters 
                      from between
            
            Green:  Characters we need to XOR
            Blue:  The calculated checksum we need to compare our calculated checksum against


Bitwise XOR on NMEA Sentences


As the documentation states, we need to exclusive OR (XOR) all of the characters (INCLUDING the commas) between the '$' and the '*'.

The process is quite simple: We want to take each character and XOR it with the previous XOR'd output from the last character. The very last character that is XOR'd will be the final checksum value that you'd then compare with the checksum value.

If you're a bit fuzzy on on XOR, the bitwise operator in most programming languages is ' ^ ' and the rules are as follows:


  • 1 ^ 1 = 0
  • 1 ^ 0 = 1
  • 0 ^ 1 = 1
  • 0 ^ 0 = 0

One thing to note, when you start of XOR'ing, you'll want to compare your first character with zero (e.g. 0, 0xFF, 0b0, etc.) so on the next character iteration (which would be the 2nd character in the NMEA string) will XOR against the binary value of the first character.  Why is this so?  If you look at the XOR rules above, we only need one bit 'on' (or '1') on to bit-flip-it and keep it 'on' (or '1').  So if we XOR against '0', we get our original value back.

Let's use our example sentence above to go through a handful of binary XOR iterations of what we'll be accomplishing in code:

0b0000000     0
0b1000111     G
----------
0b1000111     XOR output
0b1010000     P
----------
0b0010111     XOR output
0b1000111     G
----------
0b1010000     XOR output
0b1000111     G
----------
0b0010111     XOR output
0b1000001     A
----------
0b1010110     XOR output
0b0101100     ,
----------
0b1111010     XOR output
0b0110001     1
----------
0b1001011     XOR output

...

0b1010000     XOR output
0b0110000     0
----------
0b1100000     XOR output
0b0110000     0
----------
0b1010000     XOR output
0b0110000     0
----------
0b1100000     Our checksum (96 decimal, 0x60 HEX)

As you can see, we ended up with '0x60' which is the same as our example sentence above of '*60'. So we were able to validate this NMEA sentence!

NMEA Checksum Python Code


Now that we got the explanation out of the way, let's look at the code.  It's really simple:
def chksum_nmea(sentence):
    
    # This is a string, will need to convert it to hex for 
    # proper comparsion below
    cksum = sentence[len(sentence) - 2:]
    
    # String slicing: Grabs all the characters 
    # between '$' and '*' and nukes any lingering
    # newline or CRLF
    chksumdata = re.sub("(\n|\r\n)","", sentence[sentence.find("$")+1:sentence.find("*")])
    
    # Initializing our first XOR value
    csum = 0 
    
    # For each char in chksumdata, XOR against the previous 
    # XOR'd char.  The final XOR of the last char will be our 
    # checksum to verify against the checksum we sliced off 
    # the NMEA sentence
    
    for c in chksumdata:
       # XOR'ing value of csum against the next char in line
       # and storing the new XOR value in csum
       csum ^= ord(c)
    
    # Do we have a validated sentence?
    if hex(csum) == hex(int(cksum, 16)):
       return True

    return False

There you have it!

Parsing NMEA sentences from GPS with Python + PySerial

I've had a need to parse some NMEA output on my Raspberry Pi for a project I'm working on. In essence, it is pretty trivial to read from a serial port and parse ASCII data in any programming language, but to build some resiliency and efficiency in need to be handled with some care.

I happen to interfacing with an EM-408 GPS module with my Raspberry Pi off the GPIO Rx/Tx USART GPIO pins.

If you need a quick reference for NMEA sentence standard, go here.

Working with PySerial


Below is a quick and dirty code sample to interface with a USART/serial interface. The biggest thing to take into consideration is the 'timeout' option when creating your serial.Serial() object.

From my trial and error process, specifying timeout=0 (e.g. no blocking at all), while makes some sense in a GPS NMEA sentence polling application to return immediately and keep reading output, it causes serious amounts of CPU overhead (almost 100% utilization).

Eliminating the timeout altogether (wait forever) isn't a great idea either because your code will endlessly block/wait for output from the GPS module; not good if the module ever dies/power loss/etc.

Setting a gracious timeout of 5-10 seconds (e.g. timeout=5 or timeout=10) seems to help out as well and end up being the best of both worlds.

Here's a snipit of my class for the EM-408:

import serial 

class EM408GPS:
    
    def __init__(self, serialport, baudratespeed):

        self.gpsdevice = serial.Serial(port=serialport, baudrate=baudratespeed, timeout=5)
        
        self.init()

    def init(self):
        
        if self.isOpen():
            return True
        
        return False

    def open(self):
        self.gpsdevice.open()
        
    def isOpen(self):
        return self.gpsdevice.isOpen()


That rough class sketch should be a perfect class wrapper to get you going with interfacing with a GPS via serial port or USART pins on the Pi.

Reading data with PySerial: Buffer or Newline?


This was the most interesting piece so far with. PySerial has a handful of methods for reading data that I tested with:

  • read(): This method reads the size of bytes from serial port input buffer.
  • readline(): This method reads serial port data down until a "\n" (newline) character is observed, then returns back a string.

    To be clever and witty, you'd generally want to use something like readline() since each NMEA sentence that it output to the serial port is terminated with a CRLF, right? I mean, why the hell wouldn't you? The answer is wrong the second you notice the very high CPU utilization happening when reading data.

    The good thing is this isn't a new problem, as it's a documented quite extensively on stack overflow amongst other places.

    The better way I found to attack this CPU utilization problem, is to take advantage of another method that PySerial offers:

  • inWaiting(): Return the number of bytes currently in the input buffer.

    ...and used this in combination with reading just '1' byte with read() then read whatever is left in PySerial's input buffer, then return for me to parse.

    Here's my class method called 'readBuffer()' partly solves this issue:

    def readBuffer(self):
    
            try:
                data = self.gpsdevice.read(1)
                
                n = self.gpsdevice.inWaiting()
                
                if n:
                    data = data + self.gpsdevice.read(n)
            
                return data
    
            except Exception, e:
                print "Big time read error, what happened: ", e
                sys.exit(1)
    

    The next part to deal with is now that we are reading everything out of the input buffer, our NMEA sentences aren't exactly in sentence order anymore.

    Now we have to leverage a bit of coding to properly find the start and end of a NMEA sentence. It's not too bad of an effort since we know a NMEA sentence starts with a '$' and ends with 'CRLF'. The key point is to find the CRLF in your read data buffer, then ensure to use the right end of that CRLF split (which is the start and some data of your other NMEA sentence) as the new start of the data buffer to construct the next line until you find the next CRLF, and so on...

    Here's the code snipit from my main() area that shows the initialization of the GPS and the read out of the NMEA sentences from my readBuffer() method:

    import re
    
    ...
    
    def main():
    
        device = EM408GPS("/dev/ttyAMA0", 4800)
    
        newdata = ""
        line = ""
    
        while device.isOpen():
             # If we have new data from the data CRLF split, then 
             # it's the start + data of our next NMEA sentence.  
             # Have it be the start of the new line
             if newdata: 
                 line = newdata
                 newdata = ""
             
             # Read from the input buffer and append it to our line 
             # being constructed
             line = line + device.readBuffer()
                
             # Look for  \x0d\x0a or \r\n at the end of the line (CRLF) 
             # after each input buffer read so we can find the end of our 
             # line being constructed
             if re.search("\r\n", line):
                 
                 # Since we found a CRLF, split it out
                 data, newdata = line.split("\r\n")
    
                 print "----" + str(datetime.datetime.now()) + "----"
                 print data
                 print
                        
                 # Reset our line constructer variable
                 line = ""
    


    Below is graphed output from 'vmstat' on the Raspberry Pi (in 2 second intervals) showing the performance benefit from using readBuffer() approach with read() + inWaiting() vs. using PySerial's readline():