8 #define DEFAULT_REFRESH_PERIOD_MS 100 |
8 #define DEFAULT_REFRESH_PERIOD_MS 100 |
9 #define HMI_BUFFER_SIZE %(buffer_size)d |
9 #define HMI_BUFFER_SIZE %(buffer_size)d |
10 #define HMI_ITEM_COUNT %(item_count)d |
10 #define HMI_ITEM_COUNT %(item_count)d |
11 #define HMI_HASH_SIZE 8 |
11 #define HMI_HASH_SIZE 8 |
12 #define MAX_CONNECTIONS %(max_connections)d |
12 #define MAX_CONNECTIONS %(max_connections)d |
|
13 #define MAX_CON_INDEX MAX_CONNECTIONS - 1 |
13 |
14 |
14 static uint8_t hmi_hash[HMI_HASH_SIZE] = {%(hmi_hash_ints)s}; |
15 static uint8_t hmi_hash[HMI_HASH_SIZE] = {%(hmi_hash_ints)s}; |
15 |
16 |
16 /* PLC reads from that buffer */ |
17 /* PLC reads from that buffer */ |
17 static char rbuf[HMI_BUFFER_SIZE]; |
18 static char rbuf[HMI_BUFFER_SIZE]; |
18 |
19 |
19 /* PLC writes to that buffer */ |
20 /* PLC writes to that buffer */ |
20 static char wbuf[HMI_BUFFER_SIZE]; |
21 static char wbuf[HMI_BUFFER_SIZE]; |
21 |
22 |
22 /* TODO change that in case of multiclient... */ |
|
23 /* worst biggest send buffer. FIXME : use dynamic alloc ? */ |
23 /* worst biggest send buffer. FIXME : use dynamic alloc ? */ |
24 static char sbuf[HMI_HASH_SIZE + HMI_BUFFER_SIZE + (HMI_ITEM_COUNT * sizeof(uint32_t))]; |
24 static char sbuf[HMI_HASH_SIZE + HMI_BUFFER_SIZE + (HMI_ITEM_COUNT * sizeof(uint32_t))]; |
25 static unsigned int sbufidx; |
25 static unsigned int sbufidx; |
26 |
26 |
27 %(extern_variables_declarations)s |
27 %(extern_variables_declarations)s |
40 |
40 |
41 static int global_write_dirty = 0; |
41 static int global_write_dirty = 0; |
42 static long hmitree_rlock = 0; |
42 static long hmitree_rlock = 0; |
43 static long hmitree_wlock = 0; |
43 static long hmitree_wlock = 0; |
44 |
44 |
45 typedef struct { |
45 typedef struct hmi_tree_item_s hmi_tree_item_t; |
|
46 struct hmi_tree_item_s{ |
46 void *ptr; |
47 void *ptr; |
47 __IEC_types_enum type; |
48 __IEC_types_enum type; |
48 uint32_t buf_index; |
49 uint32_t buf_index; |
49 |
50 |
|
51 /* retrieve/read/recv */ |
|
52 buf_state_t rstate; |
|
53 |
50 /* publish/write/send */ |
54 /* publish/write/send */ |
51 buf_state_t wstate[MAX_CONNECTIONS]; |
55 buf_state_t wstate[MAX_CONNECTIONS]; |
52 |
56 |
53 /* zero means not subscribed */ |
57 /* zero means not subscribed */ |
54 uint16_t refresh_period_ms[MAX_CONNECTIONS]; |
58 uint16_t refresh_period_ms[MAX_CONNECTIONS]; |
55 uint16_t age_ms[MAX_CONNECTIONS]; |
59 uint16_t age_ms[MAX_CONNECTIONS]; |
56 |
60 |
57 /* retrieve/read/recv */ |
61 /* dual linked list for subscriptions */ |
58 buf_state_t rstate; |
62 hmi_tree_item_t *subscriptions_next; |
59 |
63 hmi_tree_item_t *subscriptions_prev; |
60 } hmi_tree_item_t; |
64 |
61 |
65 /* single linked list for changes from HMI */ |
62 static hmi_tree_item_t hmi_tree_item[] = { |
66 hmi_tree_item_t *incoming_prev; |
|
67 |
|
68 }; |
|
69 |
|
70 #define HMITREE_ITEM_INITIALIZER(cpath,type,buf_index) { \ |
|
71 &(cpath), /*ptr*/ \ |
|
72 type, /*type*/ \ |
|
73 buf_index, /*buf_index*/ \ |
|
74 buf_free, /*rstate*/ \ |
|
75 {[0 ... MAX_CON_INDEX] = buf_free}, /*wstate*/ \ |
|
76 {[0 ... MAX_CON_INDEX] = 0}, /*refresh_period_ms*/ \ |
|
77 {[0 ... MAX_CON_INDEX] = 0}, /*age_ms*/ \ |
|
78 NULL, /*subscriptions_next*/\ |
|
79 NULL, /*subscriptions_prev*/\ |
|
80 NULL} /*incoming_next*/ |
|
81 |
|
82 |
|
83 /* entry for dual linked list for HMI subscriptions */ |
|
84 /* points to the end of the list */ |
|
85 static hmi_tree_item_t *subscriptions_tail = NULL; |
|
86 |
|
87 /* entry for single linked list for changes from HMI */ |
|
88 /* points to the end of the list */ |
|
89 static hmi_tree_item_t *incoming_tail = NULL; |
|
90 |
|
91 static hmi_tree_item_t hmi_tree_items[] = { |
63 %(variable_decl_array)s |
92 %(variable_decl_array)s |
64 }; |
93 }; |
65 |
94 |
66 typedef int(*hmi_tree_iterator)(uint32_t, hmi_tree_item_t*); |
95 #define __Unpack_desc_type hmi_tree_item_t |
67 static int traverse_hmi_tree(hmi_tree_iterator fp) |
96 |
68 { |
97 %(var_access_code)s |
69 unsigned int i; |
98 |
70 for(i = 0; i < sizeof(hmi_tree_item)/sizeof(hmi_tree_item_t); i++){ |
99 static int write_iterator(hmi_tree_item_t *dsc) |
71 hmi_tree_item_t *dsc = &hmi_tree_item[i]; |
100 { |
72 int res = (*fp)(i, dsc); |
101 uint32_t session_index = 0; |
73 if(res != 0){ |
102 int value_changed = 0; |
74 return res; |
103 void *dest_p = NULL; |
75 } |
104 void *value_p = NULL; |
76 } |
105 size_t sz = 0; |
|
106 while(session_index < MAX_CONNECTIONS) { |
|
107 if(dsc->wstate[session_index] == buf_set){ |
|
108 /* if being subscribed */ |
|
109 if(dsc->refresh_period_ms[session_index]){ |
|
110 if(dsc->age_ms[session_index] + ticktime_ms < dsc->refresh_period_ms[session_index]){ |
|
111 dsc->age_ms[session_index] += ticktime_ms; |
|
112 }else{ |
|
113 dsc->wstate[session_index] = buf_tosend; |
|
114 global_write_dirty = 1; |
|
115 } |
|
116 } |
|
117 } |
|
118 |
|
119 /* variable is sample only if just subscribed |
|
120 or already subscribed and having value change */ |
|
121 int do_sample = 0; |
|
122 int just_subscribed = dsc->wstate[session_index] == buf_new; |
|
123 if(!just_subscribed){ |
|
124 int already_subscribed = dsc->refresh_period_ms[session_index] > 0; |
|
125 if(already_subscribed){ |
|
126 if(!value_changed){ |
|
127 if(!value_p){ |
|
128 UnpackVar(dsc, &value_p, NULL, &sz); |
|
129 if(__Is_a_string(dsc)){ |
|
130 sz = ((STRING*)value_p)->len + 1; |
|
131 } |
|
132 dest_p = &wbuf[dsc->buf_index]; |
|
133 } |
|
134 value_changed = memcmp(dest_p, value_p, sz) != 0; |
|
135 do_sample = value_changed; |
|
136 }else{ |
|
137 do_sample = 1; |
|
138 } |
|
139 } |
|
140 } else { |
|
141 do_sample = 1; |
|
142 } |
|
143 |
|
144 |
|
145 if(do_sample){ |
|
146 if(dsc->wstate[session_index] != buf_set && dsc->wstate[session_index] != buf_tosend) { |
|
147 if(dsc->wstate[session_index] == buf_new \ |
|
148 || ticktime_ms > dsc->refresh_period_ms[session_index]){ |
|
149 dsc->wstate[session_index] = buf_tosend; |
|
150 global_write_dirty = 1; |
|
151 } else { |
|
152 dsc->wstate[session_index] = buf_set; |
|
153 } |
|
154 dsc->age_ms[session_index] = 0; |
|
155 } |
|
156 } |
|
157 |
|
158 session_index++; |
|
159 } |
|
160 /* copy value if changed (and subscribed) */ |
|
161 if(value_changed) |
|
162 memcpy(dest_p, value_p, sz); |
77 return 0; |
163 return 0; |
78 } |
164 } |
79 |
165 |
80 #define __Unpack_desc_type hmi_tree_item_t |
166 static int send_iterator(uint32_t index, hmi_tree_item_t *dsc, uint32_t session_index) |
81 |
167 { |
82 %(var_access_code)s |
168 if(dsc->wstate[session_index] == buf_tosend) |
83 |
|
84 static int write_iterator(uint32_t index, hmi_tree_item_t *dsc) |
|
85 { |
|
86 { |
|
87 uint32_t session_index = 0; |
|
88 int value_changed = 0; |
|
89 void *dest_p = NULL; |
|
90 void *real_value_p = NULL; |
|
91 void *visible_value_p = NULL; |
|
92 USINT sz = 0; |
|
93 while(session_index < MAX_CONNECTIONS) { |
|
94 if(dsc->wstate[session_index] == buf_set){ |
|
95 /* if being subscribed */ |
|
96 if(dsc->refresh_period_ms[session_index]){ |
|
97 if(dsc->age_ms[session_index] + ticktime_ms < dsc->refresh_period_ms[session_index]){ |
|
98 dsc->age_ms[session_index] += ticktime_ms; |
|
99 }else{ |
|
100 dsc->wstate[session_index] = buf_tosend; |
|
101 global_write_dirty = 1; |
|
102 } |
|
103 } |
|
104 } |
|
105 |
|
106 /* variable is sample only if just subscribed |
|
107 or already subscribed and having value change */ |
|
108 int do_sample = 0; |
|
109 int just_subscribed = dsc->wstate[session_index] == buf_new; |
|
110 if(!just_subscribed){ |
|
111 int already_subscribed = dsc->refresh_period_ms[session_index] > 0; |
|
112 if(already_subscribed){ |
|
113 if(!value_changed){ |
|
114 if(!visible_value_p){ |
|
115 char flags = 0; |
|
116 visible_value_p = UnpackVar(dsc, &real_value_p, &flags); |
|
117 if(__Is_a_string(dsc)){ |
|
118 sz = ((STRING*)visible_value_p)->len + 1; |
|
119 } else { |
|
120 sz = __get_type_enum_size(dsc->type); |
|
121 } |
|
122 dest_p = &wbuf[dsc->buf_index]; |
|
123 } |
|
124 value_changed = memcmp(dest_p, visible_value_p, sz) != 0; |
|
125 do_sample = value_changed; |
|
126 }else{ |
|
127 do_sample = 1; |
|
128 } |
|
129 } |
|
130 } else { |
|
131 do_sample = 1; |
|
132 } |
|
133 |
|
134 |
|
135 if(do_sample){ |
|
136 if(dsc->wstate[session_index] != buf_set && dsc->wstate[session_index] != buf_tosend) { |
|
137 if(dsc->wstate[session_index] == buf_new \ |
|
138 || ticktime_ms > dsc->refresh_period_ms[session_index]){ |
|
139 dsc->wstate[session_index] = buf_tosend; |
|
140 global_write_dirty = 1; |
|
141 } else { |
|
142 dsc->wstate[session_index] = buf_set; |
|
143 } |
|
144 dsc->age_ms[session_index] = 0; |
|
145 } |
|
146 } |
|
147 |
|
148 session_index++; |
|
149 } |
|
150 /* copy value if changed (and subscribed) */ |
|
151 if(value_changed) |
|
152 memcpy(dest_p, visible_value_p, sz); |
|
153 } |
|
154 // else ... : PLC can't wait, variable will be updated next turn |
|
155 return 0; |
|
156 } |
|
157 |
|
158 static uint32_t send_session_index; |
|
159 static int send_iterator(uint32_t index, hmi_tree_item_t *dsc) |
|
160 { |
|
161 if(dsc->wstate[send_session_index] == buf_tosend) |
|
162 { |
169 { |
163 uint32_t sz = __get_type_enum_size(dsc->type); |
170 uint32_t sz = __get_type_enum_size(dsc->type); |
164 if(sbufidx + sizeof(uint32_t) + sz <= sizeof(sbuf)) |
171 if(sbufidx + sizeof(uint32_t) + sz <= sizeof(sbuf)) |
165 { |
172 { |
166 void *src_p = &wbuf[dsc->buf_index]; |
173 void *src_p = &wbuf[dsc->buf_index]; |
182 } |
189 } |
183 |
190 |
184 return 0; |
191 return 0; |
185 } |
192 } |
186 |
193 |
187 static int read_iterator(uint32_t index, hmi_tree_item_t *dsc) |
194 static int read_iterator(hmi_tree_item_t *dsc) |
188 { |
195 { |
189 if(dsc->rstate == buf_set) |
196 if(dsc->rstate == buf_set) |
190 { |
197 { |
191 void *src_p = &rbuf[dsc->buf_index]; |
198 void *src_p = &rbuf[dsc->buf_index]; |
192 void *real_value_p = NULL; |
199 void *value_p = NULL; |
193 char flags = 0; |
200 size_t sz = 0; |
194 void *visible_value_p = UnpackVar(dsc, &real_value_p, &flags); |
201 UnpackVar(dsc, &value_p, NULL, &sz); |
195 memcpy(real_value_p, src_p, __get_type_enum_size(dsc->type)); |
202 memcpy(value_p, src_p, sz); |
196 dsc->rstate = buf_free; |
203 dsc->rstate = buf_free; |
197 } |
204 } |
198 return 0; |
205 return 0; |
199 } |
206 } |
200 |
207 |
201 void update_refresh_period(hmi_tree_item_t *dsc, uint32_t session_index, uint16_t refresh_period_ms) |
208 void update_refresh_period(hmi_tree_item_t *dsc, uint32_t session_index, uint16_t refresh_period_ms) |
202 { |
209 { |
203 if(refresh_period_ms) { |
210 uint32_t other_session_index = 0; |
204 if(!dsc->refresh_period_ms[session_index]) |
211 int previously_subscribed = 0; |
|
212 int session_only_subscriber = 0; |
|
213 int session_already_subscriber = 0; |
|
214 int needs_subscription_for_session = (refresh_period_ms != 0); |
|
215 |
|
216 while(other_session_index < session_index) { |
|
217 previously_subscribed |= (dsc->refresh_period_ms[other_session_index++] != 0); |
|
218 } |
|
219 session_already_subscriber = (dsc->refresh_period_ms[other_session_index++] != 0); |
|
220 while(other_session_index < MAX_CONNECTIONS) { |
|
221 previously_subscribed |= (dsc->refresh_period_ms[other_session_index++] != 0); |
|
222 } |
|
223 session_only_subscriber = session_already_subscriber && !previously_subscribed; |
|
224 previously_subscribed |= session_already_subscriber; |
|
225 |
|
226 if(needs_subscription_for_session) { |
|
227 if(!session_already_subscriber) |
205 { |
228 { |
206 dsc->wstate[session_index] = buf_new; |
229 dsc->wstate[session_index] = buf_new; |
207 } |
230 } |
|
231 /* item is appended to list only when no session was previously subscribed */ |
|
232 if(!previously_subscribed){ |
|
233 /* append subsciption to list */ |
|
234 if(subscriptions_tail != NULL){ |
|
235 /* if list wasn't empty, link with previous tail*/ |
|
236 subscriptions_tail->subscriptions_next = dsc; |
|
237 } |
|
238 dsc->subscriptions_prev = subscriptions_tail; |
|
239 subscriptions_tail = dsc; |
|
240 dsc->subscriptions_next = NULL; |
|
241 } |
208 } else { |
242 } else { |
209 dsc->wstate[session_index] = buf_free; |
243 dsc->wstate[session_index] = buf_free; |
|
244 /* item is removed from list only when session was the only one remaining */ |
|
245 if (session_only_subscriber) { |
|
246 if(dsc->subscriptions_next == NULL){ /* remove tail */ |
|
247 /* re-link tail to previous */ |
|
248 subscriptions_tail = dsc->subscriptions_prev; |
|
249 if(subscriptions_tail != NULL){ |
|
250 subscriptions_tail->subscriptions_next = NULL; |
|
251 } |
|
252 } else if(dsc->subscriptions_prev == NULL){ /* remove head */ |
|
253 dsc->subscriptions_next->subscriptions_prev = NULL; |
|
254 } else { /* remove entry in between other entries */ |
|
255 /* re-link previous and next node */ |
|
256 dsc->subscriptions_next->subscriptions_prev = dsc->subscriptions_prev; |
|
257 dsc->subscriptions_prev->subscriptions_next = dsc->subscriptions_next; |
|
258 } |
|
259 /* unnecessary |
|
260 dsc->subscriptions_next = NULL; |
|
261 dsc->subscriptions_prev = NULL; |
|
262 */ |
|
263 } |
210 } |
264 } |
211 dsc->refresh_period_ms[session_index] = refresh_period_ms; |
265 dsc->refresh_period_ms[session_index] = refresh_period_ms; |
212 } |
|
213 |
|
214 static uint32_t reset_session_index; |
|
215 static int reset_iterator(uint32_t index, hmi_tree_item_t *dsc) |
|
216 { |
|
217 update_refresh_period(dsc, reset_session_index, 0); |
|
218 return 0; |
|
219 } |
266 } |
220 |
267 |
221 static void *svghmi_handle; |
268 static void *svghmi_handle; |
222 |
269 |
223 void SVGHMI_SuspendFromPythonThread(void) |
270 void SVGHMI_SuspendFromPythonThread(void) |
256 } |
303 } |
257 |
304 |
258 void __retrieve_svghmi() |
305 void __retrieve_svghmi() |
259 { |
306 { |
260 if(AtomicCompareExchange(&hmitree_rlock, 0, 1) == 0) { |
307 if(AtomicCompareExchange(&hmitree_rlock, 0, 1) == 0) { |
261 traverse_hmi_tree(read_iterator); |
308 hmi_tree_item_t *dsc = incoming_tail; |
|
309 /* iterate through read list (changes from HMI) */ |
|
310 while(dsc){ |
|
311 hmi_tree_item_t *_dsc = dsc->incoming_prev; |
|
312 read_iterator(dsc); |
|
313 /* unnecessary |
|
314 dsc->incoming_prev = NULL; |
|
315 */ |
|
316 dsc = _dsc; |
|
317 } |
|
318 /* flush read list */ |
|
319 incoming_tail = NULL; |
262 AtomicCompareExchange(&hmitree_rlock, 1, 0); |
320 AtomicCompareExchange(&hmitree_rlock, 1, 0); |
263 } |
321 } |
264 } |
322 } |
265 |
323 |
266 void __publish_svghmi() |
324 void __publish_svghmi() |
267 { |
325 { |
268 global_write_dirty = 0; |
326 global_write_dirty = 0; |
|
327 |
269 if(AtomicCompareExchange(&hmitree_wlock, 0, 1) == 0) { |
328 if(AtomicCompareExchange(&hmitree_wlock, 0, 1) == 0) { |
270 traverse_hmi_tree(write_iterator); |
329 hmi_tree_item_t *dsc = subscriptions_tail; |
|
330 while(dsc){ |
|
331 write_iterator(dsc); |
|
332 dsc = dsc->subscriptions_prev; |
|
333 } |
271 AtomicCompareExchange(&hmitree_wlock, 1, 0); |
334 AtomicCompareExchange(&hmitree_wlock, 1, 0); |
272 } |
335 } |
|
336 |
273 if(global_write_dirty) { |
337 if(global_write_dirty) { |
274 SVGHMI_WakeupFromRTThread(); |
338 SVGHMI_WakeupFromRTThread(); |
275 } |
339 } |
276 } |
340 } |
277 |
341 |