awlsim-cli 15.4 KB
Newer Older
Michael Büsch's avatar
Michael Büsch committed
1 2 3 4
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# AWL simulator - Commandline interface
5
#
Michael Büsch's avatar
Michael Büsch committed
6
# Copyright 2012-2014 Michael Buesch <[email protected]>
Michael Büsch's avatar
Michael Büsch committed
7
#
8 9 10 11 12 13 14 15 16 17 18 19 20
# 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.
Michael Büsch's avatar
Michael Büsch committed
21 22
#

23 24
from __future__ import division, absolute_import, print_function, unicode_literals

25
import sys
Michael Büsch's avatar
Michael Büsch committed
26
import os
Michael Büsch's avatar
Michael Büsch committed
27
import getopt
28
import traceback
29
import signal
Michael Büsch's avatar
Michael Büsch committed
30

Michael Büsch's avatar
Michael Büsch committed
31
from awlsim.common import *
32
from awlsim.core import *
33
from awlsim.coreclient import *
Michael Büsch's avatar
Michael Büsch committed
34

Michael Büsch's avatar
Michael Büsch committed
35

36 37 38
class TextInterfaceAwlSimClient(AwlSimClient):
	def handle_CPUDUMP(self, dumpText):
		emitCpuDump(dumpText)
Michael Büsch's avatar
Michael Büsch committed
39 40

def usage():
41 42
	printInfo("awlsim version %d.%d" % (VERSION_MAJOR, VERSION_MINOR))
	printInfo("")
Michael Büsch's avatar
Michael Büsch committed
43
	printInfo("%s [OPTIONS] <AWL-source or awlsim-project file>" % sys.argv[0])
44 45
	printInfo("")
	printInfo("Options:")
46
	printInfo(" -C|--cycle-limit SEC  Cycle time limit, in seconds (default 5.0)")
47
	printInfo(" -M|--max-runtime SEC  CPU will be stopped after SEC seconds (default: off)")
48 49
	printInfo(" -2|--twoaccu          Force 2-accu mode")
	printInfo(" -4|--fouraccu         Force 4-accu mode")
50 51
	printInfo(" -D|--no-cpu-dump      Do not show CPU status while running")
	printInfo(" -q|--quiet            Do not show any status messages")
52
	printInfo(" -x|--extended-insns   Enable extended instructions")
53
	printInfo(" -t|--obtemp 1/0       Enable/disable writing of OB-temp variables (Default: off)")
Michael Büsch's avatar
Michael Büsch committed
54
	printInfo(" -T|--clock-mem ADDR   Force clock memory address (Default: off)")
55
	printInfo(" -m|--mnemonics auto   Force mnemonics type: en, de, auto")
Michael Büsch's avatar
Michael Büsch committed
56
	printInfo(" -P|--profile 0        Set profiling level (Default: 0)")
Michael Büsch's avatar
Michael Büsch committed
57
	printInfo("")
58 59
	printInfo("Server backend related options:")
	printInfo(" -c|--connect IP:PORT  Connect to server backend")
60
	printInfo(" -b|--spawn-backend    Spawn a new backend server and connect to it")
61 62
	if not isWinStandalone:
		printInfo(" -i|--interpreter EXE  Set the backend interpreter executable")
63
	printInfo("")
Michael Büsch's avatar
Michael Büsch committed
64 65
	printInfo("Loading hardware modules:")
	printInfo(" -H|--hardware NAME:PARAM=VAL:PARAM=VAL...")
66 67
	printInfo("Print module information:")
	printInfo(" -I|--hardware-info NAME")
Michael Büsch's avatar
Michael Büsch committed
68 69 70
	printInfo("")
	printInfo(" Where NAME is the name of the hardware module.")
	printInfo(" PARAM=VAL are optional hardware specific parameters.")
71 72 73
	printInfo("")
	printInfo("Other options:")
	printInfo(" --list-sfc            Print a list of all supported SFCs")
74
	printInfo(" --list-sfc-verbose    Verbose SFC list")
75
	printInfo(" --list-sfb            Print a list of all supported SFBs")
76
	printInfo(" --list-sfb-verbose    Verbose SFB list")
77

78
def printSysblockInfo(blockTable, prefix, withExtended, withInterface):
79 80
	for block in sorted(blockTable.values(),
			    key = lambda b: b.name[0]):
81 82
		if block.broken:
			continue
83 84 85 86 87 88 89 90
		number, name, desc = block.name
		if number < 0 and not withExtended:
			continue
		if desc:
			desc = "  (%s)" % desc
		else:
			desc = ""
		printInfo("  %s %d  \"%s\"%s" % (prefix, number, name, desc))
91 92 93 94 95 96 97 98 99 100 101
		if withInterface:
			for ftype in (BlockInterfaceField.FTYPE_IN,
				      BlockInterfaceField.FTYPE_OUT,
				      BlockInterfaceField.FTYPE_INOUT):
				try:
					fields = block.interfaceFields[ftype]
				except KeyError:
					continue
				for field in fields:
					field.fieldType = ftype
					printInfo("        %s" % str(field))
Michael Büsch's avatar
Michael Büsch committed
102 103 104 105 106 107

def writeStdout(message):
	if not opt_quiet:
		sys.stdout.write(message)
		sys.stdout.flush()

Michael Büsch's avatar
Michael Büsch committed
108 109 110
nextScreenUpdate = 0.0
lastDump = ""

111 112 113
def clearConsole():
	# Make cursor visible, clear console and
	# move cursor to homeposition.
114 115 116 117
	if osIsPosix:
		writeStdout("\x1B[?25h\x1B[2J\x1B[H")
	elif osIsWindows:
		os.system("cls")
118 119 120

def emitCpuDump(dump):
	# Pad lines
121
	dump = '\n'.join(line + (78 - len(line)) * ' ' + '|'
122 123 124
			 for line in dump.splitlines())
	global lastDump
	lastDump = dump
125 126 127 128 129
	if osIsPosix:
		writeStdout("\x1B[H" + dump)
	else:
		clearConsole()
		writeStdout(dump)
130

131
def cpuDumpCallback(cpu):
Michael Büsch's avatar
Michael Büsch committed
132
	global nextScreenUpdate
133 134 135
	if cpu.now >= nextScreenUpdate:
		nextScreenUpdate = cpu.now + 0.1
		emitCpuDump(str(cpu))
136

Michael Büsch's avatar
Michael Büsch committed
137 138 139
def makeLoglevel():
	return Logging.LOG_ERROR if opt_quiet else Logging.LOG_INFO

140 141 142 143 144 145
def assignCpuSpecs(cpuSpecs, projectCpuSpecs):
	cpuSpecs.assignFrom(projectCpuSpecs)
	if opt_mnemonics is not None:
		cpuSpecs.setConfiguredMnemonics(opt_mnemonics)
	if opt_nrAccus is not None:
		cpuSpecs.setNrAccus(opt_nrAccus)
Michael Büsch's avatar
Michael Büsch committed
146 147
	if opt_clockMem is not None:
		cpuSpecs.setClockMemByte(opt_clockMem)
148

Michael Büsch's avatar
Michael Büsch committed
149 150 151 152 153
def readInputFile(inputFile):
	if Project.fileIsProject(inputFile):
		project = Project.fromFile(inputFile)
	else:
		# make a fake project
154
		awlSrc = AwlSource.fromFile(name = inputFile,
155
					    filepath = inputFile)
Michael Büsch's avatar
Michael Büsch committed
156
		project = Project(projectFile = None,
157
				  awlSources = [ awlSrc, ])
Michael Büsch's avatar
Michael Büsch committed
158 159 160
	return project

def run(inputFile):
161 162
	s = None
	try:
163 164
		import awlsim.cython_helper
		if awlsim.cython_helper.shouldUseCython():
165 166 167
			writeStdout("*** Using accelerated CYTHON core "
				    "(AWLSIMCYTHON environment variable is set)\n")

Michael Büsch's avatar
Michael Büsch committed
168 169
		project = readInputFile(inputFile)

170
		writeStdout("Parsing code...\n")
171
		parseTrees = []
172
		for awlSrc in project.getAwlSources():
173
			p = AwlParser()
174
			p.parseSource(awlSrc)
175
			parseTrees.append(p.getParseTree())
Michael Büsch's avatar
Michael Büsch committed
176
		symTables = []
177
		for symTabSrc in project.getSymTabSources():
178 179 180
			mnemonics = project.cpuSpecs.getConfiguredMnemonics()
			if opt_mnemonics is not None:
				mnemonics = opt_mnemonics
181 182 183
			tab = SymTabParser.parseSource(symTabSrc,
						       autodetectFormat = True,
						       mnemonics = mnemonics)
Michael Büsch's avatar
Michael Büsch committed
184
			symTables.append(tab)
185

Michael Büsch's avatar
Michael Büsch committed
186
		writeStdout("Initializing core...\n")
187
		s = AwlSim(profileLevel = opt_profile)
Michael Büsch's avatar
Michael Büsch committed
188
		s.reset()
189 190 191 192 193 194 195 196 197

		# Load hardware modules
		for name, parameters in opt_hwmods:
			writeStdout("Loading hardware module '%s'...\n" % name)
			hwClass = s.loadHardwareModule(name)
			s.registerHardwareClass(hwClass = hwClass,
						parameters = parameters)

		cpu = s.getCPU()
198
		assignCpuSpecs(cpu.getSpecs(), project.cpuSpecs)
199 200
		cpu.enableObTempPresets(project.getObTempPresetsEn() or opt_obtemp)
		cpu.enableExtendedInsns(project.getExtInsnsEn() or opt_extInsns)
201 202
		if not opt_noCpuDump and not opt_quiet:
			cpu.setBlockExitCallback(cpuDumpCallback, cpu)
203
		cpu.setCycleTimeLimit(opt_cycletime)
204
		cpu.setRunTimeLimit(opt_maxRuntime)
205
		writeStdout("Initializing CPU...\n")
Michael Büsch's avatar
Michael Büsch committed
206 207
		for symTable in symTables:
			s.loadSymbolTable(symTable)
208 209
		for parseTree in parseTrees:
			s.load(parseTree)
210 211
		for libSel in project.getLibSelections():
			s.loadLibraryBlock(libSel)
212
		s.startup()
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
		writeStdout("[Initialization finished - CPU is executing user code]\n")
		try:
			if not opt_noCpuDump:
				clearConsole()
			while 1:
				s.runCycle()
		finally:
			if not opt_noCpuDump and not opt_quiet:
				clearConsole()
				writeStdout(lastDump + '\n')
	except (AwlParserError, AwlSimError) as e:
		printError(e.getReport())
		return 1
	except KeyboardInterrupt as e:
		pass
Michael Büsch's avatar
Michael Büsch committed
228
	except MaintenanceRequest as e:
Michael Büsch's avatar
Michael Büsch committed
229
		if e.requestType in (MaintenanceRequest.TYPE_SHUTDOWN,
230 231 232
				     MaintenanceRequest.TYPE_STOP,
				     MaintenanceRequest.TYPE_RTTIMEOUT):
			writeStdout("Shutting down, as requested (%s)...\n" % str(e))
Michael Büsch's avatar
Michael Büsch committed
233
		else:
234 235
			writeStdout("Received unknown maintenance request "
				    "(%d: %s)...\n" % (e.requestType, str(e)))
236 237 238 239 240 241 242 243 244 245 246
	finally:
		if s:
			ps = s.getProfileStats()
			if ps:
				writeStdout("\n\nProfile stats (level %d) follow:\n" %\
					    opt_profile)
				writeStdout(ps)
				writeStdout("\n")
			s.shutdown()
	return 0

Michael Büsch's avatar
Michael Büsch committed
247
def runWithServerBackend(inputFile):
248 249
	client = None
	try:
250 251
		import awlsim.cython_helper
		if awlsim.cython_helper.shouldUseCython():
252 253 254 255 256
			printError("The accelerated CYTHON core currently is incompatible "
				   "with the backend server. Please remove the "
				   "AWLSIMCYTHON environment variable.")
			return 1

Michael Büsch's avatar
Michael Büsch committed
257 258
		project = readInputFile(inputFile)

259 260 261
		# Connect to the server
		client = TextInterfaceAwlSimClient()
		if opt_spawnBackend:
262 263 264
			host, port = AwlSimServer.DEFAULT_HOST, AwlSimServer.DEFAULT_PORT
			if opt_connect:
				host, port = opt_connect
265 266 267 268 269 270 271 272
			if isWinStandalone:
				client.spawnServer(serverExecutable = "awlsim-server-module.exe",
						   listenHost = host,
						   listenPort = port)
			else:
				client.spawnServer(interpreter = opt_interpreter,
						   listenHost = host,
						   listenPort = port)
273 274 275 276 277 278 279
		writeStdout("Connecting to core server...\n")
		if opt_connect:
			client.connectToServer(host = opt_connect[0],
					       port = opt_connect[1])
		else:
			client.connectToServer()

Michael Büsch's avatar
Michael Büsch committed
280
		writeStdout("Initializing core...\n")
Michael Büsch's avatar
Michael Büsch committed
281
		client.setLoglevel(makeLoglevel())
282
		client.setRunState(False)
283
		client.reset()
284 285 286 287 288 289 290 291 292 293

		# Load hardware modules
		for name, parameters in opt_hwmods:
			client.loadHardwareModule(name, parameters)

		# Configure the core
		if opt_noCpuDump:
			client.setPeriodicDumpInterval(0)
		else:
			client.setPeriodicDumpInterval(300)
294 295
		client.enableOBTempPresets(project.getObTempPresetsEn() or opt_obtemp)
		client.enableExtendedInsns(project.getExtInsnsEn() or opt_extInsns)
296
		client.setCycleTimeLimit(opt_cycletime)
297
		client.setRunTimeLimit(opt_maxRuntime)
298
		specs = client.getCpuSpecs()
299
		assignCpuSpecs(specs, project.cpuSpecs)
300
		client.setCpuSpecs(specs)
301 302 303

		# Fire up the core
		writeStdout("Initializing CPU...\n")
304
		for symTabSrc in project.getSymTabSources():
305
			client.loadSymbolTable(symTabSrc)
306
		for awlSrc in project.getAwlSources():
307
			client.loadCode(awlSrc)
308 309
		for libSel in project.getLibSelections():
			client.loadLibraryBlock(libSel)
310
		client.setRunState(True)
311 312 313 314 315 316 317

		# Run the client-side event loop
		writeStdout("[Initialization finished - Remote-CPU is executing user code]\n")
		try:
			if not opt_noCpuDump:
				clearConsole()
			while True:
318
				client.processMessages(None)
319 320 321 322 323 324 325
		finally:
			if not opt_noCpuDump and not opt_quiet:
				clearConsole()
				writeStdout(lastDump + '\n')
	except AwlSimError as e:
		printError(e.getReport())
		return 1
Michael Büsch's avatar
Michael Büsch committed
326
	except MaintenanceRequest as e:
Michael Büsch's avatar
Michael Büsch committed
327
		if e.requestType in (MaintenanceRequest.TYPE_SHUTDOWN,
328 329 330
				     MaintenanceRequest.TYPE_STOP,
				     MaintenanceRequest.TYPE_RTTIMEOUT):
			writeStdout("Shutting down, as requested (%s)...\n" % str(e))
Michael Büsch's avatar
Michael Büsch committed
331
		else:
332 333
			writeStdout("Received unknown maintenance request "
				    "(%d: %s)...\n" % (e.requestType, str(e)))
334 335 336 337 338 339
	except KeyboardInterrupt as e:
		pass
	finally:
		if client:
			client.shutdown()
	return 0
Michael Büsch's avatar
Michael Büsch committed
340

341 342 343 344 345 346
def __signalHandler(sig, frame):
	printInfo("Received signal %d" % sig)
	if sig == signal.SIGTERM:
		# Raise SIGINT. It will shut down everything.
		os.kill(os.getpid(), signal.SIGINT)

Michael Büsch's avatar
Michael Büsch committed
347
def main():
348
	global opt_cycletime
349
	global opt_maxRuntime
Michael Büsch's avatar
Michael Büsch committed
350
	global opt_quiet
351
	global opt_noCpuDump
352
	global opt_nrAccus
353 354
	global opt_extInsns
	global opt_obtemp
Michael Büsch's avatar
Michael Büsch committed
355
	global opt_clockMem
356 357 358 359 360 361 362
	global opt_mnemonics
	global opt_hwmods
	global opt_hwinfos
	global opt_profile
	global opt_connect
	global opt_spawnBackend
	global opt_interpreter
363

364
	opt_cycletime = 5.0
365
	opt_maxRuntime = -1.0
366
	opt_quiet = False
367
	opt_noCpuDump = False
368
	opt_nrAccus = None
369
	opt_extInsns = False
370
	opt_obtemp = False
Michael Büsch's avatar
Michael Büsch committed
371
	opt_clockMem = None
372
	opt_mnemonics = None
Michael Büsch's avatar
Michael Büsch committed
373
	opt_hwmods = []
374
	opt_hwinfos = []
Michael Büsch's avatar
Michael Büsch committed
375
	opt_profile = 0
376 377 378
	opt_connect = None
	opt_spawnBackend = False
	opt_interpreter = None
Michael Büsch's avatar
Michael Büsch committed
379 380 381

	try:
		(opts, args) = getopt.getopt(sys.argv[1:],
382 383
			"hC:M:24qDxt:T:m:H:I:P:c:bi:",
			[ "help", "cycle-time=", "max-runtime=", "twoaccu", "fouraccu",
384
			  "quiet", "no-cpu-dump", "extended-insns",
Michael Büsch's avatar
Michael Büsch committed
385
			  "obtemp=", "clock-mem=", "mnemonics=",
386
			  "hardware=", "hardware-info=", "profile=",
387
			  "connect=", "spawn-backend", "interpreter=",
388 389
			  "list-sfc", "list-sfc-verbose",
			  "list-sfb", "list-sfb-verbose", ])
Michael Büsch's avatar
Michael Büsch committed
390
	except getopt.GetoptError as e:
Michael Büsch's avatar
Michael Büsch committed
391
		printError(str(e))
Michael Büsch's avatar
Michael Büsch committed
392 393 394 395 396 397
		usage()
		return 1
	for (o, v) in opts:
		if o in ("-h", "--help"):
			usage()
			return 0
398 399 400 401 402 403
		if o in ("-C", "--cycle-time"):
			try:
				opt_cycletime = float(v)
			except ValueError:
				printError("-C|--cycle-time: Invalid time format")
				sys.exit(1)
404 405 406 407 408 409
		if o in ("-M", "--max-runtime"):
			try:
				opt_maxRuntime = float(v)
			except ValueError:
				printError("-M|--max-runtime: Invalid time format")
				sys.exit(1)
410 411
		if o in ("-2", "--twoaccu"):
			opt_nrAccus = 2
Michael Büsch's avatar
Michael Büsch committed
412
		if o in ("-4", "--fouraccu"):
413
			opt_nrAccus = 4
Michael Büsch's avatar
Michael Büsch committed
414 415
		if o in ("-q", "--quiet"):
			opt_quiet = True
416 417
		if o in ("-D", "--no-cpu-dump"):
			opt_noCpuDump = True
418 419
		if o in ("-x", "--extended-insns"):
			opt_extInsns = True
420 421
		if o in ("-t", "--obtemp"):
			opt_obtemp = str2bool(v)
Michael Büsch's avatar
Michael Büsch committed
422 423 424 425 426 427 428
		if o in ("-T", "--clock-mem"):
			try:
				opt_clockMem = int(v)
				if opt_clockMem < -1 or opt_clockMem > 0xFFFF:
					raise ValueError
			except ValueError:
				printError("-T|--clock-mem: Invalid byte address")
429
		if o in ("-m", "--mnemonics"):
430 431
			opt_mnemonics = v.lower()
			if opt_mnemonics not in ("en", "de", "auto"):
432 433
				printError("-m|--mnemonics: Invalid mnemonics type")
				sys.exit(1)
Michael Büsch's avatar
Michael Büsch committed
434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455
		if o in ("-H", "--hardware"):
			try:
				v = v.split(':')
				if not v:
					raise ValueError
				name = v[0]
				params = {}
				for pstr in v[1:]:
					if not pstr:
						continue
					i = pstr.find('=')
					if i < 0:
						raise ValueError
					pname = pstr[:i]
					pval = pstr[i+1:]
					if not pname or not pval:
						raise ValueError
					params[pname] = pval
				opt_hwmods.append( (name, params) )
			except (ValueError, IndexError) as e:
				printError("-H|--hardware: Invalid module name or parameters")
				sys.exit(1)
456
		if o in ("-I", "--hardware-info"):
457
			opt_hwinfos.append(v.split(':')[0])
Michael Büsch's avatar
Michael Büsch committed
458 459 460 461 462
		if o in ("-P", "--profile"):
			try:
				opt_profile = int(v)
			except ValueError:
				printError("-P|--profile: Invalid profile level")
463 464 465 466 467 468 469 470 471 472 473 474
		if o in ("-c", "--connect"):
			try:
				idx = v.rfind(":")
				if idx <= 0:
					raise ValueError
				opt_connect = (v[:idx], int(v[idx+1:]))
			except ValueError:
				printError("-c|--connect: Invalid host/port")
				sys.exit(1)
		if o in ("-b", "--spawn-backend"):
			opt_spawnBackend = True
		if o in ("-i", "--interpreter"):
475 476 477
			if isWinStandalone:
				printError("-i|--interpreter not supported on win-standalone")
				sys.exit(1)
478
			opt_interpreter = v
479
		if o in ("--list-sfc", "--list-sfc-verbose"):
480
			printInfo("The supported system functions (SFCs) are:")
481 482
			printSysblockInfo(SFC_table, "SFC", opt_extInsns,
					  o.endswith("verbose"))
483
			return 0
484
		if o in ("--list-sfb", "--list-sfb-verbose"):
485
			printInfo("The supported system function blocks (SFBs) are:")
486 487
			printSysblockInfo(SFB_table, "SFB", opt_extInsns,
					  o.endswith("verbose"))
488
			return 0
489
	if len(args) != 1 and not opt_hwinfos:
Michael Büsch's avatar
Michael Büsch committed
490 491
		usage()
		return 1
492
	if args:
Michael Büsch's avatar
Michael Büsch committed
493
		inputFile = args[0]
Michael Büsch's avatar
Michael Büsch committed
494

Michael Büsch's avatar
Michael Büsch committed
495 496
	Logging.setLoglevel(makeLoglevel())

497
	opt_mnemonics = {
498
		None	: None,
499 500 501 502 503
		"en"	: S7CPUSpecs.MNEMONICS_EN,
		"de"	: S7CPUSpecs.MNEMONICS_DE,
		"auto"	: S7CPUSpecs.MNEMONICS_AUTO,
	}[opt_mnemonics]

Michael Büsch's avatar
Michael Büsch committed
504
	try:
505 506 507
		if opt_hwinfos:
			# Just print the hardware-infos and exit.
			for name in opt_hwinfos:
508
				cls = AwlSim.loadHardwareModule(name)
509 510
				printInfo(cls.getModuleInfo())
			return 0
511 512
	except (AwlParserError, AwlSimError) as e:
		printError(e.getReport())
Michael Büsch's avatar
Michael Büsch committed
513
		return 1
514

515 516
	signal.signal(signal.SIGTERM, __signalHandler)

517 518 519 520 521 522
	if opt_interpreter and not opt_spawnBackend:
		printError("Selected an --interpreter, but no "
			   "--spawn-backend was requested.")
		return 1

	if opt_spawnBackend or opt_connect:
Michael Büsch's avatar
Michael Büsch committed
523 524
		return runWithServerBackend(inputFile)
	return run(inputFile)
Michael Büsch's avatar
Michael Büsch committed
525 526 527

if __name__ == "__main__":
	sys.exit(main())