diff -r 0ae5a15efa18 -r fdc12f7d27c8 svghmi/svghmi.c --- a/svghmi/svghmi.c Tue Nov 30 18:43:10 2021 +0100 +++ b/svghmi/svghmi.c Sun Jan 16 17:00:58 2022 +0100 @@ -10,6 +10,7 @@ #define HMI_ITEM_COUNT %(item_count)d #define HMI_HASH_SIZE 8 #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}; @@ -19,7 +20,6 @@ /* PLC writes to that buffer */ static char wbuf[HMI_BUFFER_SIZE]; -/* TODO change that in case of multiclient... */ /* 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; @@ -42,11 +42,15 @@ static long hmitree_rlock = 0; static long hmitree_wlock = 0; -typedef struct { +typedef struct hmi_tree_item_s hmi_tree_item_t; +struct hmi_tree_item_s{ void *ptr; __IEC_types_enum type; uint32_t buf_index; + /* retrieve/read/recv */ + buf_state_t rstate; + /* publish/write/send */ buf_state_t wstate[MAX_CONNECTIONS]; @@ -54,111 +58,114 @@ uint16_t refresh_period_ms[MAX_CONNECTIONS]; uint16_t age_ms[MAX_CONNECTIONS]; - /* retrieve/read/recv */ - buf_state_t rstate; - -} hmi_tree_item_t; - -static hmi_tree_item_t hmi_tree_item[] = { + /* 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) { \ + &(cpath), /*ptr*/ \ + type, /*type*/ \ + buf_index, /*buf_index*/ \ + buf_free, /*rstate*/ \ + {[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*/\ + NULL} /*incoming_next*/ + + +/* 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[] = { %(variable_decl_array)s }; -typedef int(*hmi_tree_iterator)(uint32_t, hmi_tree_item_t*); -static int traverse_hmi_tree(hmi_tree_iterator fp) -{ - unsigned int i; - for(i = 0; i < sizeof(hmi_tree_item)/sizeof(hmi_tree_item_t); i++){ - hmi_tree_item_t *dsc = &hmi_tree_item[i]; - int res = (*fp)(i, dsc); - if(res != 0){ - return res; - } - } +#define __Unpack_desc_type hmi_tree_item_t + +%(var_access_code)s + +static int write_iterator(hmi_tree_item_t *dsc) +{ + uint32_t session_index = 0; + int value_changed = 0; + void *dest_p = NULL; + void *value_p = NULL; + size_t sz = 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; + }else{ + dsc->wstate[session_index] = buf_tosend; + global_write_dirty = 1; + } + } + } + + /* variable is sample only if just subscribed + or already subscribed and having value change */ + int do_sample = 0; + int just_subscribed = dsc->wstate[session_index] == buf_new; + if(!just_subscribed){ + int already_subscribed = dsc->refresh_period_ms[session_index] > 0; + if(already_subscribed){ + if(!value_changed){ + if(!value_p){ + UnpackVar(dsc, &value_p, NULL, &sz); + if(__Is_a_string(dsc)){ + 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; + }else{ + do_sample = 1; + } + } + } else { + do_sample = 1; + } + + + if(do_sample){ + 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; + global_write_dirty = 1; + } else { + dsc->wstate[session_index] = buf_set; + } + dsc->age_ms[session_index] = 0; + } + } + + session_index++; + } + /* copy value if changed (and subscribed) */ + if(value_changed) + memcpy(dest_p, value_p, sz); return 0; } -#define __Unpack_desc_type hmi_tree_item_t - -%(var_access_code)s - -static int write_iterator(uint32_t index, hmi_tree_item_t *dsc) -{ - { - uint32_t session_index = 0; - int value_changed = 0; - void *dest_p = NULL; - void *real_value_p = NULL; - void *visible_value_p = NULL; - USINT sz = 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; - }else{ - dsc->wstate[session_index] = buf_tosend; - global_write_dirty = 1; - } - } - } - - /* variable is sample only if just subscribed - or already subscribed and having value change */ - int do_sample = 0; - int just_subscribed = dsc->wstate[session_index] == buf_new; - if(!just_subscribed){ - int already_subscribed = dsc->refresh_period_ms[session_index] > 0; - if(already_subscribed){ - if(!value_changed){ - if(!visible_value_p){ - char flags = 0; - visible_value_p = UnpackVar(dsc, &real_value_p, &flags); - if(__Is_a_string(dsc)){ - sz = ((STRING*)visible_value_p)->len + 1; - } else { - sz = __get_type_enum_size(dsc->type); - } - dest_p = &wbuf[dsc->buf_index]; - } - value_changed = memcmp(dest_p, visible_value_p, sz) != 0; - do_sample = value_changed; - }else{ - do_sample = 1; - } - } - } else { - do_sample = 1; - } - - - if(do_sample){ - 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; - global_write_dirty = 1; - } else { - dsc->wstate[session_index] = buf_set; - } - dsc->age_ms[session_index] = 0; - } - } - - session_index++; - } - /* copy value if changed (and subscribed) */ - if(value_changed) - memcpy(dest_p, visible_value_p, sz); - } - // else ... : PLC can't wait, variable will be updated next turn - return 0; -} - -static uint32_t send_session_index; -static int send_iterator(uint32_t index, hmi_tree_item_t *dsc) -{ - if(dsc->wstate[send_session_index] == buf_tosend) +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)) @@ -171,7 +178,7 @@ /* TODO : force into little endian */ memcpy(dst_p, &index, sizeof(uint32_t)); memcpy(dst_p + sizeof(uint32_t), src_p, sz); - dsc->wstate[send_session_index] = buf_free; + dsc->wstate[session_index] = buf_free; sbufidx += sizeof(uint32_t) /* index */ + sz; } else @@ -184,15 +191,15 @@ return 0; } -static int read_iterator(uint32_t index, hmi_tree_item_t *dsc) +static int read_iterator(hmi_tree_item_t *dsc) { if(dsc->rstate == buf_set) { void *src_p = &rbuf[dsc->buf_index]; - void *real_value_p = NULL; - char flags = 0; - void *visible_value_p = UnpackVar(dsc, &real_value_p, &flags); - memcpy(real_value_p, src_p, __get_type_enum_size(dsc->type)); + void *value_p = NULL; + size_t sz = 0; + UnpackVar(dsc, &value_p, NULL, &sz); + memcpy(value_p, src_p, sz); dsc->rstate = buf_free; } return 0; @@ -200,24 +207,64 @@ void update_refresh_period(hmi_tree_item_t *dsc, uint32_t session_index, uint16_t refresh_period_ms) { - if(refresh_period_ms) { - if(!dsc->refresh_period_ms[session_index]) + 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; + } } else { 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; + } + /* unnecessary + dsc->subscriptions_next = NULL; + dsc->subscriptions_prev = NULL; + */ + } } dsc->refresh_period_ms[session_index] = refresh_period_ms; } -static uint32_t reset_session_index; -static int reset_iterator(uint32_t index, hmi_tree_item_t *dsc) -{ - update_refresh_period(dsc, reset_session_index, 0); - return 0; -} - static void *svghmi_handle; void SVGHMI_SuspendFromPythonThread(void) @@ -242,7 +289,7 @@ /* create svghmi_pipe */ svghmi_handle = create_RT_to_nRT_signal("SVGHMI_pipe"); - if(!svghmi_handle) + if(!svghmi_handle) return 1; return 0; @@ -258,7 +305,18 @@ void __retrieve_svghmi() { if(AtomicCompareExchange(&hmitree_rlock, 0, 1) == 0) { - traverse_hmi_tree(read_iterator); + hmi_tree_item_t *dsc = incoming_tail; + /* iterate through read list (changes from HMI) */ + while(dsc){ + hmi_tree_item_t *_dsc = dsc->incoming_prev; + read_iterator(dsc); + /* unnecessary + dsc->incoming_prev = NULL; + */ + dsc = _dsc; + } + /* flush read list */ + incoming_tail = NULL; AtomicCompareExchange(&hmitree_rlock, 1, 0); } } @@ -266,10 +324,16 @@ void __publish_svghmi() { global_write_dirty = 0; + if(AtomicCompareExchange(&hmitree_wlock, 0, 1) == 0) { - traverse_hmi_tree(write_iterator); + hmi_tree_item_t *dsc = subscriptions_tail; + while(dsc){ + write_iterator(dsc); + dsc = dsc->subscriptions_prev; + } AtomicCompareExchange(&hmitree_wlock, 1, 0); } + if(global_write_dirty) { SVGHMI_WakeupFromRTThread(); } @@ -283,16 +347,25 @@ int svghmi_send_collect(uint32_t session_index, uint32_t *size, char **ptr){ + if(svghmi_continue_collect) { int res; sbufidx = HMI_HASH_SIZE; - send_session_index = session_index; while(AtomicCompareExchange(&hmitree_wlock, 0, 1)){ nRT_reschedule(); } - if((res = traverse_hmi_tree(send_iterator)) == 0) + hmi_tree_item_t *dsc = subscriptions_tail; + while(dsc){ + uint32_t index = dsc - hmi_tree_items; + res = send_iterator(index, dsc, session_index); + if(res != 0){ + break; + } + dsc = dsc->subscriptions_prev; + } + if(res == 0) { if(sbufidx > HMI_HASH_SIZE){ memcpy(&sbuf[0], &hmi_hash[0], HMI_HASH_SIZE); @@ -304,7 +377,6 @@ AtomicCompareExchange(&hmitree_wlock, 1, 0); return ENODATA; } - // printf("collected BAD result %%d\n", res); AtomicCompareExchange(&hmitree_wlock, 1, 0); return res; } @@ -322,8 +394,16 @@ } cmd_from_JS; int svghmi_reset(uint32_t session_index){ - reset_session_index = session_index; - traverse_hmi_tree(reset_iterator); + hmi_tree_item_t *dsc = subscriptions_tail; + while(AtomicCompareExchange(&hmitree_wlock, 0, 1)){ + nRT_reschedule(); + } + while(dsc){ + hmi_tree_item_t *_dsc = dsc->subscriptions_prev; + update_refresh_period(dsc, session_index, 0); + dsc = _dsc; + } + AtomicCompareExchange(&hmitree_wlock, 1, 0); return 1; } @@ -355,6 +435,7 @@ cmd_old = cmd; cmd = *(cursor++); + if(cmd_old != cmd){ if(got_wlock){ AtomicCompareExchange(&hmitree_wlock, 1, 0); @@ -372,20 +453,20 @@ uint32_t index = *(uint32_t*)(cursor); uint8_t const *valptr = cursor + sizeof(uint32_t); + if(index == heartbeat_index) was_hearbeat = 1; if(index < HMI_ITEM_COUNT) { - hmi_tree_item_t *dsc = &hmi_tree_item[index]; - void *real_value_p = NULL; - char flags = 0; - void *visible_value_p = UnpackVar(dsc, &real_value_p, &flags); + hmi_tree_item_t *dsc = &hmi_tree_items[index]; + size_t sz = 0; void *dst_p = &rbuf[dsc->buf_index]; - uint32_t sz = __get_type_enum_size(dsc->type); if(__Is_a_string(dsc)){ sz = ((STRING*)valptr)->len + 1; + } else { + UnpackVar(dsc, NULL, NULL, &sz); } if((valptr + sz) <= end) @@ -399,7 +480,14 @@ } memcpy(dst_p, valptr, sz); - dsc->rstate = buf_set; + + /* check that rstate is not already buf_set */ + if(dsc->rstate != buf_set){ + dsc->rstate = buf_set; + /* append entry to read list (changes from HMI) */ + dsc->incoming_prev = incoming_tail; + incoming_tail = dsc; + } progress = sz + sizeof(uint32_t) /* index */; } @@ -420,14 +508,20 @@ case reset: { progress = 0; - reset_session_index = session_index; if(!got_wlock){ while(AtomicCompareExchange(&hmitree_wlock, 0, 1)){ nRT_reschedule(); } got_wlock = 1; } - traverse_hmi_tree(reset_iterator); + { + hmi_tree_item_t *dsc = subscriptions_tail; + while(dsc){ + hmi_tree_item_t *_dsc = dsc->subscriptions_prev; + update_refresh_period(dsc, session_index, 0); + dsc = _dsc; + } + } } break; @@ -444,7 +538,7 @@ } got_wlock = 1; } - hmi_tree_item_t *dsc = &hmi_tree_item[index]; + hmi_tree_item_t *dsc = &hmi_tree_items[index]; update_refresh_period(dsc, session_index, refresh_period_ms); } else