1526
|
1 import os
|
|
2 os.environ['LIBNFC_DEFAULT_DEVICE'] = "pn532_uart:/dev/ttyUSB0"
|
|
3
|
|
4 from docopt import docopt
|
|
5 from rdfdb.patch import Patch
|
|
6 from patchablegraph import PatchableGraph, CycloneGraphHandler, CycloneGraphEventsHandler
|
|
7 from rdflib import Namespace, URIRef, Literal, Graph
|
|
8 from rdflib.parser import StringInputSource
|
|
9 from twisted.internet import reactor, task, defer
|
|
10 import cyclone.web
|
|
11 from cyclone.httpclient import fetch
|
|
12 import cyclone
|
|
13 import logging, time, json, random, string, traceback
|
|
14 from logsetup import log, enableTwistedLog
|
|
15 from greplin import scales
|
|
16 from greplin.scales.cyclonehandler import StatsHandler
|
|
17 from export_to_influxdb import InfluxExporter
|
|
18 from tags import NfcDevice, FakeNfc, NfcError, AuthFailedError
|
|
19
|
|
20 ROOM = Namespace('http://projects.bigasterisk.com/room/')
|
|
21
|
|
22 ctx = ROOM['frontDoorWindowRfidCtx']
|
|
23
|
|
24 STATS = scales.collection('/root',
|
|
25 scales.PmfStat('cardReadPoll'),
|
|
26 scales.IntStat('newCardReads'),
|
|
27 )
|
|
28
|
|
29 class OutputPage(cyclone.web.RequestHandler):
|
|
30 def put(self):
|
|
31 arg = self.request.arguments
|
|
32 if arg.get('s') and arg.get('p'):
|
|
33 self._onQueryStringStatement(arg['s'][-1], arg['p'][-1], self.request.body)
|
|
34 else:
|
|
35 self._onGraphBodyStatements(self.request.body, self.request.headers)
|
|
36 post = put
|
|
37 def _onQueryStringStatement(self, s, p, body):
|
|
38 subj = URIRef(s)
|
|
39 pred = URIRef(p)
|
|
40 turtleLiteral = self.request.body
|
|
41 try:
|
|
42 obj = Literal(float(turtleLiteral))
|
|
43 except ValueError:
|
|
44 obj = Literal(turtleLiteral)
|
|
45 self._onStatements([(subj, pred, obj)])
|
|
46
|
|
47 def _onGraphBodyStatements(self, body, headers):
|
|
48 g = Graph()
|
|
49 g.parse(StringInputSource(body), format='nt')
|
|
50 if not g:
|
|
51 raise ValueError("expected graph body")
|
|
52 self._onStatements(list(g.triples((None, None, None))))
|
|
53 post = put
|
|
54
|
|
55 def _onStatements(self, stmts):
|
|
56 # write rfid to new key, etc.
|
|
57 if len(stmts) > 0 and stmts[0][1] == ROOM['keyContents']:
|
|
58 return
|
|
59 log.warn("ignoring %s", stmts)
|
|
60
|
|
61 def uidUri(card_id):
|
|
62 return URIRef('http://bigasterisk.com/rfidCard/%s' % card_id)
|
|
63
|
|
64 BODY_VERSION = "1"
|
|
65 def randomBody():
|
|
66 return BODY_VERSION + '*' + ''.join(random.choice(string.ascii_uppercase) for n in range(16 - 2))
|
|
67
|
|
68 def looksLikeBigasterisk(text):
|
|
69 return text.startswith(BODY_VERSION + "*")
|
|
70
|
|
71 class Rewrite(cyclone.web.RequestHandler):
|
|
72 def post(self):
|
|
73 agent = URIRef(self.request.headers['x-foaf-agent'])
|
|
74 body = json.loads(self.request.body)
|
|
75
|
|
76 _, uid = reader.read_id()
|
|
77 log.info('current card id: %r %r', _, uid)
|
|
78 if uid is None:
|
|
79 self.set_status(404, "no card present")
|
|
80 # maybe retry a few more times since the card might be nearby
|
|
81 return
|
|
82
|
|
83 text = randomBody()
|
|
84 log.info('%s rewrites %s to %s, to be owned by %s',
|
|
85 agent, uid, text, body['user'])
|
|
86
|
|
87 #reader.KEY = private.rfid_key
|
|
88 reader.write(uid, text)
|
|
89 log.info('done with write')
|
|
90
|
|
91
|
|
92 sensor = ROOM['frontDoorWindowRfid']
|
|
93
|
|
94 class ReadLoop(object):
|
|
95 def __init__(self, reader, masterGraph, overwrite_any_tag):
|
|
96 self.reader = reader
|
|
97 self.masterGraph = masterGraph
|
|
98 self.overwrite_any_tag = overwrite_any_tag
|
|
99 self.log = {} # cardIdUri : most recent seentime
|
|
100
|
|
101 self.pollPeriodSecs = .1
|
|
102 self.expireSecs = 5
|
|
103
|
|
104 # now=False avoids a serious bug where the first read error
|
|
105 # could happen before reactor.run() is called, and then the
|
|
106 # error fails to crash the reactor and get us restarted.
|
|
107 task.LoopingCall(self.poll).start(self.pollPeriodSecs, now=False)
|
|
108
|
|
109 @STATS.cardReadPoll.time()
|
|
110 def poll(self):
|
|
111 now = time.time()
|
|
112
|
|
113 self.flushOldReads(now)
|
|
114
|
|
115 try:
|
|
116 for tag in self.reader.getTags(): # blocks for a bit
|
|
117 uid = tag.uid()
|
|
118 log.debug('detected tag uid=%r', uid)
|
|
119 cardIdUri = uidUri(uid)
|
|
120
|
|
121 is_new = cardIdUri not in self.log
|
|
122 self.log[cardIdUri] = now
|
|
123 if is_new:
|
|
124 STATS.newCardReads += 1
|
|
125 tag.connect()
|
|
126 try:
|
|
127 textLit = Literal(tag.readBlock(1).rstrip('\x00'))
|
|
128 if self.overwrite_any_tag and not looksLikeBigasterisk(textLit):
|
|
129 log.info("block 1 was %r; rewriting it", textLit)
|
|
130 tag.writeBlock(1, randomBody())
|
|
131 textLit = Literal(tag.readBlock(1).rstrip('\x00'))
|
|
132 finally:
|
|
133 # This might not be appropriate to call after
|
|
134 # readBlock fails. I am getting double
|
|
135 # exceptions.
|
|
136 tag.disconnect()
|
|
137 self.startCardRead(cardIdUri, textLit)
|
|
138 except AuthFailedError as e:
|
|
139 log.error(e)
|
|
140 except (NfcError, OSError) as e:
|
|
141 traceback.print_exc()
|
|
142 log.error(e)
|
|
143 reactor.stop()
|
|
144
|
|
145 def flushOldReads(self, now):
|
|
146 for uri in list(self.log):
|
|
147 if self.log[uri] < now - self.expireSecs:
|
|
148 self.endCardRead(uri)
|
|
149 del self.log[uri]
|
|
150
|
|
151 def startCardRead(self, cardUri, text):
|
|
152 self.masterGraph.patch(Patch(addQuads=[
|
|
153 (sensor, ROOM['reading'], cardUri, ctx),
|
|
154 (cardUri, ROOM['cardText'], text, ctx)],
|
|
155 delQuads=[]))
|
|
156 log.info('%s :cardText %s .', cardUri.n3(), text.n3())
|
|
157 self._sendOneshot([(sensor, ROOM['startReading'], cardUri),
|
|
158 (cardUri, ROOM['cardText'], text)])
|
|
159
|
|
160 def endCardRead(self, cardUri):
|
|
161 log.debug(f'{cardUri} has been gone for {self.expireSecs} sec')
|
|
162 delQuads = []
|
|
163 for spo in self.masterGraph._graph.triples(
|
|
164 (sensor, ROOM['reading'], cardUri)):
|
|
165 delQuads.append(spo + (ctx,))
|
|
166 for spo in self.masterGraph._graph.triples(
|
|
167 (cardUri, ROOM['cardText'], None)):
|
|
168 delQuads.append(spo + (ctx,))
|
|
169
|
|
170 self.masterGraph.patch(Patch(addQuads=[], delQuads=delQuads))
|
|
171
|
|
172 def _sendOneshot(self, oneshot):
|
|
173 body = (' '.join('%s %s %s .' % (s.n3(), p.n3(), o.n3())
|
|
174 for s,p,o in oneshot)).encode('utf8')
|
|
175 url = b'http://bang:9071/oneShot'
|
|
176 d = fetch(method=b'POST',
|
|
177 url=url,
|
|
178 headers={b'Content-Type': [b'text/n3']},
|
|
179 postdata=body,
|
|
180 timeout=5)
|
|
181 def err(e):
|
|
182 log.info('oneshot post to %r failed: %s',
|
|
183 url, e.getErrorMessage())
|
|
184 d.addErrback(err)
|
|
185
|
|
186
|
|
187
|
|
188 if __name__ == '__main__':
|
|
189 arg = docopt("""
|
|
190 Usage: rfid.py [options]
|
|
191
|
|
192 -v Verbose
|
|
193 --overwrite_any_tag Rewrite any unknown tag with a new random body
|
|
194 -n Fake reader
|
|
195 """)
|
|
196 log.setLevel(logging.INFO)
|
|
197 if arg['-v']:
|
|
198 enableTwistedLog()
|
|
199 log.setLevel(logging.DEBUG)
|
|
200 log.info(f'cyclone {cyclone.__version__}')
|
|
201 defer.setDebugging(True)
|
|
202
|
|
203 masterGraph = PatchableGraph()
|
|
204 reader = NfcDevice() if not arg['-n'] else FakeNfc()
|
|
205
|
|
206 ie=InfluxExporter(Graph())
|
|
207 ie.exportStats(STATS, ['root.cardReadPoll.count',
|
|
208 'root.cardReadPoll.95percentile',
|
|
209 'root.newCardReads',
|
|
210 ],
|
|
211 period_secs=10,
|
|
212 retain_days=7,
|
|
213 )
|
|
214
|
|
215 loop = ReadLoop(reader, masterGraph, overwrite_any_tag=arg['--overwrite_any_tag'])
|
|
216
|
|
217 port = 10012
|
|
218 reactor.listenTCP(port, cyclone.web.Application([
|
|
219 (r"/(|.+\.html)", cyclone.web.StaticFileHandler,
|
|
220 {"path": ".", "default_filename": "index.html"}),
|
|
221 (r"/graph/rfid", CycloneGraphHandler, {'masterGraph': masterGraph}),
|
|
222 (r"/graph/rfid/events", CycloneGraphEventsHandler,
|
|
223 {'masterGraph': masterGraph}),
|
|
224 (r'/output', OutputPage),
|
|
225 (r'/rewrite', Rewrite),
|
|
226 (r'/stats/(.*)', StatsHandler, {'serverName': 'rfid'}),
|
|
227 ], masterGraph=masterGraph, debug=arg['-v']), interface='::')
|
|
228 log.warn('serving on %s', port)
|
|
229
|
|
230 reactor.run()
|