# HG changeset patch
# User drewp@bigasterisk.com
# Date 1546483216 28800
# Node ID c146fa2bc7d46b2ee4a8b44df80cc5a43cfbf631
# Parent 486d5d6c34e0f8966d7a7d13d0f6b09d533eb854
tinyscreen can flip images and render news
Ignore-this: 72c89530e71d4c6f2550bb4deeccd807
diff -r 486d5d6c34e0 -r c146fa2bc7d4 service/tinyScreen/Dockerfile
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/service/tinyScreen/Dockerfile Wed Jan 02 18:40:16 2019 -0800
@@ -0,0 +1,14 @@
+FROM bang6:5000/base_x86
+
+WORKDIR /opt
+
+COPY requirements.txt ./
+RUN pip install -r requirements.txt
+
+COPY font ./font
+COPY anim ./anim
+COPY *.py *.html ./
+
+EXPOSE 11013
+
+CMD [ "python", "./tiny_screen.py" ]
diff -r 486d5d6c34e0 -r c146fa2bc7d4 service/tinyScreen/Dockerfile.pi
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/service/tinyScreen/Dockerfile.pi Wed Jan 02 18:40:16 2019 -0800
@@ -0,0 +1,14 @@
+FROM bang6:5000/base_pi
+
+WORKDIR /opt
+
+COPY requirements.txt ./
+RUN pip install -r requirements.txt
+
+COPY font ./font
+COPY anim ./anim
+COPY *.py *.html ./
+
+EXPOSE 11013
+
+CMD [ "python", "./tiny_screen.py" ]
diff -r 486d5d6c34e0 -r c146fa2bc7d4 service/tinyScreen/index.html
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/service/tinyScreen/index.html Wed Jan 02 18:40:16 2019 -0800
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+ request state: boot sleep locked lockedUnknownKey unlockNews
+ goal state:
+ current state:
+ news: ____
+
+
diff -r 486d5d6c34e0 -r c146fa2bc7d4 service/tinyScreen/makefile
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/service/tinyScreen/makefile Wed Jan 02 18:40:16 2019 -0800
@@ -0,0 +1,32 @@
+JOB=tiny_screen
+PORT=10013
+
+TAG=bang6:5000/${JOB}_x86:latest
+TAG_PI=bang6:5000/${JOB}_pi:latest
+
+push:
+ docker push ${TAG}
+
+build_x86:
+ rm -rf tmp_ctx
+ mkdir -p tmp_ctx
+ cp -a Dockerfile ../../lib/*.py *.py *.txt *.html anim font tmp_ctx
+ docker build --network=host -t ${TAG} tmp_ctx
+ rm -rf tmp_ctx
+
+build_image: build_x86 push
+
+build_pi:
+ rm -rf tmp_ctx
+ mkdir -p tmp_ctx
+ cp -a Dockerfile.pi ../../lib/*.py *.py *.txt *.html anim font tmp_ctx
+ docker build -f Dockerfile.pi --network=host -t ${TAG_PI} tmp_ctx
+ rm -rf tmp_ctx
+
+build_image_pi: build_pi push
+
+shell:
+ docker run --rm -it --cap-add SYS_PTRACE --net=host $(TAG) /bin/sh
+
+local_run: build_x86
+ docker run --rm -it --net=host -e DISPLAY=$(DISPLAY) -e HOME=$(HOME) -v $(HOME):$(HOME) -v /tmp/.X11-unix:/tmp/.X11-unix -v `pwd`/index.html:/opt/index.html bang6:5000/tiny_screen_x86:latest python ./tiny_screen.py -v -x
diff -r 486d5d6c34e0 -r c146fa2bc7d4 service/tinyScreen/requirements.txt
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/service/tinyScreen/requirements.txt Wed Jan 02 18:40:16 2019 -0800
@@ -0,0 +1,6 @@
+luma.oled
+rdflib==4.2.2
+cyclone
+https://projects.bigasterisk.com/rdfdb/rdfdb-0.6.0.tar.gz
+rdflib-jsonld==0.3
+pygame
diff -r 486d5d6c34e0 -r c146fa2bc7d4 service/tinyScreen/requrements.txt
--- a/service/tinyScreen/requrements.txt Wed Jan 02 18:39:35 2019 -0800
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-luma.oled
diff -r 486d5d6c34e0 -r c146fa2bc7d4 service/tinyScreen/tiny_screen.py
--- a/service/tinyScreen/tiny_screen.py Wed Jan 02 18:39:35 2019 -0800
+++ b/service/tinyScreen/tiny_screen.py Wed Jan 02 18:40:16 2019 -0800
@@ -1,11 +1,161 @@
+from docopt import docopt
+from patchablegraph import PatchableGraph, CycloneGraphHandler, CycloneGraphEventsHandler
+from rdflib import Namespace, URIRef, Literal, Graph
+from rdflib.parser import StringInputSource
+from twisted.internet import reactor
+import cyclone.web
+import sys, logging, time, textwrap
+
from luma.core.interface.serial import spi
-from luma.core.render import canvas
from luma.oled.device import ssd1331
+from PIL import Image, ImageFont, ImageDraw
+ROOM = Namespace('http://projects.bigasterisk.com/room/')
+logging.basicConfig()
+log = logging.getLogger()
class Screen(object):
def __init__(self, spiDevice=1, rotation=0):
- self._dev = ssd1331(spi(device=spiDevice, port=0), rotation=rotation)
- with canvas(self._dev) as draw:
- draw.rectangle(d.bounding_box, outline="white", fill="red")
+ self._initOutput(spiDevice, rotation)
+ self.news = ""
+ self.animateTo(ROOM['boot'])
+
+ def _stateImage(self, state):
+ return Image.open('anim/%s.png' % state.rsplit('/')[-1])
+
+ def _initOutput(self, spiDevice, rotation):
+ self._dev = ssd1331(spi(device=spiDevice, port=0), rotation=rotation)
+
+ def setContrast(self, contrast):
+ """0..255"""
+ self._dev.contrast(contrast)
+
+ def hide(self):
+ """Switches the display mode OFF, putting the device in low-power sleep mode."""
+ self._dev.hide()
+
+ def show(self):
+ self._dev.show()
+
+ def display(self, img):
+ self._dev.display(img)
+
+ def animateTo(self, state):
+ """
+ boot
+ sleep
+ locked
+ lockedUnknownKey
+ unlockNews
+ """
+ self.goalState = state
+ self.display(self._stateImage(state))
+ if state == ROOM['unlockNews']:
+ self.renderNews()
+
+ def setNews(self, text):
+ self.news = text
+ if self.goalState == ROOM['unlockNews']:
+ # wrong during animation
+ self.renderNews()
+
+ def renderNews(self):
+ bg = self._stateImage(ROOM['unlockNews'])
+ draw = ImageDraw.Draw(bg)
+
+ font = ImageFont.truetype("font/Oswald-SemiBold.ttf", 12)
+ #w, h = font.getsize('txt')
+ for i, line in enumerate(
+ textwrap.fill(self.news, width=12).splitlines()):
+ draw.text((24, 0 + 10 * i), line, font=font)
+ self.display(bg)
+
+class ScreenSim(Screen):
+ def _initOutput(self, spiDevice, rotation):
+ self.windowScale = 2
+ import pygame
+ self.pygame = pygame
+ pygame.init()
+ self.surf = pygame.display.set_mode(
+ (96 * self.windowScale, 64 * self.windowScale))
+ time.sleep(.05) # something was causing the 1st update not to show
+
+ def display(self, img):
+ pgi = self.pygame.image.fromstring(
+ img.tobytes(), img.size, img.mode)
+ self.pygame.transform.scale(pgi, self.surf.get_size(), self.surf)
+ self.pygame.display.flip()
+def rdfGraphBody(body, headers):
+ g = Graph()
+ g.parse(StringInputSource(body), format='nt')
+ return g
+
+class OutputPage(cyclone.web.RequestHandler):
+ def put(self):
+ arg = self.request.arguments
+ if arg.get('s') and arg.get('p'):
+ subj = URIRef(arg['s'][-1])
+ pred = URIRef(arg['p'][-1])
+ turtleLiteral = self.request.body
+ try:
+ obj = Literal(float(turtleLiteral))
+ except ValueError:
+ obj = Literal(turtleLiteral)
+ stmts = [(subj, pred, obj)]
+ else:
+ nt = self.request.body.replace("\\n", "\n") # wrong, but i can't quote right in curl
+ g = rdfGraphBody(nt, self.request.headers)
+ assert len(g)
+ stmts = list(g.triples((None, None, None)))
+ self._onStatement(stmts)
+
+ def _onStatement(self, stmts):
+ """
+ (disp :brightness 1.0 . )
+ (disp :state :locked . )
+ (disp :state :sleep . )
+ (disp :state :readKeyUnlock . disp :news "some news text" . )
+ """
+ disp = ROOM['frontDoorOled']
+ for stmt in stmts:
+ if stmt[:2] == (disp, ROOM['news']):
+ self.settings.screen.setNews(stmt[2].toPython())
+ elif stmt[:2] == (disp, ROOM['state']):
+ self.settings.screen.animateTo(stmt[2])
+ else:
+ log.warn("ignoring %s", stmt)
+
+if __name__ == '__main__':
+ arg = docopt("""
+ Usage: tiny_screen.py [options]
+
+ -v Verbose
+ -x Draw to X11 window, not output hardware
+ """)
+ log.setLevel(logging.WARN)
+ if arg['-v']:
+ from twisted.python import log as twlog
+ twlog.startLogging(sys.stdout)
+ log.setLevel(logging.DEBUG)
+
+ masterGraph = PatchableGraph()
+
+ if arg['-x']:
+ screen = ScreenSim()
+ else:
+ screen = Screen(spiDevice=1)
+
+ port = 10013
+ reactor.listenTCP(port, cyclone.web.Application([
+ (r"/()", cyclone.web.StaticFileHandler,
+ {"path": ".", "default_filename": "index.html"}),
+ (r"/graph", CycloneGraphHandler, {'masterGraph': masterGraph}),
+ (r"/graph/events", CycloneGraphEventsHandler,
+ {'masterGraph': masterGraph}),
+ (r'/output', OutputPage),
+ ], screen=screen, masterGraph=masterGraph, debug=arg['-v']),
+ interface='::')
+ log.warn('serving on %s', port)
+
+ reactor.run()