py_ext/plc_python.c
author Andrey Skvortsov <andrej.skvortzov@gmail.com>
Sun, 06 Jan 2019 03:11:39 +0300
changeset 2501 eba2bbb2dd9a
parent 1470 f65ab5ff91d1
child 2272 28b0a783975e
permissions -rw-r--r--
Make online debug optional

It could be useful for very small targets like Atmega (Arduino) and
for target bring-up there developer want to have running PLC program,
but has not implemented runtime communication yet.


TARGET_DEBUG_AND_RETAIN_DISABLE - completely disable debug and retain
functionality. Previously named TARGET_DEBUG_DISABLE.

TARGET_ONLINE_DEBUG_DISABLE - can be used to enable retain
functionality (no define TARGET_DEBUG_AND_RETAIN_DISABLE is used), but disable
online debug with corresponding RAM/FLASH overhead.

TARGET_LOGGING_DISABLE - disables logging functionality from runtime and PLC program

TARGET_EXT_SYNC_DISABLE - disables PLC program synchronization with
external events. For example, it could be used to synchronize several
PLCs that control motors for different axes.

By default all these options are off.

To test generate program for Generic target, put following files in
project files directory and run build.sh after generating PLC program.
This is very easy to integrate into makefile (Generic target).

[------------- build.sh --------------------------]
files=$(find $PWD/../build -iname '*.c' | grep -v POUS.c)
arm-none-eabi-gcc \
-DTARGET_DEBUG_AND_RETAIN_DISABLE \
-DTARGET_ONLINE_DEBUG_DISABLE \
-DTARGET_LOGGING_DISABLE \
-DTARGET_EXT_SYNC_DISABLE \
-flto -ffunction-sections -fdata-sections -I../../../../matiec/lib/C \
$files \
main.c \
-Wl,--Map=./program.map,--cref \
-nodefaultlibs --specs=nano.specs -Wl,--static -Wl,--gc-section -Wl,--start-group -lc -lm -lnosys -lgcc -Wl,--end-group
[------------------------------------------------]

[------------- main.c --------------------------]
#ifndef TARGET_DEBUG_AND_RETAIN_DISABLE
void Retain(void){}
void InValidateRetainBuffer(void){}
void ValidateRetainBuffer(void){}
#endif

extern void __run(void);
int main(void)
{
for(;;) {
__run();
// sleep common_ticktime__ ns
// add common_ticktime__ ns to __CURRENT_TIME
}
return 0;
}
[------------------------------------------------]
/*
 * 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;
}