Mercurial > code > home > repos > homeauto
view service/rfid_pn532_py/rfid.py @ 722:a93fbf0d0daa
dep updates; graph url renames; and other build updates
Ignore-this: 4603ef3d8db650a13e543dad8580ade8
author | drewp@bigasterisk.com |
---|---|
date | Wed, 05 Feb 2020 00:23:06 -0800 |
parents | e1f33b9fb3df |
children | b87b6e9cedb2 |
line wrap: on
line source
import os os.environ['LIBNFC_DEFAULT_DEVICE'] = "pn532_uart:/dev/ttyUSB0" 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, defer import cyclone.web from cyclone.httpclient import fetch import cyclone import logging, time, json, random, string, traceback from logsetup import log, enableTwistedLog from greplin import scales from greplin.scales.cyclonehandler import StatsHandler from export_to_influxdb import InfluxExporter from tags import NfcDevice, FakeNfc, NfcError, AuthFailedError ROOM = Namespace('http://projects.bigasterisk.com/room/') ctx = ROOM['frontDoorWindowRfidCtx'] STATS = scales.collection('/root', scales.PmfStat('cardReadPoll'), scales.IntStat('newCardReads'), ) 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 = .1 self.expireSecs = 5 # now=False avoids a serious bug where the first read error # could happen before reactor.run() is called, and then the # error fails to crash the reactor and get us restarted. task.LoopingCall(self.poll).start(self.pollPeriodSecs, now=False) @STATS.cardReadPoll.time() def poll(self): now = time.time() self.flushOldReads(now) try: for tag in self.reader.getTags(): # blocks for a bit uid = tag.uid() log.debug('detected tag uid=%r', uid) cardIdUri = uidUri(uid) is_new = cardIdUri not in self.log self.log[cardIdUri] = now if is_new: STATS.newCardReads += 1 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: # This might not be appropriate to call after # readBlock fails. I am getting double # exceptions. tag.disconnect() self.startCardRead(cardIdUri, textLit) except AuthFailedError as e: log.error(e) except (NfcError, OSError) as e: traceback.print_exc() log.error(e) reactor.stop() 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('%s :cardText %s .', cardUri.n3(), text.n3()) self._sendOneshot([(sensor, ROOM['startReading'], cardUri), (cardUri, ROOM['cardText'], text)]) def endCardRead(self, cardUri): log.debug(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://bang:9071/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__}') defer.setDebugging(True) masterGraph = PatchableGraph() reader = NfcDevice() if not arg['-n'] else FakeNfc() ie=InfluxExporter(Graph()) ie.exportStats(STATS, ['root.cardReadPoll.count', 'root.cardReadPoll.95percentile', 'root.newCardReads', ], period_secs=10, retain_days=7, ) loop = ReadLoop(reader, masterGraph, overwrite_any_tag=arg['--overwrite_any_tag']) port = 10012 reactor.listenTCP(port, cyclone.web.Application([ (r"/(|.+\.html)", cyclone.web.StaticFileHandler, {"path": ".", "default_filename": "index.html"}), (r"/graph/rfid", CycloneGraphHandler, {'masterGraph': masterGraph}), (r"/graph/rfid/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()