awlsim-test 16 KB

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