# HG changeset patch # User drewp # Date 1546483216 28800 # Node ID 08a6eb5edf3df96af560ccc9cc1b99079dc676ab # Parent 532942f6d4058901698c31096376f31d49b64ce2 tinyscreen can flip images and render news Ignore-this: 72c89530e71d4c6f2550bb4deeccd807 darcs-hash:1e6cd5b4615297bd91d63e92951e90594ab98f25 diff -r 532942f6d405 -r 08a6eb5edf3d 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 532942f6d405 -r 08a6eb5edf3d 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 532942f6d405 -r 08a6eb5edf3d 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 532942f6d405 -r 08a6eb5edf3d 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 532942f6d405 -r 08a6eb5edf3d 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 532942f6d405 -r 08a6eb5edf3d 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 532942f6d405 -r 08a6eb5edf3d 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()