platform: add a tool to flash an LED pattern
Installers would like to have a visual indication of what state the
FiberJack provisioning process is in. Tapping the physical reset button
will cause the LED to blink a series of patterns based on the current
state.
Change-Id: I7e0ab322e3837474273f671e9b3727d144adea5e
diff --git a/Makefile b/Makefile
index f433d89..0d5a5da 100644
--- a/Makefile
+++ b/Makefile
@@ -84,6 +84,10 @@
DIRS+=presterastats
endif
+ifeq ($(BUILD_LEDPATTERN),y)
+DIRS+=ledpattern
+endif
+
PREFIX=/usr
BINDIR=$(DESTDIR)$(PREFIX)/bin
LIBDIR=$(DESTDIR)$(PREFIX)/lib
diff --git a/ledpattern/Makefile b/ledpattern/Makefile
new file mode 100644
index 0000000..705a398
--- /dev/null
+++ b/ledpattern/Makefile
@@ -0,0 +1,28 @@
+default:
+
+PREFIX=/
+BINDIR=$(DESTDIR)$(PREFIX)/bin
+PYTHON?=python
+
+all:
+
+install:
+ mkdir -p $(BINDIR)
+ cp ledpattern.py $(BINDIR)/ledpattern
+
+install-libs:
+ @echo "No libs to install."
+
+test: lint
+ set -e; \
+ for pytest in $(wildcard *_test.py); do \
+ echo; \
+ echo "Testing $$pytest"; \
+ $(PYTHON) $$pytest; \
+ done
+
+clean:
+ rm -rf *.pyc
+
+lint:
+ gpylint ledpattern.py ledpattern_test.py
diff --git a/ledpattern/ledpattern.py b/ledpattern/ledpattern.py
new file mode 100755
index 0000000..bcd3b03
--- /dev/null
+++ b/ledpattern/ledpattern.py
@@ -0,0 +1,163 @@
+#!/usr/bin/python
+
+"""Blinks a specific LED pattern read from a simple pattern file.
+
+The first value is the state that the LED pattern represents. Followed by
+any combination of the following values:
+
+ R = Red blink
+ B = Blue blink
+ P = Purple blink
+
+An example pattern file might look like:
+
+ echo "blink,R,R,R" > /tmp/test.pat
+
+Invoking "ledpattern.py /tmp/test.pat blink" would result in the red LED
+blinking three times.
+"""
+
+__author__ = 'Chris Gibson <cgibson@google.com>'
+
+import csv
+import os
+import sys
+import time
+
+# Unit tests can override these values.
+DISABLE_GPIOMAILBOX = '/tmp/gpio/disable'
+SYSFS_RED_BRIGHTNESS = '/sys/class/leds/sys-red/brightness'
+SYSFS_BLUE_BRIGHTNESS = '/sys/class/leds/sys-blue/brightness'
+SLEEP_TIMEOUT = 0.5
+
+
+class LedPattern(object):
+ """Read, parse, and blink LEDs based on a pattern from a file."""
+
+ def ReadCsvPatternFile(self, pattern_file, state):
+ """Read a CSV pattern file."""
+ if not os.path.exists(pattern_file):
+ print 'Error: Pattern file: "%s" not found.' % pattern_file
+ return None
+ if not state:
+ print 'Error: led state cannot be empty.'
+ return None
+ try:
+ with open(pattern_file, 'r') as f:
+ patterns = csv.reader(f, delimiter=',')
+ for row in patterns:
+ if row[0] == state:
+ return [c for c in row[1:] if c in ['R', 'B', 'P']]
+ print ('Error: Could not find led state: "%s" in pattern file: %s'
+ % (state, pattern_file))
+ return None
+ except (IOError, OSError) as ex:
+ print ('Failed to open the pattern file: %s, error: %s.'
+ % (pattern_file, ex))
+ return None
+
+ def SetRedBrightness(self, level):
+ with open(SYSFS_RED_BRIGHTNESS, 'w') as f:
+ f.write(level)
+
+ def SetBlueBrightness(self, level):
+ with open(SYSFS_BLUE_BRIGHTNESS, 'w') as f:
+ f.write(level)
+
+ def SetLedsOff(self):
+ self.SetRedBrightness('0')
+ self.SetBlueBrightness('0')
+
+ def RedBlink(self):
+ self.SetLedsOff()
+ time.sleep(SLEEP_TIMEOUT)
+ self.SetRedBrightness('100')
+ self.SetBlueBrightness('0')
+ time.sleep(SLEEP_TIMEOUT)
+ self.SetLedsOff()
+
+ def BlueBlink(self):
+ self.SetLedsOff()
+ time.sleep(SLEEP_TIMEOUT)
+ self.SetRedBrightness('0')
+ self.SetBlueBrightness('100')
+ time.sleep(SLEEP_TIMEOUT)
+ self.SetLedsOff()
+
+ def PurpleBlink(self):
+ self.SetLedsOff()
+ time.sleep(SLEEP_TIMEOUT)
+ self.SetRedBrightness('100')
+ self.SetBlueBrightness('100')
+ time.sleep(SLEEP_TIMEOUT)
+ self.SetLedsOff()
+
+ def PlayPattern(self, pattern):
+ for color in pattern:
+ if color == 'R':
+ self.RedBlink()
+ elif color == 'B':
+ self.BlueBlink()
+ elif color == 'P':
+ self.PurpleBlink()
+
+ def Run(self, pattern_file, state):
+ """Sets up an LED pattern to play.
+
+ Arguments:
+ pattern_file: Pattern file containing a list of LED states/patterns.
+ state: Name of the LED state to play.
+
+ Returns:
+ An integer exit code: 0 means everything succeeded. Non-zero exit codes
+ mean something went wrong.
+ """
+ try:
+ open(DISABLE_GPIOMAILBOX, 'w').close()
+ except (IOError, OSError) as ex:
+ # If we can't disable gpio-mailbox then we can't guarantee control
+ # over the LEDs, so we just have to admit defeat.
+ print 'Error: Failed to disable gpio-mailbox! %s' % ex
+ return 1
+
+ try:
+ pattern = self.ReadCsvPatternFile(pattern_file, state)
+ if not pattern:
+ print 'Reading pattern failed! Exiting!'
+ return 1
+ except (IOError, OSError) as ex:
+ print 'Error: Failed to read pattern file, %s' % ex
+ return 1
+
+ # Depending on what state the LEDs are in when we touched the gpio-mailbox
+ # disable file, the LEDs will remain in that last state. Firstly, turn both
+ # LEDs off then sleep for a second to indicate that the pattern is about
+ # to begin.
+ self.SetLedsOff()
+ time.sleep(1)
+
+ self.PlayPattern(pattern)
+
+ # Turn off the LEDs and sleep for a second to clearly delineate the end of
+ # the current pattern.
+ self.SetLedsOff()
+ time.sleep(1)
+
+ os.unlink(DISABLE_GPIOMAILBOX)
+ return 0
+
+
+def Usage():
+ print 'Usage:'
+ print ' %s {pattern file} {state}' % sys.argv[0]
+ print
+ print ' pattern file: path to a pattern file'
+ print ' state: the LED state to select'
+
+
+if __name__ == '__main__':
+ if len(sys.argv) != 3:
+ Usage()
+ sys.exit(1)
+ ledpattern = LedPattern()
+ sys.exit(ledpattern.Run(sys.argv[1], sys.argv[2]))
diff --git a/ledpattern/ledpattern_test.py b/ledpattern/ledpattern_test.py
new file mode 100755
index 0000000..0a42dad
--- /dev/null
+++ b/ledpattern/ledpattern_test.py
@@ -0,0 +1,70 @@
+#!/usr/bin/python
+
+"""Tests for ledpattern."""
+
+import os
+import tempfile
+import unittest
+
+import ledpattern
+
+
+class TestLedpattern(unittest.TestCase):
+
+ def setUp(self):
+ self.ledpattern = ledpattern.LedPattern()
+ self.fd_red, ledpattern.SYSFS_RED_BRIGHTNESS = tempfile.mkstemp()
+ print ledpattern.SYSFS_RED_BRIGHTNESS
+ self.fd_blue, ledpattern.SYSFS_BLUE_BRIGHTNESS = tempfile.mkstemp()
+ print ledpattern.SYSFS_BLUE_BRIGHTNESS
+
+ def tearDown(self):
+ os.close(self.fd_red)
+ os.close(self.fd_blue)
+ os.unlink(ledpattern.SYSFS_RED_BRIGHTNESS)
+ os.unlink(ledpattern.SYSFS_BLUE_BRIGHTNESS)
+
+ def testReadCsvPatternFileTest(self):
+ expected = ['R', 'B', 'P']
+ actual = self.ledpattern.ReadCsvPatternFile('./testdata/test.pat', 'test')
+ self.assertEqual(expected, actual)
+
+ def testReadCsvPatternFileNotFound(self):
+ actual = self.ledpattern.ReadCsvPatternFile('/does/not/exist.pat', 'test')
+ self.assertEqual(None, actual)
+
+ def testReadCsvPatternFileNoState(self):
+ actual = self.ledpattern.ReadCsvPatternFile('./testdata/test.pat', '')
+ self.assertEqual(None, actual)
+
+ def testReadCsvPatternFileIsMissingState(self):
+ actual = self.ledpattern.ReadCsvPatternFile('./testdata/test.pat', 'foo')
+ self.assertEqual(None, actual)
+
+ def testReadCsvPatternFileInvalidCharsGetFiltered(self):
+ expected = ['B', 'B', 'B']
+ actual = self.ledpattern.ReadCsvPatternFile('./testdata/test.pat',
+ 'test_with_invalid_chars')
+ self.assertEqual(expected, actual)
+
+ def testReadCsvPatternFileEmptyPattern(self):
+ expected = []
+ actual = self.ledpattern.ReadCsvPatternFile('./testdata/test.pat',
+ 'test_empty_pattern')
+ self.assertEqual(expected, actual)
+
+ def testSetRedBrightness(self):
+ self.ledpattern.SetRedBrightness('foo')
+ with open(ledpattern.SYSFS_RED_BRIGHTNESS, 'r') as f:
+ actual = f.read()
+ self.assertEqual('foo', actual)
+
+ def testSetBlueBrightness(self):
+ self.ledpattern.SetBlueBrightness('bar')
+ with open(ledpattern.SYSFS_BLUE_BRIGHTNESS, 'r') as f:
+ actual = f.read()
+ self.assertEqual('bar', actual)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/ledpattern/testdata/test.pat b/ledpattern/testdata/test.pat
new file mode 100644
index 0000000..6b49acf
--- /dev/null
+++ b/ledpattern/testdata/test.pat
@@ -0,0 +1,3 @@
+test_with_invalid_chars,B,B,B,X,X,X
+test,R,B,P
+test_empty_pattern,,