Mercurial > code > home > repos > homeauto
comparison service/arduinoNode/arduinoNode.py @ 164:49c1756b2edb
start arduinonode
Ignore-this: 6ddc4d3af9ab8468e25b346bddf15835
author | drewp@bigasterisk.com |
---|---|
date | Mon, 06 Apr 2015 02:13:39 -0700 |
parents | |
children | c0180bd2b33a |
comparison
equal
deleted
inserted
replaced
163:4b0f221d790c | 164:49c1756b2edb |
---|---|
1 import shutil | |
2 import tempfile | |
3 import glob, sys, logging, subprocess | |
4 import cyclone.web | |
5 from rdflib import Graph, Namespace, URIRef, Literal, RDF | |
6 from twisted.internet import reactor, task | |
7 import devices | |
8 | |
9 logging.basicConfig(level=logging.DEBUG) | |
10 | |
11 from loggingserial import LoggingSerial | |
12 | |
13 sys.path.append("/my/site/magma") | |
14 from stategraph import StateGraph | |
15 | |
16 log = logging.getLogger() | |
17 logging.getLogger('serial').setLevel(logging.WARN) | |
18 | |
19 | |
20 ROOM = Namespace('http://projects.bigasterisk.com/room/') | |
21 | |
22 class Config(object): | |
23 def __init__(self): | |
24 self.graph = Graph() | |
25 log.info('read config') | |
26 self.graph.bind('', ROOM) | |
27 self.graph.bind('rdf', RDF) | |
28 self.graph.parse('config.n3', format='n3') | |
29 | |
30 def serialDevices(self): | |
31 return dict([(row.dev, row.board) for row in self.graph.query( | |
32 """SELECT ?board ?dev WHERE { | |
33 ?board :device ?dev; | |
34 a :ArduinoBoard . | |
35 }""", initNs={'': ROOM})]) | |
36 | |
37 class Board(object): | |
38 """an arduino connected to this computer""" | |
39 baudrate = 115200 | |
40 def __init__(self, dev, graph, uri, onChange): | |
41 """ | |
42 each connected thing has some pins. | |
43 | |
44 We'll call onChange when we know the currentGraph() has | |
45 changed (and not just in creation time). | |
46 """ | |
47 self.uri = uri | |
48 self.graph = graph | |
49 self.dev = dev | |
50 | |
51 # The order of this list needs to be consistent between the | |
52 # deployToArduino call and the poll call. | |
53 self._inputs = [devices.PingInput(graph, self.uri)] | |
54 for row in graph.query("""SELECT ?dev WHERE { | |
55 ?board :hasPin ?pin . | |
56 ?pin :connectedTo ?dev . | |
57 } ORDER BY ?dev""", | |
58 initBindings=dict(board=self.uri), | |
59 initNs={'': ROOM}): | |
60 self._inputs.append(devices.makeBoardInput(graph, row.dev)) | |
61 | |
62 self._statementsFromInputs = {} # input uri: latest statements | |
63 | |
64 | |
65 def open(self): | |
66 self.ser = LoggingSerial(port=self.dev, baudrate=self.baudrate, timeout=2) | |
67 | |
68 def startPolling(self): | |
69 task.LoopingCall(self._poll).start(.5) | |
70 | |
71 def _poll(self): | |
72 """ | |
73 even boards with no inputs need some polling to see if they're | |
74 still ok | |
75 """ | |
76 try: | |
77 self._pollWork() | |
78 except Exception as e: | |
79 log.warn("poll: %r" % e) | |
80 | |
81 def _pollWork(self): | |
82 self.ser.write("\x60\x00") | |
83 for i in self._inputs: | |
84 self._statementsFromInputs[i.uri] = i.readFromPoll(self.ser.read) | |
85 #plus statements about succeeding or erroring on the last poll | |
86 | |
87 def currentGraph(self): | |
88 g = Graph() | |
89 for si in self._statementsFromInputs.values(): | |
90 for s in si: | |
91 g.add(s) | |
92 return g | |
93 | |
94 def generateArduinoCode(self): | |
95 generated = {'baudrate': self.baudrate, 'setups': '', 'polls': ''} | |
96 for attr in ['setups', 'polls']: | |
97 for i in self._inputs: | |
98 gen = (i.generateSetupCode() if attr == 'setups' | |
99 else i.generatePollCode()) | |
100 generated[attr] += '// for %s\n%s\n' % (i.uri, gen) | |
101 | |
102 return ''' | |
103 void setup() { | |
104 Serial.begin(%(baudrate)d); | |
105 Serial.flush(); | |
106 %(setups)s | |
107 } | |
108 | |
109 void loop() { | |
110 byte head, cmd; | |
111 if (Serial.available() >= 2) { | |
112 head = Serial.read(); | |
113 if (head != 0x60) { | |
114 Serial.flush(); | |
115 return; | |
116 } | |
117 cmd = Serial.read(); | |
118 if (cmd == 0x00) { | |
119 %(polls)s; | |
120 } | |
121 } | |
122 } | |
123 ''' % generated | |
124 | |
125 def deployToArduino(self): | |
126 code = self.generateArduinoCode() | |
127 try: | |
128 if hasattr(self, 'ser'): | |
129 self.ser.close() | |
130 workDir = tempfile.mkdtemp(prefix='arduinoNode_board_deploy') | |
131 try: | |
132 self._arduinoMake(workDir, code) | |
133 finally: | |
134 shutil.rmtree(workDir) | |
135 finally: | |
136 self.open() | |
137 | |
138 def _arduinoMake(self, workDir, code): | |
139 with open(workDir + '/makefile', 'w') as makefile: | |
140 makefile.write(''' | |
141 BOARD_TAG = %(tag)s | |
142 USER_LIB_PATH := | |
143 ARDUINO_LIBS = | |
144 MONITOR_PORT = %(dev)s | |
145 | |
146 include /usr/share/arduino/Arduino.mk | |
147 ''' % { | |
148 'dev': self.dev, | |
149 'tag': self.graph.value(self.uri, ROOM['boardTag']), | |
150 }) | |
151 | |
152 with open(workDir + '/main.ino', 'w') as main: | |
153 main.write(code) | |
154 | |
155 subprocess.check_call(['make', 'upload'], cwd=workDir) | |
156 | |
157 | |
158 class Index(cyclone.web.RequestHandler): | |
159 def get(self): | |
160 self.set_header("Content-Type", "text/html") | |
161 self.write(open("index.html").read()) | |
162 | |
163 class GraphPage(cyclone.web.RequestHandler): | |
164 def get(self): | |
165 g = StateGraph(ctx=ROOM['arduinosOn%s' % 'host']) | |
166 | |
167 for b in self.settings.boards: | |
168 for stmt in b.currentGraph(): | |
169 g.add(stmt) | |
170 | |
171 if self.get_argument('config', 'no') == 'yes': | |
172 for stmt in self.settings.config.graph: | |
173 g.add(stmt) | |
174 | |
175 self.set_header('Content-type', 'application/x-trig') | |
176 self.write(g.asTrig()) | |
177 | |
178 class Dot(cyclone.web.RequestHandler): | |
179 def get(self): | |
180 nodes = {} # uri: nodeline | |
181 edges = [] | |
182 | |
183 serial = [0] | |
184 def addNode(node): | |
185 if node not in nodes or isinstance(node, Literal): | |
186 id = 'node%s' % serial[0] | |
187 if isinstance(node, URIRef): | |
188 short = self.settings.config.graph.qname(node) | |
189 else: | |
190 short = str(node) | |
191 nodes[node] = ( | |
192 id, | |
193 '%s [ label="%s", shape = record, color = blue ];' % ( | |
194 id, short)) | |
195 serial[0] += 1 | |
196 else: | |
197 id = nodes[node][0] | |
198 return id | |
199 def addStmt(stmt): | |
200 ns = addNode(stmt[0]) | |
201 no = addNode(stmt[2]) | |
202 edges.append('%s -> %s [ label="%s" ];' % (ns, no, stmt[1])) | |
203 for b in self.settings.boards: | |
204 for stmt in b.currentGraph(): | |
205 # color these differently from config ones | |
206 addStmt(stmt) | |
207 for stmt in self.settings.config.graph: | |
208 addStmt(stmt) | |
209 | |
210 nodes = '\n'.join(line for _, line in nodes.values()) | |
211 edges = '\n'.join(edges) | |
212 dot = ''' | |
213 digraph { | |
214 rankdir = TB; | |
215 charset="utf-8"; | |
216 %(nodes)s | |
217 %(edges)s | |
218 } | |
219 ''' % dict(nodes=nodes, edges=edges) | |
220 self.write(dot) | |
221 | |
222 class ArduinoCode(cyclone.web.RequestHandler): | |
223 def get(self): | |
224 board = [b for b in self.settings.boards if | |
225 b.uri == URIRef(self.get_argument('board'))][0] | |
226 self.set_header('Content-type', 'text/plain') | |
227 self.write(board.generateArduinoCode()) | |
228 | |
229 | |
230 def currentSerialDevices(): | |
231 log.info('find connected boards') | |
232 return glob.glob('/dev/serial/by-id/*') | |
233 | |
234 def main(): | |
235 config = Config() | |
236 current = currentSerialDevices() | |
237 | |
238 def onChange(): | |
239 # notify reasoning | |
240 pass | |
241 | |
242 boards = [] | |
243 for dev, board in config.serialDevices().items(): | |
244 if str(dev) not in current: | |
245 continue | |
246 log.info("we have board %s connected at %s" % (board, dev)) | |
247 b = Board(dev, config.graph, board, onChange) | |
248 boards.append(b) | |
249 | |
250 #boards[0].deployToArduino() | |
251 | |
252 log.info('open boards') | |
253 for b in boards: | |
254 b.open() | |
255 b.startPolling() | |
256 | |
257 from twisted.python import log as twlog | |
258 twlog.startLogging(sys.stdout) | |
259 | |
260 log.setLevel(logging.DEBUG) | |
261 reactor.listenTCP(9059, cyclone.web.Application([ | |
262 (r"/", Index), | |
263 (r"/graph", GraphPage), | |
264 (r'/arduinoCode', ArduinoCode), | |
265 (r'/dot', Dot), | |
266 ], config=config, boards=boards)) | |
267 reactor.run() | |
268 | |
269 main() |