awlsim-test 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. #
  4. # AWL simulator - Commandline testing interface
  5. #
  6. # Copyright 2012-2016 Michael Buesch <m@bues.ch>
  7. #
  8. # This program is free software; you can redistribute it and/or modify
  9. # it under the terms of the GNU General Public License as published by
  10. # the Free Software Foundation; either version 2 of the License, or
  11. # (at your option) any later version.
  12. #
  13. # This program is distributed in the hope that it will be useful,
  14. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. # GNU General Public License for more details.
  17. #
  18. # You should have received a copy of the GNU General Public License along
  19. # with this program; if not, write to the Free Software Foundation, Inc.,
  20. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  21. #
  22. from __future__ import division, absolute_import, print_function, unicode_literals
  23. import sys
  24. import os
  25. import getopt
  26. import traceback
  27. import signal
  28. from awlsim_loader.common import *
  29. from awlsim_loader.core import *
  30. from awlsim_loader.coreclient import *
  31. from awlsim_loader.fupcompiler import *
  32. import awlsim_loader.cython_helper as cython_helper
  33. class TestAwlSimClient(AwlSimClient):
  34. def handle_CPUDUMP(self, dumpText):
  35. emitCpuDump(dumpText)
  36. class ConsoleSSHTunnel(SSHTunnel):
  37. def sshMessage(self, message, isDebug):
  38. if opt_loglevel > Logging.LOG_INFO:
  39. isDebug = False
  40. super(ConsoleSSHTunnel, self).sshMessage(message, isDebug)
  41. def usage():
  42. print("awlsim version %s" % VERSION_STRING)
  43. print("")
  44. print("Usage: awlsim-test [OPTIONS] <AWL-source or awlsim-project file>")
  45. print("")
  46. print("Options:")
  47. print(" -Y|--cycle-limit SEC Cycle time limit, in seconds (default 5.0)")
  48. print(" -M|--max-runtime SEC CPU will be stopped after SEC seconds (default: off)")
  49. print(" -2|--twoaccu Force 2-accu mode")
  50. print(" -4|--fouraccu Force 4-accu mode")
  51. print(" -D|--no-cpu-dump Do not show CPU status while running")
  52. print(" -x|--extended-insns Enable extended instructions")
  53. print(" -t|--obtemp 1/0 Enable/disable writing of OB-temp variables (Default: off)")
  54. print(" -T|--clock-mem ADDR Force clock memory address (Default: off)")
  55. print(" -m|--mnemonics auto Force mnemonics type: en, de, auto")
  56. print(" -P|--profile 0 Set profiling level (Default: 0)")
  57. print(" -L|--loglevel LVL Set the log level:")
  58. print(" 0: Log nothing")
  59. print(" 1: Log errors")
  60. print(" 2: Log errors and warnings")
  61. print(" 3: Log errors, warnings and info messages (default)")
  62. print(" 4: Verbose logging")
  63. print(" 5: Extremely verbose logging")
  64. print("")
  65. print("Server backend related options:")
  66. print(" -c|--connect Connect to server backend")
  67. print(" -C|--connect-to IP:PORT Connect to server backend")
  68. print(" -b|--spawn-backend Spawn a new backend server and connect to it")
  69. if not isWinStandalone:
  70. print(" -i|--interpreter EXE Set the backend interpreter executable")
  71. print("")
  72. print("Loading hardware modules:")
  73. print(" -H|--hardware NAME:PARAM=VAL:PARAM=VAL...")
  74. print("Print module information:")
  75. print(" -I|--hardware-info NAME")
  76. print("")
  77. print(" Where NAME is the name of the hardware module.")
  78. print(" PARAM=VAL are optional hardware specific parameters.")
  79. print("")
  80. print("Other options:")
  81. print(" --list-sfc Print a list of all supported SFCs")
  82. print(" --list-sfc-verbose Verbose SFC list")
  83. print(" --list-sfb Print a list of all supported SFBs")
  84. print(" --list-sfb-verbose Verbose SFB list")
  85. def printSysblockInfo(blockTable, prefix, withExtended, withInterface):
  86. for block in sorted(dictValues(blockTable),
  87. key = lambda b: b.name[0]):
  88. if block.broken:
  89. continue
  90. number, name, desc = block.name
  91. if number < 0 and not withExtended:
  92. continue
  93. if desc:
  94. desc = " (%s)" % desc
  95. else:
  96. desc = ""
  97. print(" %s %d \"%s\"%s" % (prefix, number, name, desc))
  98. if withInterface:
  99. for ftype in (BlockInterfaceField.FTYPE_IN,
  100. BlockInterfaceField.FTYPE_OUT,
  101. BlockInterfaceField.FTYPE_INOUT):
  102. try:
  103. fields = block.interfaceFields[ftype]
  104. except KeyError:
  105. continue
  106. for field in fields:
  107. field.fieldType = ftype
  108. print(" %s" % str(field))
  109. def writeStdout(message):
  110. if Logging.loglevel >= Logging.LOG_INFO:
  111. sys.stdout.write(message)
  112. sys.stdout.flush()
  113. nextScreenUpdate = 0.0
  114. lastDump = ""
  115. def clearConsole():
  116. # Make cursor visible, clear console and
  117. # move cursor to homeposition.
  118. if osIsPosix:
  119. writeStdout("\x1B[?25h\x1B[2J\x1B[H")
  120. elif osIsWindows:
  121. os.system("cls")
  122. def emitCpuDump(dump):
  123. # Pad lines
  124. dump = '\n'.join(line + (78 - len(line)) * ' ' + '|'
  125. for line in dump.splitlines())
  126. global lastDump
  127. lastDump = dump
  128. if osIsPosix:
  129. writeStdout("\x1B[H" + dump)
  130. else:
  131. clearConsole()
  132. writeStdout(dump)
  133. def cpuDumpCallback(cpu):
  134. global nextScreenUpdate
  135. if cpu.now >= nextScreenUpdate:
  136. nextScreenUpdate = cpu.now + 0.1
  137. emitCpuDump(str(cpu))
  138. def assignCpuSpecs(cpuSpecs, projectCpuSpecs):
  139. cpuSpecs.assignFrom(projectCpuSpecs)
  140. if opt_mnemonics is not None:
  141. cpuSpecs.setConfiguredMnemonics(opt_mnemonics)
  142. if opt_nrAccus is not None:
  143. cpuSpecs.setNrAccus(opt_nrAccus)
  144. if opt_clockMem is not None:
  145. cpuSpecs.setClockMemByte(opt_clockMem)
  146. def run(inputFile):
  147. s = None
  148. try:
  149. if cython_helper.shouldUseCython():
  150. writeStdout("*** Using accelerated CYTHON core "
  151. "(AWLSIM_CYTHON environment variable is set)\n")
  152. project = Project.fromProjectOrRawAwlFile(inputFile)
  153. writeStdout("Parsing code...\n")
  154. generatedAwlSrcs = []
  155. # Get mnemonics type
  156. mnemonics = project.getCpuSpecs().getConfiguredMnemonics()
  157. if opt_mnemonics is not None:
  158. mnemonics = opt_mnemonics
  159. # Parse FUP sources
  160. for fupSrc in project.getFupSources():
  161. generatedAwlSrcs.append(FupCompiler().compile(
  162. fupSource=fupSrc, mnemonics=mnemonics))
  163. # Parse KOP sources
  164. for kopSrc in project.getKopSources():
  165. pass#TODO
  166. # Parse AWL sources
  167. parseTrees = []
  168. for awlSrc in itertools.chain(project.getAwlSources(),
  169. generatedAwlSrcs):
  170. p = AwlParser()
  171. p.parseSource(awlSrc)
  172. parseTrees.append(p.getParseTree())
  173. # Parse symbol tables
  174. symTables = []
  175. for symTabSrc in project.getSymTabSources():
  176. tab = SymTabParser.parseSource(symTabSrc,
  177. autodetectFormat = True,
  178. mnemonics = mnemonics)
  179. symTables.append(tab)
  180. writeStdout("Initializing core...\n")
  181. s = AwlSim(profileLevel = opt_profile)
  182. s.reset()
  183. # Load hardware modules
  184. def loadMod(name, parameters):
  185. writeStdout("Loading hardware module '%s'...\n" % name)
  186. hwClass = s.loadHardwareModule(name)
  187. s.registerHardwareClass(hwClass = hwClass,
  188. parameters = parameters)
  189. for modDesc in project.getHwmodSettings().getLoadedModules():
  190. loadMod(modDesc.getModuleName(),
  191. modDesc.getParameters())
  192. for name, parameters in opt_hwmods:
  193. loadMod(name, parameters)
  194. # Configure the CPU
  195. cpu = s.getCPU()
  196. assignCpuSpecs(cpu.getSpecs(), project.getCpuSpecs())
  197. cpu.enableObTempPresets(project.getObTempPresetsEn() or opt_obtemp)
  198. cpu.enableExtendedInsns(project.getExtInsnsEn() or opt_extInsns)
  199. if not opt_noCpuDump and opt_loglevel >= Logging.LOG_INFO:
  200. cpu.setBlockExitCallback(cpuDumpCallback, cpu)
  201. cpu.setCycleTimeLimit(opt_cycletime)
  202. cpu.setRunTimeLimit(opt_maxRuntime)
  203. # Download the program
  204. writeStdout("Initializing CPU...\n")
  205. for symTable in symTables:
  206. s.loadSymbolTable(symTable)
  207. for libSel in project.getLibSelections():
  208. s.loadLibraryBlock(libSel)
  209. for parseTree in parseTrees:
  210. s.load(parseTree)
  211. # Run the program
  212. s.startup()
  213. writeStdout("[Initialization finished - CPU is executing user code]\n")
  214. try:
  215. if not opt_noCpuDump:
  216. clearConsole()
  217. while 1:
  218. s.runCycle()
  219. finally:
  220. if not opt_noCpuDump and opt_loglevel >= Logging.LOG_INFO:
  221. clearConsole()
  222. writeStdout(lastDump + '\n')
  223. except (AwlParserError, AwlSimError) as e:
  224. printError(e.getReport())
  225. return ExitCodes.EXIT_ERR_SIM
  226. except KeyboardInterrupt as e:
  227. pass
  228. except MaintenanceRequest as e:
  229. if e.requestType in (MaintenanceRequest.TYPE_SHUTDOWN,
  230. MaintenanceRequest.TYPE_STOP,
  231. MaintenanceRequest.TYPE_RTTIMEOUT):
  232. writeStdout("Shutting down, as requested (%s)...\n" % str(e))
  233. else:
  234. writeStdout("Received unknown maintenance request "
  235. "(%d: %s)...\n" % (e.requestType, str(e)))
  236. finally:
  237. if s:
  238. s.unregisterAllHardware()
  239. ps = s.getProfileStats()
  240. if ps:
  241. writeStdout("\n\nProfile stats (level %d) follow:\n" %\
  242. opt_profile)
  243. writeStdout(ps)
  244. writeStdout("\n")
  245. return ExitCodes.EXIT_OK
  246. def runWithServerBackend(inputFile):
  247. client = None
  248. tunnel = None
  249. try:
  250. if cython_helper.shouldUseCython():
  251. printError("The accelerated CYTHON core currently is incompatible "
  252. "with the backend server. Please remove the "
  253. "AWLSIM_CYTHON environment variable.")
  254. return ExitCodes.EXIT_ERR_INTERP
  255. project = Project.fromProjectOrRawAwlFile(inputFile)
  256. linkSettings = project.getCoreLinkSettings()
  257. if opt_spawnBackend:
  258. host = AwlSimServer.DEFAULT_HOST
  259. port = AwlSimServer.DEFAULT_PORT
  260. else:
  261. host = linkSettings.getConnectHost()
  262. port = linkSettings.getConnectPort()
  263. if opt_connectTo:
  264. host, port = opt_connectTo
  265. # Establish SSH tunnel, if requested.
  266. if linkSettings.getTunnel() == linkSettings.TUNNEL_SSH:
  267. writeStdout("Establishing SSH tunnel...\n")
  268. localPort = linkSettings.getTunnelLocalPort()
  269. if localPort == linkSettings.TUNNEL_LOCPORT_AUTO:
  270. localPort = None
  271. tunnel = ConsoleSSHTunnel(
  272. remoteHost = host,
  273. remotePort = port,
  274. localPort = localPort,
  275. sshUser = linkSettings.getSSHUser(),
  276. sshPort = linkSettings.getSSHPort(),
  277. sshExecutable = linkSettings.getSSHExecutable(),
  278. )
  279. host, port = tunnel.connect()
  280. # Connect to the server
  281. client = TestAwlSimClient()
  282. if opt_spawnBackend:
  283. client.spawnServer(interpreter = opt_interpreter,
  284. listenHost = host,
  285. listenPort = port)
  286. writeStdout("Connecting to core server...\n")
  287. client.connectToServer(host = host, port = port)
  288. writeStdout("Initializing core...\n")
  289. client.setLoglevel(opt_loglevel)
  290. client.setRunState(False)
  291. client.reset()
  292. # Load hardware modules
  293. client.loadHardwareModules(project.getHwmodSettings().getLoadedModules())
  294. for name, parameters in opt_hwmods:
  295. client.loadHardwareModule(HwmodDescriptor(name, parameters))
  296. # Configure the core
  297. if opt_noCpuDump:
  298. client.setPeriodicDumpInterval(0)
  299. else:
  300. client.setPeriodicDumpInterval(300)
  301. client.enableOBTempPresets(project.getObTempPresetsEn() or opt_obtemp)
  302. client.enableExtendedInsns(project.getExtInsnsEn() or opt_extInsns)
  303. client.setCycleTimeLimit(opt_cycletime)
  304. client.setRunTimeLimit(opt_maxRuntime)
  305. specs = client.getCpuSpecs()
  306. assignCpuSpecs(specs, project.getCpuSpecs())
  307. client.setCpuSpecs(specs)
  308. # Fire up the core
  309. writeStdout("Initializing CPU...\n")
  310. client.loadProject(project, loadCpuSpecs=False,
  311. loadTempPresets=False,
  312. loadExtInsns=False,
  313. loadHwMods=False)
  314. client.setRunState(True)
  315. # Run the client-side event loop
  316. writeStdout("[Initialization finished - Remote-CPU is executing user code]\n")
  317. try:
  318. if not opt_noCpuDump:
  319. clearConsole()
  320. while True:
  321. client.processMessages(None)
  322. finally:
  323. if not opt_noCpuDump and opt_loglevel >= Logging.LOG_INFO:
  324. clearConsole()
  325. writeStdout(lastDump + '\n')
  326. except AwlSimError as e:
  327. printError(e.getReport())
  328. return ExitCodes.EXIT_ERR_SIM
  329. except MaintenanceRequest as e:
  330. if e.requestType in (MaintenanceRequest.TYPE_SHUTDOWN,
  331. MaintenanceRequest.TYPE_STOP,
  332. MaintenanceRequest.TYPE_RTTIMEOUT):
  333. writeStdout("Shutting down, as requested (%s)...\n" % str(e))
  334. else:
  335. writeStdout("Received unknown maintenance request "
  336. "(%d: %s)...\n" % (e.requestType, str(e)))
  337. except KeyboardInterrupt as e:
  338. pass
  339. finally:
  340. if tunnel:
  341. tunnel.shutdown()
  342. if client:
  343. client.shutdown()
  344. return ExitCodes.EXIT_OK
  345. def __signalHandler(sig, frame):
  346. printInfo("Received signal %d" % sig)
  347. if sig == signal.SIGTERM:
  348. # Raise SIGINT. It will shut down everything.
  349. os.kill(os.getpid(), signal.SIGINT)
  350. def main():
  351. global opt_cycletime
  352. global opt_maxRuntime
  353. global opt_noCpuDump
  354. global opt_nrAccus
  355. global opt_extInsns
  356. global opt_obtemp
  357. global opt_clockMem
  358. global opt_mnemonics
  359. global opt_hwmods
  360. global opt_hwinfos
  361. global opt_profile
  362. global opt_loglevel
  363. global opt_connect
  364. global opt_connectTo
  365. global opt_spawnBackend
  366. global opt_interpreter
  367. opt_cycletime = 5.0
  368. opt_maxRuntime = -1.0
  369. opt_noCpuDump = False
  370. opt_nrAccus = None
  371. opt_extInsns = False
  372. opt_obtemp = False
  373. opt_clockMem = None
  374. opt_mnemonics = None
  375. opt_hwmods = []
  376. opt_hwinfos = []
  377. opt_profile = 0
  378. opt_loglevel = Logging.LOG_INFO
  379. opt_connect = None
  380. opt_connectTo = False
  381. opt_spawnBackend = False
  382. opt_interpreter = None
  383. try:
  384. (opts, args) = getopt.getopt(sys.argv[1:],
  385. "hY:M:24qDxt:T:m:H:I:P:L:cC:bi:",
  386. [ "help", "cycle-time=", "max-runtime=", "twoaccu", "fouraccu",
  387. "quiet", "no-cpu-dump", "extended-insns",
  388. "obtemp=", "clock-mem=", "mnemonics=",
  389. "hardware=", "hardware-info=", "profile=",
  390. "loglevel=",
  391. "connect", "connect-to=", "spawn-backend", "interpreter=",
  392. "list-sfc", "list-sfc-verbose",
  393. "list-sfb", "list-sfb-verbose", ])
  394. except getopt.GetoptError as e:
  395. printError(str(e))
  396. usage()
  397. return ExitCodes.EXIT_ERR_CMDLINE
  398. for (o, v) in opts:
  399. if o in ("-h", "--help"):
  400. usage()
  401. return ExitCodes.EXIT_OK
  402. if o in ("-Y", "--cycle-time"):
  403. try:
  404. opt_cycletime = float(v)
  405. except ValueError:
  406. printError("-Y|--cycle-time: Invalid time format")
  407. sys.exit(1)
  408. if o in ("-M", "--max-runtime"):
  409. try:
  410. opt_maxRuntime = float(v)
  411. except ValueError:
  412. printError("-M|--max-runtime: Invalid time format")
  413. sys.exit(1)
  414. if o in ("-2", "--twoaccu"):
  415. opt_nrAccus = 2
  416. if o in ("-4", "--fouraccu"):
  417. opt_nrAccus = 4
  418. if o in ("-D", "--no-cpu-dump"):
  419. opt_noCpuDump = True
  420. if o in ("-x", "--extended-insns"):
  421. opt_extInsns = True
  422. if o in ("-t", "--obtemp"):
  423. opt_obtemp = str2bool(v)
  424. if o in ("-T", "--clock-mem"):
  425. try:
  426. opt_clockMem = int(v)
  427. if opt_clockMem < -1 or opt_clockMem > 0xFFFF:
  428. raise ValueError
  429. except ValueError:
  430. printError("-T|--clock-mem: Invalid byte address")
  431. if o in ("-m", "--mnemonics"):
  432. opt_mnemonics = v.lower()
  433. if opt_mnemonics not in ("en", "de", "auto"):
  434. printError("-m|--mnemonics: Invalid mnemonics type")
  435. sys.exit(1)
  436. if o in ("-H", "--hardware"):
  437. try:
  438. v = v.split(':')
  439. if not v:
  440. raise ValueError
  441. name = v[0]
  442. params = {}
  443. for pstr in v[1:]:
  444. if not pstr:
  445. continue
  446. i = pstr.find('=')
  447. if i < 0:
  448. raise ValueError
  449. pname = pstr[:i]
  450. pval = pstr[i+1:]
  451. if not pname or not pval:
  452. raise ValueError
  453. params[pname] = pval
  454. opt_hwmods.append( (name, params) )
  455. except (ValueError, IndexError) as e:
  456. printError("-H|--hardware: Invalid module name or parameters")
  457. sys.exit(1)
  458. if o in ("-I", "--hardware-info"):
  459. opt_hwinfos.append(v.split(':')[0])
  460. if o in ("-P", "--profile"):
  461. try:
  462. opt_profile = int(v)
  463. except ValueError:
  464. printError("-P|--profile: Invalid profile level")
  465. if o in ("-L", "--loglevel"):
  466. try:
  467. opt_loglevel = int(v)
  468. except ValueError:
  469. printError("-L|--loglevel: Invalid log level")
  470. sys.exit(1)
  471. if o in ("-c", "--connect"):
  472. opt_connect = True
  473. if o in ("-C", "--connect-to"):
  474. try:
  475. idx = v.rfind(":")
  476. if idx <= 0:
  477. raise ValueError
  478. opt_connectTo = (v[:idx], int(v[idx+1:]))
  479. except ValueError:
  480. printError("-c|--connect: Invalid host/port")
  481. sys.exit(1)
  482. if o in ("-b", "--spawn-backend"):
  483. opt_spawnBackend = True
  484. if o in ("-i", "--interpreter"):
  485. if isWinStandalone:
  486. printError("-i|--interpreter not supported on win-standalone")
  487. sys.exit(1)
  488. opt_interpreter = v
  489. if o in ("--list-sfc", "--list-sfc-verbose"):
  490. print("The supported system functions (SFCs) are:")
  491. printSysblockInfo(SFC_table, "SFC", opt_extInsns,
  492. o.endswith("verbose"))
  493. return ExitCodes.EXIT_OK
  494. if o in ("--list-sfb", "--list-sfb-verbose"):
  495. print("The supported system function blocks (SFBs) are:")
  496. printSysblockInfo(SFB_table, "SFB", opt_extInsns,
  497. o.endswith("verbose"))
  498. return ExitCodes.EXIT_OK
  499. if len(args) != 1 and not opt_hwinfos:
  500. usage()
  501. return ExitCodes.EXIT_ERR_CMDLINE
  502. if args:
  503. inputFile = args[0]
  504. Logging.setLoglevel(opt_loglevel)
  505. opt_mnemonics = {
  506. None : None,
  507. "en" : S7CPUSpecs.MNEMONICS_EN,
  508. "de" : S7CPUSpecs.MNEMONICS_DE,
  509. "auto" : S7CPUSpecs.MNEMONICS_AUTO,
  510. }[opt_mnemonics]
  511. try:
  512. if opt_hwinfos:
  513. # Just print the hardware-infos and exit.
  514. for name in opt_hwinfos:
  515. cls = AwlSim.loadHardwareModule(name)
  516. print(cls.getModuleInfo())
  517. return ExitCodes.EXIT_OK
  518. except (AwlParserError, AwlSimError) as e:
  519. printError(e.getReport())
  520. return ExitCodes.EXIT_ERR_SIM
  521. signal.signal(signal.SIGTERM, __signalHandler)
  522. if opt_interpreter and not opt_spawnBackend:
  523. printError("Selected an --interpreter, but no "
  524. "--spawn-backend was requested.")
  525. return ExitCodes.EXIT_ERR_CMDLINE
  526. if opt_spawnBackend or opt_connect or opt_connectTo:
  527. return runWithServerBackend(inputFile)
  528. return run(inputFile)
  529. if __name__ == "__main__":
  530. sys.exit(main())