1 /* |
|
2 * Python Asynchronous execution code |
|
3 * |
|
4 * PLC put python commands in a fifo, respecting execution order |
|
5 * with the help of C pragmas inserted in python_eval FB code |
|
6 * |
|
7 * Buffer content is read asynchronously, (from non real time part), |
|
8 * commands are executed and result stored for later use by PLC. |
|
9 * |
|
10 * In this implementation, fifo is a list of pointer to python_eval |
|
11 * function blocks structures. Some local variables have been added in |
|
12 * python_eval interface. We use those local variables as buffer and state |
|
13 * flags. |
|
14 * |
|
15 * */ |
|
16 |
|
17 #include "iec_types_all.h" |
|
18 #include "POUS.h" |
|
19 #include <string.h> |
|
20 |
|
21 /* The fifo (fixed size, as number of FB is fixed) */ |
|
22 static PYTHON_EVAL* EvalFBs[%(python_eval_fb_count)d]; |
|
23 /* Producer and consumer cursors */ |
|
24 static int Current_PLC_EvalFB; |
|
25 static int Current_Python_EvalFB; |
|
26 |
|
27 /* A global IEC-Python gateway state, for use inside python_eval FBs*/ |
|
28 static int PythonState; |
|
29 #define PYTHON_LOCKED_BY_PYTHON 0 |
|
30 #define PYTHON_LOCKED_BY_PLC 1 |
|
31 #define PYTHON_MUSTWAKEUP 2 |
|
32 #define PYTHON_FINISHED 4 |
|
33 |
|
34 /* Each python_eval FunctionBlock have it own state */ |
|
35 #define PYTHON_FB_FREE 0 |
|
36 #define PYTHON_FB_REQUESTED 1 |
|
37 #define PYTHON_FB_PROCESSING 2 |
|
38 #define PYTHON_FB_ANSWERED 3 |
|
39 |
|
40 int WaitPythonCommands(void); |
|
41 void UnBlockPythonCommands(void); |
|
42 int TryLockPython(void); |
|
43 void UnLockPython(void); |
|
44 void LockPython(void); |
|
45 |
|
46 int __init_%(location)s() |
|
47 { |
|
48 int i; |
|
49 /* Initialize cursors */ |
|
50 Current_Python_EvalFB = 0; |
|
51 Current_PLC_EvalFB = 0; |
|
52 PythonState = PYTHON_LOCKED_BY_PYTHON; |
|
53 for(i = 0; i < %(python_eval_fb_count)d; i++) |
|
54 EvalFBs[i] = NULL; |
|
55 return 0; |
|
56 } |
|
57 |
|
58 void __cleanup_%(location)s() |
|
59 { |
|
60 PythonState = PYTHON_FINISHED; |
|
61 UnBlockPythonCommands(); |
|
62 } |
|
63 |
|
64 void __retrieve_%(location)s() |
|
65 { |
|
66 /* Check Python thread is not being |
|
67 * modifying internal python_eval data */ |
|
68 PythonState = TryLockPython() ? |
|
69 PYTHON_LOCKED_BY_PLC : |
|
70 PYTHON_LOCKED_BY_PYTHON; |
|
71 /* If python thread _is_ in, then PythonState remains PYTHON_LOCKED_BY_PYTHON |
|
72 * and python_eval will no do anything */ |
|
73 } |
|
74 |
|
75 void __publish_%(location)s() |
|
76 { |
|
77 if(PythonState & PYTHON_LOCKED_BY_PLC){ |
|
78 /* If runnig PLC did push something in the fifo*/ |
|
79 if(PythonState & PYTHON_MUSTWAKEUP){ |
|
80 /* WakeUp python thread */ |
|
81 UnBlockPythonCommands(); |
|
82 } |
|
83 UnLockPython(); |
|
84 } |
|
85 } |
|
86 /** |
|
87 * Called by the PLC, each time a python_eval |
|
88 * FB instance is executed |
|
89 */ |
|
90 void __PythonEvalFB(int poll, PYTHON_EVAL* data__) |
|
91 { |
|
92 /* detect rising edge on TRIG to trigger evaluation */ |
|
93 if(((__GET_VAR(data__->TRIG) && !__GET_VAR(data__->TRIGM1)) || |
|
94 /* polling is equivalent to trig on value rather than on rising edge*/ |
|
95 (poll && __GET_VAR(data__->TRIG) )) && |
|
96 /* trig only if not already trigged */ |
|
97 __GET_VAR(data__->TRIGGED) == 0){ |
|
98 /* mark as trigged */ |
|
99 __SET_VAR(data__->, TRIGGED, 1); |
|
100 /* make a safe copy of the code */ |
|
101 __SET_VAR(data__->, PREBUFFER, __GET_VAR(data__->CODE)); |
|
102 } |
|
103 /* retain value for next rising edge detection */ |
|
104 __SET_VAR(data__->, TRIGM1, __GET_VAR(data__->TRIG)); |
|
105 |
|
106 /* python thread is not in ? */ |
|
107 if( PythonState & PYTHON_LOCKED_BY_PLC){ |
|
108 /* if some answer are waiting, publish*/ |
|
109 if(__GET_VAR(data__->STATE) == PYTHON_FB_ANSWERED){ |
|
110 /* Copy buffer content into result*/ |
|
111 __SET_VAR(data__->, RESULT, __GET_VAR(data__->BUFFER)); |
|
112 /* signal result presece to PLC*/ |
|
113 __SET_VAR(data__->, ACK, 1); |
|
114 /* Mark as free */ |
|
115 __SET_VAR(data__->, STATE, PYTHON_FB_FREE); |
|
116 /* mark as not trigged */ |
|
117 if(!poll) |
|
118 __SET_VAR(data__->, TRIGGED, 0); |
|
119 /*printf("__PythonEvalFB pop %%d - %%*s\n",Current_PLC_EvalFB, data__->BUFFER.len, data__->BUFFER.body);*/ |
|
120 }else if(poll){ |
|
121 /* when in polling, no answer == ack down */ |
|
122 __SET_VAR(data__->, ACK, 0); |
|
123 } |
|
124 /* got the order to act ?*/ |
|
125 if(__GET_VAR(data__->TRIGGED) == 1 && |
|
126 /* and not already being processed */ |
|
127 __GET_VAR(data__->STATE) == PYTHON_FB_FREE) |
|
128 { |
|
129 /* Enter the block in the fifo |
|
130 * Don't have to check if fifo cell is free |
|
131 * as fifo size == FB count, and a FB cannot |
|
132 * be requested twice */ |
|
133 EvalFBs[Current_PLC_EvalFB] = data__; |
|
134 /* copy into BUFFER local*/ |
|
135 __SET_VAR(data__->, BUFFER, __GET_VAR(data__->PREBUFFER)); |
|
136 /* Set ACK pin to low so that we can set a rising edge on result */ |
|
137 if(!poll){ |
|
138 /* when not polling, a new answer imply reseting ack*/ |
|
139 __SET_VAR(data__->, ACK, 0); |
|
140 }else{ |
|
141 /* when in polling, acting reset trigger */ |
|
142 __SET_VAR(data__->, TRIGGED, 0); |
|
143 } |
|
144 /* Mark FB busy */ |
|
145 __SET_VAR(data__->, STATE, PYTHON_FB_REQUESTED); |
|
146 /* Have to wakeup python thread in case he was asleep */ |
|
147 PythonState |= PYTHON_MUSTWAKEUP; |
|
148 /*printf("__PythonEvalFB push %%d - %%*s\n",Current_PLC_EvalFB, data__->BUFFER.len, data__->BUFFER.body);*/ |
|
149 /* Get a new line */ |
|
150 Current_PLC_EvalFB = (Current_PLC_EvalFB + 1) %% %(python_eval_fb_count)d; |
|
151 } |
|
152 } |
|
153 } |
|
154 |
|
155 char* PythonIterator(char* result) |
|
156 { |
|
157 char* next_command; |
|
158 PYTHON_EVAL* data__; |
|
159 //printf("PythonIterator result %%s\n", result); |
|
160 /*emergency exit*/ |
|
161 if(PythonState & PYTHON_FINISHED) return NULL; |
|
162 /* take python mutex to prevent changing PLC data while PLC running */ |
|
163 LockPython(); |
|
164 /* Get current FB */ |
|
165 data__ = EvalFBs[Current_Python_EvalFB]; |
|
166 if(data__ && /* may be null at first run */ |
|
167 __GET_VAR(data__->STATE) == PYTHON_FB_PROCESSING){ /* some answer awaited*/ |
|
168 /* If result not None */ |
|
169 if(result){ |
|
170 /* Get results len */ |
|
171 __SET_VAR(data__->, BUFFER, strlen(result), .len); |
|
172 /* prevent results overrun */ |
|
173 if(__GET_VAR(data__->BUFFER, .len) > STR_MAX_LEN) |
|
174 { |
|
175 __SET_VAR(data__->, BUFFER, STR_MAX_LEN, .len ); |
|
176 /* TODO : signal error */ |
|
177 } |
|
178 /* Copy results to buffer */ |
|
179 strncpy((char*)__GET_VAR(data__->BUFFER, .body), result, __GET_VAR(data__->BUFFER,.len)); |
|
180 }else{ |
|
181 __SET_VAR(data__->, BUFFER, 0, .len); |
|
182 } |
|
183 /* remove block from fifo*/ |
|
184 EvalFBs[Current_Python_EvalFB] = NULL; |
|
185 /* Mark block as answered */ |
|
186 __SET_VAR(data__->, STATE, PYTHON_FB_ANSWERED); |
|
187 /* Get a new line */ |
|
188 Current_Python_EvalFB = (Current_Python_EvalFB + 1) %% %(python_eval_fb_count)d; |
|
189 //printf("PythonIterator ++ Current_Python_EvalFB %%d\n", Current_Python_EvalFB); |
|
190 } |
|
191 /* while next slot is empty */ |
|
192 while(((data__ = EvalFBs[Current_Python_EvalFB]) == NULL) || |
|
193 /* or doesn't contain command */ |
|
194 __GET_VAR(data__->STATE) != PYTHON_FB_REQUESTED) |
|
195 { |
|
196 UnLockPython(); |
|
197 /* wait next FB to eval */ |
|
198 //printf("PythonIterator wait\n"); |
|
199 if(WaitPythonCommands()) return NULL; |
|
200 /*emergency exit*/ |
|
201 if(PythonState & PYTHON_FINISHED) return NULL; |
|
202 LockPython(); |
|
203 } |
|
204 /* Mark block as processing */ |
|
205 __SET_VAR(data__->, STATE, PYTHON_FB_PROCESSING); |
|
206 //printf("PythonIterator\n"); |
|
207 /* make BUFFER a null terminated string */ |
|
208 __SET_VAR(data__->, BUFFER, 0, .body[__GET_VAR(data__->BUFFER, .len)]); |
|
209 /* next command is BUFFER */ |
|
210 next_command = (char*)__GET_VAR(data__->BUFFER, .body); |
|
211 /* free python mutex */ |
|
212 UnLockPython(); |
|
213 /* return the next command to eval */ |
|
214 return next_command; |
|
215 } |
|
216 |
|