awlsimcli 13.8 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-2013 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 25
from __future__ import division, absolute_import, print_function, unicode_literals
from awlsim.core.compat import *

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

32 33
from awlsim.core import *
from awlsim.coreserver 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 46
	printInfo("")
	printInfo("Options:")
	printInfo(" -1|--onecycle         Only run one cycle")
47
	printInfo(" -C|--cycle-limit SEC  Cycle time limit, in seconds (default 5.0)")
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)")
54
	printInfo(" -m|--mnemonics auto   Force mnemonics type: en, de, auto")
Michael Büsch's avatar
Michael Büsch committed
55
	printInfo(" -P|--profile 0        Set profiling level (Default: 0)")
Michael Büsch's avatar
Michael Büsch committed
56
	printInfo("")
57 58
	printInfo("Server backend related options:")
	printInfo(" -c|--connect IP:PORT  Connect to server backend")
59
	printInfo(" -b|--spawn-backend    Spawn a new backend server and connect to it")
60 61
	printInfo(" -i|--interpreter EXE  Set the backend interpreter executable")
	printInfo("")
Michael Büsch's avatar
Michael Büsch committed
62 63
	printInfo("Loading hardware modules:")
	printInfo(" -H|--hardware NAME:PARAM=VAL:PARAM=VAL...")
64 65
	printInfo("Print module information:")
	printInfo(" -I|--hardware-info NAME")
Michael Büsch's avatar
Michael Büsch committed
66 67 68
	printInfo("")
	printInfo(" Where NAME is the name of the hardware module.")
	printInfo(" PARAM=VAL are optional hardware specific parameters.")
69 70 71
	printInfo("")
	printInfo("Other options:")
	printInfo(" --list-sfc            Print a list of all supported SFCs")
72
	printInfo(" --list-sfc-verbose    Verbose SFC list")
73
	printInfo(" --list-sfb            Print a list of all supported SFBs")
74
	printInfo(" --list-sfb-verbose    Verbose SFB list")
75

76
def printSysblockInfo(blockTable, prefix, withExtended, withInterface):
77 78
	for block in sorted(blockTable.values(),
			    key = lambda b: b.name[0]):
79 80
		if block.broken:
			continue
81 82 83 84 85 86 87 88
		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))
89 90 91 92 93 94 95 96 97 98 99
		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
100 101 102 103 104 105

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

Michael Büsch's avatar
Michael Büsch committed
106 107 108
nextScreenUpdate = 0.0
lastDump = ""

109 110 111 112 113 114 115 116 117 118 119 120 121
def clearConsole():
	# Make cursor visible, clear console and
	# move cursor to homeposition.
	writeStdout("\x1B[?25h\x1B[2J\x1B[H")

def emitCpuDump(dump):
	# Pad lines
	dump = '\n'.join(line + (79 - len(line)) * ' ' + '|'
			 for line in dump.splitlines())
	global lastDump
	lastDump = dump
	writeStdout("\x1B[H" + dump)

122
def cpuDumpCallback(cpu):
Michael Büsch's avatar
Michael Büsch committed
123
	global nextScreenUpdate
124
	if cpu.now < nextScreenUpdate and\
Michael Büsch's avatar
Michael Büsch committed
125 126
	   not opt_onecycle:
		return
127
	nextScreenUpdate = cpu.now + 0.1
128 129
	emitCpuDump(str(cpu))

Michael Büsch's avatar
Michael Büsch committed
130 131 132
def makeLoglevel():
	return Logging.LOG_ERROR if opt_quiet else Logging.LOG_INFO

133 134 135 136 137 138
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)
139

Michael Büsch's avatar
Michael Büsch committed
140 141 142 143 144 145 146 147 148 149
def readInputFile(inputFile):
	if Project.fileIsProject(inputFile):
		project = Project.fromFile(inputFile)
	else:
		# make a fake project
		project = Project(projectFile = None,
				  awlFiles = [ inputFile, ])
	return project

def run(inputFile):
150 151
	s = None
	try:
152 153
		import awlsim.cython_helper
		if awlsim.cython_helper.shouldUseCython():
154 155 156
			writeStdout("*** Using accelerated CYTHON core "
				    "(AWLSIMCYTHON environment variable is set)\n")

Michael Büsch's avatar
Michael Büsch committed
157 158
		project = readInputFile(inputFile)

159
		writeStdout("Parsing code...\n")
160 161 162 163 164
		parseTrees = []
		for awlFile in project.awlFiles:
			p = AwlParser()
			p.parseFile(awlFile)
			parseTrees.append(p.getParseTree())
Michael Büsch's avatar
Michael Büsch committed
165 166
		symTables = []
		for symTabFile in project.symTabFiles:
167 168 169
			mnemonics = project.cpuSpecs.getConfiguredMnemonics()
			if opt_mnemonics is not None:
				mnemonics = opt_mnemonics
Michael Büsch's avatar
Michael Büsch committed
170 171
			tab = SymTabParser.parseFile(symTabFile,
						     autodetectFormat = True,
172
						     mnemonics = mnemonics)
Michael Büsch's avatar
Michael Büsch committed
173
			symTables.append(tab)
174

175 176
		writeStdout("Initializing simulator...\n")
		s = AwlSim(profileLevel = opt_profile)
Michael Büsch's avatar
Michael Büsch committed
177
		s.reset()
178 179 180 181 182 183 184 185 186

		# 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()
187
		assignCpuSpecs(cpu.getSpecs(), project.cpuSpecs)
188 189 190 191
		cpu.enableObTempPresets(opt_obtemp)
		cpu.enableExtendedInsns(opt_extInsns)
		if not opt_noCpuDump and not opt_quiet:
			cpu.setBlockExitCallback(cpuDumpCallback, cpu)
192
		cpu.setCycleTimeLimit(opt_cycletime)
193
		writeStdout("Initializing CPU...\n")
Michael Büsch's avatar
Michael Büsch committed
194 195
		for symTable in symTables:
			s.loadSymbolTable(symTable)
196 197
		for parseTree in parseTrees:
			s.load(parseTree)
198
		s.startup()
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
		writeStdout("[Initialization finished - CPU is executing user code]\n")
		try:
			if not opt_noCpuDump:
				clearConsole()
			while 1:
				s.runCycle()
				if opt_onecycle:
					break
		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
216 217 218 219 220
	except MaintenanceRequest as e:
		if e.requestType == MaintenanceRequest.TYPE_SHUTDOWN:
			writeStdout("Shutting down, as requested...\n")
		else:
			assert(0)
221 222 223 224 225 226 227 228 229 230 231
	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
232
def runWithServerBackend(inputFile):
233 234
	client = None
	try:
235 236
		import awlsim.cython_helper
		if awlsim.cython_helper.shouldUseCython():
237 238 239 240 241
			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
242 243
		project = readInputFile(inputFile)

244 245 246
		# Connect to the server
		client = TextInterfaceAwlSimClient()
		if opt_spawnBackend:
247 248 249 250 251 252
			host, port = AwlSimServer.DEFAULT_HOST, AwlSimServer.DEFAULT_PORT
			if opt_connect:
				host, port = opt_connect
			client.spawnServer(interpreter = opt_interpreter,
					   listenHost = host,
					   listenPort = port)
253 254 255 256 257 258 259 260
		writeStdout("Connecting to core server...\n")
		if opt_connect:
			client.connectToServer(host = opt_connect[0],
					       port = opt_connect[1])
		else:
			client.connectToServer()

		writeStdout("Initializing simulator...\n")
Michael Büsch's avatar
Michael Büsch committed
261
		client.setLoglevel(makeLoglevel())
262
		client.setRunState(False)
263
		client.reset()
264 265 266 267 268 269 270 271 272 273 274 275

		# 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)
		client.enableOBTempPresets(opt_obtemp)
		client.enableExtendedInsns(opt_extInsns)
276
		client.setCycleTimeLimit(opt_cycletime)
277
		specs = client.getCpuSpecs()
278
		assignCpuSpecs(specs, project.cpuSpecs)
279
		client.setCpuSpecs(specs)
280 281 282

		# Fire up the core
		writeStdout("Initializing CPU...\n")
Michael Büsch's avatar
Michael Büsch committed
283
		for symTabFile in project.symTabFiles:
284 285
			client.loadSymbolTable(awlFileRead(symTabFile,
							   encoding="binary"))
286 287
		for awlFile in project.awlFiles:
			client.loadCode(awlFileRead(awlFile))
288
		client.setRunState(True)
289 290 291 292 293 294 295

		# Run the client-side event loop
		writeStdout("[Initialization finished - Remote-CPU is executing user code]\n")
		try:
			if not opt_noCpuDump:
				clearConsole()
			while True:
296
				client.processMessages(None)
297 298 299 300 301 302 303
		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
304 305 306 307 308
	except MaintenanceRequest as e:
		if e.requestType == MaintenanceRequest.TYPE_SHUTDOWN:
			writeStdout("Shutting down, as requested...\n")
		else:
			assert(0)
309 310 311 312 313 314
	except KeyboardInterrupt as e:
		pass
	finally:
		if client:
			client.shutdown()
	return 0
Michael Büsch's avatar
Michael Büsch committed
315

316 317 318 319 320 321
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
322 323
def main():
	global opt_onecycle
324
	global opt_cycletime
Michael Büsch's avatar
Michael Büsch committed
325
	global opt_quiet
326
	global opt_noCpuDump
327
	global opt_nrAccus
328 329 330 331 332 333 334 335 336
	global opt_extInsns
	global opt_obtemp
	global opt_mnemonics
	global opt_hwmods
	global opt_hwinfos
	global opt_profile
	global opt_connect
	global opt_spawnBackend
	global opt_interpreter
337

338
	opt_onecycle = False
339
	opt_cycletime = 5.0
340
	opt_quiet = False
341
	opt_noCpuDump = False
342
	opt_nrAccus = None
343
	opt_extInsns = False
344
	opt_obtemp = False
345
	opt_mnemonics = None
Michael Büsch's avatar
Michael Büsch committed
346
	opt_hwmods = []
347
	opt_hwinfos = []
Michael Büsch's avatar
Michael Büsch committed
348
	opt_profile = 0
349 350 351
	opt_connect = None
	opt_spawnBackend = False
	opt_interpreter = None
Michael Büsch's avatar
Michael Büsch committed
352 353 354

	try:
		(opts, args) = getopt.getopt(sys.argv[1:],
355 356
			"h1C:24qDxt:m:H:I:P:c:bi:",
			[ "help", "onecycle", "cycle-time=", "twoaccu", "fouraccu",
357
			  "quiet", "no-cpu-dump", "extended-insns",
Michael Büsch's avatar
Michael Büsch committed
358
			  "obtemp=", "mnemonics=",
359
			  "hardware=", "hardware-info=", "profile=",
360
			  "connect=", "spawn-backend", "interpreter=",
361 362
			  "list-sfc", "list-sfc-verbose",
			  "list-sfb", "list-sfb-verbose", ])
Michael Büsch's avatar
Michael Büsch committed
363
	except getopt.GetoptError as e:
Michael Büsch's avatar
Michael Büsch committed
364
		printError(str(e))
Michael Büsch's avatar
Michael Büsch committed
365 366 367 368 369 370 371 372
		usage()
		return 1
	for (o, v) in opts:
		if o in ("-h", "--help"):
			usage()
			return 0
		if o in ("-1", "--onecycle"):
			opt_onecycle = True
373 374 375 376 377 378
		if o in ("-C", "--cycle-time"):
			try:
				opt_cycletime = float(v)
			except ValueError:
				printError("-C|--cycle-time: Invalid time format")
				sys.exit(1)
379 380
		if o in ("-2", "--twoaccu"):
			opt_nrAccus = 2
Michael Büsch's avatar
Michael Büsch committed
381
		if o in ("-4", "--fouraccu"):
382
			opt_nrAccus = 4
Michael Büsch's avatar
Michael Büsch committed
383 384
		if o in ("-q", "--quiet"):
			opt_quiet = True
385 386
		if o in ("-D", "--no-cpu-dump"):
			opt_noCpuDump = True
387 388
		if o in ("-x", "--extended-insns"):
			opt_extInsns = True
389 390
		if o in ("-t", "--obtemp"):
			opt_obtemp = str2bool(v)
391
		if o in ("-m", "--mnemonics"):
392 393
			opt_mnemonics = v.lower()
			if opt_mnemonics not in ("en", "de", "auto"):
394 395
				printError("-m|--mnemonics: Invalid mnemonics type")
				sys.exit(1)
Michael Büsch's avatar
Michael Büsch committed
396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417
		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)
418
		if o in ("-I", "--hardware-info"):
419
			opt_hwinfos.append(v.split(':')[0])
Michael Büsch's avatar
Michael Büsch committed
420 421 422 423 424
		if o in ("-P", "--profile"):
			try:
				opt_profile = int(v)
			except ValueError:
				printError("-P|--profile: Invalid profile level")
425 426 427 428 429 430 431 432 433 434 435 436 437
		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"):
			opt_interpreter = v
438
		if o in ("--list-sfc", "--list-sfc-verbose"):
439
			printInfo("The supported system functions (SFCs) are:")
440 441
			printSysblockInfo(SFC_table, "SFC", opt_extInsns,
					  o.endswith("verbose"))
442
			return 0
443
		if o in ("--list-sfb", "--list-sfb-verbose"):
444
			printInfo("The supported system function blocks (SFBs) are:")
445 446
			printSysblockInfo(SFB_table, "SFB", opt_extInsns,
					  o.endswith("verbose"))
447
			return 0
448
	if len(args) != 1 and not opt_hwinfos:
Michael Büsch's avatar
Michael Büsch committed
449 450
		usage()
		return 1
451
	if args:
Michael Büsch's avatar
Michael Büsch committed
452
		inputFile = args[0]
Michael Büsch's avatar
Michael Büsch committed
453

Michael Büsch's avatar
Michael Büsch committed
454 455
	Logging.setLoglevel(makeLoglevel())

456
	opt_mnemonics = {
457
		None	: None,
458 459 460 461 462
		"en"	: S7CPUSpecs.MNEMONICS_EN,
		"de"	: S7CPUSpecs.MNEMONICS_DE,
		"auto"	: S7CPUSpecs.MNEMONICS_AUTO,
	}[opt_mnemonics]

Michael Büsch's avatar
Michael Büsch committed
463
	try:
464 465 466
		if opt_hwinfos:
			# Just print the hardware-infos and exit.
			for name in opt_hwinfos:
467
				cls = AwlSim.loadHardwareModule(name)
468 469
				printInfo(cls.getModuleInfo())
			return 0
470 471
	except (AwlParserError, AwlSimError) as e:
		printError(e.getReport())
Michael Büsch's avatar
Michael Büsch committed
472
		return 1
473

474 475
	signal.signal(signal.SIGTERM, __signalHandler)

476 477 478 479 480 481
	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
482 483
		return runWithServerBackend(inputFile)
	return run(inputFile)
Michael Büsch's avatar
Michael Büsch committed
484 485 486

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