beremiz

Proper fix for error 'object has no attribute 'getSlave' in EtherCAT extension

traceback:
File "/home/developer/WorkData/PLC/beremiz/beremiz/IDEFrame.py", line 1433, in OnPouSelectedChanged
window.RefreshView()
File "/home/developer/WorkData/PLC/beremiz/beremiz/etherlab/ConfigEditor.py", line 837, in RefreshView
self.RefreshProcessVariables()
File "/home/developer/WorkData/PLC/beremiz/beremiz/etherlab/ConfigEditor.py", line 886, in RefreshProcessVariables
slaves = self.Controler.GetSlaves(**self.CurrentNodesFilter)
File "/home/developer/WorkData/PLC/beremiz/beremiz/etherlab/EthercatMaster.py", line 341, in GetSlaves
for slave in self.Config.getConfig().getSlave():
:_'lxml.etree._Element'_object_has_no_attribute_'getSlave'

Steps to reproduce problem:

- Add new EtherCAT master
- Add new EthercatNode to the master
- double click on


Revert commit "Dirty fix for error '_object_has_no_attribute_'getSlave' in EtherCAT extension"
[a3ac46366b86a0b237dac93be6b2281ac70b98a8].

The problem was that XML elements (proxy object) in some cases were created using custom XML
classes constructors and lxml.etree.Element() call and live python
patching. This causes that lxml backend doesn't know that custom python class
should be used for these XML elements.
Proxy object can be move/deleted and recreated by lxml
backend at any point in time or this can be done in python by copy/deepcopy operations.
If this happens, then newly created
proxy elements are using default class lxml.etree._Element. And all
custom functionality is lost.

All created XML elements should be always created through corresponding
parser and class lookup callback done by lxml backend.
It's described in more details in lxml documentation:
https://lxml.de/element_classes.html
/*
* Python Asynchronous execution code
*
* PLC put python commands in a fifo, respecting execution order
* with the help of C pragmas inserted in python_eval FB code
*
* Buffer content is read asynchronously, (from non real time part),
* commands are executed and result stored for later use by PLC.
*
* In this implementation, fifo is a list of pointer to python_eval
* function blocks structures. Some local variables have been added in
* python_eval interface. We use those local variables as buffer and state
* flags.
*
* */
#include "iec_types_all.h"
#include "POUS.h"
#include <string.h>
/* The fifo (fixed size, as number of FB is fixed) */
static PYTHON_EVAL* EvalFBs[%(python_eval_fb_count)d];
/* Producer and consumer cursors */
static int Current_PLC_EvalFB;
static int Current_Python_EvalFB;
/* A global IEC-Python gateway state, for use inside python_eval FBs*/
static int PythonState;
#define PYTHON_LOCKED_BY_PYTHON 0
#define PYTHON_LOCKED_BY_PLC 1
#define PYTHON_MUSTWAKEUP 2
#define PYTHON_FINISHED 4
/* Each python_eval FunctionBlock have it own state */
#define PYTHON_FB_FREE 0
#define PYTHON_FB_REQUESTED 1
#define PYTHON_FB_PROCESSING 2
#define PYTHON_FB_ANSWERED 3
int WaitPythonCommands(void);
void UnBlockPythonCommands(void);
int TryLockPython(void);
void UnLockPython(void);
void LockPython(void);
int __init_py_ext()
{
int i;
/* Initialize cursors */
Current_Python_EvalFB = 0;
Current_PLC_EvalFB = 0;
PythonState = PYTHON_LOCKED_BY_PYTHON;
for(i = 0; i < %(python_eval_fb_count)d; i++)
EvalFBs[i] = NULL;
return 0;
}
void __cleanup_py_ext()
{
PythonState = PYTHON_FINISHED;
UnBlockPythonCommands();
}
void __retrieve_py_ext()
{
/* Check Python thread is not being
* modifying internal python_eval data */
PythonState = TryLockPython() ?
PYTHON_LOCKED_BY_PLC :
PYTHON_LOCKED_BY_PYTHON;
/* If python thread _is_ in, then PythonState remains PYTHON_LOCKED_BY_PYTHON
* and python_eval will no do anything */
}
void __publish_py_ext()
{
if(PythonState & PYTHON_LOCKED_BY_PLC){
/* If runnig PLC did push something in the fifo*/
if(PythonState & PYTHON_MUSTWAKEUP){
/* WakeUp python thread */
UnBlockPythonCommands();
}
UnLockPython();
}
}
/**
* Called by the PLC, each time a python_eval
* FB instance is executed
*/
void __PythonEvalFB(int poll, PYTHON_EVAL* data__)
{
/* detect rising edge on TRIG to trigger evaluation */
if(((__GET_VAR(data__->TRIG) && !__GET_VAR(data__->TRIGM1)) ||
/* polling is equivalent to trig on value rather than on rising edge*/
(poll && __GET_VAR(data__->TRIG) )) &&
/* trig only if not already trigged */
__GET_VAR(data__->TRIGGED) == 0){
/* mark as trigged */
__SET_VAR(data__->, TRIGGED,, 1);
/* make a safe copy of the code */
__SET_VAR(data__->, PREBUFFER,, __GET_VAR(data__->CODE));
}
/* retain value for next rising edge detection */
__SET_VAR(data__->, TRIGM1,, __GET_VAR(data__->TRIG));
/* python thread is not in ? */
if( PythonState & PYTHON_LOCKED_BY_PLC){
/* if some answer are waiting, publish*/
if(__GET_VAR(data__->STATE) == PYTHON_FB_ANSWERED){
/* Copy buffer content into result*/
__SET_VAR(data__->, RESULT,, __GET_VAR(data__->BUFFER));
/* signal result presece to PLC*/
__SET_VAR(data__->, ACK,, 1);
/* Mark as free */
__SET_VAR(data__->, STATE,, PYTHON_FB_FREE);
/* mark as not trigged */
if(!poll)
__SET_VAR(data__->, TRIGGED,, 0);
/*printf("__PythonEvalFB pop %%d - %%*s\n",Current_PLC_EvalFB, data__->BUFFER.len, data__->BUFFER.body);*/
}else if(poll){
/* when in polling, no answer == ack down */
__SET_VAR(data__->, ACK,, 0);
}
/* got the order to act ?*/
if(__GET_VAR(data__->TRIGGED) == 1 &&
/* and not already being processed */
__GET_VAR(data__->STATE) == PYTHON_FB_FREE)
{
/* Enter the block in the fifo
* Don't have to check if fifo cell is free
* as fifo size == FB count, and a FB cannot
* be requested twice */
EvalFBs[Current_PLC_EvalFB] = data__;
/* copy into BUFFER local*/
__SET_VAR(data__->, BUFFER,, __GET_VAR(data__->PREBUFFER));
/* Set ACK pin to low so that we can set a rising edge on result */
if(!poll){
/* when not polling, a new answer imply reseting ack*/
__SET_VAR(data__->, ACK,, 0);
}else{
/* when in polling, acting reset trigger */
__SET_VAR(data__->, TRIGGED,, 0);
}
/* Mark FB busy */
__SET_VAR(data__->, STATE,, PYTHON_FB_REQUESTED);
/* Have to wakeup python thread in case he was asleep */
PythonState |= PYTHON_MUSTWAKEUP;
/*printf("__PythonEvalFB push %%d - %%*s\n",Current_PLC_EvalFB, data__->BUFFER.len, data__->BUFFER.body);*/
/* Get a new line */
Current_PLC_EvalFB = (Current_PLC_EvalFB + 1) %% %(python_eval_fb_count)d;
}
}
}
char* PythonIterator(char* result, void** id)
{
char* next_command;
PYTHON_EVAL* data__;
//printf("PythonIterator result %%s\n", result);
/*emergency exit*/
if(PythonState & PYTHON_FINISHED) return NULL;
/* take python mutex to prevent changing PLC data while PLC running */
LockPython();
/* Get current FB */
data__ = EvalFBs[Current_Python_EvalFB];
if(data__ && /* may be null at first run */
__GET_VAR(data__->STATE) == PYTHON_FB_PROCESSING){ /* some answer awaited*/
/* If result not None */
if(result){
/* Get results len */
__SET_VAR(data__->, BUFFER, .len, strlen(result));
/* prevent results overrun */
if(__GET_VAR(data__->BUFFER, .len) > STR_MAX_LEN)
{
__SET_VAR(data__->, BUFFER, .len, STR_MAX_LEN);
/* TODO : signal error */
}
/* Copy results to buffer */
strncpy((char*)__GET_VAR(data__->BUFFER, .body), result, __GET_VAR(data__->BUFFER,.len));
}else{
__SET_VAR(data__->, BUFFER, .len, 0);
}
/* remove block from fifo*/
EvalFBs[Current_Python_EvalFB] = NULL;
/* Mark block as answered */
__SET_VAR(data__->, STATE,, PYTHON_FB_ANSWERED);
/* Get a new line */
Current_Python_EvalFB = (Current_Python_EvalFB + 1) %% %(python_eval_fb_count)d;
//printf("PythonIterator ++ Current_Python_EvalFB %%d\n", Current_Python_EvalFB);
}
/* while next slot is empty */
while(((data__ = EvalFBs[Current_Python_EvalFB]) == NULL) ||
/* or doesn't contain command */
__GET_VAR(data__->STATE) != PYTHON_FB_REQUESTED)
{
UnLockPython();
/* wait next FB to eval */
//printf("PythonIterator wait\n");
if(WaitPythonCommands()) return NULL;
/*emergency exit*/
if(PythonState & PYTHON_FINISHED) return NULL;
LockPython();
}
/* Mark block as processing */
__SET_VAR(data__->, STATE,, PYTHON_FB_PROCESSING);
//printf("PythonIterator\n");
/* make BUFFER a null terminated string */
__SET_VAR(data__->, BUFFER, .body[__GET_VAR(data__->BUFFER, .len)], 0);
/* next command is BUFFER */
next_command = (char*)__GET_VAR(data__->BUFFER, .body);
*id=data__;
/* free python mutex */
UnLockPython();
/* return the next command to eval */
return next_command;
}