123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620 |
- # -*- coding: utf-8 -*-
- #
- # AWL simulator - PLC core server client
- #
- # Copyright 2013-2015 Michael Buesch <m@bues.ch>
- #
- # This program is free software; you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation; either version 2 of the License, or
- # (at your option) any later version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License along
- # with this program; if not, write to the Free Software Foundation, Inc.,
- # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- #
- from __future__ import division, absolute_import, print_function, unicode_literals
- from awlsim.common.compat import *
- from awlsim.common.util import *
- from awlsim.common.subprocess import *
- from awlsim.coreserver.server import *
- from awlsim.coreserver.messages import *
- from awlsim.coreserver.memarea import *
- import sys
- import socket
- import errno
- import time
- class AwlSimClient(object):
- def __init__(self):
- self.serverProcess = None
- self.serverProcessHost = None
- self.serverProcessPort = None
- self.__transceiver = None
- self.__defaultTimeout = 3.0
- def spawnServer(self,
- interpreter=None,
- serverExecutable=None,
- listenHost=AwlSimServer.DEFAULT_HOST,
- listenPort=AwlSimServer.DEFAULT_PORT):
- """Spawn a new AwlSim-core server process.
- interpreter -> The python interpreter to use. Must be either:
- - None: Use sys.executable as interpreter.
- - a string: Use the specified interpreter binary.
- - list of strings: Try with the interpreters in the
- list, until the first working one is found.
- serverExecutable -> The server executable to run.
- This has precedence over 'interpreter'.
- May be a list of strings.
- listenHost -> The hostname or IP address to listen on.
- listenPort -> The port to listen on.
- Returns the spawned process' PID."""
- if self.serverProcess:
- raise AwlSimError("Server already running")
- if serverExecutable:
- for serverExe in toList(serverExecutable):
- if not findExecutable(serverExe):
- continue
- self.serverProcess = AwlSimServer.start(listenHost = listenHost,
- listenPort = listenPort,
- forkServerProcess = serverExe)
- break
- else:
- raise AwlSimError("Unable to fork any of the supplied "
- "server executables: %s" %\
- str(toList(serverExecutable)))
- else:
- if interpreter is None:
- interpreter = sys.executable
- for interp in toList(interpreter):
- if not findExecutable(interp):
- continue
- self.serverProcess = AwlSimServer.start(listenHost = listenHost,
- listenPort = listenPort,
- forkInterpreter = interp)
- break
- else:
- raise AwlSimError("Unable to fork an awlsim core server with "
- "any of the supplied Python interpreters: %s\n"
- "No interpreter found." %\
- str(toList(interpreter)))
- self.serverProcessHost = listenHost
- self.serverProcessPort = listenPort
- if isJython:
- #XXX Workaround: Jython's socket module does not like connecting
- # to a starting server. Wait a few seconds for the server
- # to start listening on the socket.
- time.sleep(10)
- def killSpawnedServer(self):
- """Shutdown the server process started with spawnServer()."""
- if not self.serverProcess:
- return
- if self.__transceiver:
- try:
- msg = AwlSimMessage_SHUTDOWN()
- status = self.__sendAndWaitFor_REPLY(msg)
- if status != AwlSimMessage_REPLY.STAT_OK:
- printError("AwlSimClient: Failed to shut "
- "down server via message")
- except (AwlSimError, MaintenanceRequest) as e:
- pass
- self.serverProcess.terminate()
- self.serverProcess.wait()
- self.serverProcess = None
- self.serverProcessHost = None
- self.serverProcessPort = None
- def connectToServer(self,
- host=AwlSimServer.DEFAULT_HOST,
- port=AwlSimServer.DEFAULT_PORT,
- timeout=3.0):
- """Connect to a AwlSim-core server.
- host -> The hostname or IP address to connect to.
- port -> The port to connect to."""
- self.__defaultTimeout = timeout
- startTime = monotonic_time()
- readableSockaddr = host
- try:
- family, socktype, sockaddr = AwlSimServer.getaddrinfo(host, port)
- if family == AF_UNIX:
- readableSockaddr = sockaddr
- else:
- readableSockaddr = "[%s]:%d" % (sockaddr[0], sockaddr[1])
- printInfo("AwlSimClient: Connecting to server '%s'..." % readableSockaddr)
- sock = socket.socket(family, socktype)
- while 1:
- if monotonic_time() - startTime > timeout:
- raise AwlSimError("Timeout connecting "
- "to AwlSimServer %s" % readableSockaddr)
- try:
- sock.connect(sockaddr)
- except SocketErrors as e:
- if e.errno == errno.ECONNREFUSED or\
- e.errno == errno.ENOENT:
- self.sleep(0.1)
- continue
- if isJython and\
- e.strerror.endswith("java.nio.channels.CancelledKeyException"):
- # XXX Jython workaround: Ignore this exception
- printInfo("Warning: Jython connect workaround")
- continue
- raise
- break
- except SocketErrors as e:
- raise AwlSimError("Failed to connect to AwlSimServer %s: %s" %\
- (readableSockaddr, str(e)))
- printInfo("AwlSimClient: Connected.")
- self.__transceiver = AwlSimMessageTransceiver(sock, readableSockaddr)
- self.lastRxMsg = None
- # Ping the server
- try:
- self.__transceiver.send(AwlSimMessage_PING())
- msg = self.__transceiver.receive(timeout = timeout)
- if not msg:
- raise AwlSimError("AwlSimClient: Server did not "
- "respond to PING request.")
- if msg.msgId != AwlSimMessage.MSG_ID_PONG:
- raise AwlSimError("AwlSimClient: Server did not "
- "respond properly to PING request. "
- "(Expected ID %d, but got ID %d)" %\
- (AwlSimMessage.MSG_ID_PONG, msg.msgId))
- except TransferError as e:
- raise AwlSimError("AwlSimClient: PING to server failed")
- def shutdownTransceiver(self):
- """Shutdown transceiver and close the socket."""
- if not self.__transceiver:
- return
- self.__transceiver.shutdown()
- self.__transceiver = None
- def shutdown(self):
- """Shutdown all sockets and spawned processes."""
- self.killSpawnedServer()
- self.shutdownTransceiver()
- def __rx_NOP(self, msg):
- pass # Nothing
- def handle_EXCEPTION(self, exception):
- raise exception
- def __rx_EXCEPTION(self, msg):
- self.handle_EXCEPTION(msg.exception)
- def __rx_PING(self, msg):
- self.__send(AwlSimMessage_PONG())
- def handle_PONG(self):
- printInfo("AwlSimClient: Received PONG")
- def __rx_PONG(self, msg):
- self.handle_PONG()
- def handle_AWLSRC(self, awlSource):
- pass # Don't do anything by default
- def __rx_AWLSRC(self, msg):
- self.handle_AWLSRC(msg.source)
- def handle_SYMTABSRC(self, symTabSource):
- pass # Don't do anything by default
- def __rx_SYMTABSRC(self, msg):
- self.handle_SYMTABSRC(msg.source)
- def handle_CPUDUMP(self, dumpText):
- pass # Don't do anything by default
- def __rx_CPUDUMP(self, msg):
- self.handle_CPUDUMP(msg.dumpText)
- def __rx_MAINTREQ(self, msg):
- raise msg.maintRequest
- def handle_MEMORY(self, memAreas):
- pass # Don't do anything by default
- def __rx_MEMORY(self, msg):
- self.handle_MEMORY(msg.memAreas)
- if msg.flags & msg.FLG_SYNC:
- # The server should never send us a synchronous
- # memory image. So just output an error message.
- printError("Received synchronous memory request")
- def handle_INSNSTATE(self, msg):
- pass # Don't do anything by default
- def __rx_INSNSTATE(self, msg):
- self.handle_INSNSTATE(msg)
- def handle_IDENTS(self, msg):
- pass # Don't do anything by default
- def __rx_IDENTS(self, msg):
- self.handle_IDENTS(msg)
- def handle_BLOCKINFO(self, msg):
- pass # Don't do anything by default
- def __rx_BLOCKINFO(self, msg):
- self.handle_BLOCKINFO(msg)
- __msgRxHandlers = {
- AwlSimMessage.MSG_ID_REPLY : __rx_NOP,
- AwlSimMessage.MSG_ID_EXCEPTION : __rx_EXCEPTION,
- AwlSimMessage.MSG_ID_MAINTREQ : __rx_MAINTREQ,
- AwlSimMessage.MSG_ID_PING : __rx_PING,
- AwlSimMessage.MSG_ID_PONG : __rx_PONG,
- AwlSimMessage.MSG_ID_AWLSRC : __rx_AWLSRC,
- AwlSimMessage.MSG_ID_SYMTABSRC : __rx_SYMTABSRC,
- AwlSimMessage.MSG_ID_IDENTS : __rx_IDENTS,
- AwlSimMessage.MSG_ID_BLOCKINFO : __rx_BLOCKINFO,
- AwlSimMessage.MSG_ID_CPUSPECS : __rx_NOP,
- AwlSimMessage.MSG_ID_RUNSTATE : __rx_NOP,
- AwlSimMessage.MSG_ID_CPUDUMP : __rx_CPUDUMP,
- AwlSimMessage.MSG_ID_MEMORY : __rx_MEMORY,
- AwlSimMessage.MSG_ID_INSNSTATE : __rx_INSNSTATE,
- }
- # Main message processing
- # timeout: None -> Blocking. Block until packet is received.
- # 0 -> No timeout (= Nonblocking). Return immediately.
- # x -> Timeout, in seconds.
- def processMessages(self, timeout=None):
- self.lastRxMsg = None
- if not self.__transceiver:
- return False
- try:
- msg = self.__transceiver.receive(timeout)
- except TransferError as e:
- if e.reason == e.REASON_BLOCKING:
- return False
- raise AwlSimError("AwlSimClient: "
- "I/O error in connection to server '%s':\n"
- "%s (errno = %s)" %\
- (self.__transceiver.peerInfoString, str(e), str(e.errno)))
- except TransferError as e:
- raise AwlSimError("AwlSimClient: "
- "Connection to server '%s' died. "
- "Failed to receive message." %\
- self.__transceiver.peerInfoString)
- if not msg:
- return False
- self.lastRxMsg = msg
- try:
- handler = self.__msgRxHandlers[msg.msgId]
- except KeyError:
- raise AwlSimError("AwlSimClient: Received unsupported "
- "message 0x%02X" % msg.msgId)
- handler(self, msg)
- return True
- def sleep(self, seconds):
- time.sleep(seconds)
- def __send(self, txMsg):
- try:
- self.__transceiver.send(txMsg)
- except TransferError as e:
- raise AwlSimError("AwlSimClient: "
- "Send error in connection to server '%s':\n"
- "%s (errno = %s)" %\
- (self.__transceiver.peerInfoString,
- str(e), str(e.errno)))
- def __sendAndWait(self, txMsg, checkRxMsg,
- waitTimeout=None,
- ignoreMaintenanceRequests=False):
- self.__send(txMsg)
- now = monotonic_time()
- end = now + (self.__defaultTimeout if waitTimeout is None\
- else waitTimeout)
- while now < end:
- try:
- if self.processMessages(0.1):
- rxMsg = self.lastRxMsg
- if rxMsg is not None and\
- checkRxMsg(rxMsg):
- return rxMsg
- except MaintenanceRequest as e:
- if not ignoreMaintenanceRequests:
- raise e
- now = monotonic_time()
- raise AwlSimError("AwlSimClient: Timeout waiting for server reply.")
- def __sendAndWaitFor_REPLY(self, msg, timeout=None,
- ignoreMaintenanceRequests=False):
- def checkRxMsg(rxMsg):
- return rxMsg.msgId == AwlSimMessage.MSG_ID_REPLY and\
- rxMsg.inReplyToId == msg.msgId and\
- rxMsg.inReplyToSeq == msg.seq
- return self.__sendAndWait(msg, checkRxMsg, timeout,
- ignoreMaintenanceRequests).status
- def reset(self):
- if not self.__transceiver:
- return False
- msg = AwlSimMessage_RESET()
- status = self.__sendAndWaitFor_REPLY(msg,
- ignoreMaintenanceRequests = True)
- if status != AwlSimMessage_REPLY.STAT_OK:
- raise AwlSimError("AwlSimClient: Failed to reset CPU")
- return True
- def setRunState(self, run=True):
- if not self.__transceiver:
- return False
- if run:
- runState = AwlSimMessage_RUNSTATE.STATE_RUN
- else:
- runState = AwlSimMessage_RUNSTATE.STATE_STOP
- msg = AwlSimMessage_RUNSTATE(runState)
- status = self.__sendAndWaitFor_REPLY(msg,
- ignoreMaintenanceRequests = True)
- if status != AwlSimMessage_REPLY.STAT_OK:
- raise AwlSimError("AwlSimClient: Failed to set run state")
- return True
- def getRunState(self):
- if not self.__transceiver:
- return False
- msg = AwlSimMessage_GET_RUNSTATE()
- rxMsg = self.__sendAndWait(msg,
- lambda rxMsg: rxMsg.msgId == AwlSimMessage.MSG_ID_RUNSTATE)
- if rxMsg.runState == AwlSimMessage_RUNSTATE.STATE_RUN:
- return True
- return False
- def getAwlSource(self, identHash, sync=True):
- if not self.__transceiver:
- return False
- msg = AwlSimMessage_GET_AWLSRC(identHash)
- if sync:
- rxMsg = self.__sendAndWait(msg,
- lambda rxMsg: rxMsg.msgId == AwlSimMessage.MSG_ID_AWLSRC)
- return rxMsg.source
- else:
- self.__send(msg)
- return True
- def loadAwlSource(self, awlSource):
- if not self.__transceiver:
- return False
- msg = AwlSimMessage_AWLSRC(awlSource)
- self.__transceiver.txCork(True)
- try:
- status = self.__sendAndWaitFor_REPLY(msg, 10.0)
- finally:
- self.__transceiver.txCork(False)
- if status != AwlSimMessage_REPLY.STAT_OK:
- raise AwlSimError("AwlSimClient: Failed to AWL source")
- return True
- def getSymTabSource(self, identHash, sync=True):
- if not self.__transceiver:
- return False
- msg = AwlSimMessage_GET_SYMTABSRC(identHash)
- if sync:
- rxMsg = self.__sendAndWait(msg,
- lambda rxMsg: rxMsg.msgId == AwlSimMessage.MSG_ID_SYMTABSRC)
- return rxMsg.source
- else:
- self.__send(msg)
- return True
- def loadSymTabSource(self, symTabSource):
- if not self.__transceiver:
- return False
- msg = AwlSimMessage_SYMTABSRC(symTabSource)
- self.__transceiver.txCork(True)
- try:
- status = self.__sendAndWaitFor_REPLY(msg)
- finally:
- self.__transceiver.txCork(False)
- if status != AwlSimMessage_REPLY.STAT_OK:
- raise AwlSimError("AwlSimClient: Failed to load symbol table source")
- return True
- def loadLibraryBlock(self, libSelection):
- if not self.__transceiver:
- return False
- msg = AwlSimMessage_LIBSEL(libSelection)
- status = self.__sendAndWaitFor_REPLY(msg)
- if status != AwlSimMessage_REPLY.STAT_OK:
- raise AwlSimError("AwlSimClient: Failed to load library block selection")
- return True
- def loadHardwareModule(self, hwmodDesc):
- if not self.__transceiver:
- return False
- msg = AwlSimMessage_HWMOD(hwmodDesc)
- status = self.__sendAndWaitFor_REPLY(msg)
- if status != AwlSimMessage_REPLY.STAT_OK:
- raise AwlSimError("AwlSimClient: Failed to load hardware module")
- return True
- def removeSource(self, identHash):
- if not self.__transceiver:
- return False
- msg = AwlSimMessage_REMOVESRC(identHash)
- status = self.__sendAndWaitFor_REPLY(msg)
- if status != AwlSimMessage_REPLY.STAT_OK:
- raise AwlSimError("AwlSimClient: Failed to remove source")
- return True
- def removeBlock(self, blockInfo):
- if not self.__transceiver:
- return False
- msg = AwlSimMessage_REMOVEBLK(blockInfo)
- status = self.__sendAndWaitFor_REPLY(msg)
- if status != AwlSimMessage_REPLY.STAT_OK:
- raise AwlSimError("AwlSimClient: Failed to remove block")
- return True
- # Request the (source) ident hashes from the CPU.
- # This method is asynchronous.
- # The idents are returned via handle_IDENTS()
- def requestIdents(self, reqAwlSources = False,
- reqSymTabSources = False,
- reqHwModules = False,
- reqLibSelections = False):
- if not self.__transceiver:
- return False
- self.__send(AwlSimMessage_GET_IDENTS(
- (AwlSimMessage_GET_IDENTS.GET_AWLSRCS if reqAwlSources else 0) |\
- (AwlSimMessage_GET_IDENTS.GET_SYMTABSRCS if reqSymTabSources else 0) |\
- (AwlSimMessage_GET_IDENTS.GET_HWMODS if reqHwModules else 0) |\
- (AwlSimMessage_GET_IDENTS.GET_LIBSELS if reqLibSelections else 0)))
- return True
- # Request the compiled block info from the CPU.
- # This method is asynchronous.
- # The idents are returned via handle_BLOCKINFO()
- def requestBlockInfo(self, reqOBInfo = False,
- reqFCInfo = False,
- reqFBInfo = False,
- reqDBInfo = False):
- if not self.__transceiver:
- return False
- self.__send(AwlSimMessage_GET_BLOCKINFO(
- (AwlSimMessage_GET_BLOCKINFO.GET_OB_INFO if reqOBInfo else 0) |\
- (AwlSimMessage_GET_BLOCKINFO.GET_FC_INFO if reqFCInfo else 0) |\
- (AwlSimMessage_GET_BLOCKINFO.GET_FB_INFO if reqFBInfo else 0) |\
- (AwlSimMessage_GET_BLOCKINFO.GET_DB_INFO if reqDBInfo else 0)))
- return True
- def __setOption(self, name, value, sync=True):
- if not self.__transceiver:
- return False
- msg = AwlSimMessage_OPT(name, str(value))
- if sync:
- status = self.__sendAndWaitFor_REPLY(msg)
- if status != AwlSimMessage_REPLY.STAT_OK:
- raise AwlSimError("AwlSimClient: Failed to set option '%s'" % name)
- else:
- self.__send(msg)
- return True
- def setLoglevel(self, level=Logging.LOG_INFO):
- Logging.setLoglevel(level)
- return self.__setOption("loglevel", int(level))
- def enableOBTempPresets(self, enable=True):
- return self.__setOption("ob_temp_presets", int(bool(enable)))
- def enableExtendedInsns(self, enable=True):
- return self.__setOption("extended_insns", int(bool(enable)))
- def setPeriodicDumpInterval(self, interval=0):
- return self.__setOption("periodic_dump_int", int(interval))
- def setCycleTimeLimit(self, seconds=5.0):
- return self.__setOption("cycle_time_limit", float(seconds))
- def setRunTimeLimit(self, seconds=0.0):
- return self.__setOption("runtime_limit", float(seconds))
- # Set instruction state dumping.
- # fromLine, toLine is the range of AWL line numbers for which
- # dumping is enabled.
- def setInsnStateDump(self, enable=True,
- sourceId=0, fromLine=1, toLine=0x7FFFFFFF,
- sync=True):
- if not self.__transceiver:
- return None
- msg = AwlSimMessage_INSNSTATE_CONFIG(
- flags = 0,
- sourceId = sourceId,
- fromLine = fromLine,
- toLine = toLine)
- if enable:
- msg.flags |= msg.FLG_CLEAR
- else:
- msg.flags |= msg.FLG_CLEAR_ONLY
- if sync:
- msg.flags |= msg.FLG_SYNC
- status = self.__sendAndWaitFor_REPLY(msg)
- if status != AwlSimMessage_REPLY.STAT_OK:
- raise AwlSimError("AwlSimClient: Failed to set insn state dump")
- else:
- self.__send(msg)
- def getCpuSpecs(self):
- if not self.__transceiver:
- return None
- msg = AwlSimMessage_GET_CPUSPECS()
- rxMsg = self.__sendAndWait(msg,
- lambda rxMsg: rxMsg.msgId == AwlSimMessage.MSG_ID_CPUSPECS)
- return rxMsg.cpuspecs
- def setCpuSpecs(self, cpuspecs):
- if not self.__transceiver:
- return False
- msg = AwlSimMessage_CPUSPECS(cpuspecs)
- status = self.__sendAndWaitFor_REPLY(msg)
- if status != AwlSimMessage_REPLY.STAT_OK:
- raise AwlSimError("AwlSimClient: Failed to set cpuspecs")
- return True
- # Set the memory areas we are interested in receiving
- # dumps for, in the server.
- # memAreas is a list of MemoryArea instances.
- # The repetitionPeriod tells whether to
- # - only run the request once (repetitionneriod < 0.0)
- # - repeat every n'th second (repetitionFactor = n)
- # If sync is true, wait for a reply from the server.
- def setMemoryReadRequests(self, memAreas, repetitionPeriod = -1.0,
- sync = False):
- if not self.__transceiver:
- return False
- msg = AwlSimMessage_REQ_MEMORY(0, repetitionPeriod, memAreas)
- if sync:
- msg.flags |= msg.FLG_SYNC
- status = self.__sendAndWaitFor_REPLY(msg)
- if status != AwlSimMessage_REPLY.STAT_OK:
- raise AwlSimError("AwlSimClient: Failed to set "
- "memory read reqs")
- else:
- self.__send(msg)
- return True
- # Write memory areas in the server.
- # memAreas is a list of MemoryAreaData instances.
- # If sync is true, wait for a reply from the server.
- def writeMemory(self, memAreas, sync=False):
- if not self.__transceiver:
- return False
- msg = AwlSimMessage_MEMORY(0, memAreas)
- if sync:
- msg.flags |= msg.FLG_SYNC
- status = self.__sendAndWaitFor_REPLY(msg)
- if status != AwlSimMessage_REPLY.STAT_OK:
- raise AwlSimError("AwlSimClient: Failed to write "
- "to memory")
- else:
- self.__send(msg)
- return True
|