Mercurial > code > home > repos > homeauto
changeset 412:91162a54553c
copy rest of rfid service from the first try. fix some crashes in tags.py
Ignore-this: a3fdcc0a8494e358d3c0abc109e8ed4d
author | drewp@bigasterisk.com |
---|---|
date | Sat, 23 Mar 2019 04:26:03 -0700 |
parents | 9fbd2d0193bf |
children | 5fc75de6b905 |
files | service/rfid_pn532_py/Dockerfile.pi service/rfid_pn532_py/Dockerfile.x86 service/rfid_pn532_py/index.html service/rfid_pn532_py/makefile service/rfid_pn532_py/requirements.txt service/rfid_pn532_py/rfid.py service/rfid_pn532_py/tags.py |
diffstat | 7 files changed, 394 insertions(+), 11 deletions(-) [+] |
line wrap: on
line diff
--- a/service/rfid_pn532_py/Dockerfile.pi Sat Mar 16 18:22:57 2019 -0700 +++ b/service/rfid_pn532_py/Dockerfile.pi Sat Mar 23 04:26:03 2019 -0700 @@ -1,18 +1,17 @@ FROM bang6:5000/base_pi - - - WORKDIR /opt RUN apt-get install -y libnfc5 libfreefare0 libnfc-dev libfreefare-dev RUN apt-get install -y python3-nose2 COPY pyfreefare-build-pi ./pyfreefare-build COPY requirements.txt . -RUN pip install -r requirements.txt +RUN pip3 install -r requirements.txt +RUN pip3 install -v -U 'https://github.com/drewp/cyclone/archive/patch-1.zip' COPY *.py *.html ./ ENV PYTHONPATH=/opt/pyfreefare-build EXPOSE 10012 +CMD ["/usr/bin/python3", "rfid.py"]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/service/rfid_pn532_py/Dockerfile.x86 Sat Mar 23 04:26:03 2019 -0700 @@ -0,0 +1,17 @@ +FROM bang6:5000/base_x86 + +WORKDIR /opt +RUN apt-get install -y libnfc5 libfreefare0 libnfc-dev libfreefare-dev +RUN apt-get install -y python3-nose2 +COPY pyfreefare-build-x86 ./pyfreefare-build + +COPY requirements.txt . +RUN pip3 install -r requirements.txt +RUN pip3 install -U 'https://github.com/drewp/cyclone/archive/patch-1.zip' + +COPY *.py *.html ./ + +ENV PYTHONPATH=/opt/pyfreefare-build +EXPOSE 10012 + +CMD ["/usr/bin/python3", "rfid.py", "-v"]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/service/rfid_pn532_py/index.html Sat Mar 23 04:26:03 2019 -0700 @@ -0,0 +1,128 @@ +<!doctype html> +<html> + <head> + <title>rfid</title> + <meta charset="utf-8" /> + <meta name="mobile-web-app-capable" content="yes"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <script src="/lib/polymer/1.0.9/webcomponentsjs/webcomponents.min.js"></script> + <script src="/lib/require/require-2.3.3.js"></script> + <script> + requirejs.config({ + paths: { + "streamed-graph": "/rdf/streamed-graph", + "quadstore": "/rdf/quadstore", + "async-module": "/lib/async/80f1793/async", + "async": "/lib/async/80f1793/async", + "jsonld-module": "/lib/jsonld.js/0.4.11/js/jsonld", + "jsonld": "/lib/jsonld.js/0.4.11/js/jsonld", + "rdfstore": "/lib/rdf_store/0.9.7/dist/rdfstore", + "moment": "/lib/moment.min", + "underscore": "/lib/underscore-1.5.2.min", + } + }); + </script> + <script> + window.NS = { + dev: 'http://projects.bigasterisk.com/device/', + room: 'http://projects.bigasterisk.com/room/', + rdfs: 'http://www.w3.org/2000/01/rdf-schema#', + sensor: 'http://bigasterisk.com/homeauto/sensor/', + }; + </script> + <link rel="import" href="/lib/polymer/1.0.9/iron-ajax/iron-ajax.html"> + <link rel="import" href="/rdf/streamed-graph.html"> + <link rel="import" href="/lib/polymer/1.0.9/polymer/polymer.html"> + <link rel="import" href="/rdf/rdf-oneshot.html"> + <link rel="import" href="/rdf/rdf-uri.html"> + </head> + <body> + <dom-module id="rfid-control"> + <style> + button { + min-width: 60px; + min-height: 40px; + } + table { + border-collapse: collapse; + } + + td, th { + border: 1px solid gray; + } + </style> + <template> + <div> + <streamed-graph url="graph/events" graph="{{graph}}"></streamed-graph> + <!-- also get a graph of users so we can look up cards --> + </div> + + <iron-ajax id="rewrite" url="rewrite" method="POST"></iron-ajax> + + Current reads: + <table> + <tr><th>Card UID</th><th>Card text</th><th></th></tr> + <template is="dom-repeat" items="{{currentReads}}"> + <tr> + <td>{{item.uidDisplay}}</td> + <td>{{item.text}}</td> + <td> + <div id="form"> + <button on-click="rewrite">Rewrite</button> + </div> + </td> + </tr> + </template> + </table> + + </template> + <script> + HTMLImports.whenReady(function () { + Polymer({ + is: 'rfid-control', + properties: { + graph: { type: Object, notify: true, observer: "_onGraph" }, + currentReads: { type: Array, value: [] }, + }, + behaviors: [BigastUri], + _onGraph: function(graph) { + if (!graph.graph) return; + const env = graph.graph.store.rdf; + + this.splice('currentReads', 0, this.currentReads.length); + graph.graph.quadStore.quads( + {subject: env.createNamedNode('room:frontDoorWindowRfid'), + predicate: env.createNamedNode('room:reading'), + }, + (q) => { + graph.graph.quadStore.quads( + {subject: q.object, + predicate: env.createNamedNode('room:cardText'), + }, + (q2) => { + this.push( + 'currentReads', { + 'cardUid': q.object, + 'uidDisplay': q.object.toString().replace(/.*\//, ""), + 'text': q2.object.toString() + }); + }); + }); + }, + rewrite: function(ev) { + const cardUid = ev.model.item.cardUid; + + // ask for user first + + this.$.rewrite.contentType = "application/json"; + this.$.rewrite.body = {'cardUid': cardUid.toString(), + 'user': "some foaf"}; + this.$.rewrite.generateRequest(); + } + }); + }); + </script> + </dom-module> + <rfid-control></rfid-control> + </body> +</html>
--- a/service/rfid_pn532_py/makefile Sat Mar 16 18:22:57 2019 -0700 +++ b/service/rfid_pn532_py/makefile Sat Mar 23 04:26:03 2019 -0700 @@ -1,3 +1,8 @@ +SERVICE=rfid_pn532_py +JOB=rfid +PORT=10012 +RUNHOST=frontdoor + pyfreefare-build-x86/nfc.py: Dockerfile.pyfreefare.x86 pyfreefare/nfc.h pyfreefare/freefare.h build_ctypes_modules.sh docker build --file Dockerfile.pyfreefare.x86 -t pyfreefare_build_x86:latest . docker run --rm --net=host \ @@ -17,9 +22,27 @@ PYTHONPATH=pyfreefare-build-x86 nose2-3 tags_test -rfid_build_image_pi: pyfreefare-build-pi/nfc.py pyfreefare-build-pi/freefare.py - docker build --file Dockerfile.pi -t bang6:5000/rfid_pn532_py_pi:latest . - docker push bang6:5000/rfid_pn532_py_pi:latest +build_image_x86: pyfreefare-build-x86/nfc.py pyfreefare-build-x86/freefare.py Dockerfile.x86 + rm -rf tmp_ctx + mkdir -p tmp_ctx + cp -a Dockerfile.x86 ../../lib/*.py *.py *.txt *.html pyfreefare-build-x86 tmp_ctx + docker build --network=host --file Dockerfile.x86 -t bang6:5000/$(SERVICE)_x86:latest tmp_ctx + docker push bang6:5000/$(SERVICE)_x86:latest + +build_image_pi: pyfreefare-build-pi/nfc.py pyfreefare-build-pi/freefare.py Dockerfile.pi + rm -rf tmp_ctx + mkdir -p tmp_ctx + cp -a Dockerfile.pi ../../lib/*.py *.py *.txt *.html pyfreefare-build-pi tmp_ctx + docker build --network=host --file Dockerfile.pi -t bang6:5000/$(SERVICE)_pi:latest tmp_ctx + docker push bang6:5000/$(SERVICE)_pi:latest + +run_local_x86: build_image_x86 + docker run -it --rm --privileged --net=host --cap-add=SYS_PTRACE --name $(JOB)_run bang6:5000/$(SERVICE)_x86:latest python3 rfid.py -v + # test on pi: # docker pull bang6:5000/rfid_pn532_py_pi:latest && docker run -it --rm --privileged --name rfid_shell bang6:5000/rfid_pn532_py_pi:latest nose2-3 tags_test + +redeploy: build_image_pi + sudo /my/proj/ansible/playbook -l $(RUNHOST) -t rfid + supervisorctl -s http://$(RUNHOST):9001/ restart $(JOB)_$(PORT)
--- a/service/rfid_pn532_py/requirements.txt Sat Mar 16 18:22:57 2019 -0700 +++ b/service/rfid_pn532_py/requirements.txt Sat Mar 23 04:26:03 2019 -0700 @@ -1,1 +1,7 @@ +cyclone +docopt +rdflib-jsonld==0.4.0 +rdflib==4.2.2 +https://projects.bigasterisk.com/rdfdb/rdfdb-0.7.0.tar.gz +git+http://github.com/drewp/scales.git@448d59fb491b7631877528e7695a93553bfaaa93#egg=scales
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/service/rfid_pn532_py/rfid.py Sat Mar 23 04:26:03 2019 -0700 @@ -0,0 +1,204 @@ +import os +os.environ['LIBNFC_DEFAULT_DEVICE'] = "pn532_i2c:/dev/i2c-1" + +from docopt import docopt +from rdfdb.patch import Patch +from patchablegraph import PatchableGraph, CycloneGraphHandler, CycloneGraphEventsHandler +from rdflib import Namespace, URIRef, Literal, Graph +from rdflib.parser import StringInputSource +from twisted.internet import reactor, task +import cyclone.web +from cyclone.httpclient import fetch +import cyclone +import logging, time, json, random, string +from logsetup import log, enableTwistedLog +from greplin import scales +from greplin.scales.cyclonehandler import StatsHandler +from tags import NfcDevice, FakeNfc + +ROOM = Namespace('http://projects.bigasterisk.com/room/') + +ctx = ROOM['frontDoorWindowRfidCtx'] + +STATS = scales.collection('/web', + scales.PmfStat('cardReadPoll'), +) + +class OutputPage(cyclone.web.RequestHandler): + def put(self): + arg = self.request.arguments + if arg.get('s') and arg.get('p'): + self._onQueryStringStatement(arg['s'][-1], arg['p'][-1], self.request.body) + else: + self._onGraphBodyStatements(self.request.body, self.request.headers) + post = put + def _onQueryStringStatement(self, s, p, body): + subj = URIRef(s) + pred = URIRef(p) + turtleLiteral = self.request.body + try: + obj = Literal(float(turtleLiteral)) + except ValueError: + obj = Literal(turtleLiteral) + self._onStatements([(subj, pred, obj)]) + + def _onGraphBodyStatements(self, body, headers): + g = Graph() + g.parse(StringInputSource(body), format='nt') + if not g: + raise ValueError("expected graph body") + self._onStatements(list(g.triples((None, None, None)))) + post = put + + def _onStatements(self, stmts): + # write rfid to new key, etc. + if len(stmts) > 0 and stmts[0][1] == ROOM['keyContents']: + return + log.warn("ignoring %s", stmts) + +def uidUri(card_id): + return URIRef('http://bigasterisk.com/rfidCard/%s' % card_id) + +BODY_VERSION = "1" +def randomBody(): + return BODY_VERSION + '*' + ''.join(random.choice(string.ascii_uppercase) for n in range(16 - 2)) + +def looksLikeBigasterisk(text): + return text.startswith(BODY_VERSION + "*") + +class Rewrite(cyclone.web.RequestHandler): + def post(self): + agent = URIRef(self.request.headers['x-foaf-agent']) + body = json.loads(self.request.body) + + _, uid = reader.read_id() + log.info('current card id: %r %r', _, uid) + if uid is None: + self.set_status(404, "no card present") + # maybe retry a few more times since the card might be nearby + return + + text = randomBody() + log.info('%s rewrites %s to %s, to be owned by %s', + agent, uid, text, body['user']) + + #reader.KEY = private.rfid_key + reader.write(uid, text) + log.info('done with write') + + +sensor = ROOM['frontDoorWindowRfid'] + +class ReadLoop(object): + def __init__(self, reader, masterGraph, overwrite_any_tag): + self.reader = reader + self.masterGraph = masterGraph + self.overwrite_any_tag = overwrite_any_tag + self.log = {} # cardIdUri : most recent seentime + + self.pollPeriodSecs = 5.1 + self.expireSecs = 5 + + task.LoopingCall(self.poll).start(self.pollPeriodSecs) + + @STATS.cardReadPoll.time() + def poll(self): + now = time.time() + + self.flushOldReads(now) + + for tag in self.reader.getTags(): # blocks for a bit + uid = tag.uid() + log.info('detected tag uid=%r', uid) + cardIdUri = uidUri(uid) + + is_new = cardIdUri not in self.log + self.log[cardIdUri] = now + if is_new: + tag.connect() + try: + textLit = Literal(tag.readBlock(1).rstrip('\x00')) + if self.overwrite_any_tag and not looksLikeBigasterisk(textLit): + log.info("block 1 was %r; rewriting it", textLit) + tag.writeBlock(1, randomBody()) + textLit = Literal(tag.readBlock(1).rstrip('\x00')) + finally: + tag.disconnect() + self.startCardRead(cardIdUri, textLit) + + def flushOldReads(self, now): + for uri in list(self.log): + if self.log[uri] < now - self.expireSecs: + self.endCardRead(uri) + del self.log[uri] + + def startCardRead(self, cardUri, text): + self.masterGraph.patch(Patch(addQuads=[ + (sensor, ROOM['reading'], cardUri, ctx), + (cardUri, ROOM['cardText'], text, ctx)], + delQuads=[])) + log.info('read card at id=%s %r', cardUri, str(text)) + self._sendOneshot([(sensor, ROOM['startReading'], cardUri), + (cardUri, ROOM['cardText'], text)]) + + def endCardRead(self, cardUri): + log.info(f'{cardUri} has been gone for {self.expireSecs} sec') + delQuads = [] + for spo in self.masterGraph._graph.triples( + (sensor, ROOM['reading'], cardUri)): + delQuads.append(spo + (ctx,)) + for spo in self.masterGraph._graph.triples( + (cardUri, ROOM['cardText'], None)): + delQuads.append(spo + (ctx,)) + + self.masterGraph.patch(Patch(addQuads=[], delQuads=delQuads)) + + def _sendOneshot(self, oneshot): + body = (' '.join('%s %s %s .' % (s.n3(), p.n3(), o.n3()) + for s,p,o in oneshot)).encode('utf8') + url = b'http://bang6:19071/oneShot' + d = fetch(method=b'POST', + url=url, + headers={b'Content-Type': [b'text/n3']}, + postdata=body, + timeout=5) + def err(e): + log.info('oneshot post to %r failed: %s', + url, e.getErrorMessage()) + d.addErrback(err) + + + +if __name__ == '__main__': + arg = docopt(""" + Usage: rfid.py [options] + + -v Verbose + --overwrite_any_tag Rewrite any unknown tag with a new random body + -n Fake reader + """) + log.setLevel(logging.INFO) + if arg['-v']: + enableTwistedLog() + log.setLevel(logging.DEBUG) + log.info(f'cyclone {cyclone.__version__}') + + masterGraph = PatchableGraph() + reader = NfcDevice() if not arg['-n'] else FakeNfc() + + loop = ReadLoop(reader, masterGraph, overwrite_any_tag=arg['--overwrite_any_tag']) + + port = 10012 + 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), + (r'/rewrite', Rewrite), + (r'/stats/(.*)', StatsHandler, {'serverName': 'rfid'}), + ], masterGraph=masterGraph, debug=arg['-v']), interface='::') + log.warn('serving on %s', port) + + reactor.run()
--- a/service/rfid_pn532_py/tags.py Sat Mar 16 18:22:57 2019 -0700 +++ b/service/rfid_pn532_py/tags.py Sat Mar 23 04:26:03 2019 -0700 @@ -4,6 +4,10 @@ import logging log = logging.getLogger('tags') +class FakeNfc(object): + def getTags(self): + return [] + class NfcDevice(object): def __init__(self): @@ -17,14 +21,14 @@ devices_found = nfc.nfc_list_devices(self.context, conn_strings, 10) log.info(f'{devices_found} connection strings') for i in range(devices_found): - log.info(f' dev {i}: {conn_strings[i]}') + log.info(f' dev {i}: {cast(conn_strings[i], c_char_p).value}') if devices_found < 1: raise IOError("no devices") log.info("open dev") self.dev = nfc.nfc_open(self.context, conn_strings[0]) - if nfc.nfc_device_get_last_error(self.dev): - raise IOError(f'nfc.open failed on {conn_strings[0]}') + if not self.dev or nfc.nfc_device_get_last_error(self.dev): + raise IOError(f'nfc.open failed on {cast(conn_strings[0], c_char_p).value}') def __del__(self): if self.dev: @@ -40,6 +44,8 @@ try: log.info(f"found tags in {time.time() - t0}") for t in ret: + if not t: + break yield NfcTag(t) finally: freefare.freefare_free_tags(ret) @@ -91,4 +97,4 @@ dataBlock = (c_ubyte*16)(*dataBytes) self._check(freefare.mifare_classic_write(self.tag, blocknum, dataBlock)) - log.info(" wrote block {blocknum}: {dataBlock}") + log.info(f" wrote block {blocknum}: {dataBlock}")