Change parser to require semicolons in variable sections
[awlsim.git] / awlsim / coreserver / client.py
1 # -*- coding: utf-8 -*-
2 #
3 # AWL simulator - PLC core server client
4 #
5 # Copyright 2013 Michael Buesch <m@bues.ch>
6 #
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 2 of the License, or
10 # (at your option) any later version.
11 #
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License along
18 # with this program; if not, write to the Free Software Foundation, Inc.,
19 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #
21
22 from __future__ import division, absolute_import, print_function, unicode_literals
23 from awlsim.core.compat import *
24
25 from awlsim.coreserver.server import *
26 from awlsim.coreserver.messages import *
27
28 import sys
29 import socket
30 import errno
31 import time
32
33
34 class AwlSimClient(object):
35         def __init__(self):
36                 self.serverProcess = None
37                 self.transceiver = None
38
39         def spawnServer(self, interpreter=None,
40                         listenHost=AwlSimServer.DEFAULT_HOST,
41                         listenPort=AwlSimServer.DEFAULT_PORT):
42                 """Spawn a new AwlSim-core server process.
43                 interpreter -> The python interpreter to use.
44                 listenHost -> The hostname or IP address to listen on.
45                 listenPort -> The port to listen on.
46                 Returns the spawned process' PID."""
47
48                 if self.serverProcess:
49                         raise AwlSimError("Server already running")
50                 if not interpreter:
51                         interpreter = sys.executable
52                 assert(interpreter)
53
54                 self.serverProcess = AwlSimServer.start(listenHost = listenHost,
55                                                         listenPort = listenPort,
56                                                         forkInterpreter = interpreter)
57
58         def connectToServer(self,
59                             host=AwlSimServer.DEFAULT_HOST,
60                             port=AwlSimServer.DEFAULT_PORT):
61                 """Connect to a AwlSim-core server.
62                 host -> The hostname or IP address to connect to.
63                 port -> The port to connect to."""
64
65                 printInfo("AwlSimClient: Connecting to server '%s (port %d)'..." %\
66                         (host, port))
67                 try:
68                         family, socktype, proto, canonname, sockaddr =\
69                                 socket.getaddrinfo(host, port, socket.AF_INET, socket.SOCK_STREAM)[0]
70                         sock = socket.socket(family, socktype)
71                         count = 0
72                         while 1:
73                                 try:
74                                         sock.connect(sockaddr)
75                                 except (OSError, socket.error) as e:
76                                         if e.errno == errno.ECONNREFUSED:
77                                                 count += 1
78                                                 if count >= 100:
79                                                         raise AwlSimError("Timeout connecting "
80                                                                 "to AwlSimServer %s (port %d)" %\
81                                                                 (host, port))
82                                                 self.sleep(0.1)
83                                                 continue
84                                         raise
85                                 break
86                 except socket.error as e:
87                         raise AwlSimError("Failed to connect to AwlSimServer %s (port %d): %s" %\
88                                 (host, port, str(e)))
89                 printInfo("AwlSimClient: Connected.")
90                 self.transceiver = AwlSimMessageTransceiver(sock)
91                 self.lastRxMsg = None
92
93                 # Ping the server
94                 try:
95                         self.transceiver.send(AwlSimMessage_PING())
96                         msg = self.transceiver.receiveBlocking(timeoutSec = 5.0)
97                         if not msg:
98                                 raise AwlSimError("AwlSimClient: Server did not "
99                                         "respond to PING request.")
100                         if msg.msgId != AwlSimMessage.MSG_ID_PONG:
101                                 raise AwlSimError("AwlSimClient: Server did not "
102                                         "respond properly to PING request. "
103                                         "(Expected ID %d, but got ID %d)" %\
104                                         (AwlSimMessage.MSG_ID_PONG, msg.msgId))
105                 except TransferError as e:
106                         raise AwlSimError("AwlSimClient: PING to server failed")
107
108         def shutdown(self):
109                 """Shutdown all sockets and spawned processes."""
110
111                 if self.transceiver:
112                         self.transceiver.shutdown()
113                         self.transceiver = None
114                 if self.serverProcess:
115                         self.serverProcess.terminate()
116                         self.serverProcess.wait()
117                         self.serverProcess = None
118
119         def __rx_NOP(self, msg):
120                 pass # Nothing
121
122         def __rx_EXCEPTION(self, msg):
123                 raise AwlSimErrorText(msg.exceptionText)
124
125         def __rx_PING(self, msg):
126                 self.transceiver.send(AwlSimMessage_PONG())
127
128         def handle_PONG(self):
129                 printInfo("AwlSimClient: Received PONG")
130
131         def __rx_PONG(self, msg):
132                 self.handle_PONG()
133
134         def handle_CPUDUMP(self, dumpText):
135                 pass # Don't do anything by default
136
137         def __rx_CPUDUMP(self, msg):
138                 self.handle_CPUDUMP(msg.dumpText)
139
140         def __rx_MAINTREQ(self, msg):
141                 if msg.requestType == MaintenanceRequest.TYPE_SHUTDOWN:
142                         raise MaintenanceRequest(msg.requestType)
143                 else:
144                         printError("Received unknown maintenance request: %d" %\
145                                    msg.requestType)
146
147         def handle_MEMORY(self, memAreas):
148                 pass # Don't do anything by default
149
150         def __rx_MEMORY(self, msg):
151                 self.handle_MEMORY(msg.memAreas)
152                 if msg.flags & msg.FLG_SYNC:
153                         # The server should never send us a synchronous
154                         # memory image. So just output an error message.
155                         printError("Received synchronous memory request")
156
157         __msgRxHandlers = {
158                 AwlSimMessage.MSG_ID_REPLY              : __rx_NOP,
159                 AwlSimMessage.MSG_ID_EXCEPTION          : __rx_EXCEPTION,
160                 AwlSimMessage.MSG_ID_PING               : __rx_PING,
161                 AwlSimMessage.MSG_ID_PONG               : __rx_PONG,
162                 AwlSimMessage.MSG_ID_CPUDUMP            : __rx_CPUDUMP,
163                 AwlSimMessage.MSG_ID_MAINTREQ           : __rx_MAINTREQ,
164                 AwlSimMessage.MSG_ID_CPUSPECS           : __rx_NOP,
165                 AwlSimMessage.MSG_ID_MEMORY             : __rx_MEMORY,
166         }
167
168         def processMessages(self, blocking=False):
169                 self.lastRxMsg = None
170                 if not self.transceiver:
171                         return
172                 try:
173                         if blocking:
174                                 msg = self.transceiver.receiveBlocking()
175                         else:
176                                 msg = self.transceiver.receive()
177                 except socket.error as e:
178                         if e.errno == errno.EAGAIN:
179                                 return
180                         host, port = self.socket.getpeername()
181                         raise AwlSimError("AwlSimClient: "
182                                 "I/O error in connection to server '%s (port %d)':\n%s" %\
183                                 (host, port, str(e)))
184                 except (AwlSimMessageTransceiver.RemoteEndDied, TransferError) as e:
185                         host, port = self.socket.getpeername()
186                         raise AwlSimError("AwlSimClient: "
187                                 "Connection to server '%s:%s' died. "
188                                 "Failed to receive message." %\
189                                 (host, port))
190                 if not msg:
191                         return
192                 self.lastRxMsg = msg
193                 try:
194                         handler = self.__msgRxHandlers[msg.msgId]
195                 except KeyError:
196                         raise AwlSimError("AwlSimClient: Received unsupported "
197                                 "message 0x%02X" % msg.msgId)
198                 handler(self, msg)
199
200         def sleep(self, seconds):
201                 time.sleep(seconds)
202
203         def __sendAndWait(self, txMsg, checkRxMsg, waitTimeout=3.0):
204                 self.transceiver.send(txMsg)
205                 now = monotonic_time()
206                 end = now + waitTimeout
207                 while now < end:
208                         self.processMessages()
209                         rxMsg = self.lastRxMsg
210                         if rxMsg:
211                                 if checkRxMsg(rxMsg):
212                                         return rxMsg
213                         else:
214                                 # Queue is empty.
215                                 pass#XXX self.sleep(0.01)
216                         now = monotonic_time()
217                 raise AwlSimError("AwlSimClient: Timeout waiting for server reply.")
218
219         def __sendAndWaitFor_REPLY(self, msg, timeout=3.0):
220                 def checkRxMsg(rxMsg):
221                         return rxMsg.msgId == AwlSimMessage.MSG_ID_REPLY and\
222                                rxMsg.inReplyToId == msg.msgId and\
223                                rxMsg.inReplyToSeq == msg.seq
224                 return self.__sendAndWait(msg, checkRxMsg, timeout).status
225
226         def reset(self):
227                 msg = AwlSimMessage_RESET()
228                 status = self.__sendAndWaitFor_REPLY(msg)
229                 if status != AwlSimMessage_REPLY.STAT_OK:
230                         raise AwlSimError("AwlSimClient: Failed to reset CPU")
231
232         def setRunState(self, run=True):
233                 if not self.transceiver:
234                         return False
235                 if run:
236                         runState = AwlSimMessage_RUNSTATE.STATE_RUN
237                 else:
238                         runState = AwlSimMessage_RUNSTATE.STATE_STOP
239                 msg = AwlSimMessage_RUNSTATE(runState)
240                 status = self.__sendAndWaitFor_REPLY(msg)
241                 if status != AwlSimMessage_REPLY.STAT_OK:
242                         raise AwlSimError("AwlSimClient: Failed to set run state")
243                 return True
244
245         def loadCode(self, code):
246                 if not self.transceiver:
247                         return False
248                 msg = AwlSimMessage_LOAD_CODE(code)
249                 status = self.__sendAndWaitFor_REPLY(msg, 10.0)
250                 if status != AwlSimMessage_REPLY.STAT_OK:
251                         raise AwlSimError("AwlSimClient: Failed to load code")
252                 return True
253
254         def loadSymbolTable(self, symTabText):
255                 msg = AwlSimMessage_LOAD_SYMTAB(symTabText)
256                 status = self.__sendAndWaitFor_REPLY(msg)
257                 if status != AwlSimMessage_REPLY.STAT_OK:
258                         raise AwlSimError("AwlSimClient: Failed to load symbol table")
259
260         def loadHardwareModule(self, name, parameters={}):
261                 if not self.transceiver:
262                         return False
263                 msg = AwlSimMessage_LOAD_HW(name = name,
264                                             paramDict = parameters)
265                 status = self.__sendAndWaitFor_REPLY(msg)
266                 if status != AwlSimMessage_REPLY.STAT_OK:
267                         raise AwlSimError("AwlSimClient: Failed to load hardware module")
268                 return True
269
270         def __setOption(self, name, value):
271                 if not self.transceiver:
272                         return False
273                 msg = AwlSimMessage_SET_OPT(name, str(value))
274                 status = self.__sendAndWaitFor_REPLY(msg)
275                 if status != AwlSimMessage_REPLY.STAT_OK:
276                         raise AwlSimError("AwlSimClient: Failed to set option '%s'" % name)
277                 return True
278
279         def setLoglevel(self, level=Logging.LOG_INFO):
280                 Logging.setLoglevel(level)
281                 return self.__setOption("loglevel", int(level))
282
283         def enableOBTempPresets(self, enable=True):
284                 return self.__setOption("ob_temp_presets", int(bool(enable)))
285
286         def enableExtendedInsns(self, enable=True):
287                 return self.__setOption("extended_insns", int(bool(enable)))
288
289         def setPeriodicDumpInterval(self, interval=0):
290                 return self.__setOption("periodic_dump_int", int(interval))
291
292         def setCycleTimeLimit(self, seconds=5.0):
293                 return self.__setOption("cycle_time_limit", float(seconds))
294
295         def getCpuSpecs(self):
296                 if not self.transceiver:
297                         return None
298                 msg = AwlSimMessage_GET_CPUSPECS()
299                 rxMsg = self.__sendAndWait(msg,
300                         lambda rxMsg: rxMsg.msgId == AwlSimMessage.MSG_ID_CPUSPECS)
301                 return rxMsg.cpuspecs
302
303         def setCpuSpecs(self, cpuspecs):
304                 if not self.transceiver:
305                         return False
306                 msg = AwlSimMessage_CPUSPECS(cpuspecs)
307                 status = self.__sendAndWaitFor_REPLY(msg)
308                 if status != AwlSimMessage_REPLY.STAT_OK:
309                         raise AwlSimError("AwlSimClient: Failed to set cpuspecs")
310                 return True
311
312         # Set the memory areas we are interested in receiving
313         # dumps for, in the server.
314         # memAreas is a list of MemoryArea instances.
315         # The repetitionFactor tells whether to
316         #  - only run the request once (repetitionFactor=0)
317         #  - repeat on n'th every cycle (repetitionFactor=n)
318         # If sync is true, wait for a reply from the server.
319         def setMemoryReadRequests(self, memAreas, repetitionFactor=0, sync=False):
320                 if not self.transceiver:
321                         return False
322                 msg = AwlSimMessage_REQ_MEMORY(0, repetitionFactor, memAreas)
323                 if sync:
324                         msg.flags |= msg.FLG_SYNC
325                         status = self.__sendAndWaitFor_REPLY(msg)
326                         if status != AwlSimMessage_REPLY.STAT_OK:
327                                 raise AwlSimError("AwlSimClient: Failed to set "
328                                         "memory read reqs")
329                 else:
330                         self.transceiver.send(msg)
331                 return True
332
333         # Write memory areas in the server.
334         # memAreas is a list of MemoryAreaData instances.
335         # If sync is true, wait for a reply from the server.
336         def writeMemory(self, memAreas, sync=False):
337                 if not self.transceiver:
338                         return False
339                 msg = AwlSimMessage_MEMORY(0, memAreas)
340                 if sync:
341                         msg.flags |= msg.FLG_SYNC
342                         status = self.__sendAndWaitFor_REPLY(msg)
343                         if status != AwlSimMessage_REPLY.STAT_OK:
344                                 raise AwlSimError("AwlSimClient: Failed to write "
345                                         "to memory")
346                 else:
347                         self.transceiver.send(msg)
348                 return True