I decided to build a display for my desk at work that shows my current bug count using the 7 digit segment display that I purchased from DealExtreme. I do not want an Arduino hanging off a USB cable cluttering my desk (or draining my wallet). Enter the Digispark. Designed and produced by a local Portland company, the Digispark is an ATtiny85 that fits directly into a USB port.
On the computer side, A python program runs on my desktop. It queries Trac every once in a while and if the bug count changes, sends a message to the display.
Code for the Digispark:
#include <DigiUSB.h> #define latch 0 #define clock 1 #define data 2 #define kDigitCount 8 char gDisplayBuffer[kDigitCount+1]; void setup() { pinMode(latch, OUTPUT); pinMode(clock, OUTPUT); pinMode(data, OUTPUT); memset(gDisplayBuffer, ' ', kDigitCount); writeToDisplay(true); DigiUSB.begin(); // open DigiUSB } void writeDigit(int digit, char inValue) { byte value = 0; byte bitsToSend = 0; bitSet(bitsToSend, digit); digitalWrite(latch,LOW); shiftOut(data,clock,MSBFIRST, bitsToSend); switch (inValue) { case '.': value = B01111111; break; case '1': value = B11111001; break; case '2': value = B10100100; break; case '3': value = B10110000; break; case '4': value = B10011001; break; case '5': value = B10010010; break; case '6': value = B10000011; break; case '7': value = B11111000; break; case '8': value = B10000000; break; case '9': value = B10010000; break; case '0': value = B11000000; break; case 'H': value = B10001001; break; case 'i': value = B11111011; break; default: case ' ': value = B11111111; break; } shiftOut(data,clock,MSBFIRST,value); digitalWrite(latch,HIGH); } void writeToDisplay(bool inOveride) { for (int i = 0 ; i < kDigitCount ; i++) { if (' ' != gDisplayBuffer[i] || inOveride) writeDigit(i, gDisplayBuffer[i]); } } int readBytesUntil(char delim, char* outBuffer, int inMaxSize) { int bytesRead = 0; int lastRead = 0; while ( bytesRead < inMaxSize ) { if (DigiUSB.available()) { lastRead = DigiUSB.read(); if ('\n' == lastRead) break; outBuffer[bytesRead] = lastRead; ++bytesRead; } // refresh the usb port for 10 milliseconds DigiUSB.delay(10); } outBuffer[bytesRead] = 0; return bytesRead; } void loop() { char cmdBuffer[16]; if (DigiUSB.available()) { memset(cmdBuffer, ' ', 16); int iBytes = readBytesUntil('\n', cmdBuffer, 12); cmdBuffer[iBytes] = 0; if (!strncmp(cmdBuffer, "SET ", 4)) { if (12 != strlen(cmdBuffer)) DigiUSB.println("ERR"); else memcpy(gDisplayBuffer, &cmdBuffer[4], kDigitCount); } else if (!strncmp(cmdBuffer, "CLR", 3)) { memcpy(gDisplayBuffer, " ", kDigitCount); writeToDisplay(true); } } writeToDisplay(false); DigiUSB.refresh(); }
The Python code:
''' The Windows (or desktop) software that periodically gets the bug count from TRAC and sends it to the 8 digit display. ''' import urllib import time from arduino.usbdevice import ArduinoUsbDevice kTracUrl = "http://trac/report?asc=1&format=csv" ################################################################################ class TracAggregation: def __init__(self): self.m_BugTickets = 0 def incrementBugs(self): self.m_BugTickets += 1 def totalBugs(self): return self.m_BugTickets def __str__(self): return str(self.m_BugTickets) ################################################################################ class TracEntry: def __init__(self): self.m_Ticket = None self.m_Summary = None self.m_Type = None self.m_Priority = None self.m_Milestone = None ################################################################################ def parseLine(inLine): tokens = inLine.split(',') oEntry = TracEntry() # change for your customized Trac query here oEntry.m_Ticket = tokens[0] oEntry.m_Summary = tokens[1] oEntry.m_Type = tokens[4] oEntry.m_Priority = tokens[5] oEntry.m_Milestone = tokens[6] return oEntry ################################################################################ def parseTracData(inData): entryList = [] lineList = inData.splitlines() for line in lineList[1:]: # skip the header try: oEntry = parseLine(line) entryList.append(oEntry) except: pass return entryList ################################################################################ def aggregateTracData(parsedData, bExcludeBacklog=True): oData = TracAggregation() for oEntry in parsedData: if "Bug" == oEntry.m_Type: if bExcludeBacklog and "Backlog" != oEntry.m_Milestone: oData.incrementBugs() elif not bExcludeBacklog: oData.incrementBugs() return oData ################################################################################ def getTracData(url=kTracUrl): try: tracConnection = urllib.urlopen(url) tracData = tracConnection.read() tracConnection.close() except Exception as e: print(e) return (False, None) parsedData = parseTracData(tracData) aggregatedData = aggregateTracData(parsedData) return (True, aggregatedData) ################################################################################ def sendToDisplay(inMessage): try: theDevice = ArduinoUsbDevice(idVendor=0x16c0, idProduct=0x05df) except: return False for c in "SET ": theDevice.write(ord(c)) for c in inMessage: theDevice.write(ord(c)) return True ################################################################################ if __name__ == "__main__": lastTotalBugs = 0 while (True): (ok, tracData) = getTracData() if ok and lastTotalBugs != tracData.totalBugs(): displayMessage = str(tracData.totalBugs()).rjust(8, ' ') lastTotalBugs = tracData.totalBugs() # only send the new bug count if it changed sendToDisplay(displayMessage) time.sleep(60)