Python OPC UA pass variables to uamethod - python

I have an example where a method is being added to the server space. The example is set up to take two input values and add them together.
Code example
from opcua import Server, ua, uamethod, instantiate
import time
from opcua.ua import NodeId, NodeIdType
#uamethod
def add_func(self, a, b):
print("Adding parameters:\n", a, "\tand\t", b)
return a + b
if __name__ == "__main__":
# setup server
server = Server()
server.set_endpoint("opc.tcp://localhost:4840") #alternative:
server.set_endpoint("opc.tcp://0.0.0.0:4840")
server.set_server_name("Server example")
server.set_security_policy([ua.SecurityPolicyType.NoSecurity])
uri = "http://example.uri.de"
idx = server.register_namespace(uri)
# get Objects node, this is where we should adress our custom stuff to
objects = server.get_objects_node() # alternative: root_node = server.get_root_node()
# add structure to the namespace (idx) below the Objects node
myobj = objects.add_object(idx, "MyObject")
# add variables, type can be defined by assigning a value or ua Variant Type definition
var1 = myobj.add_variable(idx, "var1", 321, ua.VariantType.Int64)
var2 = myobj.add_variable(idx, "var2", 123, ua.VariantType.Int64)
# set variables writable for clients (e.g. UaExpert)
var1.set_writable()
var2.set_writable()
# set input arguments for method
input_arg1 = ua.Argument() # create new argument
input_arg1.Name = "a" # name for argument
input_arg1.DataType = ua.NodeId(ua.ObjectIds.Int64) # data type
input_arg1.ValueRank = -1 # value rank to be treated a scalar number
input_arg1.Description = ua.LocalizedText("First integer a") # add description text
input_arg2 = ua.Argument()
input_arg2.Name = "b"
input_arg2.DataType = ua.NodeId(ua.ObjectIds.Int64)
input_arg2.ValueRank = -1
input_arg2.Description = ua.LocalizedText("Second integer b")
# set output arguments
output_arg = ua.Argument()
output_arg.Name = "Result"
output_arg.DataType = ua.NodeId(ua.ObjectIds.Int64)
output_arg.ValueRank = -1
output_arg.Description = ua.LocalizedText("Sum")
print(var1.nodeid)
print(var2.nodeid)
# add method to namespace
mymethod = myobj.add_method(idx, "MyAddFunction", add_func, [input_arg1, input_arg2], [output_arg])
server.start()
The change I want to make is to remove the input arguments and automatically take var1 & var2 as input when calling MyAddFunction.
I use UaExpert to call this method.
UaExpert when calling MyAddFunction
I've tried to pass the variables by using their NodeID like this:
mymethod = myobj.add_method(idx, "MyAddFunction", add_func, [var1.nodeid, var1.nodeid], [output_arg])
and get following error message:
opcua.ua.uaerrors._base.UaError: NodeId: Could not guess type of NodeId, set NodeIdType
Is the type not defined when creating the variables?
What changes have to made for the method to take var1 & var2 as input argument? is there a way to assign these values to the created input arguments (input_arg1 & input_arg2)?
Thank you in advance!
regards Nando

Related

How to know the name of a classs loade like parameter on other class - Pyhton [duplicate]

This question already has answers here:
Getting the name of a variable as a string
(32 answers)
Closed 4 months ago.
Is it possible to get the original variable name of a variable passed to a function? E.g.
foobar = "foo"
def func(var):
print var.origname
So that:
func(foobar)
Returns:
>>foobar
EDIT:
All I was trying to do was make a function like:
def log(soup):
f = open(varname+'.html', 'w')
print >>f, soup.prettify()
f.close()
.. and have the function generate the filename from the name of the variable passed to it.
I suppose if it's not possible I'll just have to pass the variable and the variable's name as a string each time.
EDIT: To make it clear, I don't recommend using this AT ALL, it will break, it's a mess, it won't help you in any way, but it's doable for entertainment/education purposes.
You can hack around with the inspect module, I don't recommend that, but you can do it...
import inspect
def foo(a, f, b):
frame = inspect.currentframe()
frame = inspect.getouterframes(frame)[1]
string = inspect.getframeinfo(frame[0]).code_context[0].strip()
args = string[string.find('(') + 1:-1].split(',')
names = []
for i in args:
if i.find('=') != -1:
names.append(i.split('=')[1].strip())
else:
names.append(i)
print names
def main():
e = 1
c = 2
foo(e, 1000, b = c)
main()
Output:
['e', '1000', 'c']
To add to Michael Mrozek's answer, you can extract the exact parameters versus the full code by:
import re
import traceback
def func(var):
stack = traceback.extract_stack()
filename, lineno, function_name, code = stack[-2]
vars_name = re.compile(r'\((.*?)\).*$').search(code).groups()[0]
print vars_name
return
foobar = "foo"
func(foobar)
# PRINTS: foobar
Looks like Ivo beat me to inspect, but here's another implementation:
import inspect
def varName(var):
lcls = inspect.stack()[2][0].f_locals
for name in lcls:
if id(var) == id(lcls[name]):
return name
return None
def foo(x=None):
lcl='not me'
return varName(x)
def bar():
lcl = 'hi'
return foo(lcl)
bar()
# 'lcl'
Of course, it can be fooled:
def baz():
lcl = 'hi'
x='hi'
return foo(lcl)
baz()
# 'x'
Moral: don't do it.
Another way you can try if you know what the calling code will look like is to use traceback:
def func(var):
stack = traceback.extract_stack()
filename, lineno, function_name, code = stack[-2]
code will contain the line of code that was used to call func (in your example, it would be the string func(foobar)). You can parse that to pull out the argument
You can't. It's evaluated before being passed to the function. All you can do is pass it as a string.
#Ivo Wetzel's answer works in the case of function call are made in one line, like
e = 1 + 7
c = 3
foo(e, 100, b=c)
In case that function call is not in one line, like:
e = 1 + 7
c = 3
foo(e,
1000,
b = c)
below code works:
import inspect, ast
def foo(a, f, b):
frame = inspect.currentframe()
frame = inspect.getouterframes(frame)[1]
string = inspect.findsource(frame[0])[0]
nodes = ast.parse(''.join(string))
i_expr = -1
for (i, node) in enumerate(nodes.body):
if hasattr(node, 'value') and isinstance(node.value, ast.Call)
and hasattr(node.value.func, 'id') and node.value.func.id == 'foo' # Here goes name of the function:
i_expr = i
break
i_expr_next = min(i_expr + 1, len(nodes.body)-1)
lineno_start = nodes.body[i_expr].lineno
lineno_end = nodes.body[i_expr_next].lineno if i_expr_next != i_expr else len(string)
str_func_call = ''.join([i.strip() for i in string[lineno_start - 1: lineno_end]])
params = str_func_call[str_func_call.find('(') + 1:-1].split(',')
print(params)
You will get:
[u'e', u'1000', u'b = c']
But still, this might break.
You can use python-varname package
from varname import nameof
s = 'Hey!'
print (nameof(s))
Output:
s
Package below:
https://github.com/pwwang/python-varname
For posterity, here's some code I wrote for this task, in general I think there is a missing module in Python to give everyone nice and robust inspection of the caller environment. Similar to what rlang eval framework provides for R.
import re, inspect, ast
#Convoluted frame stack walk and source scrape to get what the calling statement to a function looked like.
#Specifically return the name of the variable passed as parameter found at position pos in the parameter list.
def _caller_param_name(pos):
#The parameter name to return
param = None
#Get the frame object for this function call
thisframe = inspect.currentframe()
try:
#Get the parent calling frames details
frames = inspect.getouterframes(thisframe)
#Function this function was just called from that we wish to find the calling parameter name for
function = frames[1][3]
#Get all the details of where the calling statement was
frame,filename,line_number,function_name,source,source_index = frames[2]
#Read in the source file in the parent calling frame upto where the call was made
with open(filename) as source_file:
head=[source_file.next() for x in xrange(line_number)]
source_file.close()
#Build all lines of the calling statement, this deals with when a function is called with parameters listed on each line
lines = []
#Compile a regex for matching the start of the function being called
regex = re.compile(r'\.?\s*%s\s*\(' % (function))
#Work backwards from the parent calling frame line number until we see the start of the calling statement (usually the same line!!!)
for line in reversed(head):
lines.append(line.strip())
if re.search(regex, line):
break
#Put the lines we have groked back into sourcefile order rather than reverse order
lines.reverse()
#Join all the lines that were part of the calling statement
call = "".join(lines)
#Grab the parameter list from the calling statement for the function we were called from
match = re.search('\.?\s*%s\s*\((.*)\)' % (function), call)
paramlist = match.group(1)
#If the function was called with no parameters raise an exception
if paramlist == "":
raise LookupError("Function called with no parameters.")
#Use the Python abstract syntax tree parser to create a parsed form of the function parameter list 'Name' nodes are variable names
parameter = ast.parse(paramlist).body[0].value
#If there were multiple parameters get the positional requested
if type(parameter).__name__ == 'Tuple':
#If we asked for a parameter outside of what was passed complain
if pos >= len(parameter.elts):
raise LookupError("The function call did not have a parameter at postion %s" % pos)
parameter = parameter.elts[pos]
#If there was only a single parameter and another was requested raise an exception
elif pos != 0:
raise LookupError("There was only a single calling parameter found. Parameter indices start at 0.")
#If the parameter was the name of a variable we can use it otherwise pass back None
if type(parameter).__name__ == 'Name':
param = parameter.id
finally:
#Remove the frame reference to prevent cyclic references screwing the garbage collector
del thisframe
#Return the parameter name we found
return param
If you want a Key Value Pair relationship, maybe using a Dictionary would be better?
...or if you're trying to create some auto-documentation from your code, perhaps something like Doxygen (http://www.doxygen.nl/) could do the job for you?
I wondered how IceCream solves this problem. So I looked into the source code and came up with the following (slightly simplified) solution. It might not be 100% bullet-proof (e.g. I dropped get_text_with_indentation and I assume exactly one function argument), but it works well for different test cases. It does not need to parse source code itself, so it should be more robust and simpler than previous solutions.
#!/usr/bin/env python3
import inspect
from executing import Source
def func(var):
callFrame = inspect.currentframe().f_back
callNode = Source.executing(callFrame).node
source = Source.for_frame(callFrame)
expression = source.asttokens().get_text(callNode.args[0])
print(expression, '=', var)
i = 1
f = 2.0
dct = {'key': 'value'}
obj = type('', (), {'value': 42})
func(i)
func(f)
func(s)
func(dct['key'])
func(obj.value)
Output:
i = 1
f = 2.0
s = string
dct['key'] = value
obj.value = 42
Update: If you want to move the "magic" into a separate function, you simply have to go one frame further back with an additional f_back.
def get_name_of_argument():
callFrame = inspect.currentframe().f_back.f_back
callNode = Source.executing(callFrame).node
source = Source.for_frame(callFrame)
return source.asttokens().get_text(callNode.args[0])
def func(var):
print(get_name_of_argument(), '=', var)
If you want to get the caller params as in #Matt Oates answer answer without using the source file (ie from Jupyter Notebook), this code (combined from #Aeon answer) will do the trick (at least in some simple cases):
def get_caller_params():
# get the frame object for this function call
thisframe = inspect.currentframe()
# get the parent calling frames details
frames = inspect.getouterframes(thisframe)
# frame 0 is the frame of this function
# frame 1 is the frame of the caller function (the one we want to inspect)
# frame 2 is the frame of the code that calls the caller
caller_function_name = frames[1][3]
code_that_calls_caller = inspect.findsource(frames[2][0])[0]
# parse code to get nodes of abstract syntact tree of the call
nodes = ast.parse(''.join(code_that_calls_caller))
# find the node that calls the function
i_expr = -1
for (i, node) in enumerate(nodes.body):
if _node_is_our_function_call(node, caller_function_name):
i_expr = i
break
# line with the call start
idx_start = nodes.body[i_expr].lineno - 1
# line with the end of the call
if i_expr < len(nodes.body) - 1:
# next expression marks the end of the call
idx_end = nodes.body[i_expr + 1].lineno - 1
else:
# end of the source marks the end of the call
idx_end = len(code_that_calls_caller)
call_lines = code_that_calls_caller[idx_start:idx_end]
str_func_call = ''.join([line.strip() for line in call_lines])
str_call_params = str_func_call[str_func_call.find('(') + 1:-1]
params = [p.strip() for p in str_call_params.split(',')]
return params
def _node_is_our_function_call(node, our_function_name):
node_is_call = hasattr(node, 'value') and isinstance(node.value, ast.Call)
if not node_is_call:
return False
function_name_correct = hasattr(node.value.func, 'id') and node.value.func.id == our_function_name
return function_name_correct
You can then run it as this:
def test(*par_values):
par_names = get_caller_params()
for name, val in zip(par_names, par_values):
print(name, val)
a = 1
b = 2
string = 'text'
test(a, b,
string
)
to get the desired output:
a 1
b 2
string text
Since you can have multiple variables with the same content, instead of passing the variable (content), it might be safer (and will be simpler) to pass it's name in a string and get the variable content from the locals dictionary in the callers stack frame. :
def displayvar(name):
import sys
return name+" = "+repr(sys._getframe(1).f_locals[name])
If it just so happens that the variable is a callable (function), it will have a __name__ property.
E.g. a wrapper to log the execution time of a function:
def time_it(func, *args, **kwargs):
start = perf_counter()
result = func(*args, **kwargs)
duration = perf_counter() - start
print(f'{func.__name__} ran in {duration * 1000}ms')
return result

Where do I assign a variable in order for it to be used in a function (inside a class)?

I'm creating a class using tkinter that lets you input multiple product's information, and I've got everything else down except for changing the entry fields to set values for the other products.
I'm putting the product changeover process into a function called saveVars which saves the entered information to the specific product variable, and then clears the entry fields, and switches the saveVars to be performed on the second product variable.
i = 1
def saveVars(i):
if i == 1:
product1.productName = self.prodName.get()
product1.productID = self.prodID.get()
product1.productSize = self.prodSize.get()
product1.productPrice = self.prodPrice.get()
product1.productQuant = self.quantity.get()
elif i == 2:
product2.productName = self.prodName.get()
product2.productName = self.prodID.get()
product2.productSize = self.prodSize.get()
product2.productPrice = self.prodPrice.get()
product2.productQuant = self.quantity.get()
elif i == 3:
product3.productName = self.prodName.get()
product3.productName = self.prodID.get()
product3.productSize = self.prodSize.get()
product3.productPrice = self.prodPrice.get()
product3.productQuant = self.quantity.get()
newProduct()
i += 1
return i
I'm expecting to get it to switch the variable the entries are being saved to to the next respective product based on a +1 function, I'm having it return the i function as the new i, which should then save the entries to the next variable in the process, but it keeps telling me that I'm 'missing 1 required positional argument: 'i'
You are trying to call the function without parameter as the command attribute does not take parameters just the name of the function.
To pass parameters you could use partial form functools package
import statement :
from functools import partial
your call to function would look like :
addItemButton = Button(window, text = "Add to Cart", fg='black',bg='yellow',width = 10, height = 2, command = partial(saveVars,i)) addItemButton.place(x=800,y=375)
You can set the inital value to i in your class using a global variable instead of declaring it outside the class. You can save it anywhere you wish and pass it while calling the funtion.
It seems like you are calling saveVars without the parameter in some case. To prevent this you can set a default value of i, eg.
def saveVars(i=0)

Python function replacing part of variable

I am writing a code for a project in particle physics (using pyroot).
In my first draft, I use the following line
for i in MyTree:
pion.SetXYZM(K_plus_PX, K_plus_PY, K_plus_PZ,K_plus_MM)
This basically assigns to the pion the values of variables in the parenthesis, ie momenta and inv. mass of the kaon.
Physics aside, I would like to write a function "of the form":
def myfunc(particle):
return %s_PX % particle
I know this is wrong. What I would like to achieve is to write a function that allows, for a given particle, to set particle_PX, particle_PY etc to be the arguments of SetXYZM.
Thank you for your help,
B
To access class attributes from string variables you can use python's getattr:
import ROOT
inputfile = ROOT.TFile.Open("somefile.root","read")
inputtree = inputfile.Get("NameOfTTree")
inputtree.Print()
# observe that there are branches
# K_plus_PX
# K_plus_PY
# K_plus_PZ
# K_plus_MM
# K_minus_PX
# K_minus_PY
# K_minus_PZ
# K_minus_MM
# pi_minus_PX
# pi_minus_PY
# pi_minus_PZ
# pi_minus_MM
def getx(ttree,particlename):
return getattr(ttree,particlename+"_PX")
def gety(ttree,particlename):
return getattr(ttree,particlename+"_PY")
def getz(ttree,particlename):
return getattr(ttree,particlename+"_PZ")
def getm(ttree,particlename):
return getattr(ttree,particlename+"_MM")
def getallfour(ttree,particlename):
x = getattr(ttree,particlename+"_PX")
y = getattr(ttree,particlename+"_PY")
z = getattr(ttree,particlename+"_PZ")
m = getattr(ttree,particlename+"_MM")
return x,y,z,m
for entry in xrange(inputtree.GetEntries()):
inputtree.GetEntry(entry)
pion1 = ROOT.TLorentzVector()
x = getx(inputtree,"K_plus")
y = gety(inputtree,"K_plus")
z = getz(inputtree,"K_plus")
m = getm(inputtree,"K_plus")
pion2.SetXYZM(x,y,z,m)
x,y,z,m = getallfour(inputtree,"pi_minus")
pion2 = ROOT.TLorentzVector()
pion2.SetXYZM(x,y,z,m)
As linked by Josh Caswell, you can similarly access variable names:
def getx(particlename):
x = globals()[partilcename+"_PX"]
though that might get nasty quickly as of whether your variables are global or local and for local, in which context.

Dynamic keyword with database value

I have a model like this:
class meter1(models.Model):
U1N = models.FloatField(default=0)
U2N = models.FloatField(default=0)
In my view (simplified) I want to set the value for the database dynamically:
def import_data:
dict = {"1":"U1N","2":"U2N",}
c = "1"
q = meter1()
q.eval('dict[c]') = "1"
q.save()
In real dict contains 60 items, and c changes every time in a for loop. The code in this example results in an error: NameError: name 'U1N' is not defined.
How do I dynamically set the keyword for q?
You can also dynamically build a dict from your dict (you shouldn't use a builtin name as identifier BTW) and pass it as kwargs to meter1() (you shouldn't use all-lowers as class names BTW). Oh and yes: you may want to have a look at modelmanager.create() too.
def import_data():
fieldmap = {"1":"U1N","2":"U2N",}
fieldindex = "1"
kw = {fieldmap[fieldindex]: 1.0}
# either:
q = Meter1(**kw)
q.save()
# or:
q = Meter1.objects.create(**kw)
You can use setattr to dynamically set attributes to the objects.

Dynamic variable name in python

I'd like to call a query with a field name filter that I wont know before run time... Not sure how to construct the variable name ...Or maybe I am tired.
field_name = funct()
locations = Locations.objects.filter(field_name__lte=arg1)
where if funct() returns name would equal to
locations = Locations.objects.filter(name__lte=arg1)
Not sure how to do that ...
You can create a dictionary, set the parameters and pass this to the function by unpacking the dictionary as keyword arguments:
field_name = funct()
params = {field_name + '__lte': arg1, # field_name should still contain string
'some_other_field_name': arg2}
locations = Locations.objects.filter(**params)
# is the same as (assuming field_name = 'some_name'):
# Locations.objects.filter(some_name__lte=arg1, some_other_field_name=arg2)

Categories

Resources