1470 lines
50 KiB
Python
Executable File
1470 lines
50 KiB
Python
Executable File
#!/usr/bin/env python
|
|
# Script Context Driver Generator
|
|
# Author: Douglas Clowes (douglas.clowes@ansto.gov.au) Jan/Feb 2014
|
|
# vim: ft=python ts=8 sts=2 sw=2 expandtab autoindent smartindent
|
|
#
|
|
# This program generates Script Context Driver TCL files.
|
|
#
|
|
# It takes one or more "Script Context Driver Description" files.
|
|
# Each file may contain one or more driver descriptions. Each driver
|
|
# description will result in one TCL file.
|
|
#
|
|
# The name of the file produced, the TCL namespace used and names within
|
|
# the file are based on the driver name givin in the driver description.
|
|
#
|
|
# TODO:
|
|
# implement attributes and units on vars
|
|
# - type part ???
|
|
# - nxalias xxxxx
|
|
# - sdsinfo
|
|
# fix code for driving
|
|
# - make each node driveable
|
|
# - use [sct] in place of ${tc_root}
|
|
# - associate sensor and call ansto_makesctdrive
|
|
# check simulation works
|
|
# handle environmental monitoring (emon)
|
|
# - figure out how to do it
|
|
# - make nodes monitorable
|
|
# - make it conditional
|
|
# handle the driving settling time in checkstatus
|
|
# Questions:
|
|
# what it the 'plain spy none' on hfactory
|
|
# what is 'mugger' vs 'manager' - seems like alias?
|
|
# should some hset commands be hupdate commands?
|
|
#
|
|
|
|
import os
|
|
import ply.lex as lex
|
|
import ply.yacc as yacc
|
|
|
|
global Verbose
|
|
global DriverDump
|
|
global CodeDump
|
|
global UsingCreateNode
|
|
global FunctionTypes
|
|
global DriveableFunctionTypes
|
|
global NumberOfLinesIn
|
|
global NumberOfLinesOut
|
|
|
|
FunctionTypes = [
|
|
'read_function',
|
|
'write_function',
|
|
'fetch_function',
|
|
'check_function',
|
|
'checkrange_function',
|
|
]
|
|
|
|
DriveableFunctionTypes = [
|
|
'halt_function',
|
|
'checklimits_function',
|
|
'checkstatus_function',
|
|
]
|
|
UsingCreateNode = False
|
|
|
|
Verbose = False
|
|
DriverDump = False
|
|
CodeDump = False
|
|
|
|
#
|
|
# Tokenizer: This recognizes the tokens which can be keywords, identifiers,
|
|
# numbers or strings plus the punctuation.
|
|
#
|
|
|
|
#
|
|
# Reserved words (keywords) in the form reserved['KEYWORD'] = 'TOKEN'
|
|
#
|
|
|
|
reserved = {
|
|
# Driver keywords
|
|
'DRIVER' : 'DRIVER',
|
|
'VENDOR' : 'VENDOR',
|
|
'DEVICE' : 'DEVICE',
|
|
'PROTOCOL' : 'PROTOCOL',
|
|
'DRIVER_PROPERTY' : 'DRIVER_PROPERTY',
|
|
'CLASS' : 'CLASS',
|
|
'SIMULATION_GROUP' : 'SIMULATION_GROUP',
|
|
'CODE' : 'CODE',
|
|
'ADD_ARGS' : 'ADD_ARGS',
|
|
'MAKE_ARGS' : 'MAKE_ARGS',
|
|
'PROTOCOL_ARGS' : 'PROTOCOL_ARGS',
|
|
'USECREATENODE' : 'USECREATENODE',
|
|
# Group keywords
|
|
'GROUP' : 'GROUP',
|
|
'GROUP_PROPERTY' : 'GROUP_PROPERTY',
|
|
# Variable keywords
|
|
'VAR' : 'VAR',
|
|
'PROPERTY' : 'PROPERTY',
|
|
'CONTROL' : 'CONTROL',
|
|
'DATA' : 'DATA',
|
|
'NXSAVE' : 'NXSAVE',
|
|
'MUTABLE' : 'MUTABLE',
|
|
'READABLE' : 'READABLE',
|
|
'WRITEABLE' : 'WRITEABLE',
|
|
'DRIVEABLE' : 'DRIVEABLE',
|
|
'TRUE' : 'TRUE',
|
|
'FALSE' : 'FALSE',
|
|
# Data Types
|
|
'TYPE' : 'TYPE',
|
|
'FLOAT' : 'FLOAT',
|
|
'INT' : 'INT',
|
|
'TEXT' : 'TEXT',
|
|
'NONE' : 'NONE',
|
|
# Privilege levels
|
|
'PRIV' : 'PRIV',
|
|
'SPY' : 'SPY',
|
|
'USER' : 'USER',
|
|
'MANAGER' : 'MANAGER',
|
|
'READONLY' : 'READONLY',
|
|
'INTERNAL' : 'INTERNAL',
|
|
# Functions and Commands
|
|
'READ_COMMAND' : 'READ_COMMAND',
|
|
'READ_FUNCTION' : 'READ_FUNCTION',
|
|
'FETCH_FUNCTION' : 'FETCH_FUNCTION',
|
|
'WRITE_COMMAND' : 'WRITE_COMMAND',
|
|
'WRITE_FUNCTION' : 'WRITE_FUNCTION',
|
|
'CHECK_FUNCTION' : 'CHECK_FUNCTION',
|
|
'CHECKRANGE_FUNCTION' : 'CHECKRANGE_FUNCTION',
|
|
'CHECKLIMITS_FUNCTION' : 'CHECKLIMITS_FUNCTION',
|
|
'CHECKSTATUS_FUNCTION' : 'CHECKSTATUS_FUNCTION',
|
|
'HALT_FUNCTION' : 'HALT_FUNCTION',
|
|
# Value setting
|
|
'VALUE' : 'VALUE',
|
|
'ALLOWED' : 'ALLOWED',
|
|
'LOWERLIMIT' : 'LOWERLIMIT',
|
|
'UPPERLIMIT' : 'UPPERLIMIT',
|
|
'TOLERANCE' : 'TOLERANCE',
|
|
'UNITS' : 'UNITS',
|
|
}
|
|
|
|
#
|
|
# Tokens list with keyword tokens added at the end
|
|
#
|
|
|
|
tokens = [
|
|
'LBRACE',
|
|
'RBRACE',
|
|
'SLASH',
|
|
'INTEGER',
|
|
'FLOATER',
|
|
'CODE_STRING',
|
|
'TEXT_STRING',
|
|
'EQUALS',
|
|
'ID',
|
|
] + list(reserved.values())
|
|
|
|
#
|
|
# Token rules
|
|
#
|
|
|
|
t_EQUALS = r'='
|
|
t_LBRACE = r'{'
|
|
t_RBRACE = r'}'
|
|
t_SLASH = r'/'
|
|
|
|
def t_TEXT_STRING(t):
|
|
r'\'[^\']+\''
|
|
t.value = t.value[1:-1]
|
|
return t
|
|
|
|
def t_CODE_STRING(t):
|
|
r'\@.*'
|
|
t.value = t.value[1:]
|
|
return t
|
|
|
|
def t_COMMENT(t):
|
|
r'\#.*'
|
|
pass
|
|
# No Return Value. Token discarded
|
|
|
|
def t_FLOATER(t):
|
|
r'\d+\.\d*([eE]\d+)?'
|
|
try:
|
|
t.value = float(t.value)
|
|
except ValueError:
|
|
print "Floating value invalid:", t.value
|
|
t.value = 0.0
|
|
return t
|
|
|
|
def t_INTEGER(t):
|
|
r'\d+'
|
|
try:
|
|
t.value = int(t.value)
|
|
except ValueError:
|
|
print "Integer value too large:", t.value
|
|
t.value = 0
|
|
return t
|
|
|
|
def t_ID(t):
|
|
r'[a-zA-Z_][a-zA-Z_0-9]*'
|
|
t.type = reserved.get(t.value.upper(), 'ID') # Check for reserved words
|
|
# Force reserved words to lower case for map lookup and comparisson
|
|
if t.value.upper() in reserved:
|
|
t.type = reserved[t.value.upper()]
|
|
t.value = t.value.lower()
|
|
return t
|
|
|
|
# Ignored characters
|
|
t_ignore = " \t;"
|
|
|
|
def t_newline(t):
|
|
r'\n+'
|
|
t.lexer.lineno += t.value.count("\n")
|
|
|
|
def t_error(t):
|
|
print("Illegal character '%s'" % t.value[0])
|
|
t.lexer.skip(1)
|
|
|
|
#
|
|
# Parser
|
|
#
|
|
|
|
#
|
|
# Parsing rules
|
|
#
|
|
|
|
#
|
|
# We don't yet have a need for precedence so leave it empty here
|
|
#
|
|
precedence = (
|
|
#('left','PLUS','MINUS'),
|
|
#('left','TIMES','DIVIDE'),
|
|
#('right','UMINUS'),
|
|
)
|
|
|
|
#
|
|
# The head token - it all reduces to this
|
|
#
|
|
def p_driver(p):
|
|
'driver : DRIVER id_or_str EQUALS driver_block'
|
|
p[0] = [{ 'Driver' : {p[2] : p[4]}}]
|
|
if Verbose:
|
|
print "Driver:", p[0]
|
|
global PathName
|
|
global TheDrivers
|
|
TheDrivers[p[2]] = p[4] + [{'PathName':PathName}]
|
|
|
|
def p_driver_block(p):
|
|
'driver_block : LBRACE driver_statement_list RBRACE'
|
|
p[0] = p[2]
|
|
|
|
def p_driver_statement_list(p):
|
|
'''driver_statement_list : driver_statement
|
|
| driver_statement_list driver_statement
|
|
'''
|
|
if len(p) == 2:
|
|
p[0] = [p[1]]
|
|
else:
|
|
p[0] = p[1] + [p[2]]
|
|
|
|
def p_driver_statement(p):
|
|
'''driver_statement : driver_assignment
|
|
| group
|
|
| code
|
|
| driver_property
|
|
'''
|
|
p[0] = p[1]
|
|
|
|
def p_driver_assignment(p):
|
|
'''
|
|
driver_assignment : VENDOR EQUALS id_or_str
|
|
| DEVICE EQUALS id_or_str
|
|
| PROTOCOL EQUALS id_or_str
|
|
| CLASS EQUALS id_or_str
|
|
| SIMULATION_GROUP EQUALS id_or_str
|
|
| USECREATENODE EQUALS true_false
|
|
| ADD_ARGS EQUALS TEXT_STRING
|
|
| MAKE_ARGS EQUALS TEXT_STRING
|
|
| PROTOCOL_ARGS EQUALS TEXT_STRING
|
|
'''
|
|
p[0] = { p[1] : p[3] }
|
|
|
|
#
|
|
# The GROUP block
|
|
#
|
|
def p_group(p):
|
|
'''
|
|
group : GROUP group_id EQUALS LBRACE group_statement_list RBRACE
|
|
'''
|
|
p[0] = { 'Group' : [{'name': p[2]}] + p[5] }
|
|
|
|
def p_group_id(p):
|
|
'''
|
|
group_id : id_or_str
|
|
| empty
|
|
'''
|
|
p[0] = p[1]
|
|
|
|
def p_group_statement_list(p):
|
|
'''
|
|
group_statement_list : group_statement
|
|
| group_statement_list group_statement
|
|
'''
|
|
if len(p) == 2:
|
|
p[0] = [p[1]]
|
|
else:
|
|
p[0] = p[1] + [p[2]]
|
|
|
|
def p_group_statement(p):
|
|
'''group_statement : group_assignment
|
|
| variable
|
|
'''
|
|
p[0] = p[1]
|
|
|
|
def p_group_assignment(p):
|
|
'''group_assignment : group_property
|
|
| var_typ_ass
|
|
| property
|
|
'''
|
|
p[0] = p[1]
|
|
|
|
#
|
|
# The VAR block
|
|
#
|
|
def p_variable(p):
|
|
'''
|
|
variable : VAR id_or_str EQUALS LBRACE variable_statement_list RBRACE
|
|
| VAR id_or_str
|
|
'''
|
|
if len(p) > 3:
|
|
p[0] = { 'Variable' : [{'name' : p[2]}] + p[5] }
|
|
else:
|
|
p[0] = { 'Variable' : [{'name' : p[2]}] }
|
|
|
|
def p_variable_statement_list(p):
|
|
'''variable_statement_list : variable_statement
|
|
| variable_statement_list variable_statement
|
|
'''
|
|
if len(p) == 2:
|
|
p[0] = [p[1]]
|
|
else:
|
|
p[0] = p[1] + [p[2]]
|
|
|
|
def p_variable_statement(p):
|
|
'''variable_statement : var_typ_ass
|
|
| var_val_ass
|
|
| property
|
|
'''
|
|
p[0] = p[1]
|
|
|
|
def p_var_typ_ass(p):
|
|
'''
|
|
var_typ_ass : READABLE EQUALS INTEGER
|
|
| WRITEABLE EQUALS INTEGER
|
|
| READ_COMMAND EQUALS TEXT_STRING
|
|
| READ_FUNCTION EQUALS id_or_str
|
|
| FETCH_FUNCTION EQUALS id_or_str
|
|
| WRITE_COMMAND EQUALS TEXT_STRING
|
|
| WRITE_FUNCTION EQUALS id_or_str
|
|
| CHECK_FUNCTION EQUALS id_or_str
|
|
| CHECKRANGE_FUNCTION EQUALS id_or_str
|
|
| CHECKLIMITS_FUNCTION EQUALS id_or_str
|
|
| CHECKSTATUS_FUNCTION EQUALS id_or_str
|
|
| HALT_FUNCTION EQUALS id_or_str
|
|
| TYPE EQUALS type_code
|
|
| PRIV EQUALS priv_code
|
|
| CONTROL EQUALS true_false
|
|
| DATA EQUALS true_false
|
|
| NXSAVE EQUALS true_false
|
|
| MUTABLE EQUALS true_false
|
|
'''
|
|
p[0] = { p[1] : p[3] }
|
|
|
|
def p_var_path(p):
|
|
'''
|
|
var_path : id_or_str
|
|
| var_path SLASH id_or_str
|
|
'''
|
|
if len(p) == 2:
|
|
p[0] = p[1]
|
|
else:
|
|
p[0] = p[1] + '/' + p[3]
|
|
|
|
def p_var_val_ass(p):
|
|
'''
|
|
var_val_ass : VALUE EQUALS FLOATER
|
|
| VALUE EQUALS TEXT_STRING
|
|
| VALUE EQUALS INTEGER
|
|
| ALLOWED EQUALS TEXT_STRING
|
|
| UNITS EQUALS TEXT_STRING
|
|
| LOWERLIMIT EQUALS number
|
|
| UPPERLIMIT EQUALS number
|
|
| TOLERANCE EQUALS number
|
|
| DRIVEABLE EQUALS var_path
|
|
'''
|
|
p[0] = { p[1] : p[3] }
|
|
|
|
def p_driver_property(p):
|
|
'''
|
|
driver_property : DRIVER_PROPERTY id_or_str EQUALS value
|
|
'''
|
|
p[0] = { 'DriverProperty' : ( p[2], p[4] ) }
|
|
|
|
def p_group_property(p):
|
|
'''
|
|
group_property : GROUP_PROPERTY id_or_str EQUALS value
|
|
'''
|
|
p[0] = { 'GroupProperty' : ( p[2], p[4] ) }
|
|
|
|
def p_property(p):
|
|
'''
|
|
property : PROPERTY id_or_str EQUALS value
|
|
'''
|
|
p[0] = { 'Property' : ( p[2], p[4] ) }
|
|
|
|
def p_value(p):
|
|
'''
|
|
value : number
|
|
| id_or_str
|
|
| true_false
|
|
'''
|
|
p[0] = p[1]
|
|
|
|
def p_number(p):
|
|
'''
|
|
number : INTEGER
|
|
| FLOATER
|
|
'''
|
|
p[0] = p[1]
|
|
|
|
def p_type_code(p):
|
|
'''
|
|
type_code : FLOAT
|
|
| INT
|
|
| TEXT
|
|
| NONE
|
|
'''
|
|
p[0] = p[1]
|
|
|
|
def p_priv_code(p):
|
|
'''
|
|
priv_code : SPY
|
|
| USER
|
|
| MANAGER
|
|
| READONLY
|
|
| INTERNAL
|
|
'''
|
|
p[0] = p[1]
|
|
|
|
def p_true_false(p):
|
|
'''
|
|
true_false : TRUE
|
|
| FALSE
|
|
'''
|
|
p[0] = p[1]
|
|
|
|
#
|
|
# The CODE block
|
|
#
|
|
def p_code(p):
|
|
'''
|
|
code : CODE code_type id_or_str EQUALS LBRACE code_block RBRACE
|
|
'''
|
|
p[0] = { 'Code' : { 'name' : p[3], 'type' : p[2], 'text' : p[6] }}
|
|
|
|
def p_code_type(p):
|
|
'''
|
|
code_type : READ_FUNCTION
|
|
| FETCH_FUNCTION
|
|
| WRITE_FUNCTION
|
|
| CHECK_FUNCTION
|
|
| CHECKRANGE_FUNCTION
|
|
| CHECKLIMITS_FUNCTION
|
|
| CHECKSTATUS_FUNCTION
|
|
| HALT_FUNCTION
|
|
| empty
|
|
'''
|
|
p[0] = p[1]
|
|
|
|
def p_code_block(p):
|
|
'''code_block : empty
|
|
| code_block CODE_STRING
|
|
'''
|
|
if len(p) == 2:
|
|
p[0] = []
|
|
else:
|
|
p[0] = p[1] + [p[2]]
|
|
|
|
def p_id_or_str(p):
|
|
'''
|
|
id_or_str : ID
|
|
| TEXT_STRING
|
|
'''
|
|
p[0] = p[1]
|
|
|
|
def p_empty(p):
|
|
'''
|
|
empty :
|
|
'''
|
|
pass
|
|
|
|
def p_error(t):
|
|
print("Syntax error at '%s'" % t.value), t
|
|
|
|
#
|
|
# This section handles building a driver tree from the Abstract Syntax Tree
|
|
# generated by the parser. The driver tree has all of the defaults and
|
|
# cascading context explicitly stated to make the code generation simpler.
|
|
#
|
|
def init_context():
|
|
global ContextStack, ContextIndex
|
|
ContextStack = [{}]
|
|
ContextIndex = 0
|
|
ContextStack[ContextIndex]['type'] = 'none'
|
|
ContextStack[ContextIndex]['priv'] = 'user'
|
|
ContextStack[ContextIndex]['readable'] = 0
|
|
ContextStack[ContextIndex]['writeable'] = 0
|
|
ContextStack[ContextIndex]['driveable'] = None
|
|
ContextStack[ContextIndex]['control'] = 'true'
|
|
ContextStack[ContextIndex]['data'] = 'true'
|
|
ContextStack[ContextIndex]['mutable'] = 'false'
|
|
ContextStack[ContextIndex]['nxsave'] = 'true'
|
|
ContextStack[ContextIndex]['read_function'] = 'rdValue'
|
|
ContextStack[ContextIndex]['write_function'] = 'setValue'
|
|
ContextStack[ContextIndex]['fetch_function'] = 'getValue'
|
|
ContextStack[ContextIndex]['check_function'] = 'noResponse'
|
|
ContextStack[ContextIndex]['checkrange_function'] = 'checkrange'
|
|
ContextStack[ContextIndex]['path'] = ''
|
|
|
|
def push_context():
|
|
global ContextStack, ContextIndex
|
|
ContextIndex = ContextIndex + 1
|
|
if len(ContextStack) <= ContextIndex:
|
|
ContextStack.append({})
|
|
ContextStack[ContextIndex] = {}
|
|
for k in ContextStack[ContextIndex - 1].keys():
|
|
ContextStack[ContextIndex][k] = ContextStack[ContextIndex - 1][k]
|
|
|
|
def pop_context():
|
|
global ContextStack, ContextIndex
|
|
ContextIndex = ContextIndex - 1
|
|
|
|
def build_code(MyDriver, p):
|
|
if Verbose:
|
|
print 'Code:', p
|
|
print "Function:", p['name']
|
|
MyCode = {}
|
|
MyCode['name'] = p['name']
|
|
MyCode['reference_count'] = 0
|
|
if 'type' in p:
|
|
MyCode['type'] = p['type']
|
|
MyCode['text'] = p['text']
|
|
if Verbose:
|
|
for line in p['text']:
|
|
print " Line:", line
|
|
return MyCode
|
|
|
|
def build_variable(MyDriver, p):
|
|
global FunctionTypes
|
|
global DriveableFunctionTypes
|
|
if Verbose:
|
|
print 'Variable:', p
|
|
MyVar = {}
|
|
# Copy items for this variable
|
|
for item in p:
|
|
if Verbose:
|
|
print "Variable Item:", item
|
|
for key in item.keys():
|
|
if key == 'Property':
|
|
if not 'Property' in MyVar:
|
|
MyVar['Property'] = {}
|
|
MyVar['Property'][item[key][0]] = item[key][1]
|
|
else:
|
|
MyVar[key] = item[key]
|
|
# copy the defaults for missing items
|
|
for key in ContextStack[ContextIndex]:
|
|
if key == 'Property':
|
|
if 'Property' not in MyVar:
|
|
MyVar['Property'] = {}
|
|
for key2 in ContextStack[ContextIndex][key]:
|
|
if key2 not in MyVar['Property']:
|
|
MyVar['Property'][key2] = ContextStack[ContextIndex][key][key2]
|
|
elif not key in MyVar:
|
|
MyVar[key] = ContextStack[ContextIndex][key]
|
|
# if this variable is driveable
|
|
if 'driveable' in MyVar and MyVar['driveable']:
|
|
# insert defaults for missing driveable functions
|
|
if 'checklimits_function' not in MyVar:
|
|
MyVar['checklimits_function'] = 'checklimits'
|
|
if 'checkstatus_function' not in MyVar:
|
|
MyVar['checkstatus_function'] = 'checkstatus'
|
|
if 'halt_function' not in MyVar:
|
|
MyVar['halt_function'] = 'halt'
|
|
|
|
for func in FunctionTypes + DriveableFunctionTypes:
|
|
if func in MyVar and MyVar[func] != 'none':
|
|
if Verbose:
|
|
print 'Var:', MyVar['name'], 'Func:', func, '=', MyVar[func]
|
|
if MyVar[func] not in MyDriver['Funcs']:
|
|
MyDriver['Funcs'][MyVar[func]] = { 'type' : func, 'text' : [], 'reference_count' : 0 }
|
|
if Verbose:
|
|
print MyVar['name'], 'Add func ' + MyVar[func], MyDriver['Funcs'][MyVar[func]]
|
|
elif not MyDriver['Funcs'][MyVar[func]]['type'] == func:
|
|
# allow override of type none else error message
|
|
if not MyDriver['Funcs'][MyVar[func]]['type']:
|
|
if Verbose:
|
|
print MyVar['name'], 'Mod func type:', MyDriver['Funcs'][MyVar[func]], '= ' + func
|
|
MyDriver['Funcs'][MyVar[func]]['type'] = func
|
|
else:
|
|
# TODO FIXME error message
|
|
print 'Error: Function type mismatch: var = ' + str(MyVar) + ', code = ' + str(MyDriver['Funcs'][MyVar[func]]) + ', func = ' + str(func)
|
|
MyDriver['Funcs'][MyVar[func]]['reference_count'] += 1
|
|
if Verbose:
|
|
print '==>>MyVar:', MyVar
|
|
return MyVar
|
|
|
|
def build_group(MyDriver, p):
|
|
if Verbose:
|
|
print 'Group:', p[0]['name'], p
|
|
push_context()
|
|
MyGroup = {}
|
|
MyGroup['Groups'] = {}
|
|
MyGroup['Vars'] = {}
|
|
# the sequence of both variables and non-variables is significant
|
|
# Therefore, they have to be processed in a single sequence
|
|
if p[0]['name']:
|
|
if len(ContextStack[ContextIndex]['path']) > 0:
|
|
ContextStack[ContextIndex]['path'] += '/'
|
|
ContextStack[ContextIndex]['path'] += p[0]['name']
|
|
for item in p:
|
|
if 'Variable' in item:
|
|
MyVar = build_variable(MyDriver, item['Variable'])
|
|
MyGroup['Vars'][MyVar['name']] = MyVar
|
|
else:
|
|
if Verbose:
|
|
print "Group Item:", item
|
|
if 'GroupProperty' in item:
|
|
if 'GroupProperty' not in MyGroup:
|
|
MyGroup['GroupProperty'] = {}
|
|
MyGroup['GroupProperty'][item['GroupProperty'][0]] = item['GroupProperty'][1]
|
|
elif 'Property' in item:
|
|
if 'Property' not in MyGroup:
|
|
MyGroup['Property'] = {}
|
|
MyGroup['Property'][item['Property'][0]] = item['Property'][1]
|
|
if 'Property' not in ContextStack[ContextIndex]:
|
|
ContextStack[ContextIndex]['Property'] = {}
|
|
ContextStack[ContextIndex]['Property'][item['Property'][0]] = item['Property'][1]
|
|
else:
|
|
for key in item:
|
|
MyGroup[key] = item[key]
|
|
if key in ContextStack[ContextIndex]:
|
|
ContextStack[ContextIndex][key] = item[key]
|
|
pop_context()
|
|
return MyGroup
|
|
|
|
def build_driver(MyDriver, TheTree):
|
|
if Verbose:
|
|
print "TheTree:", TheTree
|
|
init_context()
|
|
for item in [x for x in TheTree if 'Code' in x]:
|
|
MyCode = build_code(MyDriver, item['Code'])
|
|
MyDriver['Funcs'][MyCode['name']] = MyCode
|
|
for item in [x for x in TheTree if 'Group' in x]:
|
|
MyGroup = build_group(MyDriver, item['Group'])
|
|
MyDriver['Groups'][MyGroup['name']] = MyGroup
|
|
for item in TheTree:
|
|
if Verbose:
|
|
print "Driver Item:", item
|
|
if 'Group' in item:
|
|
continue
|
|
elif 'Code' in item:
|
|
continue
|
|
else:
|
|
if 'DriverProperty' in item:
|
|
if 'DriverProperty' not in MyDriver:
|
|
MyDriver['DriverProperty'] = {}
|
|
MyDriver['DriverProperty'][item['DriverProperty'][0]] = item['DriverProperty'][1]
|
|
continue
|
|
for key in item:
|
|
MyDriver[key] = item[key]
|
|
if Verbose:
|
|
print "MyDriver:", MyDriver
|
|
#
|
|
# Driver Dump Functions
|
|
#
|
|
def dump_driver_vars(vars):
|
|
global FunctionTypes
|
|
global DriveableFunctionTypes
|
|
for item in sorted(vars):
|
|
print ' VAR %s = {' % item
|
|
for subitem in sorted([i for i in vars[item] if i not in FunctionTypes + DriveableFunctionTypes]):
|
|
print ' %s =' % subitem, vars[item][subitem]
|
|
for subitem in sorted([i for i in vars[item] if i in FunctionTypes]):
|
|
print ' %s =' % subitem, vars[item][subitem]
|
|
for subitem in sorted([i for i in vars[item] if i in DriveableFunctionTypes]):
|
|
print ' %s =' % subitem, vars[item][subitem]
|
|
print ' }'
|
|
def dump_driver_groups(groups):
|
|
for item in sorted(groups):
|
|
if item:
|
|
print ' GROUP ' + item + ' = {'
|
|
else:
|
|
print ' GROUP = {'
|
|
for subitem in sorted([x for x in groups[item] if not x in ['Groups', 'Vars']]):
|
|
print ' ', subitem, '=', groups[item][subitem]
|
|
dump_driver_vars(groups[item]['Vars'])
|
|
print ' }'
|
|
|
|
def dump_driver_funcs(funcs):
|
|
for item in sorted(funcs):
|
|
if 'type' in funcs[item] and funcs[item]['type']:
|
|
print ' CODE ' + funcs[item]['type'] + ' ' + item + ' = {'
|
|
else:
|
|
print ' CODE ' + item + ' = {'
|
|
for line in funcs[item]['text']:
|
|
print ' @%s' % line
|
|
print ' }'
|
|
|
|
def dump_driver(MyDriver):
|
|
print 'DRIVER ' + MyDriver['name'] + ' = {'
|
|
for item in sorted([x for x in MyDriver if x not in ['Groups', 'Funcs']]):
|
|
print ' ' + item + ' =', MyDriver[item]
|
|
#print 'Groups:', MyDriver['Groups']
|
|
dump_driver_groups(MyDriver['Groups'])
|
|
#print 'Funcs:', MyDriver['Funcs']
|
|
dump_driver_funcs(MyDriver['Funcs'])
|
|
print '}'
|
|
#
|
|
# Code Generation Functions
|
|
#
|
|
def emit(txt):
|
|
global NumberOfLinesOut
|
|
NumberOfLinesOut += len(txt)
|
|
for line in txt:
|
|
fdo.write(line)
|
|
if not line.endswith('\n'):
|
|
fdo.write('\n')
|
|
|
|
def put_preamble(MyDriver):
|
|
txt = []
|
|
txt += ['# Generated driver for %s' % MyDriver['name']]
|
|
txt += ['# vim: tabstop=8 softtabstop=2 shiftwidth=2 nocindent smartindent']
|
|
txt += ['#']
|
|
txt += ['']
|
|
txt += ['namespace eval %s {' % MyDriver['namespace']]
|
|
txt += [' set debug_threshold 0']
|
|
txt += ['}']
|
|
txt += ['']
|
|
txt += ['proc %s::debug_log {debug_level debug_string} {' % MyDriver['namespace']]
|
|
txt += [' if {${debug_level} >= ${%s::debug_threshold}} {' % MyDriver['namespace']]
|
|
txt += [' set fd [open "/tmp/%s.log" "a"]' % MyDriver['name']]
|
|
txt += [' set line "[clock format [clock seconds] -format "%T"] ${debug_string}"']
|
|
txt += [' puts ${fd} "${line}"']
|
|
txt += [' close ${fd}']
|
|
txt += [' }']
|
|
txt += ['}']
|
|
emit(txt)
|
|
|
|
def put_write_function(MyDriver, func):
|
|
txt = ['']
|
|
txt += ['# function to write a parameter value on a device']
|
|
txt += ['proc %s::%s {tc_root nextState cmd_str} {' % (MyDriver['namespace'], func)]
|
|
txt += [' debug_log 1 "%s tc_root=${tc_root} sct=[sct] cmd=${cmd_str}"' % func]
|
|
txt += [' if { [hpropexists [sct] geterror] } {']
|
|
txt += [' hdelprop [sct] geterror']
|
|
txt += [' }']
|
|
txt += [' set par [sct target]']
|
|
txt += [' set cmd "${cmd_str}${par}"']
|
|
if func in MyDriver['Funcs'] and len(MyDriver['Funcs'][func]['text']) > 0:
|
|
txt += ['# hook code starts']
|
|
txt += MyDriver['Funcs'][func]['text']
|
|
txt += ['# hook code ends']
|
|
txt += [' if { [hpropexists [sct] geterror] } {']
|
|
txt += [' debug_log 1 "[sct] error: [sct geterror]"']
|
|
txt += [' return -code error "[sct geterror]"']
|
|
txt += [' }']
|
|
txt += [' debug_log 1 "%s sct send ${cmd}"' % func]
|
|
txt += [' sct send "${cmd}"']
|
|
txt += [' return ${nextState}']
|
|
txt += ['}']
|
|
emit(txt)
|
|
|
|
def put_check_function(MyDriver, func):
|
|
txt = ['']
|
|
txt += ['# function to check the write parameter on a device']
|
|
txt += ['proc %s::%s {tc_root} {' % (MyDriver['namespace'], func)]
|
|
txt += [' debug_log 1 "%s tc_root=${tc_root} sct=[sct] resp=[sct result]"' % func]
|
|
if func in MyDriver['Funcs'] and len(MyDriver['Funcs'][func]['text']) > 0:
|
|
txt += ['# hook code starts']
|
|
txt += MyDriver['Funcs'][func]['text']
|
|
txt += ['# hook code ends']
|
|
txt += [' return "idle"']
|
|
txt += ['}']
|
|
emit(txt)
|
|
|
|
def put_fetch_function(MyDriver, func):
|
|
txt = ['']
|
|
txt += ['# function to request the read of a parameter on a device']
|
|
txt += ['proc %s::%s {tc_root nextState cmd_str} {' % (MyDriver['namespace'], func)]
|
|
txt += [' debug_log 1 "%s tc_root=${tc_root} sct=[sct] cmd=${cmd_str}"' % func]
|
|
txt += [' if { [hpropexists [sct] geterror] } {']
|
|
txt += [' hdelprop [sct] geterror']
|
|
txt += [' }']
|
|
txt += [' set cmd "${cmd_str}"']
|
|
if func in MyDriver['Funcs'] and len(MyDriver['Funcs'][func]['text']) > 0:
|
|
txt += ['# hook code starts']
|
|
txt += MyDriver['Funcs'][func]['text']
|
|
txt += ['# hook code ends']
|
|
txt += [' if { [hpropexists [sct] geterror] } {']
|
|
txt += [' debug_log 1 "[sct] error: [sct geterror]"']
|
|
txt += [' return -code error "[sct geterror]"']
|
|
txt += [' }']
|
|
txt += [' debug_log 1 "%s sct send ${cmd}"' % func]
|
|
txt += [' sct send "${cmd}"']
|
|
txt += [' return ${nextState}']
|
|
txt += ['}']
|
|
emit(txt)
|
|
|
|
def put_read_function(MyDriver, func):
|
|
txt = ['']
|
|
txt += ['# function to parse the read of a parameter on a device']
|
|
txt += ['proc %s::%s {tc_root} {' % (MyDriver['namespace'], func)]
|
|
txt += [' debug_log 1 "%s tc_root=${tc_root} sct=[sct] result=[sct result]"' % func]
|
|
txt += [' if { [hpropexists [sct] geterror] } {']
|
|
txt += [' hdelprop [sct] geterror']
|
|
txt += [' }']
|
|
txt += [' set data [sct result]']
|
|
txt += [' set nextState "idle"']
|
|
txt += [' if {[string equal -nocase -length 7 ${data} "ASCERR:"]} {']
|
|
txt += [' # the protocol driver has reported an error']
|
|
txt += [' sct geterror "${data}"']
|
|
txt += [' return -code error "[sct geterror]"']
|
|
txt += [' }']
|
|
if func in MyDriver['Funcs'] and len(MyDriver['Funcs'][func]['text']) > 0:
|
|
txt += ['# hook code starts']
|
|
txt += MyDriver['Funcs'][func]['text']
|
|
txt += ['# hook code ends']
|
|
txt += [' if { [hpropexists [sct] geterror] } {']
|
|
txt += [' debug_log 1 "[sct] error: [sct geterror]"']
|
|
txt += [' return -code error "[sct geterror]"']
|
|
txt += [' }']
|
|
txt += [' if { ${data} != [sct oldval] } {']
|
|
txt += [' debug_log 1 "[sct] changed to new:${data}, from old:[sct oldval]"']
|
|
txt += [' sct oldval ${data}']
|
|
txt += [' sct update ${data}']
|
|
txt += [' sct utime readtime']
|
|
txt += [' }']
|
|
txt += [' return ${nextState}']
|
|
txt += ['}']
|
|
emit(txt)
|
|
|
|
def put_checkrange_function(MyDriver, func):
|
|
txt = ['']
|
|
txt += ['# check function for hset change']
|
|
txt += ['proc %s::%s {tc_root} {' % (MyDriver['namespace'], func)]
|
|
txt += [' debug_log 1 "%s tc_root=${tc_root} sct=[sct] target=[sct target]"' % func]
|
|
txt += [' set setpoint [sct target]']
|
|
txt += [' if { [hpropexists [sct] lowerlimit] } {']
|
|
txt += [' set lolimit [sct lowerlimit]']
|
|
txt += [' } else {']
|
|
txt += [' # lowerlimit not set, use target']
|
|
txt += [' set lolimit [sct target]']
|
|
txt += [' }']
|
|
txt += [' if { [hpropexists [sct] upperlimit] } {']
|
|
txt += [' set hilimit [sct upperlimit]']
|
|
txt += [' } else {']
|
|
txt += [' # upperlimit not set, use target']
|
|
txt += [' set hilimit [sct target]']
|
|
txt += [' }']
|
|
if func in MyDriver['Funcs'] and len(MyDriver['Funcs'][func]['text']) > 0:
|
|
txt += ['# hook code starts']
|
|
txt += MyDriver['Funcs'][func]['text']
|
|
txt += ['# hook code ends']
|
|
txt += [' if { ${setpoint} < ${lolimit} || ${setpoint} > ${hilimit} } {']
|
|
txt += [' error "setpoint ${setpoint} violates limits (${lolimit}..${hilimit}) on [sct]"']
|
|
txt += [' }']
|
|
txt += [' return OK']
|
|
txt += ['}']
|
|
emit(txt)
|
|
|
|
def put_checklimits_function(MyDriver, func):
|
|
txt = ['']
|
|
txt += ['# checklimits function for driveable interface']
|
|
txt += ['proc %s::%s {tc_root} {' % (MyDriver['namespace'], func)]
|
|
txt += [' debug_log 1 "%s tc_root=${tc_root} sct=[sct] target=[sct target]"' % func]
|
|
txt += [' set setpoint [sct target]']
|
|
txt += [' if { [hpropexists [sct] lowerlimit] } {']
|
|
txt += [' set lolimit [sct lowerlimit]']
|
|
txt += [' } else {']
|
|
txt += [' # lowerlimit not set, use target']
|
|
txt += [' set lolimit [sct target]']
|
|
txt += [' }']
|
|
txt += [' if { [hpropexists [sct] upperlimit] } {']
|
|
txt += [' set hilimit [sct upperlimit]']
|
|
txt += [' } else {']
|
|
txt += [' # upperlimit not set, use target']
|
|
txt += [' set hilimit [sct target]']
|
|
txt += [' }']
|
|
if func in MyDriver['Funcs'] and len(MyDriver['Funcs'][func]['text']) > 0:
|
|
txt += ['# hook code starts']
|
|
txt += MyDriver['Funcs'][func]['text']
|
|
txt += ['# hook code ends']
|
|
txt += [' if { ${setpoint} < ${lolimit} || ${setpoint} > ${hilimit} } {']
|
|
txt += [' sct driving 0']
|
|
txt += [' error "setpoint ${setpoint} violates limits (${lolimit}..${hilimit}) on [sct]"']
|
|
txt += [' }']
|
|
txt += [' return OK']
|
|
txt += ['}']
|
|
emit(txt)
|
|
|
|
def put_checkstatus_function(MyDriver, func):
|
|
txt = ['']
|
|
txt += ['# checkstatus function for driveable interface']
|
|
txt += ['proc %s::%s {tc_root} {' % (MyDriver['namespace'], func)]
|
|
if func in MyDriver['Funcs'] and len(MyDriver['Funcs'][func]['text']) > 0:
|
|
txt += ['# hook code starts']
|
|
txt += MyDriver['Funcs'][func]['text']
|
|
txt += ['# hook code ends']
|
|
txt += [' if {[sct driving]} {']
|
|
txt += [' set sp "[sct target]"']
|
|
txt += [' set pv "[hget ${tc_root}/[sct driveable]]"']
|
|
txt += [' if { ${pv} > ${sp} - [sct tolerance] && ${pv} < ${sp} + [sct tolerance] } {']
|
|
txt += [' sct driving 0']
|
|
txt += [' return "idle"']
|
|
txt += [' }']
|
|
txt += [' return "busy"']
|
|
txt += [' } else {']
|
|
txt += [' return "idle"']
|
|
txt += [' }']
|
|
txt += ['}']
|
|
emit(txt)
|
|
|
|
def put_halt_function(MyDriver, func):
|
|
txt = ['']
|
|
txt += ['# halt function for driveable interface']
|
|
txt += ['proc %s::%s {tc_root} {' % (MyDriver['namespace'], func)]
|
|
txt += [' debug_log 1 "%s tc_root=${tc_root} sct=[sct] driving=[sct driving]"' % func]
|
|
txt += [' ### TODO hset [sct] [hval [sct]]']
|
|
if func in MyDriver['Funcs'] and len(MyDriver['Funcs'][func]['text']) > 0:
|
|
txt += ['# hook code starts']
|
|
txt += MyDriver['Funcs'][func]['text']
|
|
txt += ['# hook code ends']
|
|
txt += [' sct driving 0']
|
|
txt += [' return "idle"']
|
|
txt += ['}']
|
|
emit(txt)
|
|
|
|
def put_create_node(MyDriver):
|
|
txt = ['']
|
|
txt += ['##']
|
|
txt += ['# @brief createNode() creates a node for the given nodename with the properties given']
|
|
txt += ['#']
|
|
txt += ['# @param scobj_hpath string variable holding the path to the object\'s base node in sics (/sample/tc1)']
|
|
txt += ['# @param sct_controller name of the scriptcontext object (typically sct_xxx_yyy)']
|
|
txt += ['# @param cmdGroup subdirectory (below /sample/tc*/) in which the node is to be created']
|
|
txt += ['# @param varName name of the actual node typically representing one device command']
|
|
txt += ['# @param readable set to 1 if the node represents a query command, 0 if it is not']
|
|
txt += ['# @param writable set to 1 if the node represents a request for a change in settings sent to the device']
|
|
txt += ['# @param drivable if set to 1 it prepares the node to provide a drivable interface']
|
|
txt += ['# @param dataType data type of the node, must be one of none, int, float, text']
|
|
txt += ['# @param permission defines what user group may read/write to this node (is one of spy, user, manager)']
|
|
txt += ['# @param rdCmd actual device query command to be sent to the device']
|
|
txt += ['# @param rdFunc nextState Function to be called after the getValue function, typically rdValue()']
|
|
txt += ['# @param wrCmd actual device write command to be sent to the device']
|
|
txt += ['# @param wrFunc Function to be called to send the wrCmd to the device, typically setValue()']
|
|
txt += ['# @param allowedValues allowed values for the node data - does not permit other']
|
|
txt += ['# @param klass Nexus class name (?)']
|
|
txt += ['# @return OK']
|
|
txt += ['proc %s::createNode {scobj_hpath sct_controller\\' %MyDriver['namespace']]
|
|
txt += [' cmdGroup varName\\']
|
|
txt += [' readable writable drivable\\']
|
|
txt += [' dataType permission\\']
|
|
txt += [' rdCmd rdFunc\\']
|
|
txt += [' wrCmd wrFunc\\']
|
|
txt += [' allowedValues klass} {']
|
|
txt += ['']
|
|
txt += [' set catch_status [ catch {']
|
|
txt += [' set ns "[namespace current]"']
|
|
txt += [' set nodeName "${scobj_hpath}/${cmdGroup}/${varName}"']
|
|
txt += [' if {1 > [string length ${cmdGroup}]} {']
|
|
txt += [' set nodeName "${scobj_hpath}/${varName}"']
|
|
txt += [' }']
|
|
txt += [' debug_log 1 "Creating node ${nodeName}"']
|
|
txt += [' hfactory ${nodeName} plain ${permission} ${dataType}']
|
|
txt += [' if {${readable} > 0} {']
|
|
txt += [' # the node is readable so set it up to be polled using the rdFunc']
|
|
txt += [' # rdFunc is getValueFunc.rdValueFunc with both explicit functions']
|
|
txt += [' # or rdValueFunc where "getValue" is the implied getValueFunc']
|
|
txt += [' set parts [split "${rdFunc}" "."]']
|
|
txt += [' if { [llength ${parts}] == 2 } {']
|
|
txt += [' set func_name [lindex ${parts} 0]']
|
|
txt += [' set next_state [lindex ${parts} 1]']
|
|
txt += [' } else {']
|
|
txt += [' set func_name "getValue"']
|
|
txt += [' set next_state [lindex ${parts} 0]']
|
|
txt += [' }']
|
|
txt += [' hsetprop ${nodeName} read ${ns}::${func_name} ${scobj_hpath} ${next_state} ${rdCmd}']
|
|
txt += [' hsetprop ${nodeName} ${next_state} ${ns}::${next_state} ${scobj_hpath}']
|
|
txt += [' # set the poll rate as a period in seconds']
|
|
txt += [' # TODO allow directly settable value in seconds']
|
|
txt += [' set poll_period 5']
|
|
txt += [' if { ${readable} >= 0 && ${readable} <= 300 } {']
|
|
txt += [' set poll_period [expr {int(${readable})}]']
|
|
txt += [' }']
|
|
txt += [' debug_log 1 "Registering node ${nodeName} for poll at ${poll_period} seconds"']
|
|
txt += [' ${sct_controller} poll ${nodeName} ${poll_period}']
|
|
txt += [' }']
|
|
txt += [' if {${writable} > 0} {']
|
|
txt += [' # the node is writable so set it up to invoke a callback when written']
|
|
txt += [' # rdFunc is putValueFunc.chkWriteFunc with both explicit functions']
|
|
txt += [' # or putValueFunc where "noResponse" is the implied chkWriteFunc']
|
|
txt += [' set parts [split "${wrFunc}" "."]']
|
|
txt += [' if { [llength ${parts}] == 2 } {']
|
|
txt += [' set func_name [lindex ${parts} 0]']
|
|
txt += [' set next_state [lindex ${parts} 1]']
|
|
txt += [' } else {']
|
|
txt += [' set func_name [lindex ${parts} 0]']
|
|
txt += [' set next_state "noResponse"']
|
|
txt += [' }']
|
|
txt += [' hsetprop ${nodeName} write ${ns}::${func_name} ${scobj_hpath} ${next_state} ${wrCmd}']
|
|
txt += [' hsetprop ${nodeName} ${next_state} ${ns}::${next_state} ${scobj_hpath}']
|
|
txt += [' hsetprop ${nodeName} writestatus UNKNOWN']
|
|
txt += [' debug_log 1 "Registering node ${nodeName} for write callback"']
|
|
txt += [' ${sct_controller} write ${nodeName}']
|
|
txt += [' }']
|
|
txt += [' # Initialise the previous value to test against']
|
|
txt += [' switch -exact ${dataType} {']
|
|
txt += [' "none" { }']
|
|
txt += [' "int" { hsetprop ${nodeName} oldval -1 }']
|
|
txt += [' "float" { hsetprop ${nodeName} oldval -1.0 }']
|
|
txt += [' default { hsetprop ${nodeName} oldval UNKNOWN }']
|
|
txt += [' }']
|
|
txt += [' # Set the allowed values property']
|
|
txt += [' if {1 < [string length ${allowedValues}]} {']
|
|
txt += [' hsetprop ${nodeName} values ${allowedValues}']
|
|
txt += [' }']
|
|
txt += [' # Drive adapter interface']
|
|
txt += [' # TODO make it a separate function and pass in all this stuff']
|
|
txt += [' if {${drivable} > 0} {']
|
|
txt += [' hsetprop ${nodeName} check ${ns}::check ${scobj_hpath}']
|
|
txt += [' hsetprop ${nodeName} driving 0']
|
|
txt += [' hsetprop ${nodeName} checklimits ${ns}::checklimits ${scobj_hpath}']
|
|
txt += [' hsetprop ${nodeName} checkstatus ${ns}::checkstatus ${scobj_hpath}']
|
|
txt += [' hsetprop ${nodeName} halt ${ns}::halt ${scobj_hpath}']
|
|
txt += [' }']
|
|
txt += [' } catch_message ]']
|
|
txt += [' handle_exception ${catch_status} ${catch_message} "in [namespace current]::createNode"']
|
|
txt += [' return OK']
|
|
txt += ['}']
|
|
emit(txt)
|
|
|
|
def get_one_var(group_name, MyVar):
|
|
lst = [ group_name ]
|
|
lst.append(MyVar['name'])
|
|
if 'readable' in MyVar:
|
|
lst.append(str(MyVar['readable']))
|
|
else:
|
|
lst.append('0')
|
|
if 'writeable' in MyVar:
|
|
lst.append(str(MyVar['writeable']))
|
|
else:
|
|
lst.append('0')
|
|
if 'driveable' in MyVar:
|
|
lst.append(str(MyVar['driveable']))
|
|
else:
|
|
lst.append('0')
|
|
if 'type' in MyVar:
|
|
lst.append(MyVar['type'])
|
|
else:
|
|
lst.append('text')
|
|
if 'priv' in MyVar:
|
|
lst.append(MyVar['priv'])
|
|
else:
|
|
lst.append('user')
|
|
if 'read_command' in MyVar:
|
|
lst.append('{' + MyVar['read_command'] + '}')
|
|
else:
|
|
lst.append('{}')
|
|
if 'read_function' in MyVar:
|
|
if not MyVar['fetch_function'] == 'none':
|
|
lst.append('{' + MyVar['fetch_function'] + '.' + MyVar['read_function'] + '}')
|
|
else:
|
|
lst.append('{' + MyVar['read_function'] + '}')
|
|
else:
|
|
lst.append('{}')
|
|
if 'write_command' in MyVar:
|
|
lst.append('{' + MyVar['write_command'] + '}')
|
|
else:
|
|
lst.append('{}')
|
|
if 'write_function' in MyVar:
|
|
if not MyVar['check_function'] == 'none':
|
|
lst.append('{' + MyVar['check_function'] + '.' + MyVar['write_function'] + '}')
|
|
else:
|
|
lst.append('{' + MyVar['write_function'] + '}')
|
|
else:
|
|
lst.append('{}')
|
|
if 'allowed' in MyVar:
|
|
lst.append('{' + MyVar['allowed'] + '}')
|
|
else:
|
|
lst.append('{}')
|
|
return lst
|
|
|
|
def put_group(MyDriver, MyGroup):
|
|
readable_or_writeable = False
|
|
txt = []
|
|
if MyGroup['name']:
|
|
txt += ['']
|
|
txt += [' hfactory ${scobj_hpath}/%s plain spy none' % MyGroup['name']]
|
|
if 'GroupProperty' in MyGroup:
|
|
for key in sorted(MyGroup['GroupProperty']):
|
|
txt += [' hsetprop ${scobj_hpath}/%s %s "%s"' % (MyGroup['name'], key, MyGroup['GroupProperty'][key])]
|
|
groupname = MyGroup['name'] + '/'
|
|
else:
|
|
groupname = ''
|
|
for var in sorted(MyGroup['Vars']):
|
|
txt += ['']
|
|
MyVar = MyGroup['Vars'][var]
|
|
nodename = groupname + MyVar['name']
|
|
# Check driveable attributes are present if required
|
|
if MyVar['driveable']:
|
|
for attr in ('lowerlimit', 'upperlimit', 'tolerance'):
|
|
if attr not in MyVar:
|
|
msg = 'Driveable: %s does not have required attribute: %s' % (nodename, attr)
|
|
print 'Warning:', msg
|
|
txt += [' # Warning: ' + msg]
|
|
txt += [' hfactory ${scobj_hpath}/%s plain %s %s' % (nodename, MyVar['priv'], MyVar['type'])]
|
|
if MyVar['readable'] > 0:
|
|
readable_or_writeable = True
|
|
fetch_func = MyVar['fetch_function']
|
|
if fetch_func == 'none':
|
|
fetch_func = 'getValue'
|
|
read_func = MyVar['read_function']
|
|
read_command = MyVar['read_command']
|
|
txt += [' hsetprop ${scobj_hpath}/%s read ${ns}::%s ${scobj_hpath} %s {%s}' % (nodename, fetch_func, read_func, read_command)]
|
|
txt += [' hsetprop ${scobj_hpath}/%s %s ${ns}::%s ${scobj_hpath}' % (nodename, read_func, read_func)]
|
|
if MyVar['writeable'] > 0 or MyVar['driveable']:
|
|
readable_or_writeable = True
|
|
check_func = MyVar['check_function']
|
|
checkrange_func = MyVar['checkrange_function']
|
|
write_func = MyVar['write_function']
|
|
if 'write_command' in MyVar:
|
|
write_command = MyVar['write_command']
|
|
else:
|
|
write_command = ''
|
|
txt += [' hsetprop ${scobj_hpath}/%s write ${ns}::%s ${scobj_hpath} %s {%s}' % (nodename, write_func, check_func, write_command)]
|
|
txt += [' hsetprop ${scobj_hpath}/%s %s ${ns}::%s ${scobj_hpath}' % (nodename, check_func, check_func)]
|
|
txt += [' hsetprop ${scobj_hpath}/%s check ${ns}::%s ${scobj_hpath}' % (nodename, checkrange_func)]
|
|
if MyVar['driveable']:
|
|
halt_func = MyVar['halt_function']
|
|
checklimits_func = MyVar['checklimits_function']
|
|
checkstatus_func = MyVar['checkstatus_function']
|
|
txt += [' hsetprop ${scobj_hpath}/%s driving 0' % nodename]
|
|
txt += [' hsetprop ${scobj_hpath}/%s checklimits ${ns}::%s ${scobj_hpath}' % (nodename, checklimits_func)]
|
|
txt += [' hsetprop ${scobj_hpath}/%s checkstatus ${ns}::%s ${scobj_hpath}' % (nodename, checkstatus_func)]
|
|
txt += [' hsetprop ${scobj_hpath}/%s halt ${ns}::%s ${scobj_hpath}' % (nodename, halt_func)]
|
|
txt += [' hsetprop ${scobj_hpath}/%s driveable %s' % (nodename, MyVar['driveable'])]
|
|
if 'control' in MyVar:
|
|
txt += [' hsetprop ${scobj_hpath}/%s control %s' % (nodename, MyVar['control'])]
|
|
if 'data' in MyVar:
|
|
txt += [' hsetprop ${scobj_hpath}/%s data %s' % (nodename, MyVar['data'])]
|
|
if 'mutable' in MyVar:
|
|
txt += [' hsetprop ${scobj_hpath}/%s mutable %s' % (nodename, MyVar['mutable'])]
|
|
if 'nxsave' in MyVar:
|
|
txt += [' hsetprop ${scobj_hpath}/%s nxsave %s' % (nodename, MyVar['nxsave'])]
|
|
if 'lowerlimit' in MyVar:
|
|
txt += [' hsetprop ${scobj_hpath}/%s lowerlimit %s' % (nodename, MyVar['lowerlimit'])]
|
|
if 'upperlimit' in MyVar:
|
|
txt += [' hsetprop ${scobj_hpath}/%s upperlimit %s' % (nodename, MyVar['upperlimit'])]
|
|
if 'tolerance' in MyVar:
|
|
txt += [' hsetprop ${scobj_hpath}/%s tolerance %s' % (nodename, MyVar['tolerance'])]
|
|
if 'units' in MyVar:
|
|
txt += [' hsetprop ${scobj_hpath}/%s units %s' % (nodename, MyVar['units'])]
|
|
if 'allowed' in MyVar:
|
|
txt += [' hsetprop ${scobj_hpath}/%s values %s' % (nodename, MyVar['allowed'])]
|
|
if 'value' in MyVar:
|
|
txt += [' hsetprop ${scobj_hpath}/%s oldval %s' % (nodename, MyVar['value'])]
|
|
txt += [' hset ${scobj_hpath}/%s %s' % (nodename, MyVar['value'])]
|
|
else:
|
|
if MyVar['type'] == 'none':
|
|
pass
|
|
elif MyVar['type'] == 'int':
|
|
txt += [' hsetprop ${scobj_hpath}/%s oldval 0' % nodename]
|
|
elif MyVar['type'] == 'float':
|
|
txt += [' hsetprop ${scobj_hpath}/%s oldval 0.0' % nodename]
|
|
else:
|
|
txt += [' hsetprop ${scobj_hpath}/%s oldval UNKNOWN' % nodename]
|
|
if 'Property' in MyVar:
|
|
for key in sorted(MyVar['Property']):
|
|
txt += [' hsetprop ${scobj_hpath}/%s %s "%s"' % (nodename, key, MyVar['Property'][key])]
|
|
if not MyGroup['name']:
|
|
if 'GroupProperty' in MyGroup:
|
|
txt += ['']
|
|
for key in sorted(MyGroup['GroupProperty']):
|
|
txt += [' hsetprop ${scobj_hpath} %s "%s"' % (key, MyGroup['GroupProperty'][key])]
|
|
if readable_or_writeable:
|
|
txt += ['']
|
|
txt += [' if {[SplitReply [%s]]=="false"} {' % MyDriver['simulation_group']]
|
|
for var in sorted(MyGroup['Vars']):
|
|
MyVar = MyGroup['Vars'][var]
|
|
nodename = groupname + MyVar['name']
|
|
if MyVar['readable'] > 0:
|
|
poll_period = MyVar['readable']
|
|
if poll_period < 1 or poll_period > 300:
|
|
poll_period = 5
|
|
txt += [' ${sct_controller} poll ${scobj_hpath}/%s %s' % (nodename, poll_period)]
|
|
for var in sorted(MyGroup['Vars']):
|
|
MyVar = MyGroup['Vars'][var]
|
|
nodename = groupname + MyVar['name']
|
|
if MyVar['writeable'] > 0 or MyVar['driveable']:
|
|
txt += [' ${sct_controller} write ${scobj_hpath}/%s' % nodename]
|
|
if MyVar['driveable']:
|
|
txt += [' ansto_makesctdrive ${name}_%s ${scobj_hpath}/%s ${scobj_hpath}/%s ${sct_controller}' % (MyVar['name'], nodename, MyVar['driveable'])]
|
|
txt += [' }']
|
|
return txt
|
|
|
|
def put_mk_sct_driver(MyDriver):
|
|
txt = ['']
|
|
if 'make_args' in MyDriver:
|
|
line = 'proc %s::mk_sct_%s { sct_controller name %s } {' % (MyDriver['namespace'], MyDriver['name'], MyDriver['make_args'])
|
|
else:
|
|
line = 'proc %s::mk_sct_%s { sct_controller name } {' % (MyDriver['namespace'], MyDriver['name'])
|
|
txt += [line]
|
|
txt += [' debug_log 1 "mk_sct_%s for ${name}"' % MyDriver['name']]
|
|
txt += [' set ns "[namespace current]"']
|
|
txt += [' set catch_status [ catch {']
|
|
txt += ['']
|
|
txt += [' MakeSICSObj ${name} SCT_OBJECT']
|
|
txt += ['']
|
|
txt += [' sicslist setatt ${name} klass %s' % MyDriver['class']]
|
|
txt += [' sicslist setatt ${name} long_name ${name}']
|
|
if 'DriverProperty' in MyDriver:
|
|
for key in MyDriver['DriverProperty']:
|
|
txt += [' sicslist setatt ${name} %s "%s"' % (key, MyDriver['DriverProperty'][key])]
|
|
txt += ['']
|
|
txt += [' set scobj_hpath /sics/${name}']
|
|
if UsingCreateNode:
|
|
groups = MyDriver['Groups']
|
|
if len(groups) > 0:
|
|
for group in groups:
|
|
if group:
|
|
txt += [' hfactory ${scobj_hpath}/%s plain spy none' % group]
|
|
txt += ['']
|
|
device_commands = []
|
|
txt += [' set deviceCommand {']
|
|
for grp in sorted(MyDriver['Groups']):
|
|
if grp:
|
|
group_name = grp
|
|
else:
|
|
group_name = '{}'
|
|
for var in sorted(MyDriver['Groups'][grp]['Vars']):
|
|
device_commands.append(get_one_var(group_name, MyDriver['Groups'][grp]['Vars'][var]))
|
|
lens = {}
|
|
for cmd in device_commands:
|
|
for idx, val in enumerate(cmd):
|
|
if not idx in lens:
|
|
lens[idx] = 0
|
|
if len(val) > lens[idx]:
|
|
lens[idx] = len(val)
|
|
|
|
for cmd in device_commands:
|
|
line = ' '
|
|
for idx, val in enumerate(cmd):
|
|
line += " %-*s" % (lens[idx], val)
|
|
txt += [line + '\\']
|
|
txt += [' }']
|
|
txt += [' foreach { grpName varName\\']
|
|
txt += [' readable writeable driveable\\']
|
|
txt += [' dataType permission\\']
|
|
txt += [' rdCmd rdFunc wrCmd wrFunc allowedvalues} ${deviceCommand} {']
|
|
txt += [' createNode ${scobj_hpath} ${sct_controller}\\']
|
|
txt += [' ${cmdGroup} ${varName}\\']
|
|
txt += [' ${readable} ${writable} ${drivable}\\']
|
|
txt += [' ${dataType} ${permission}\\']
|
|
txt += [' ${rdCmd} ${rdFunc}\\']
|
|
txt += [' ${wrCmd} ${wrFunc}\\']
|
|
txt += [' ${allowedValues} %s' % MyDriver['class']]
|
|
txt += [' }']
|
|
else:
|
|
for group in sorted(MyDriver['Groups']):
|
|
txt += put_group(MyDriver, MyDriver['Groups'][group])
|
|
|
|
txt += [' hsetprop ${scobj_hpath} klass %s' % MyDriver['class']]
|
|
if 'mkDriver' in MyDriver['Funcs']:
|
|
txt += ['# hook code starts']
|
|
txt += MyDriver['Funcs']['mkDriver']['text']
|
|
txt += ['# hook code ends']
|
|
txt += [' } catch_message ]']
|
|
txt += [' handle_exception ${catch_status} ${catch_message} "in ${ns}::mk_sct_%s"' % MyDriver['name']]
|
|
txt += ['}']
|
|
emit(txt)
|
|
|
|
def put_postamble(MyDriver):
|
|
txt = ['']
|
|
txt += ['namespace eval %s {' % MyDriver['namespace']]
|
|
txt += [' namespace export debug_log']
|
|
txt += [' namespace export mk_sct_%s' % MyDriver['name']]
|
|
txt += ['}']
|
|
txt += ['']
|
|
if 'add_args' in MyDriver:
|
|
line = 'proc add_%s {name IP port %s} {' % (MyDriver['name'], MyDriver['add_args'])
|
|
else:
|
|
line = 'proc add_%s {name IP port} {' % MyDriver['name']
|
|
txt += [line]
|
|
txt += [' set ns "%s"' % MyDriver['namespace']]
|
|
txt += [' ${ns}::debug_log 1 "add_%s ${name} ${IP} ${port}"' % MyDriver['name']]
|
|
txt += [' if {[SplitReply [%s]]=="false"} {' % MyDriver['simulation_group']]
|
|
txt += [' if {[string equal -nocase "aqadapter" "${IP}"]} {']
|
|
txt += [' ${ns}::debug_log 1 "makesctcontroller sct_${name} aqadapter ${port}"']
|
|
txt += [' makesctcontroller sct_${name} aqadapter ${port}']
|
|
txt += [' } else {']
|
|
if 'protocol_args' in MyDriver:
|
|
protocol_args = MyDriver['protocol_args'].replace('\\', '\\\\').replace('"', '\\"')
|
|
txt += [' ${ns}::debug_log 1 "makesctcontroller sct_${name} %s ${IP}:${port} %s"' % (MyDriver['protocol'], protocol_args)]
|
|
txt += [' makesctcontroller sct_${name} %s ${IP}:${port} %s' % (MyDriver['protocol'], MyDriver['protocol_args'])]
|
|
else:
|
|
txt += [' ${ns}::debug_log 1 "makesctcontroller sct_${name} %s ${IP}:${port}"' % MyDriver['protocol']]
|
|
txt += [' makesctcontroller sct_${name} %s ${IP}:${port}' % MyDriver['protocol']]
|
|
txt += [' }']
|
|
txt += [' }']
|
|
make_args = ''
|
|
if 'make_args' in MyDriver:
|
|
for arg in MyDriver['make_args'].split():
|
|
if len(make_args) > 0:
|
|
make_args += ' '
|
|
make_args += '${' + arg + '}'
|
|
txt += [' ${ns}::debug_log 1 "mk_sct_%s sct_${name} ${name} %s"' % (MyDriver['name'], make_args)]
|
|
txt += [' ${ns}::mk_sct_%s sct_${name} ${name} %s' % (MyDriver['name'], make_args)]
|
|
else:
|
|
txt += [' ${ns}::debug_log 1 "mk_sct_%s sct_${name} ${name}"' % MyDriver['name']]
|
|
txt += [' ${ns}::mk_sct_%s sct_${name} ${name}' % MyDriver['name']]
|
|
# TODO
|
|
#txt += [' ${ns}::debug_log 1 "makesctemon ${name} /sics/${name}/emon/monmode /sics/${name}/emon/isintol /sics/${name}/emon/errhandler"']
|
|
# txt += [' makesctemon ${name} /sics/${name}/emon/monmode /sics/${name}/emon/isintol /sics/${name}/emon/errhandler']
|
|
txt += [' close ${fd}']
|
|
txt += ['}']
|
|
txt += ['']
|
|
txt += ['puts stdout "file evaluation of sct_%s.tcl"' % MyDriver['name']]
|
|
txt += ['%s::debug_log 1 "file evaluation of sct_%s.tcl"' % (MyDriver['namespace'], MyDriver['name'])]
|
|
emit(txt)
|
|
|
|
def put_standard_code(MyDriver):
|
|
# emit all of the functions in Funcs
|
|
for func in sorted(MyDriver['Funcs']):
|
|
theFunc = MyDriver['Funcs'][func]
|
|
# Don't generate functions which are not referenced
|
|
if theFunc['reference_count'] == 0:
|
|
continue
|
|
if theFunc['type'] == 'read_function':
|
|
put_read_function(MyDriver, func);
|
|
elif theFunc['type'] == 'write_function':
|
|
put_write_function(MyDriver, func);
|
|
elif theFunc['type'] == 'fetch_function':
|
|
put_fetch_function(MyDriver, func);
|
|
elif theFunc['type'] == 'check_function':
|
|
put_check_function(MyDriver, func);
|
|
elif theFunc['type'] == 'checkrange_function':
|
|
put_checkrange_function(MyDriver, func);
|
|
elif theFunc['type'] == 'checklimits_function':
|
|
put_checklimits_function(MyDriver, func);
|
|
elif theFunc['type'] == 'checkstatus_function':
|
|
put_checkstatus_function(MyDriver, func);
|
|
elif theFunc['type'] == 'halt_function':
|
|
put_halt_function(MyDriver, func);
|
|
|
|
def generate_driver(MyDriver):
|
|
global NumberOfLinesOut
|
|
global fdo
|
|
NumberOfLinesOut = 0
|
|
full_filename = filename = "sct_%s.tcl" % MyDriver['name']
|
|
if 'PathName' in MyDriver:
|
|
full_filename = os.path.join(MyDriver['PathName'], filename)
|
|
fdo = open(full_filename, 'w')
|
|
put_preamble(MyDriver)
|
|
put_standard_code(MyDriver)
|
|
if UsingCreateNode:
|
|
put_create_node(MyDriver)
|
|
put_mk_sct_driver(MyDriver)
|
|
put_postamble(MyDriver)
|
|
fdo.close()
|
|
if CodeDump or Verbose:
|
|
print "Code Fragments:", MyDriver['Funcs']
|
|
for f in sorted(MyDriver['Funcs'].keys()):
|
|
print "Function:", f, "Type:", MyDriver['Funcs'][f]['type'], '#Uses:', MyDriver['Funcs'][f]['reference_count']
|
|
for l in MyDriver['Funcs'][f]['text']:
|
|
print " ", l
|
|
print "Procuced file %s with %d lines." % (filename, NumberOfLinesOut)
|
|
|
|
def process_drivers(TheDrivers):
|
|
global UsingCreateNode
|
|
if Verbose:
|
|
print "TheDrivers:", TheDrivers
|
|
for driver in TheDrivers:
|
|
MyDriver = {'name':driver}
|
|
MyDriver['namespace'] = '::scobj::%s' % driver
|
|
MyDriver['Groups'] = {}
|
|
MyDriver['Funcs'] = {}
|
|
build_driver(MyDriver, TheDrivers[driver])
|
|
if Verbose:
|
|
print "MyDriver:", MyDriver['name'], '=', MyDriver
|
|
if 'usecreatenode' in MyDriver:
|
|
if MyDriver['usecreatenode'] == 'true':
|
|
UsingCreateNode = True
|
|
else:
|
|
UsingCreateNode = False
|
|
generate_driver(MyDriver)
|
|
if DriverDump or Verbose:
|
|
dump_driver(MyDriver)
|
|
|
|
def process_source(source_files):
|
|
global PathName
|
|
global TheDrivers
|
|
global NumberOfLinesIn
|
|
|
|
TheDrivers = {}
|
|
|
|
#
|
|
# Build the lexer
|
|
#
|
|
lexer = lex.lex()
|
|
|
|
|
|
#
|
|
# Build the parser
|
|
#
|
|
yaccer = yacc.yacc()
|
|
|
|
for source_file in source_files:
|
|
PathName = os.path.realpath(os.path.abspath(os.path.dirname(source_file)))
|
|
fd = open(source_file, 'r')
|
|
data = fd.read()
|
|
fd.close()
|
|
NumberOfLinesIn = data.count('\n')
|
|
print 'Consumed file %s with %d lines' % (source_file, NumberOfLinesIn)
|
|
yaccer.parse(data)
|
|
|
|
process_drivers(TheDrivers)
|
|
|
|
def main():
|
|
global Verbose
|
|
global DriverDump
|
|
global CodeDump
|
|
import argparse
|
|
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("-c", "--code", help="dump code",
|
|
action="store_true")
|
|
parser.add_argument("-d", "--driver", help="dump driver",
|
|
action="store_true")
|
|
parser.add_argument("-v", "--verbose", help="verbose output",
|
|
action="store_true")
|
|
parser.add_argument("driver_source", help="driver source file", nargs="*")
|
|
args = parser.parse_args()
|
|
print args
|
|
if args.code:
|
|
CodeDump = True
|
|
else:
|
|
CodeDump = False
|
|
if args.driver:
|
|
DriverDump = True
|
|
else:
|
|
DriverDump = False
|
|
if args.verbose:
|
|
Verbose = True
|
|
else:
|
|
Verbose = False
|
|
source_files = args.driver_source
|
|
if source_files:
|
|
process_source(source_files)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|