awlsim-cli 15.7 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 <m@bues.ch>
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
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

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

84
def printSysblockInfo(blockTable, prefix, withExtended, withInterface):
85 86
	for block in sorted(blockTable.values(),
			    key = lambda b: b.name[0]):
87 88
		if block.broken:
			continue
89 90 91 92 93 94 95
		number, name, desc = block.name
		if number < 0 and not withExtended:
			continue
		if desc:
			desc = "  (%s)" % desc
		else:
			desc = ""
96
		print("  %s %d  \"%s\"%s" % (prefix, number, name, desc))
97 98 99 100 101 102 103 104 105 106
		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
107
					print("        %s" % str(field))
Michael Büsch's avatar
Michael Büsch committed
108 109

def writeStdout(message):
110
	if Logging.loglevel >= Logging.LOG_INFO:
Michael Büsch's avatar
Michael Büsch committed
111 112 113
		sys.stdout.write(message)
		sys.stdout.flush()

114 115 116
nextScreenUpdate = 0.0
lastDump = ""

117 118 119
def clearConsole():
	# Make cursor visible, clear console and
	# move cursor to homeposition.
120 121 122 123
	if osIsPosix:
		writeStdout("\x1B[?25h\x1B[2J\x1B[H")
	elif osIsWindows:
		os.system("cls")
124 125 126

def emitCpuDump(dump):
	# Pad lines
127
	dump = '\n'.join(line + (78 - len(line)) * ' ' + '|'
128 129 130
			 for line in dump.splitlines())
	global lastDump
	lastDump = dump
131 132 133 134 135
	if osIsPosix:
		writeStdout("\x1B[H" + dump)
	else:
		clearConsole()
		writeStdout(dump)
136

137
def cpuDumpCallback(cpu):
138
	global nextScreenUpdate
139 140 141
	if cpu.now >= nextScreenUpdate:
		nextScreenUpdate = cpu.now + 0.1
		emitCpuDump(str(cpu))
142

143 144 145 146 147 148
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)
149 150
	if opt_clockMem is not None:
		cpuSpecs.setClockMemByte(opt_clockMem)
151

152 153 154 155 156
def readInputFile(inputFile):
	if Project.fileIsProject(inputFile):
		project = Project.fromFile(inputFile)
	else:
		# make a fake project
157
		awlSrc = AwlSource.fromFile(name = inputFile,
158
					    filepath = inputFile)
159
		project = Project(projectFile = None,
160
				  awlSources = [ awlSrc, ])
161 162 163
	return project

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

171 172
		project = readInputFile(inputFile)

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

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

		# 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()
201
		assignCpuSpecs(cpu.getSpecs(), project.cpuSpecs)
202 203
		cpu.enableObTempPresets(project.getObTempPresetsEn() or opt_obtemp)
		cpu.enableExtendedInsns(project.getExtInsnsEn() or opt_extInsns)
204
		if not opt_noCpuDump and opt_loglevel >= Logging.LOG_INFO:
205
			cpu.setBlockExitCallback(cpuDumpCallback, cpu)
206
		cpu.setCycleTimeLimit(opt_cycletime)
207
		cpu.setRunTimeLimit(opt_maxRuntime)
208
		writeStdout("Initializing CPU...\n")
Michael Büsch's avatar
Michael Büsch committed
209 210
		for symTable in symTables:
			s.loadSymbolTable(symTable)
211 212
		for libSel in project.getLibSelections():
			s.loadLibraryBlock(libSel)
213 214
		for parseTree in parseTrees:
			s.load(parseTree)
215
		s.startup()
216 217 218 219 220 221 222
		writeStdout("[Initialization finished - CPU is executing user code]\n")
		try:
			if not opt_noCpuDump:
				clearConsole()
			while 1:
				s.runCycle()
		finally:
223
			if not opt_noCpuDump and opt_loglevel >= Logging.LOG_INFO:
224 225 226 227 228 229 230
				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
231
	except MaintenanceRequest as e:
232
		if e.requestType in (MaintenanceRequest.TYPE_SHUTDOWN,
233 234 235
				     MaintenanceRequest.TYPE_STOP,
				     MaintenanceRequest.TYPE_RTTIMEOUT):
			writeStdout("Shutting down, as requested (%s)...\n" % str(e))
Michael Büsch's avatar
Michael Büsch committed
236
		else:
237 238
			writeStdout("Received unknown maintenance request "
				    "(%d: %s)...\n" % (e.requestType, str(e)))
239 240 241 242 243 244 245 246 247 248 249
	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

250
def runWithServerBackend(inputFile):
251 252
	client = None
	try:
253 254
		import awlsim.cython_helper
		if awlsim.cython_helper.shouldUseCython():
255 256 257 258 259
			printError("The accelerated CYTHON core currently is incompatible "
				   "with the backend server. Please remove the "
				   "AWLSIMCYTHON environment variable.")
			return 1

260 261
		project = readInputFile(inputFile)

262 263 264
		# Connect to the server
		client = TextInterfaceAwlSimClient()
		if opt_spawnBackend:
265 266 267
			host, port = AwlSimServer.DEFAULT_HOST, AwlSimServer.DEFAULT_PORT
			if opt_connect:
				host, port = opt_connect
268 269 270 271 272 273 274 275
			if isWinStandalone:
				client.spawnServer(serverExecutable = "awlsim-server-module.exe",
						   listenHost = host,
						   listenPort = port)
			else:
				client.spawnServer(interpreter = opt_interpreter,
						   listenHost = host,
						   listenPort = port)
276 277 278 279 280 281 282
		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
283
		writeStdout("Initializing core...\n")
284
		client.setLoglevel(opt_loglevel)
285
		client.setRunState(False)
286
		client.reset()
287 288 289 290 291 292 293 294 295 296

		# 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)
297 298
		client.enableOBTempPresets(project.getObTempPresetsEn() or opt_obtemp)
		client.enableExtendedInsns(project.getExtInsnsEn() or opt_extInsns)
299
		client.setCycleTimeLimit(opt_cycletime)
300
		client.setRunTimeLimit(opt_maxRuntime)
301
		specs = client.getCpuSpecs()
302
		assignCpuSpecs(specs, project.cpuSpecs)
303
		client.setCpuSpecs(specs)
304 305 306

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

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

344 345 346 347 348 349
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
350
def main():
351
	global opt_cycletime
352
	global opt_maxRuntime
353
	global opt_noCpuDump
354
	global opt_nrAccus
355 356
	global opt_extInsns
	global opt_obtemp
357
	global opt_clockMem
358 359 360 361
	global opt_mnemonics
	global opt_hwmods
	global opt_hwinfos
	global opt_profile
362
	global opt_loglevel
363 364 365
	global opt_connect
	global opt_spawnBackend
	global opt_interpreter
366

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 = []
Michael Büsch's avatar
Michael Büsch committed
377
	opt_profile = 0
378
	opt_loglevel = Logging.LOG_INFO
379 380 381
	opt_connect = None
	opt_spawnBackend = False
	opt_interpreter = None
Michael Büsch's avatar
Michael Büsch committed
382 383 384

	try:
		(opts, args) = getopt.getopt(sys.argv[1:],
385
			"hC:M:24qDxt:T:m:H:I:P:L:c: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=", "spawn-backend", "interpreter=",
392 393
			  "list-sfc", "list-sfc-verbose",
			  "list-sfb", "list-sfb-verbose", ])
Michael Büsch's avatar
Michael Büsch committed
394
	except getopt.GetoptError as e:
395
		printError(str(e))
Michael Büsch's avatar
Michael Büsch committed
396 397 398 399 400 401
		usage()
		return 1
	for (o, v) in opts:
		if o in ("-h", "--help"):
			usage()
			return 0
402 403 404 405 406 407
		if o in ("-C", "--cycle-time"):
			try:
				opt_cycletime = float(v)
			except ValueError:
				printError("-C|--cycle-time: Invalid time format")
				sys.exit(1)
408 409 410 411 412 413
		if o in ("-M", "--max-runtime"):
			try:
				opt_maxRuntime = float(v)
			except ValueError:
				printError("-M|--max-runtime: Invalid time format")
				sys.exit(1)
414 415
		if o in ("-2", "--twoaccu"):
			opt_nrAccus = 2
Michael Büsch's avatar
Michael Büsch committed
416
		if o in ("-4", "--fouraccu"):
417
			opt_nrAccus = 4
418 419
		if o in ("-D", "--no-cpu-dump"):
			opt_noCpuDump = True
420 421
		if o in ("-x", "--extended-insns"):
			opt_extInsns = True
422 423
		if o in ("-t", "--obtemp"):
			opt_obtemp = str2bool(v)
424 425 426 427 428 429 430
		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")
431
		if o in ("-m", "--mnemonics"):
432 433
			opt_mnemonics = v.lower()
			if opt_mnemonics not in ("en", "de", "auto"):
434 435
				printError("-m|--mnemonics: Invalid mnemonics type")
				sys.exit(1)
436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457
		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)
458
		if o in ("-I", "--hardware-info"):
459
			opt_hwinfos.append(v.split(':')[0])
Michael Büsch's avatar
Michael Büsch committed
460 461 462 463 464
		if o in ("-P", "--profile"):
			try:
				opt_profile = int(v)
			except ValueError:
				printError("-P|--profile: Invalid profile level")
465 466 467 468 469 470
		if o in ("-L", "--loglevel"):
			try:
				opt_loglevel = int(v)
			except ValueError:
				printError("-L|--loglevel: Invalid log level")
				sys.exit(1)
471 472 473 474 475 476 477 478 479 480 481 482
		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"):
483 484 485
			if isWinStandalone:
				printError("-i|--interpreter not supported on win-standalone")
				sys.exit(1)
486
			opt_interpreter = v
487
		if o in ("--list-sfc", "--list-sfc-verbose"):
488
			print("The supported system functions (SFCs) are:")
489 490
			printSysblockInfo(SFC_table, "SFC", opt_extInsns,
					  o.endswith("verbose"))
491
			return 0
492
		if o in ("--list-sfb", "--list-sfb-verbose"):
493
			print("The supported system function blocks (SFBs) are:")
494 495
			printSysblockInfo(SFB_table, "SFB", opt_extInsns,
					  o.endswith("verbose"))
496
			return 0
497
	if len(args) != 1 and not opt_hwinfos:
Michael Büsch's avatar
Michael Büsch committed
498 499
		usage()
		return 1
500
	if args:
501
		inputFile = args[0]
Michael Büsch's avatar
Michael Büsch committed
502

503
	Logging.setLoglevel(opt_loglevel)
Michael Büsch's avatar
Michael Büsch committed
504

505
	opt_mnemonics = {
506
		None	: None,
507 508 509 510 511
		"en"	: S7CPUSpecs.MNEMONICS_EN,
		"de"	: S7CPUSpecs.MNEMONICS_DE,
		"auto"	: S7CPUSpecs.MNEMONICS_AUTO,
	}[opt_mnemonics]

Michael Büsch's avatar
Michael Büsch committed
512
	try:
513 514 515
		if opt_hwinfos:
			# Just print the hardware-infos and exit.
			for name in opt_hwinfos:
516
				cls = AwlSim.loadHardwareModule(name)
517
				print(cls.getModuleInfo())
518
			return 0
519 520
	except (AwlParserError, AwlSimError) as e:
		printError(e.getReport())
Michael Büsch's avatar
Michael Büsch committed
521
		return 1
522

523 524
	signal.signal(signal.SIGTERM, __signalHandler)

525 526 527 528 529 530
	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:
531 532
		return runWithServerBackend(inputFile)
	return run(inputFile)
Michael Büsch's avatar
Michael Büsch committed
533 534 535

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