summaryrefslogtreecommitdiff
path: root/utilities
diff options
context:
space:
mode:
Diffstat (limited to 'utilities')
-rw-r--r--utilities/blinkenrocket.py315
-rw-r--r--utilities/flasher.py78
-rw-r--r--utilities/font_to_json.py27
-rw-r--r--utilities/mirror_font.py25
-rwxr-xr-xutilities/modem_transmit16
-rw-r--r--utilities/test_blinkenrocket.py117
6 files changed, 578 insertions, 0 deletions
diff --git a/utilities/blinkenrocket.py b/utilities/blinkenrocket.py
new file mode 100644
index 0000000..d16b7cf
--- /dev/null
+++ b/utilities/blinkenrocket.py
@@ -0,0 +1,315 @@
+#!/usr/bin/env python
+
+import sys, wave
+
+class modem:
+
+ # Modem specific constants
+ bits = [[3 * chr(0), 5 * chr(0)], [3 * chr(255), 5 * chr(255)]]
+ sync = [17 * chr(0), 17 * chr(255)]
+ # Variable to alternate high and low
+ hilo = 0
+ supportedFrequencies = [16000,22050,24000,32000,44100,48000]
+ cnt = 0
+ # Data variables
+ data = []
+ parity = True
+ frequency = 48000
+
+ # Hamming code translation table
+ _hammingCalculateParityLowNibble = [0, 3, 5, 6, 6, 5, 3, 0, 7, 4, 2, 1, 1, 2, 4, 7]
+ _hammingCalculateParityHighNibble = [0, 9, 10, 3, 11, 2, 1, 8, 12, 5, 6, 15, 7, 14, 13, 4]
+
+ # Almost nothing here
+ def __init__(self, data=[], parity=True, frequency=48000):
+ self.data = data
+ self.parity = parity
+ self.frequency = frequency if frequency in self.supportedFrequencies else 48000
+ self.cnt = 0
+
+ # Calculate Hamming parity for 12,8 code (12 bit of which 8bit data)
+ def hammingCalculateParity128(self, byte):
+ return self._hammingCalculateParityLowNibble[byte&0x0F] ^ self._hammingCalculateParityHighNibble[byte >> 4]
+
+ # Calculate Hamming parity for 24,16 code (24 bit of which 16 bit are data)
+ def hammingCalculateParity2416(self, first, second):
+ return self.hammingCalculateParity128(second) << 4 | self.hammingCalculateParity128(first)
+
+ # Generate one sync-pulse
+ def syncsignal(self):
+ self.hilo ^= 1
+ return self.sync[self.hilo]
+
+ # Generate a number of sync signals
+ def generateSyncSignal(self, number):
+ sound = ""
+ for i in xrange(number):
+ sound += self.syncsignal()
+ return sound
+
+ # Decode bits to modem signals
+ def modemcode(self, byte):
+ bleep = ""
+ for x in xrange(8):
+ self.hilo ^= 1
+ bleep += self.bits[self.hilo][byte & 0x01]
+ byte >>= 1
+ return bleep
+
+ # Return <length> samples of silence
+ def silence(self, length):
+ return chr(127) * length
+
+ # Set data for modem code
+ def setData(self, data):
+ self.data = data
+
+ # Set whether to use parity or not
+ def setParity(self, parity):
+ self.parity = parity
+
+ # Set the frequency for the audio
+ def setFrequency(self, frequency):
+ self.frequency = frequency if frequency in self.supportedFrequencies else 48000
+
+ # Generates the audio frames based on the data
+ def generateAudioFrames(self):
+ if self.parity:
+ tmpdata = []
+ # for uneven length data, we have to append a null byte
+ if not len(self.data) % 2 == 0:
+ self.data.append(chr(0))
+ # insert the parity information every two bytes, sorry for the heavy casting
+ for index in range(0, len(self.data), 2):
+ tmpdata.extend(self.data[index:index+2])
+ tmpdata.append(chr(self.hammingCalculateParity2416(ord(self.data[index]),ord(self.data[index+1]))))
+ self.data = tmpdata
+ # generate the audio itself
+ # add 1000ms of sync signal before the data
+ # (some sound cards take a while to produce a proper output signal)
+ sound = self.generateSyncSignal(3000)
+ # process the data and insert sync signal every 10 bytes
+ for byte in self.data:
+ sound += self.modemcode(ord(byte))
+ self.cnt += 1
+ if self.cnt == 9: # ! do not send sync inside (byte1 byte2 parity) triples
+ sound += self.generateSyncSignal(4)
+ self.cnt = 0
+ # add some sync signals in the end
+ sound += self.generateSyncSignal(4)
+ return sound
+
+ def saveAudio(self,filename):
+ wav = wave.open(filename, 'wb')
+ wav.setparams((1, 1, self.frequency, 0, "NONE", None))
+ wav.writeframes(self.generateAudioFrames())
+ wav.close()
+
+class Frame( object ):
+ """ Returns the frame information """
+ def getFrameHeader(self):
+ raise NotImplementedError("You should implement this!")
+
+ """ Returns the representation """
+ def getRepresentation(self):
+ raise NotImplementedError("Should have implemented this")
+
+class textFrame(Frame):
+ text = ""
+ speed = 0
+ delay = 0
+ direction = 0
+ # identifier as of message specification: 0001
+ identifier = 0x01
+
+ def __init__(self,text,speed=13,delay=0,direction=0):
+ self.text = text
+ self.setSpeed(speed)
+ self.setDelay(delay)
+ self.setDirection(direction)
+
+ def setSpeed(self,speed):
+ self.speed = speed if speed < 16 else 1
+
+ def setDelay(self,delay):
+ self.delay = delay if delay < 16 else 0
+
+ def setDirection(self,direction):
+ self.direction = direction if direction in [0,1] else 0
+
+ # Frame header: 4 bit type + 12 bit length
+ def getFrameHeader(self):
+ return [chr(self.identifier << 4 | len(self.text) >> 8), chr(len(self.text) & 0xFF) ]
+
+ # Header -> 4bit speed, 4 bit delay, 4 bit direction, 4 bit zero
+ def getHeader(self):
+ return [chr(self.speed << 4 | self.delay), chr(self.direction << 4 | 0x00)]
+
+ def getRepresentation(self):
+ retval = []
+ retval.extend(self.getFrameHeader())
+ retval.extend(self.getHeader())
+ retval.extend(list(self.text))
+ return retval
+
+class animationFrame(Frame):
+ animation = []
+ speed = 0
+ delay = 0
+ # identifier as per specification: 0010
+ identifier = 0x02
+
+ def __init__(self,animation,speed=13,delay=0):
+ self.setAnimation(animation)
+ self.setSpeed(speed)
+ self.setDelay(delay)
+
+ def setAnimation(self,animation):
+ if len(animation) % 8 is not 0:
+ raise Exception
+ else:
+ self.animation = animation
+
+ def setSpeed(self,speed):
+ self.speed = speed if speed < 16 else 1
+
+ def setDelay(self,delay):
+ self.delay = delay if delay < 16 else 0
+
+ # Frame header: 4 bit type + 12 bit length
+ def getFrameHeader(self):
+ return [chr(self.identifier << 4 | len(self.animation) >> 8), chr(len(self.animation) & 0xFF) ]
+
+ # Header -> 4bit zero, 4bit speed, 4 bit zero, 4 bit direction
+ def getHeader(self):
+ return [chr(self.speed), chr(self.delay)]
+
+ def getRepresentation(self):
+ retval = []
+ retval.extend(self.getFrameHeader())
+ retval.extend(self.getHeader())
+ retval.extend(self.animation)
+ return retval
+
+
+class blinkenrocket():
+
+ eeprom_size = 65536
+ startcode = chr(0x99)
+ patterncode = chr(0xA9)
+ endcode = chr(0x84)
+ frames = []
+
+ def __init__(self,eeprom_size=65536):
+ self.eeprom_size = eeprom_size if eeprom_size < 256*1024*1024 else 65536
+
+ def addFrame(self, frame):
+ if not isinstance(frame, Frame):
+ raise RuntimeError("Incorrect frame supplied")
+ else:
+ self.frames.append(frame)
+
+ def getMessage(self):
+ output = [self.startcode, self.startcode]
+ for frame in self.frames:
+ output.extend([self.patterncode,self.patterncode])
+ output.extend(frame.getRepresentation())
+ output.extend([self.endcode,self.endcode])
+ return output
+
+
+
+
+if __name__ == '__main__':
+ m = modem(parity=True, frequency=48000)
+ b = blinkenrocket()
+
+ for message in sys.argv[2:]:
+ b.addFrame(textFrame(message, speed=13))
+ b.addFrame(textFrame(" \x04 "))
+ b.addFrame(animationFrame(map(lambda x : chr(x), [0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255]), speed=0))
+ b.addFrame(animationFrame(map(lambda x : chr(x), [
+ 1, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 0, 0, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 0, 0,
+ 0, 0, 0, 0, 0, 0, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1,
+ 0, 0, 0, 0, 0, 0, 0, 2,
+ 0, 0, 0, 0, 0, 0, 0, 4,
+ 0, 0, 0, 0, 0, 0, 0, 8,
+ 0, 0, 0, 0, 0, 0, 0, 16,
+ 0, 0, 0, 0, 0, 0, 0, 32,
+ 0, 0, 0, 0, 0, 0, 0, 64,
+ 0, 0, 0, 0, 0, 0, 0, 128,
+ 0, 0, 0, 0, 0, 0, 128, 0,
+ 0, 0, 0, 0, 0, 128, 0, 0,
+ 0, 0, 0, 0, 128, 0, 0, 0,
+ 0, 0, 0, 128, 0, 0, 0, 0,
+ 0, 0, 128, 0, 0, 0, 0, 0,
+ 0, 128, 0, 0, 0, 0, 0, 0,
+ 128, 0, 0, 0, 0, 0, 0, 0,
+ 64, 0, 0, 0, 0, 0, 0, 0,
+ 32, 0, 0, 0, 0, 0, 0, 0,
+ 16, 0, 0, 0, 0, 0, 0, 0,
+ 8, 0, 0, 0, 0, 0, 0, 0,
+ 4, 0, 0, 0, 0, 0, 0, 0,
+ 2, 0, 0, 0, 0, 0, 0, 0,
+ 0, 2, 0, 0, 0, 0, 0, 0,
+ 0, 0, 2, 0, 0, 0, 0, 0,
+ 0, 0, 0, 2, 0, 0, 0, 0,
+ 0, 0, 0, 0, 2, 0, 0, 0,
+ 0, 0, 0, 0, 0, 2, 0, 0,
+ 0, 0, 0, 0, 0, 0, 2, 0,
+ 0, 0, 0, 0, 0, 0, 4, 0,
+ 0, 0, 0, 0, 0, 0, 8, 0,
+ 0, 0, 0, 0, 0, 0, 16, 0,
+ 0, 0, 0, 0, 0, 0, 32, 0,
+ 0, 0, 0, 0, 0, 0, 64, 0,
+ 0, 0, 0, 0, 0, 64, 0, 0,
+ 0, 0, 0, 0, 64, 0, 0, 0,
+ 0, 0, 0, 64, 0, 0, 0, 0,
+ 0, 0, 64, 0, 0, 0, 0, 0,
+ 0, 64, 0, 0, 0, 0, 0, 0,
+ 0, 32, 0, 0, 0, 0, 0, 0,
+ 0, 16, 0, 0, 0, 0, 0, 0,
+ 0, 8, 0, 0, 0, 0, 0, 0,
+ 0, 4, 0, 0, 0, 0, 0, 0,
+ 0, 0, 4, 0, 0, 0, 0, 0,
+ 0, 0, 0, 4, 0, 0, 0, 0,
+ 0, 0, 0, 0, 4, 0, 0, 0,
+ 0, 0, 0, 0, 0, 4, 0, 0,
+ 0, 0, 0, 0, 0, 8, 0, 0,
+ 0, 0, 0, 0, 0, 16, 0, 0,
+ 0, 0, 0, 0, 0, 32, 0, 0,
+ 0, 0, 0, 0, 32, 0, 0, 0,
+ 0, 0, 0, 32, 0, 0, 0, 0,
+ 0, 0, 32, 0, 0, 0, 0, 0,
+ 0, 0, 16, 0, 0, 0, 0, 0,
+ 0, 0, 8, 0, 0, 0, 0, 0,
+ 0, 0, 0, 8, 0, 0, 0, 0,
+ 0, 0, 0, 0, 8, 0, 0, 0,
+ 0, 0, 0, 0, 16, 0, 0, 0,
+ 0, 0, 0, 16, 0, 0, 0, 0,
+ 0, 0, 0, 24, 24, 0, 0, 0,
+ 0, 0, 60, 36, 36, 60, 0, 0,
+ 0, 126, 66, 66, 66, 66, 126, 0,
+ 255, 129, 129, 129, 129, 129, 129, 255,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ ]), speed=15, delay=1))
+ b.addFrame(animationFrame(map(lambda x : chr(x), [
+ 0, 0, 0, 24, 24, 0, 0, 0,
+ 0, 0, 60, 36, 36, 60, 0, 0,
+ 0, 126, 66, 66, 66, 66, 126, 0,
+ 255, 129, 129, 129, 129, 129, 129, 255,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ ]), speed=14, delay=1))
+
+ m.setData(b.getMessage())
+ m.saveAudio(sys.argv[1])
+
+ #print b.getMessage()
+
+
diff --git a/utilities/flasher.py b/utilities/flasher.py
new file mode 100644
index 0000000..b39d913
--- /dev/null
+++ b/utilities/flasher.py
@@ -0,0 +1,78 @@
+#!/usr/bin/python
+
+import sys
+import os
+from termcolor import colored
+
+try:
+ import tty, termios
+except ImportError:
+ try:
+ import msvcrt
+ except ImportError:
+ raise ImportError('getch not available')
+ else:
+ getch = msvcrt.getch
+else:
+ def getch():
+ """getch() -> key character
+
+ Read a single keypress from stdin and return the resulting character.
+ Nothing is echoed to the console. This call will block if a keypress
+ is not already available, but will not wait for Enter to be pressed.
+
+ If the pressed key was a modifier key, nothing will be detected; if
+ it were a special function key, it may return the first character of
+ of an escape sequence, leaving additional characters in the buffer.
+ """
+ fd = sys.stdin.fileno()
+ old_settings = termios.tcgetattr(fd)
+ try:
+ tty.setraw(fd)
+ ch = sys.stdin.read(1)
+ finally:
+ termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
+ return ch
+
+code_okay = """
+ ####### ## ## ### ## ##
+## ## ## ## ## ## ## ##
+## ## ## ## ## ## ####
+## ## ##### ## ## ##
+## ## ## ## ######### ##
+## ## ## ## ## ## ##
+ ####### ## ## ## ## ##
+"""
+
+code_error = """
+######## ######## ######## ####### ########
+## ## ## ## ## ## ## ## ##
+## ## ## ## ## ## ## ## ##
+###### ######## ######## ## ## ########
+## ## ## ## ## ## ## ## ##
+## ## ## ## ## ## ## ## ##
+######## ## ## ## ## ####### ## ##
+"""
+
+def printOkay():
+ print colored(code_okay, 'green')
+
+def printError():
+ print colored(code_error, 'red')
+
+flash_command = sys.argv[1]
+
+print colored("Using the following command to flash: %s" % flash_command, "green")
+
+while True:
+ print colored("Press any key to continue or 'q' to quit.","yellow")
+ char = getch()
+ print char
+ if ord(char) is ord('q'):
+ sys.exit(0)
+ return_code = os.system(flash_command)
+ if return_code > 0:
+ printError()
+ else:
+ printOkay()
+
diff --git a/utilities/font_to_json.py b/utilities/font_to_json.py
new file mode 100644
index 0000000..6d1b667
--- /dev/null
+++ b/utilities/font_to_json.py
@@ -0,0 +1,27 @@
+#!/usr/bin/python
+
+import sys
+import os
+import re
+import json
+
+result = {}
+
+with open('font.h') as font:
+ for line in font:
+ if 'PROGMEM' in line:
+ hexes = re.findall(r'(0x[0-9a-fA-F]+)',line)
+ contents = hexes[1:]
+ length = len(contents)
+ literal = int(re.findall(r'chr_([0-9]+)',line)[0])
+ description = re.findall(r'\/\/(.*)$',line)[0].strip()
+ #for row in contents:
+ # print '{0:08b}'.format(int(row,16)) #.replace("1",u"\u2588").replace("0",u"\u25A2")
+ result[str(literal)] = {
+ 'literal' : literal,
+ 'description' : description,
+ 'hexcolumns' : contents,
+ 'length' : length
+ }
+
+print json.dumps(result)
diff --git a/utilities/mirror_font.py b/utilities/mirror_font.py
new file mode 100644
index 0000000..db5b5c3
--- /dev/null
+++ b/utilities/mirror_font.py
@@ -0,0 +1,25 @@
+#!/usr/bin/python
+
+import sys
+import os
+import re
+import json
+
+result = {}
+
+with open('font.h') as font:
+ for line in font:
+ if 'PROGMEM' in line:
+ hexes = re.findall(r'(0x[0-9a-fA-F]+)',line)
+ prefix = hexes[:1]
+ contents = hexes[1:]
+ length = len(contents)
+ literal = int(re.findall(r'chr_([0-9]+)',line)[0])
+ description = re.findall(r'\/\/(.*)$',line)[0].strip()
+ newhexes = []
+ newhexes.append(prefix[0])
+ for row in contents:
+ bitstring = '{0:08b}'.format(int(row,16))
+ newbits = bitstring[::-1]
+ newhexes.append( format(int(newbits,2), '#04x'))
+ print "const unsigned char PROGMEM chr_%s[] = {%s}; // %s" % (literal,','.join(newhexes),description)
diff --git a/utilities/modem_transmit b/utilities/modem_transmit
new file mode 100755
index 0000000..bb18850
--- /dev/null
+++ b/utilities/modem_transmit
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+OUT="$(mktemp)"
+trap "rm -f '$OUT'" EXIT INT QUIT TERM
+
+python blinkenrocket.py $OUT "$@"
+
+case "$(uname -s)" in
+Darwin)
+ play $OUT
+ ;;
+Linux)
+ aplay $OUT
+ ;;
+esac
+
diff --git a/utilities/test_blinkenrocket.py b/utilities/test_blinkenrocket.py
new file mode 100644
index 0000000..62bd0e3
--- /dev/null
+++ b/utilities/test_blinkenrocket.py
@@ -0,0 +1,117 @@
+#!/usr/bin/python
+import unittest
+from blinkenrocket import *
+
+class TestFrame(unittest.TestCase):
+
+ def test_notImplemented(self):
+ frame = Frame()
+ with self.assertRaises(NotImplementedError):
+ frame.getRepresentation()
+
+class TestAnimation(unittest.TestCase):
+
+ def test_speedDefault(self):
+ anim = animationFrame([])
+ self.assertEquals(ord(anim.getHeader()[0]),1)
+
+ def test_speedOkay(self):
+ anim = animationFrame([],speed=7)
+ self.assertEquals(ord(anim.getHeader()[0]),7)
+
+ def test_speedNotOkay(self):
+ anim = animationFrame([],speed=70)
+ self.assertEquals(ord(anim.getHeader()[0]),1)
+
+ def test_delayDefault(self):
+ anim = animationFrame([])
+ self.assertEquals(ord(anim.getHeader()[1]),0)
+
+ def test_delayOkay(self):
+ anim = animationFrame([],delay=7)
+ self.assertEquals(ord(anim.getHeader()[1]),7)
+
+ def test_delayNotOkay(self):
+ anim = animationFrame([],delay=70)
+ self.assertEquals(ord(anim.getHeader()[1]),0)
+
+ def test_illegalLength(self):
+ with self.assertRaises(Exception):
+ anim = animationFrame([0x11,0x12,0x13,0x14])
+
+ def test_allowedLength(self):
+ anim = animationFrame([0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18])
+
+ def test_defaultHeaderOK(self):
+ anim = animationFrame([])
+ self.assertEquals(anim.getHeader(),[chr(1),chr(0)])
+
+ def test_differentHeaderOK(self):
+ anim = animationFrame([],speed=7,delay=8)
+ self.assertEquals(anim.getHeader(),[chr(7),chr(8)])
+
+class TestText(unittest.TestCase):
+
+ def test_speedDefault(self):
+ text = textFrame([])
+ self.assertEquals(ord(text.getHeader()[0]),(1 << 4 | 0))
+
+ def test_speedOkay(self):
+ text = textFrame([],speed=7)
+ self.assertEquals(ord(text.getHeader()[0]),(7 << 4 | 0))
+
+ def test_speedNotOkay(self):
+ text = textFrame([],speed=70)
+ self.assertEquals(ord(text.getHeader()[0]),(1 << 4 | 0))
+
+ def test_delayDefault(self):
+ text = textFrame([])
+ self.assertEquals(ord(text.getHeader()[0]),(1 << 4 | 0))
+
+ def test_delayOkay(self):
+ text = textFrame([],delay=7)
+ self.assertEquals(ord(text.getHeader()[0]),(1 << 4 | 7))
+
+ def test_delayNotOkay(self):
+ text = textFrame([],delay=70)
+ self.assertEquals(ord(text.getHeader()[0]),(1 << 4 | 0))
+
+ def test_directionDefault(self):
+ text = textFrame([])
+ self.assertEquals(ord(text.getHeader()[1]),0)
+
+ def test_directionOkay(self):
+ text = textFrame([],direction=1)
+ self.assertEquals(ord(text.getHeader()[1]),(1 << 4 | 0))
+
+ def test_directionNotOkay(self):
+ text = textFrame([],direction=7)
+ self.assertEquals(ord(text.getHeader()[1]),0)
+
+ def test_defaultHeaderOK(self):
+ text = textFrame([])
+ self.assertEquals(text.getHeader(),[chr(1 << 4 | 0),chr(0)])
+
+ def test_differentHeaderOK(self):
+ text = textFrame([],speed=7,delay=8,direction=1)
+ self.assertEquals(text.getHeader(),[chr(7 << 4 | 8),chr(1 << 4 | 0)])
+
+class TestBlinkenrocket(unittest.TestCase):
+
+ def test_addFrameFail(self):
+ br = blinkenrocket()
+ with self.assertRaises(Exception):
+ br.addFrame([])
+
+ def test_addFrameText(self):
+ text = textFrame("MUZY",speed=7,delay=8,direction=1)
+ self.assertEquals(text.getFrameHeader(),[chr(0x01 << 4), chr(4)])
+ self.assertEquals(text.getHeader(),[chr(7 << 4 | 8),chr(1 << 4 | 0)])
+ self.assertEquals(text.getRepresentation(),[chr(0x01 << 4), chr(4),chr(7 << 4 | 8),chr(1 << 4 | 0),'M','U','Z','Y'])
+ br = blinkenrocket()
+ br.addFrame(text)
+ expect = [chr(0x99),chr(0x99),chr(0xA9),chr(0xA9),chr(0x01 << 4), chr(4),chr(7 << 4 | 8),chr(1 << 4 | 0),'M','U','Z','Y',chr(0x84),chr(0x84)]
+ self.assertEquals(br.getMessage(),expect)
+
+if __name__ == '__main__':
+ unittest.main()