# HG changeset patch
# User drewp@bigasterisk.com
# Date 1553340363 25200
# Node ID 91162a54553ced1e50926119c3ac8535ccb8bb93
# Parent 9fbd2d0193bf1ac087863f75d585ce05c8132cad
copy rest of rfid service from the first try. fix some crashes in tags.py
Ignore-this: a3fdcc0a8494e358d3c0abc109e8ed4d
diff -r 9fbd2d0193bf -r 91162a54553c service/rfid_pn532_py/Dockerfile.pi
--- 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"]
diff -r 9fbd2d0193bf -r 91162a54553c service/rfid_pn532_py/Dockerfile.x86
--- /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"]
diff -r 9fbd2d0193bf -r 91162a54553c service/rfid_pn532_py/index.html
--- /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 @@
+
+
+
+ rfid
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Current reads:
+
+ Card UID | Card text | |
+
+
+ {{item.uidDisplay}} |
+ {{item.text}} |
+
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
diff -r 9fbd2d0193bf -r 91162a54553c service/rfid_pn532_py/makefile
--- 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)
diff -r 9fbd2d0193bf -r 91162a54553c service/rfid_pn532_py/requirements.txt
--- 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
diff -r 9fbd2d0193bf -r 91162a54553c service/rfid_pn532_py/rfid.py
--- /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()
diff -r 9fbd2d0193bf -r 91162a54553c service/rfid_pn532_py/tags.py
--- 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}")