#include "iec_types_all.h"
#define DEFAULT_REFRESH_PERIOD_MS 100
#define HMI_BUFFER_SIZE %(buffer_size)d
#define HMI_ITEM_COUNT %(item_count)d
#define MAX_CONNECTIONS %(max_connections)d
#define MAX_CON_INDEX MAX_CONNECTIONS - 1
static uint8_t hmi_hash[HMI_HASH_SIZE] = {%(hmi_hash_ints)s};
/* PLC reads from that buffer */
static char rbuf[HMI_BUFFER_SIZE];
/* PLC writes to that buffer */
static char wbuf[HMI_BUFFER_SIZE];
/* worst biggest send buffer. FIXME : use dynamic alloc ? */
static char sbuf[HMI_HASH_SIZE + HMI_BUFFER_SIZE + (HMI_ITEM_COUNT * sizeof(uint32_t))];
static unsigned int sbufidx;
%(extern_variables_declarations)s
#define ticktime_ns %(PLC_ticktime)d
static uint16_t ticktime_ms = (ticktime_ns>1000000)?
static int global_write_dirty = 0;
static long hmitree_rlock = 0;
static long hmitree_wlock = 0;
typedef struct hmi_tree_item_s hmi_tree_item_t;
buf_state_t wstate[MAX_CONNECTIONS];
/* zero means not subscribed */
uint16_t refresh_period_ms[MAX_CONNECTIONS];
uint16_t age_ms[MAX_CONNECTIONS];
/* dual linked list for subscriptions */
hmi_tree_item_t *subscriptions_next;
hmi_tree_item_t *subscriptions_prev;
/* single linked list for changes from HMI */
hmi_tree_item_t *incoming_prev;
#define HMITREE_ITEM_INITIALIZER(cpath,type,buf_index) { \
buf_index, /*buf_index*/ \
{[0 ... MAX_CON_INDEX] = buf_free}, /*wstate*/ \
{[0 ... MAX_CON_INDEX] = 0}, /*refresh_period_ms*/ \
{[0 ... MAX_CON_INDEX] = 0}, /*age_ms*/ \
NULL, /*subscriptions_next*/\
NULL, /*subscriptions_prev*/\
/* entry for dual linked list for HMI subscriptions */
/* points to the end of the list */
static hmi_tree_item_t *subscriptions_tail = NULL;
/* entry for single linked list for changes from HMI */
/* points to the end of the list */
static hmi_tree_item_t *incoming_tail = NULL;
static hmi_tree_item_t hmi_tree_items[] = {
#define __Unpack_desc_type hmi_tree_item_t
static int write_iterator(hmi_tree_item_t *dsc)
uint32_t session_index = 0;
while(session_index < MAX_CONNECTIONS) {
if(dsc->wstate[session_index] == buf_set){
/* if being subscribed */
if(dsc->refresh_period_ms[session_index]){
if(dsc->age_ms[session_index] + ticktime_ms < dsc->refresh_period_ms[session_index]){
dsc->age_ms[session_index] += ticktime_ms;
dsc->wstate[session_index] = buf_tosend;
/* variable is sample only if just subscribed
or already subscribed and having value change */
int just_subscribed = dsc->wstate[session_index] == buf_new;
int already_subscribed = dsc->refresh_period_ms[session_index] > 0;
UnpackVar(dsc, &value_p, NULL, &sz);
sz = ((STRING*)value_p)->len + 1;
dest_p = &wbuf[dsc->buf_index];
value_changed = memcmp(dest_p, value_p, sz) != 0;
do_sample = value_changed;
if(dsc->wstate[session_index] != buf_set && dsc->wstate[session_index] != buf_tosend) {
if(dsc->wstate[session_index] == buf_new \
|| ticktime_ms > dsc->refresh_period_ms[session_index]){
dsc->wstate[session_index] = buf_tosend;
dsc->wstate[session_index] = buf_set;
dsc->age_ms[session_index] = 0;
/* copy value if changed (and subscribed) */
memcpy(dest_p, value_p, sz);
static int send_iterator(uint32_t index, hmi_tree_item_t *dsc, uint32_t session_index)
if(dsc->wstate[session_index] == buf_tosend)
uint32_t sz = __get_type_enum_size(dsc->type);
if(sbufidx + sizeof(uint32_t) + sz <= sizeof(sbuf))
void *src_p = &wbuf[dsc->buf_index];
void *dst_p = &sbuf[sbufidx];
sz = ((STRING*)src_p)->len + 1;
/* TODO : force into little endian */
memcpy(dst_p, &index, sizeof(uint32_t));
memcpy(dst_p + sizeof(uint32_t), src_p, sz);
dsc->wstate[session_index] = buf_free;
sbufidx += sizeof(uint32_t) /* index */ + sz;
printf("BUG!!! %%d + %%ld + %%d > %%ld \n", sbufidx, sizeof(uint32_t), sz, sizeof(sbuf));
static int read_iterator(hmi_tree_item_t *dsc)
if(dsc->rstate == buf_set)
void *src_p = &rbuf[dsc->buf_index];
UnpackVar(dsc, &value_p, NULL, &sz);
memcpy(value_p, src_p, sz);
void update_refresh_period(hmi_tree_item_t *dsc, uint32_t session_index, uint16_t refresh_period_ms)
uint32_t other_session_index = 0;
int previously_subscribed = 0;
int session_only_subscriber = 0;
int session_already_subscriber = 0;
int needs_subscription_for_session = (refresh_period_ms != 0);
while(other_session_index < session_index) {
previously_subscribed |= (dsc->refresh_period_ms[other_session_index++] != 0);
session_already_subscriber = (dsc->refresh_period_ms[other_session_index++] != 0);
while(other_session_index < MAX_CONNECTIONS) {
previously_subscribed |= (dsc->refresh_period_ms[other_session_index++] != 0);
session_only_subscriber = session_already_subscriber && !previously_subscribed;
previously_subscribed |= session_already_subscriber;
if(needs_subscription_for_session) {
if(!session_already_subscriber)
dsc->wstate[session_index] = buf_new;
/* item is appended to list only when no session was previously subscribed */
if(!previously_subscribed){
/* append subsciption to list */
if(subscriptions_tail != NULL){
/* if list wasn't empty, link with previous tail*/
subscriptions_tail->subscriptions_next = dsc;
dsc->subscriptions_prev = subscriptions_tail;
subscriptions_tail = dsc;
dsc->subscriptions_next = NULL;
dsc->wstate[session_index] = buf_free;
/* item is removed from list only when session was the only one remaining */
if (session_only_subscriber) {
if(dsc->subscriptions_next == NULL){ /* remove tail */
/* re-link tail to previous */
subscriptions_tail = dsc->subscriptions_prev;
if(subscriptions_tail != NULL){
subscriptions_tail->subscriptions_next = NULL;
} else if(dsc->subscriptions_prev == NULL){ /* remove head */
dsc->subscriptions_next->subscriptions_prev = NULL;
} else { /* remove entry in between other entries */
/* re-link previous and next node */
dsc->subscriptions_next->subscriptions_prev = dsc->subscriptions_prev;
dsc->subscriptions_prev->subscriptions_next = dsc->subscriptions_next;
dsc->subscriptions_next = NULL;
dsc->subscriptions_prev = NULL;
dsc->refresh_period_ms[session_index] = refresh_period_ms;
static void *svghmi_handle;
void SVGHMI_SuspendFromPythonThread(void)
wait_RT_to_nRT_signal(svghmi_handle);
void SVGHMI_WakeupFromRTThread(void)
unblock_RT_to_nRT_signal(svghmi_handle);
int svghmi_continue_collect;
memset(rbuf,0,sizeof(rbuf));
memset(wbuf,0,sizeof(wbuf));
svghmi_continue_collect = 1;
svghmi_handle = create_RT_to_nRT_signal("SVGHMI_pipe");
svghmi_continue_collect = 0;
SVGHMI_WakeupFromRTThread();
delete_RT_to_nRT_signal(svghmi_handle);
if(AtomicCompareExchange(&hmitree_rlock, 0, 1) == 0) {
hmi_tree_item_t *dsc = incoming_tail;
/* iterate through read list (changes from HMI) */
hmi_tree_item_t *_dsc = dsc->incoming_prev;
dsc->incoming_prev = NULL;
AtomicCompareExchange(&hmitree_rlock, 1, 0);
if(AtomicCompareExchange(&hmitree_wlock, 0, 1) == 0) {
hmi_tree_item_t *dsc = subscriptions_tail;
dsc = dsc->subscriptions_prev;
AtomicCompareExchange(&hmitree_wlock, 1, 0);
SVGHMI_WakeupFromRTThread();
SVGHMI_SuspendFromPythonThread();
int svghmi_send_collect(uint32_t session_index, uint32_t *size, char **ptr){
if(svghmi_continue_collect) {
while(AtomicCompareExchange(&hmitree_wlock, 0, 1)){
hmi_tree_item_t *dsc = subscriptions_tail;
uint32_t index = dsc - hmi_tree_items;
res = send_iterator(index, dsc, session_index);
dsc = dsc->subscriptions_prev;
if(sbufidx > HMI_HASH_SIZE){
memcpy(&sbuf[0], &hmi_hash[0], HMI_HASH_SIZE);
AtomicCompareExchange(&hmitree_wlock, 1, 0);
AtomicCompareExchange(&hmitree_wlock, 1, 0);
AtomicCompareExchange(&hmitree_wlock, 1, 0);
int svghmi_reset(uint32_t session_index){
hmi_tree_item_t *dsc = subscriptions_tail;
while(AtomicCompareExchange(&hmitree_wlock, 0, 1)){
hmi_tree_item_t *_dsc = dsc->subscriptions_prev;
update_refresh_period(dsc, session_index, 0);
AtomicCompareExchange(&hmitree_wlock, 1, 0);
// 0 is OK, <0 is error, 1 is heartbeat
int svghmi_recv_dispatch(uint32_t session_index, uint32_t size, const uint8_t *ptr){
const uint8_t* cursor = ptr + HMI_HASH_SIZE;
const uint8_t* end = ptr + size;
/* match hmitree fingerprint */
if(size <= HMI_HASH_SIZE || memcmp(ptr, hmi_hash, HMI_HASH_SIZE) != 0)
printf("svghmi_recv_dispatch MISMATCH !!\n");
cmd_from_JS cmd_old = unset;
AtomicCompareExchange(&hmitree_wlock, 1, 0);
AtomicCompareExchange(&hmitree_rlock, 1, 0);
uint32_t index = *(uint32_t*)(cursor);
uint8_t const *valptr = cursor + sizeof(uint32_t);
if(index == heartbeat_index)
if(index < HMI_ITEM_COUNT)
hmi_tree_item_t *dsc = &hmi_tree_items[index];
void *dst_p = &rbuf[dsc->buf_index];
sz = ((STRING*)valptr)->len + 1;
UnpackVar(dsc, NULL, NULL, &sz);
// rescheduling spinlock until free
while(AtomicCompareExchange(&hmitree_rlock, 0, 1)){
memcpy(dst_p, valptr, sz);
/* check that rstate is not already buf_set */
if(dsc->rstate != buf_set){
/* append entry to read list (changes from HMI) */
dsc->incoming_prev = incoming_tail;
progress = sz + sizeof(uint32_t) /* index */;
while(AtomicCompareExchange(&hmitree_wlock, 0, 1)){
hmi_tree_item_t *dsc = subscriptions_tail;
hmi_tree_item_t *_dsc = dsc->subscriptions_prev;
update_refresh_period(dsc, session_index, 0);
uint32_t index = *(uint32_t*)(cursor);
uint16_t refresh_period_ms = *(uint32_t*)(cursor + sizeof(uint32_t));
if(index < HMI_ITEM_COUNT)
while(AtomicCompareExchange(&hmitree_wlock, 0, 1)){
hmi_tree_item_t *dsc = &hmi_tree_items[index];
update_refresh_period(dsc, session_index, refresh_period_ms);
progress = sizeof(uint32_t) /* index */ +
sizeof(uint16_t) /* refresh period */;
printf("svghmi_recv_dispatch unknown %%d\n",cmd);
AtomicCompareExchange(&hmitree_wlock, 1, 0);
AtomicCompareExchange(&hmitree_rlock, 1, 0);