Commit fe8d7440 authored by Ivan Tyagov's avatar Ivan Tyagov

Add first example of a coupler with SVG HMI interface.

parent d1b74da6
<?xml version='1.0' encoding='utf-8'?>
<BeremizRoot xmlns:xsd="http://www.w3.org/2001/XMLSchema" URI_location="LOCAL://">
<TargetType/>
<Libraries Enable_SVGHMI_Library="true"/>
</BeremizRoot>
__LOCATED_VAR(DINT,__QD1_0,Q,D,1,0)
__LOCATED_VAR(DINT,__QD1_1,Q,D,1,1)
__LOCATED_VAR(DINT,__QD1_2,Q,D,1,2)
__LOCATED_VAR(DINT,__QD1_3,Q,D,1,3)
void LOGGER_init__(LOGGER *data__, BOOL retain) {
__INIT_VAR(data__->EN,__BOOL_LITERAL(TRUE),retain)
__INIT_VAR(data__->ENO,__BOOL_LITERAL(TRUE),retain)
__INIT_VAR(data__->TRIG,__BOOL_LITERAL(FALSE),retain)
__INIT_VAR(data__->MSG,__STRING_LITERAL(0,""),retain)
__INIT_VAR(data__->LEVEL,LOGLEVEL__INFO,retain)
__INIT_VAR(data__->TRIG0,__BOOL_LITERAL(FALSE),retain)
}
// Code part
void LOGGER_body__(LOGGER *data__) {
// Control execution
if (!__GET_VAR(data__->EN)) {
__SET_VAR(data__->,ENO,,__BOOL_LITERAL(FALSE));
return;
}
else {
__SET_VAR(data__->,ENO,,__BOOL_LITERAL(TRUE));
}
// Initialise TEMP variables
if ((__GET_VAR(data__->TRIG,) && !(__GET_VAR(data__->TRIG0,)))) {
#define GetFbVar(var,...) __GET_VAR(data__->var,__VA_ARGS__)
#define SetFbVar(var,val,...) __SET_VAR(data__->,var,__VA_ARGS__,val)
LogMessage(GetFbVar(LEVEL),(char*)GetFbVar(MSG, .body),GetFbVar(MSG, .len));
#undef GetFbVar
#undef SetFbVar
;
};
__SET_VAR(data__->,TRIG0,,__GET_VAR(data__->TRIG,));
goto __end;
__end:
return;
} // LOGGER_body__()
void PYTHON_EVAL_init__(PYTHON_EVAL *data__, BOOL retain) {
__INIT_VAR(data__->EN,__BOOL_LITERAL(TRUE),retain)
__INIT_VAR(data__->ENO,__BOOL_LITERAL(TRUE),retain)
__INIT_VAR(data__->TRIG,__BOOL_LITERAL(FALSE),retain)
__INIT_VAR(data__->CODE,__STRING_LITERAL(0,""),retain)
__INIT_VAR(data__->ACK,__BOOL_LITERAL(FALSE),retain)
__INIT_VAR(data__->RESULT,__STRING_LITERAL(0,""),retain)
__INIT_VAR(data__->STATE,0,retain)
__INIT_VAR(data__->BUFFER,__STRING_LITERAL(0,""),retain)
__INIT_VAR(data__->PREBUFFER,__STRING_LITERAL(0,""),retain)
__INIT_VAR(data__->TRIGM1,__BOOL_LITERAL(FALSE),retain)
__INIT_VAR(data__->TRIGGED,__BOOL_LITERAL(FALSE),retain)
}
// Code part
void PYTHON_EVAL_body__(PYTHON_EVAL *data__) {
// Control execution
if (!__GET_VAR(data__->EN)) {
__SET_VAR(data__->,ENO,,__BOOL_LITERAL(FALSE));
return;
}
else {
__SET_VAR(data__->,ENO,,__BOOL_LITERAL(TRUE));
}
// Initialise TEMP variables
__IL_DEFVAR_T __IL_DEFVAR;
__IL_DEFVAR_T __IL_DEFVAR_BACK;
#define GetFbVar(var,...) __GET_VAR(data__->var,__VA_ARGS__)
#define SetFbVar(var,val,...) __SET_VAR(data__->,var,__VA_ARGS__,val)
extern void __PythonEvalFB(int, PYTHON_EVAL*);__PythonEvalFB(0, data__);
#undef GetFbVar
#undef SetFbVar
;
goto __end;
__end:
return;
} // PYTHON_EVAL_body__()
void PYTHON_POLL_init__(PYTHON_POLL *data__, BOOL retain) {
__INIT_VAR(data__->EN,__BOOL_LITERAL(TRUE),retain)
__INIT_VAR(data__->ENO,__BOOL_LITERAL(TRUE),retain)
__INIT_VAR(data__->TRIG,__BOOL_LITERAL(FALSE),retain)
__INIT_VAR(data__->CODE,__STRING_LITERAL(0,""),retain)
__INIT_VAR(data__->ACK,__BOOL_LITERAL(FALSE),retain)
__INIT_VAR(data__->RESULT,__STRING_LITERAL(0,""),retain)
__INIT_VAR(data__->STATE,0,retain)
__INIT_VAR(data__->BUFFER,__STRING_LITERAL(0,""),retain)
__INIT_VAR(data__->PREBUFFER,__STRING_LITERAL(0,""),retain)
__INIT_VAR(data__->TRIGM1,__BOOL_LITERAL(FALSE),retain)
__INIT_VAR(data__->TRIGGED,__BOOL_LITERAL(FALSE),retain)
}
// Code part
void PYTHON_POLL_body__(PYTHON_POLL *data__) {
// Control execution
if (!__GET_VAR(data__->EN)) {
__SET_VAR(data__->,ENO,,__BOOL_LITERAL(FALSE));
return;
}
else {
__SET_VAR(data__->,ENO,,__BOOL_LITERAL(TRUE));
}
// Initialise TEMP variables
__IL_DEFVAR_T __IL_DEFVAR;
__IL_DEFVAR_T __IL_DEFVAR_BACK;
#define GetFbVar(var,...) __GET_VAR(data__->var,__VA_ARGS__)
#define SetFbVar(var,val,...) __SET_VAR(data__->,var,__VA_ARGS__,val)
extern void __PythonEvalFB(int, PYTHON_EVAL*);__PythonEvalFB(1,(PYTHON_EVAL*)(void*)data__);
#undef GetFbVar
#undef SetFbVar
;
goto __end;
__end:
return;
} // PYTHON_POLL_body__()
void PYTHON_GEAR_init__(PYTHON_GEAR *data__, BOOL retain) {
__INIT_VAR(data__->EN,__BOOL_LITERAL(TRUE),retain)
__INIT_VAR(data__->ENO,__BOOL_LITERAL(TRUE),retain)
__INIT_VAR(data__->N,0,retain)
__INIT_VAR(data__->TRIG,__BOOL_LITERAL(FALSE),retain)
__INIT_VAR(data__->CODE,__STRING_LITERAL(0,""),retain)
__INIT_VAR(data__->ACK,__BOOL_LITERAL(FALSE),retain)
__INIT_VAR(data__->RESULT,__STRING_LITERAL(0,""),retain)
PYTHON_EVAL_init__(&data__->PY_EVAL,retain);
__INIT_VAR(data__->COUNTER,0,retain)
__INIT_VAR(data__->_TMP_ADD10_OUT,0,retain)
__INIT_VAR(data__->_TMP_EQ13_OUT,__BOOL_LITERAL(FALSE),retain)
__INIT_VAR(data__->_TMP_SEL15_OUT,0,retain)
__INIT_VAR(data__->_TMP_AND7_OUT,__BOOL_LITERAL(FALSE),retain)
}
// Code part
void PYTHON_GEAR_body__(PYTHON_GEAR *data__) {
// Control execution
if (!__GET_VAR(data__->EN)) {
__SET_VAR(data__->,ENO,,__BOOL_LITERAL(FALSE));
return;
}
else {
__SET_VAR(data__->,ENO,,__BOOL_LITERAL(TRUE));
}
// Initialise TEMP variables
__SET_VAR(data__->,_TMP_ADD10_OUT,,ADD__UINT__UINT(
(BOOL)__BOOL_LITERAL(TRUE),
NULL,
(UINT)2,
(UINT)__GET_VAR(data__->COUNTER,),
(UINT)1));
__SET_VAR(data__->,_TMP_EQ13_OUT,,EQ__BOOL__UINT(
(BOOL)__BOOL_LITERAL(TRUE),
NULL,
(UINT)2,
(UINT)__GET_VAR(data__->N,),
(UINT)__GET_VAR(data__->_TMP_ADD10_OUT,)));
__SET_VAR(data__->,_TMP_SEL15_OUT,,SEL__UINT__BOOL__UINT__UINT(
(BOOL)__BOOL_LITERAL(TRUE),
NULL,
(BOOL)__GET_VAR(data__->_TMP_EQ13_OUT,),
(UINT)__GET_VAR(data__->_TMP_ADD10_OUT,),
(UINT)0));
__SET_VAR(data__->,COUNTER,,__GET_VAR(data__->_TMP_SEL15_OUT,));
__SET_VAR(data__->,_TMP_AND7_OUT,,AND__BOOL__BOOL(
(BOOL)__BOOL_LITERAL(TRUE),
NULL,
(UINT)2,
(BOOL)__GET_VAR(data__->_TMP_EQ13_OUT,),
(BOOL)__GET_VAR(data__->TRIG,)));
__SET_VAR(data__->PY_EVAL.,TRIG,,__GET_VAR(data__->_TMP_AND7_OUT,));
__SET_VAR(data__->PY_EVAL.,CODE,,__GET_VAR(data__->CODE,));
PYTHON_EVAL_body__(&data__->PY_EVAL);
__SET_VAR(data__->,ACK,,__GET_VAR(data__->PY_EVAL.ACK,));
__SET_VAR(data__->,RESULT,,__GET_VAR(data__->PY_EVAL.RESULT,));
goto __end;
__end:
return;
} // PYTHON_GEAR_body__()
void COUNTERST_init__(COUNTERST *data__, BOOL retain) {
__INIT_VAR(data__->EN,__BOOL_LITERAL(TRUE),retain)
__INIT_VAR(data__->ENO,__BOOL_LITERAL(TRUE),retain)
__INIT_VAR(data__->CNT0,0,retain)
__INIT_EXTERNAL(DINT,RELAY0,data__->RELAY0,retain)
__INIT_EXTERNAL(DINT,RELAY1,data__->RELAY1,retain)
__INIT_EXTERNAL(DINT,RELAY2,data__->RELAY2,retain)
__INIT_EXTERNAL(DINT,RELAY3,data__->RELAY3,retain)
__INIT_EXTERNAL(HMI_INT,HMI_RELAY0,data__->HMI_RELAY0,retain)
__INIT_EXTERNAL(HMI_INT,HMI_RELAY1,data__->HMI_RELAY1,retain)
__INIT_EXTERNAL(HMI_INT,HMI_RELAY2,data__->HMI_RELAY2,retain)
__INIT_EXTERNAL(HMI_INT,HMI_RELAY3,data__->HMI_RELAY3,retain)
}
// Code part
void COUNTERST_body__(COUNTERST *data__) {
// Control execution
if (!__GET_VAR(data__->EN)) {
__SET_VAR(data__->,ENO,,__BOOL_LITERAL(FALSE));
return;
}
else {
__SET_VAR(data__->,ENO,,__BOOL_LITERAL(TRUE));
}
// Initialise TEMP variables
if ((__GET_EXTERNAL(data__->HMI_RELAY2,) >= 1)) {
__SET_EXTERNAL(data__->,RELAY2,,1);
} else {
__SET_EXTERNAL(data__->,RELAY2,,0);
};
if ((__GET_EXTERNAL(data__->HMI_RELAY3,) >= 1)) {
__SET_EXTERNAL(data__->,RELAY3,,1);
} else {
__SET_EXTERNAL(data__->,RELAY3,,0);
};
__SET_VAR(data__->,CNT0,,(__GET_VAR(data__->CNT0,) + 1));
if ((__GET_VAR(data__->CNT0,) == 50)) {
__SET_EXTERNAL(data__->,RELAY0,,1);
__SET_EXTERNAL(data__->,HMI_RELAY0,,1);
__SET_EXTERNAL(data__->,RELAY1,,1);
__SET_EXTERNAL(data__->,HMI_RELAY1,,1);
};
if ((__GET_VAR(data__->CNT0,) == 100)) {
__SET_EXTERNAL(data__->,RELAY0,,0);
__SET_EXTERNAL(data__->,HMI_RELAY0,,0);
__SET_EXTERNAL(data__->,RELAY1,,0);
__SET_EXTERNAL(data__->,HMI_RELAY1,,0);
__SET_VAR(data__->,CNT0,,0);
};
goto __end;
__end:
return;
} // COUNTERST_body__()
void PLC_PRG_init__(PLC_PRG *data__, BOOL retain) {
COUNTERST_init__(&data__->COUNTERST0,retain);
}
// Code part
void PLC_PRG_body__(PLC_PRG *data__) {
// Initialise TEMP variables
COUNTERST_body__(&data__->COUNTERST0);
goto __end;
__end:
return;
} // PLC_PRG_body__()
#include "beremiz.h"
#ifndef __POUS_H
#define __POUS_H
#include "accessor.h"
#include "iec_std_lib.h"
__DECLARE_ENUMERATED_TYPE(LOGLEVEL,
LOGLEVEL__CRITICAL,
LOGLEVEL__WARNING,
LOGLEVEL__INFO,
LOGLEVEL__DEBUG
)
__DECLARE_DERIVED_TYPE(HMI_REAL,REAL)
__DECLARE_DERIVED_TYPE(HMI_NODE,BOOL)
__DECLARE_DERIVED_TYPE(HMI_STRING,STRING)
__DECLARE_DERIVED_TYPE(HMI_BOOL,BOOL)
__DECLARE_DERIVED_TYPE(HMI_INT,INT)
// FUNCTION_BLOCK LOGGER
// Data part
typedef struct {
// FB Interface - IN, OUT, IN_OUT variables
__DECLARE_VAR(BOOL,EN)
__DECLARE_VAR(BOOL,ENO)
__DECLARE_VAR(BOOL,TRIG)
__DECLARE_VAR(STRING,MSG)
__DECLARE_VAR(LOGLEVEL,LEVEL)
// FB private variables - TEMP, private and located variables
__DECLARE_VAR(BOOL,TRIG0)
} LOGGER;
void LOGGER_init__(LOGGER *data__, BOOL retain);
// Code part
void LOGGER_body__(LOGGER *data__);
// FUNCTION_BLOCK PYTHON_EVAL
// Data part
typedef struct {
// FB Interface - IN, OUT, IN_OUT variables
__DECLARE_VAR(BOOL,EN)
__DECLARE_VAR(BOOL,ENO)
__DECLARE_VAR(BOOL,TRIG)
__DECLARE_VAR(STRING,CODE)
__DECLARE_VAR(BOOL,ACK)
__DECLARE_VAR(STRING,RESULT)
// FB private variables - TEMP, private and located variables
__DECLARE_VAR(DWORD,STATE)
__DECLARE_VAR(STRING,BUFFER)
__DECLARE_VAR(STRING,PREBUFFER)
__DECLARE_VAR(BOOL,TRIGM1)
__DECLARE_VAR(BOOL,TRIGGED)
} PYTHON_EVAL;
void PYTHON_EVAL_init__(PYTHON_EVAL *data__, BOOL retain);
// Code part
void PYTHON_EVAL_body__(PYTHON_EVAL *data__);
// FUNCTION_BLOCK PYTHON_POLL
// Data part
typedef struct {
// FB Interface - IN, OUT, IN_OUT variables
__DECLARE_VAR(BOOL,EN)
__DECLARE_VAR(BOOL,ENO)
__DECLARE_VAR(BOOL,TRIG)
__DECLARE_VAR(STRING,CODE)
__DECLARE_VAR(BOOL,ACK)
__DECLARE_VAR(STRING,RESULT)
// FB private variables - TEMP, private and located variables
__DECLARE_VAR(DWORD,STATE)
__DECLARE_VAR(STRING,BUFFER)
__DECLARE_VAR(STRING,PREBUFFER)
__DECLARE_VAR(BOOL,TRIGM1)
__DECLARE_VAR(BOOL,TRIGGED)
} PYTHON_POLL;
void PYTHON_POLL_init__(PYTHON_POLL *data__, BOOL retain);
// Code part
void PYTHON_POLL_body__(PYTHON_POLL *data__);
// FUNCTION_BLOCK PYTHON_GEAR
// Data part
typedef struct {
// FB Interface - IN, OUT, IN_OUT variables
__DECLARE_VAR(BOOL,EN)
__DECLARE_VAR(BOOL,ENO)
__DECLARE_VAR(UINT,N)
__DECLARE_VAR(BOOL,TRIG)
__DECLARE_VAR(STRING,CODE)
__DECLARE_VAR(BOOL,ACK)
__DECLARE_VAR(STRING,RESULT)
// FB private variables - TEMP, private and located variables
PYTHON_EVAL PY_EVAL;
__DECLARE_VAR(UINT,COUNTER)
__DECLARE_VAR(UINT,_TMP_ADD10_OUT)
__DECLARE_VAR(BOOL,_TMP_EQ13_OUT)
__DECLARE_VAR(UINT,_TMP_SEL15_OUT)
__DECLARE_VAR(BOOL,_TMP_AND7_OUT)
} PYTHON_GEAR;
void PYTHON_GEAR_init__(PYTHON_GEAR *data__, BOOL retain);
// Code part
void PYTHON_GEAR_body__(PYTHON_GEAR *data__);
// FUNCTION_BLOCK COUNTERST
// Data part
typedef struct {
// FB Interface - IN, OUT, IN_OUT variables
__DECLARE_VAR(BOOL,EN)
__DECLARE_VAR(BOOL,ENO)
// FB private variables - TEMP, private and located variables
__DECLARE_VAR(INT,CNT0)
__DECLARE_EXTERNAL(DINT,RELAY0)
__DECLARE_EXTERNAL(DINT,RELAY1)
__DECLARE_EXTERNAL(DINT,RELAY2)
__DECLARE_EXTERNAL(DINT,RELAY3)
__DECLARE_EXTERNAL(HMI_INT,HMI_RELAY0)
__DECLARE_EXTERNAL(HMI_INT,HMI_RELAY1)
__DECLARE_EXTERNAL(HMI_INT,HMI_RELAY2)
__DECLARE_EXTERNAL(HMI_INT,HMI_RELAY3)
} COUNTERST;
void COUNTERST_init__(COUNTERST *data__, BOOL retain);
// Code part
void COUNTERST_body__(COUNTERST *data__);
// PROGRAM PLC_PRG
// Data part
typedef struct {
// PROGRAM Interface - IN, OUT, IN_OUT variables
// PROGRAM private variables - TEMP, private and located variables
COUNTERST COUNTERST0;
} PLC_PRG;
void PLC_PRG_init__(PLC_PRG *data__, BOOL retain);
// Code part
void PLC_PRG_body__(PLC_PRG *data__);
#endif //__POUS_H
// Programs
0;CONFIG.RESOURCE1.INSTANCE0;PLC_PRG;
// Variables
0;OUT;CONFIG.RELAY0;CONFIG.RELAY0;DINT;DINT;
1;OUT;CONFIG.RELAY1;CONFIG.RELAY1;DINT;DINT;
2;OUT;CONFIG.RELAY2;CONFIG.RELAY2;DINT;DINT;
3;OUT;CONFIG.RELAY3;CONFIG.RELAY3;DINT;DINT;
4;VAR;CONFIG.HMI_RELAY0;CONFIG.HMI_RELAY0;INT;HMI_INT;
5;VAR;CONFIG.HMI_RELAY1;CONFIG.HMI_RELAY1;INT;HMI_INT;
6;VAR;CONFIG.HMI_RELAY2;CONFIG.HMI_RELAY2;INT;HMI_INT;
7;VAR;CONFIG.HMI_RELAY3;CONFIG.HMI_RELAY3;INT;HMI_INT;
8;VAR;CONFIG.HMI_ROOT;CONFIG.HMI_ROOT;BOOL;HMI_NODE;
9;VAR;CONFIG.HEARTBEAT;CONFIG.HEARTBEAT;INT;HMI_INT;
10;VAR;CONFIG.CURRENT_PAGE_0;CONFIG.CURRENT_PAGE_0;STRING;HMI_STRING;
11;FB;CONFIG.RESOURCE1.INSTANCE0;CONFIG.RESOURCE1.INSTANCE0;PLC_PRG;;
12;FB;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0;COUNTERST;;
13;VAR;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.EN;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.EN;BOOL;BOOL;
14;VAR;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.ENO;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.ENO;BOOL;BOOL;
15;VAR;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.CNT0;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.CNT0;INT;INT;
16;EXT;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.RELAY0;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.RELAY0;DINT;DINT;
17;EXT;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.RELAY1;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.RELAY1;DINT;DINT;
18;EXT;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.RELAY2;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.RELAY2;DINT;DINT;
19;EXT;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.RELAY3;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.RELAY3;DINT;DINT;
20;EXT;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.HMI_RELAY0;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.HMI_RELAY0;INT;HMI_INT;
21;EXT;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.HMI_RELAY1;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.HMI_RELAY1;INT;HMI_INT;
22;EXT;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.HMI_RELAY2;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.HMI_RELAY2;INT;HMI_INT;
23;EXT;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.HMI_RELAY3;CONFIG.RESOURCE1.INSTANCE0.COUNTERST0.HMI_RELAY3;INT;HMI_INT;
// Ticktime
20000000
#ifndef _BEREMIZ_H_
#define _BEREMIZ_H_
/* Beremiz' header file for use by extensions */
#include "iec_types.h"
#define LOG_LEVELS 4
#define LOG_CRITICAL 0
#define LOG_WARNING 1
#define LOG_INFO 2
#define LOG_DEBUG 3
extern unsigned long long common_ticktime__;
#ifdef TARGET_LOGGING_DISABLE
static inline int LogMessage(uint8_t level, char* buf, uint32_t size)
{
(void)level;
(void)buf;
(void)size;
return 0;
}
#else
int LogMessage(uint8_t level, char* buf, uint32_t size);
#endif
long AtomicCompareExchange(long* atomicvar,long compared, long exchange);
void *create_RT_to_nRT_signal(char* name);
void delete_RT_to_nRT_signal(void* handle);
int wait_RT_to_nRT_signal(void* handle);
int unblock_RT_to_nRT_signal(void* handle);
void nRT_reschedule(void);
#endif
/*******************************************/
/* FILE GENERATED BY iec2c */
/* Editing this file is not recommended... */
/*******************************************/
#include "iec_std_lib.h"
#include "accessor.h"
#include "POUS.h"
// CONFIGURATION CONFIG
__DECLARE_GLOBAL_LOCATION(DINT,__QD1_0)
__DECLARE_GLOBAL_LOCATED(DINT,CONFIG,RELAY0)
__DECLARE_GLOBAL_LOCATION(DINT,__QD1_1)
__DECLARE_GLOBAL_LOCATED(DINT,CONFIG,RELAY1)
__DECLARE_GLOBAL_LOCATION(DINT,__QD1_2)
__DECLARE_GLOBAL_LOCATED(DINT,CONFIG,RELAY2)
__DECLARE_GLOBAL_LOCATION(DINT,__QD1_3)
__DECLARE_GLOBAL_LOCATED(DINT,CONFIG,RELAY3)
__DECLARE_GLOBAL(HMI_INT,CONFIG,HMI_RELAY0)
__DECLARE_GLOBAL(HMI_INT,CONFIG,HMI_RELAY1)
__DECLARE_GLOBAL(HMI_INT,CONFIG,HMI_RELAY2)
__DECLARE_GLOBAL(HMI_INT,CONFIG,HMI_RELAY3)
__DECLARE_GLOBAL(HMI_NODE,CONFIG,HMI_ROOT)
__DECLARE_GLOBAL(HMI_INT,CONFIG,HEARTBEAT)
__DECLARE_GLOBAL(HMI_STRING,CONFIG,CURRENT_PAGE_0)
void RESOURCE1_init__(void);
void config_init__(void) {
BOOL retain;
retain = 0;
__INIT_GLOBAL_LOCATED(CONFIG,RELAY0,__QD1_0,retain)
__INIT_GLOBAL(DINT,RELAY0,__INITIAL_VALUE(0),retain)
__INIT_GLOBAL_LOCATED(CONFIG,RELAY1,__QD1_1,retain)
__INIT_GLOBAL(DINT,RELAY1,__INITIAL_VALUE(0),retain)
__INIT_GLOBAL_LOCATED(CONFIG,RELAY2,__QD1_2,retain)
__INIT_GLOBAL(DINT,RELAY2,__INITIAL_VALUE(0),retain)
__INIT_GLOBAL_LOCATED(CONFIG,RELAY3,__QD1_3,retain)
__INIT_GLOBAL(DINT,RELAY3,__INITIAL_VALUE(0),retain)
__INIT_GLOBAL(HMI_INT,HMI_RELAY0,__INITIAL_VALUE(0),retain)
__INIT_GLOBAL(HMI_INT,HMI_RELAY1,__INITIAL_VALUE(0),retain)
__INIT_GLOBAL(HMI_INT,HMI_RELAY2,__INITIAL_VALUE(0),retain)
__INIT_GLOBAL(HMI_INT,HMI_RELAY3,__INITIAL_VALUE(0),retain)
__INIT_GLOBAL(HMI_NODE,HMI_ROOT,__INITIAL_VALUE(__BOOL_LITERAL(FALSE)),retain)
__INIT_GLOBAL(HMI_INT,HEARTBEAT,__INITIAL_VALUE(0),retain)
__INIT_GLOBAL(HMI_STRING,CURRENT_PAGE_0,__INITIAL_VALUE(__STRING_LITERAL(0,"")),retain)
RESOURCE1_init__();
}
void RESOURCE1_run__(unsigned long tick);
void config_run__(unsigned long tick) {
RESOURCE1_run__(tick);
}
unsigned long long common_ticktime__ = 20000000ULL * 1ULL; /*ns*/
unsigned long greatest_tick_count__ = (unsigned long)0UL; /*tick*/
#include "beremiz.h"
__DECLARE_GLOBAL_PROTOTYPE(DINT,RELAY0)
__DECLARE_GLOBAL_PROTOTYPE(DINT,RELAY1)
__DECLARE_GLOBAL_PROTOTYPE(DINT,RELAY2)
__DECLARE_GLOBAL_PROTOTYPE(DINT,RELAY3)
__DECLARE_GLOBAL_PROTOTYPE(HMI_INT,HMI_RELAY0)
__DECLARE_GLOBAL_PROTOTYPE(HMI_INT,HMI_RELAY1)
__DECLARE_GLOBAL_PROTOTYPE(HMI_INT,HMI_RELAY2)
__DECLARE_GLOBAL_PROTOTYPE(HMI_INT,HMI_RELAY3)
__DECLARE_GLOBAL_PROTOTYPE(HMI_NODE,HMI_ROOT)
__DECLARE_GLOBAL_PROTOTYPE(HMI_INT,HEARTBEAT)
__DECLARE_GLOBAL_PROTOTYPE(HMI_STRING,CURRENT_PAGE_0)
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# This file is part of Beremiz
# Copyright (C) 2019: Edouard TISSERANT
# See COPYING file for copyrights details.
from __future__ import absolute_import
import errno
from threading import RLock, Timer
try:
from runtime.spawn_subprocess import Popen
except ImportError:
from subprocess import Popen
from twisted.web.server import Site
from twisted.web.resource import Resource
from twisted.internet import reactor
from twisted.web.static import File
from autobahn.twisted.websocket import WebSocketServerFactory, WebSocketServerProtocol
from autobahn.websocket.protocol import WebSocketProtocol
from autobahn.twisted.resource import WebSocketResource
max_svghmi_sessions = None
svghmi_watchdog = None
svghmi_wait = PLCBinary.svghmi_wait
svghmi_wait.restype = ctypes.c_int # error or 0
svghmi_wait.argtypes = []
svghmi_continue_collect = ctypes.c_int.in_dll(PLCBinary, "svghmi_continue_collect")
svghmi_send_collect = PLCBinary.svghmi_send_collect
svghmi_send_collect.restype = ctypes.c_int # error or 0
svghmi_send_collect.argtypes = [
ctypes.c_uint32, # index
ctypes.POINTER(ctypes.c_uint32), # size
ctypes.POINTER(ctypes.c_void_p)] # data ptr
svghmi_reset = PLCBinary.svghmi_reset
svghmi_reset.restype = ctypes.c_int # error or 0
svghmi_reset.argtypes = [
ctypes.c_uint32] # index
svghmi_recv_dispatch = PLCBinary.svghmi_recv_dispatch
svghmi_recv_dispatch.restype = ctypes.c_int # error or 0
svghmi_recv_dispatch.argtypes = [
ctypes.c_uint32, # index
ctypes.c_uint32, # size
ctypes.c_char_p] # data ptr
class HMISessionMgr(object):
def __init__(self):
self.multiclient_sessions = set()
self.watchdog_session = None
self.session_count = 0
self.lock = RLock()
self.indexes = set()
def next_index(self):
if self.indexes:
greatest = max(self.indexes)
holes = set(range(greatest)) - self.indexes
index = min(holes) if holes else greatest+1
else:
index = 0
self.indexes.add(index)
return index
def free_index(self, index):
self.indexes.remove(index)
def register(self, session):
global max_svghmi_sessions
with self.lock:
if session.is_watchdog_session:
# Creating a new watchdog session closes pre-existing one
if self.watchdog_session is not None:
self.unregister(self.watchdog_session)
else:
assert(self.session_count < max_svghmi_sessions)
self.session_count += 1
self.watchdog_session = session
else:
assert(self.session_count < max_svghmi_sessions)
self.multiclient_sessions.add(session)
self.session_count += 1
session.session_index = self.next_index()
def unregister(self, session):
with self.lock:
if session.is_watchdog_session :
if self.watchdog_session != session:
return
self.watchdog_session = None
else:
try:
self.multiclient_sessions.remove(session)
except KeyError:
return
self.free_index(session.session_index)
self.session_count -= 1
session.kill()
def close_all(self):
for session in self.iter_sessions():
self.unregister(session)
def iter_sessions(self):
with self.lock:
lst = list(self.multiclient_sessions)
if self.watchdog_session is not None:
lst = [self.watchdog_session]+lst
for nxt_session in lst:
yield nxt_session
svghmi_session_manager = HMISessionMgr()
class HMISession(object):
def __init__(self, protocol_instance):
self.protocol_instance = protocol_instance
self._session_index = None
self.closed = False
@property
def is_watchdog_session(self):
return self.protocol_instance.has_watchdog
@property
def session_index(self):
return self._session_index
@session_index.setter
def session_index(self, value):
self._session_index = value
def reset(self):
return svghmi_reset(self.session_index)
def close(self):
if self.closed: return
self.protocol_instance.sendClose(WebSocketProtocol.CLOSE_STATUS_CODE_NORMAL)
def notify_closed(self):
self.closed = True
def kill(self):
self.close()
self.reset()
def onMessage(self, msg):
# pass message to the C side recieve_message()
if self.closed: return
return svghmi_recv_dispatch(self.session_index, len(msg), msg)
def sendMessage(self, msg):
if self.closed: return
self.protocol_instance.sendMessage(msg, True)
return 0
class Watchdog(object):
def __init__(self, initial_timeout, interval, callback):
self._callback = callback
self.lock = RLock()
self.initial_timeout = initial_timeout
self.interval = interval
self.callback = callback
with self.lock:
self._start()
def _start(self, rearm=False):
duration = self.interval if rearm else self.initial_timeout
if duration:
self.timer = Timer(duration, self.trigger)
self.timer.start()
else:
self.timer = None
def _stop(self):
if self.timer is not None:
self.timer.cancel()
self.timer = None
def cancel(self):
with self.lock:
self._stop()
def feed(self, rearm=True):
with self.lock:
self._stop()
self._start(rearm)
def trigger(self):
self._callback()
# Don't repeat trigger periodically
# # wait for initial timeout on re-start
# self.feed(rearm=False)
class HMIProtocol(WebSocketServerProtocol):
def __init__(self, *args, **kwargs):
self._hmi_session = None
self.has_watchdog = False
WebSocketServerProtocol.__init__(self, *args, **kwargs)
def onConnect(self, request):
self.has_watchdog = request.params.get("mode", [None])[0] == "watchdog"
return WebSocketServerProtocol.onConnect(self, request)
def onOpen(self):
global svghmi_session_manager
assert(self._hmi_session is None)
_hmi_session = HMISession(self)
registered = svghmi_session_manager.register(_hmi_session)
self._hmi_session = _hmi_session
def onClose(self, wasClean, code, reason):
global svghmi_session_manager
if self._hmi_session is None : return
self._hmi_session.notify_closed()
svghmi_session_manager.unregister(self._hmi_session)
self._hmi_session = None
def onMessage(self, msg, isBinary):
global svghmi_watchdog
if self._hmi_session is None : return
result = self._hmi_session.onMessage(msg)
if result == 1 and self.has_watchdog: # was heartbeat
if svghmi_watchdog is not None:
svghmi_watchdog.feed()
class HMIWebSocketServerFactory(WebSocketServerFactory):
protocol = HMIProtocol
svghmi_servers = {}
svghmi_send_thread = None
# python's errno on windows seems to have no ENODATA
ENODATA = errno.ENODATA if hasattr(errno,"ENODATA") else None
def SendThreadProc():
global svghmi_session_manager
size = ctypes.c_uint32()
ptr = ctypes.c_void_p()
res = 0
while svghmi_continue_collect:
svghmi_wait()
for svghmi_session in svghmi_session_manager.iter_sessions():
res = svghmi_send_collect(
svghmi_session.session_index,
ctypes.byref(size), ctypes.byref(ptr))
if res == 0:
svghmi_session.sendMessage(
ctypes.string_at(ptr.value,size.value))
elif res == ENODATA:
# this happens when there is no data after wakeup
# because of hmi data refresh period longer than
# PLC common ticktime
pass
else:
# this happens when finishing
break
def AddPathToSVGHMIServers(path, factory, *args, **kwargs):
for k,v in svghmi_servers.iteritems():
svghmi_root, svghmi_listener, path_list = v
svghmi_root.putChild(path, factory(*args, **kwargs))
# Called by PLCObject at start
def _runtime_00_svghmi_start():
global svghmi_send_thread
# start a thread that call the C part of SVGHMI
svghmi_send_thread = Thread(target=SendThreadProc, name="SVGHMI Send")
svghmi_send_thread.start()
# Called by PLCObject at stop
def _runtime_00_svghmi_stop():
global svghmi_send_thread, svghmi_session
svghmi_session_manager.close_all()
# plc cleanup calls svghmi_(locstring)_cleanup and unlocks send thread
svghmi_send_thread.join()
svghmi_send_thread = None
class NoCacheFile(File):
def render_GET(self, request):
request.setHeader(b"Cache-Control", b"no-cache, no-store")
return File.render_GET(self, request)
render_HEAD = render_GET
# TODO : multiple watchdog (one for each svghmi instance)
def svghmi_0_watchdog_trigger():
Popen(['echo', 'Watchdog', 'for', 'svghmi_0', '!'])
max_svghmi_sessions = 16
def _runtime_0_svghmi_start():
global svghmi_watchdog, svghmi_servers
srv = svghmi_servers.get("localhost:8008", None)
if srv is not None:
svghmi_root, svghmi_listener, path_list = srv
if 'svghmi_0' in path_list:
raise Exception("SVGHMI svghmi_0: path svghmi_0 already used on localhost:8008")
else:
svghmi_root = Resource()
factory = HMIWebSocketServerFactory()
factory.setProtocolOptions(maxConnections=16)
svghmi_root.putChild("ws", WebSocketResource(factory))
svghmi_listener = reactor.listenTCP(8008, Site(svghmi_root), interface='localhost')
path_list = []
svghmi_servers["localhost:8008"] = (svghmi_root, svghmi_listener, path_list)
svghmi_root.putChild(
'svghmi_0',
NoCacheFile('svghmi_0.xhtml',
defaultType='application/xhtml+xml'))
path_list.append("svghmi_0")
Popen(['chromium', 'http://localhost:8008/svghmi_0#watchdog'])
if True:
if svghmi_watchdog is None:
svghmi_watchdog = Watchdog(
30,
5,
svghmi_0_watchdog_trigger)
else:
raise Exception("SVGHMI svghmi_0: only one watchdog allowed")
def _runtime_0_svghmi_stop():
global svghmi_watchdog, svghmi_servers
if svghmi_watchdog is not None:
svghmi_watchdog.cancel()
svghmi_watchdog = None
svghmi_root, svghmi_listener, path_list = svghmi_servers["localhost:8008"]
svghmi_root.delEntity('svghmi_0')
path_list.remove('svghmi_0')
if len(path_list)==0:
svghmi_root.delEntity("ws")
svghmi_listener.stopListening()
svghmi_servers.pop("localhost:8008")
pass # no command given
\ No newline at end of file
<!--Made with SVGHMI. https://beremiz.org--><html xmlns="http://www.w3.org/1999/xhtml" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:xhtml="http://www.w3.org/1999/xhtml"><head><style type="text/css" media="screen"/></head><body style="margin:0;overflow:hidden;user-select:none;touch-action:none;"><!-- Created with Inkscape (http://www.inkscape.org/) --><svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="none" height="100vh" width="100vw" inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)" sodipodi:docname="svghmi.svg" id="hmi0" version="1.1" viewBox="0 0 1280 720">
<defs id="defs6"/>
<sodipodi:namedview pagecolor="#ffffff" bordercolor="#666666" borderopacity="1" objecttolerance="10" gridtolerance="10" guidetolerance="10" inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:window-width="1920" inkscape:window-height="1015" id="namedview4" showgrid="false" inkscape:zoom="1.418633" inkscape:cx="625.37152" inkscape:cy="419.21141" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1" inkscape:current-layer="hmi0" showguides="false"/>
<rect inkscape:label="HMI:Page:Home" y="-1.9937692" x="-7.9750781" height="720" width="1280" id="rect1016" style="color:#000000;opacity:1;fill:#d6d6d6;fill-opacity:1"/>
<text xml:space="preserve" style="font-style:normal;font-weight:normal;font-size:39.52233505px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.98805833" x="552.8136" y="109.08164" id="text35" transform="scale(0.98805833,1.012086)"><tspan sodipodi:role="line" id="tspan33" x="552.8136" y="109.08164" style="stroke-width:0.98805833">Relay 0</tspan></text>
<g id="g446" transform="matrix(0.2859027,0,0,0.2859027,709.48026,45.693658)" inkscape:label="HMI:Input@/HMI_RELAY0">
<text xml:space="preserve" style="font-style:normal;font-weight:normal;font-size:160px;line-height:125%;font-family:sans-serif;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#ff8c00;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" x="216.32812" y="218.24219" id="text432" inkscape:label="value"><tspan sodipodi:role="line" id="tspan430" x="216.32812" y="218.24219" style="text-align:end;text-anchor:end;fill:#ff8c00;stroke-width:1px">8</tspan></text>
<path transform="scale(1,-1)" sodipodi:type="star" style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#ff6600;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" id="path436" sodipodi:sides="3" sodipodi:cx="276.74072" sodipodi:cy="-224.98808" sodipodi:r1="29.912722" sodipodi:r2="14.956361" sodipodi:arg1="0.52359878" sodipodi:arg2="1.5707963" inkscape:flatsided="true" inkscape:rounded="0" inkscape:randomized="0" d="m 302.6459,-210.03172 -51.81035,0 25.90517,-44.86908 z" inkscape:transform-center-y="7.4781812" inkscape:label="-1"/>
<rect inkscape:label="edit" onclick="" y="95.40741" x="1.8178837" height="128" width="230.94511" id="rect438" style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ff00ff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"/>
<path inkscape:label="+1" inkscape:transform-center-y="-7.4781804" d="m 302.6459,111.4008 -51.81035,0 25.90517,-44.869079 z" inkscape:randomized="0" inkscape:rounded="0" inkscape:flatsided="true" sodipodi:arg2="1.5707963" sodipodi:arg1="0.52359878" sodipodi:r2="14.956361" sodipodi:r1="29.912722" sodipodi:cy="96.444443" sodipodi:cx="276.74072" sodipodi:sides="3" id="path442" style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#ff6600;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" sodipodi:type="star"/>
<path inkscape:label="=0" inkscape:transform-center-y="-10.828983" d="m 306.14807,189.68763 -58.37872,0.43598 -0.43597,-58.37872 58.37871,-0.43597 z" inkscape:randomized="0" inkscape:rounded="0" inkscape:flatsided="true" sodipodi:arg2="1.5633284" sodipodi:arg1="0.77793027" sodipodi:r2="21.657967" sodipodi:r1="41.281136" sodipodi:cy="160.71626" sodipodi:cx="276.74072" sodipodi:sides="4" id="path444" style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#ff6600;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" sodipodi:type="star" inkscape:transform-center-x="1.0089177e-06"/>
</g>
<g id="g446-3" transform="matrix(0.2859027,0,0,0.2859027,709.48026,145.69366)" inkscape:label="HMI:Input@/HMI_RELAY1">
<text xml:space="preserve" style="font-style:normal;font-weight:normal;font-size:160px;line-height:125%;font-family:sans-serif;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#ff8c00;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" x="216.32812" y="218.24219" id="text432-6" inkscape:label="value"><tspan sodipodi:role="line" id="tspan430-7" x="216.32812" y="218.24219" style="text-align:end;text-anchor:end;fill:#ff8c00;stroke-width:1px">8</tspan></text>
<path transform="scale(1,-1)" sodipodi:type="star" style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#ff6600;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" id="path436-5" sodipodi:sides="3" sodipodi:cx="276.74072" sodipodi:cy="-224.98808" sodipodi:r1="29.912722" sodipodi:r2="14.956361" sodipodi:arg1="0.52359878" sodipodi:arg2="1.5707963" inkscape:flatsided="true" inkscape:rounded="0" inkscape:randomized="0" d="m 302.6459,-210.03172 -51.81035,0 25.90517,-44.86908 z" inkscape:transform-center-y="7.4781812" inkscape:label="-1"/>
<rect inkscape:label="edit" onclick="" y="95.40741" x="1.8178837" height="128" width="230.94511" id="rect438-3" style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ff00ff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"/>
<path inkscape:label="+1" inkscape:transform-center-y="-7.4781804" d="m 302.6459,111.4008 -51.81035,0 25.90517,-44.869079 z" inkscape:randomized="0" inkscape:rounded="0" inkscape:flatsided="true" sodipodi:arg2="1.5707963" sodipodi:arg1="0.52359878" sodipodi:r2="14.956361" sodipodi:r1="29.912722" sodipodi:cy="96.444443" sodipodi:cx="276.74072" sodipodi:sides="3" id="path442-5" style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#ff6600;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" sodipodi:type="star"/>
<path inkscape:label="=0" inkscape:transform-center-y="-10.828983" d="m 306.14807,189.68763 -58.37872,0.43598 -0.43597,-58.37872 58.37871,-0.43597 z" inkscape:randomized="0" inkscape:rounded="0" inkscape:flatsided="true" sodipodi:arg2="1.5633284" sodipodi:arg1="0.77793027" sodipodi:r2="21.657967" sodipodi:r1="41.281136" sodipodi:cy="160.71626" sodipodi:cx="276.74072" sodipodi:sides="4" id="path444-6" style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#ff6600;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" sodipodi:type="star" inkscape:transform-center-x="1.0089177e-06"/>
</g>
<text xml:space="preserve" style="font-style:normal;font-weight:normal;font-size:40.60216904px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.01505423" x="537.90454" y="213.56741" id="text35-2" transform="scale(1.0150542,0.98516905)"><tspan sodipodi:role="line" id="tspan33-9" x="537.90454" y="213.56741" style="stroke-width:1.01505423">Relay 1</tspan></text>
<text xml:space="preserve" style="font-style:normal;font-weight:normal;font-size:39.54086685px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.9885217" x="552.5509" y="306.83713" id="text35-2-3" transform="scale(0.98852168,1.0116116)"><tspan sodipodi:role="line" id="tspan33-9-6" x="552.5509" y="306.83713" style="stroke-width:0.9885217">Relay 2</tspan></text>
<text xml:space="preserve" style="font-style:normal;font-weight:normal;font-size:39.57181549px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.98929536" x="552.11279" y="406.00681" id="text35-2-7" transform="scale(0.98929534,1.0108205)"><tspan sodipodi:role="line" id="tspan33-9-5" x="552.11279" y="406.00681" style="stroke-width:0.98929536">Relay 3</tspan></text>
<g id="g446-35" transform="matrix(0.2859027,0,0,0.2859027,709.48026,245.69366)" inkscape:label="HMI:Input@/HMI_RELAY2">
<text xml:space="preserve" style="font-style:normal;font-weight:normal;font-size:160px;line-height:125%;font-family:sans-serif;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#ff8c00;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" x="216.32812" y="218.24219" id="text432-62" inkscape:label="value"><tspan sodipodi:role="line" id="tspan430-9" x="216.32812" y="218.24219" style="text-align:end;text-anchor:end;fill:#ff8c00;stroke-width:1px">8</tspan></text>
<path transform="scale(1,-1)" sodipodi:type="star" style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#ff6600;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" id="path436-1" sodipodi:sides="3" sodipodi:cx="276.74072" sodipodi:cy="-224.98808" sodipodi:r1="29.912722" sodipodi:r2="14.956361" sodipodi:arg1="0.52359878" sodipodi:arg2="1.5707963" inkscape:flatsided="true" inkscape:rounded="0" inkscape:randomized="0" d="m 302.6459,-210.03172 -51.81035,0 25.90517,-44.86908 z" inkscape:transform-center-y="7.4781812" inkscape:label="-1"/>
<rect inkscape:label="edit" onclick="" y="95.40741" x="1.8178837" height="128" width="230.94511" id="rect438-2" style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ff00ff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"/>
<path inkscape:label="+1" inkscape:transform-center-y="-7.4781804" d="m 302.6459,111.4008 -51.81035,0 25.90517,-44.869079 z" inkscape:randomized="0" inkscape:rounded="0" inkscape:flatsided="true" sodipodi:arg2="1.5707963" sodipodi:arg1="0.52359878" sodipodi:r2="14.956361" sodipodi:r1="29.912722" sodipodi:cy="96.444443" sodipodi:cx="276.74072" sodipodi:sides="3" id="path442-7" style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#ff6600;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" sodipodi:type="star"/>
<path inkscape:label="=0" inkscape:transform-center-y="-10.828983" d="m 306.14807,189.68763 -58.37872,0.43598 -0.43597,-58.37872 58.37871,-0.43597 z" inkscape:randomized="0" inkscape:rounded="0" inkscape:flatsided="true" sodipodi:arg2="1.5633284" sodipodi:arg1="0.77793027" sodipodi:r2="21.657967" sodipodi:r1="41.281136" sodipodi:cy="160.71626" sodipodi:cx="276.74072" sodipodi:sides="4" id="path444-0" style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#ff6600;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" sodipodi:type="star" inkscape:transform-center-x="1.0089177e-06"/>
</g>
<g id="g446-9" transform="matrix(0.2859027,0,0,0.2859027,709.48026,345.69366)" inkscape:label="HMI:Input@/HMI_RELAY3">
<text xml:space="preserve" style="font-style:normal;font-weight:normal;font-size:160px;line-height:125%;font-family:sans-serif;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#ff8c00;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" x="216.32812" y="218.24219" id="text432-3" inkscape:label="value"><tspan sodipodi:role="line" id="tspan430-6" x="216.32812" y="218.24219" style="text-align:end;text-anchor:end;fill:#ff8c00;stroke-width:1px">8</tspan></text>
<path transform="scale(1,-1)" sodipodi:type="star" style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#ff6600;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" id="path436-0" sodipodi:sides="3" sodipodi:cx="276.74072" sodipodi:cy="-224.98808" sodipodi:r1="29.912722" sodipodi:r2="14.956361" sodipodi:arg1="0.52359878" sodipodi:arg2="1.5707963" inkscape:flatsided="true" inkscape:rounded="0" inkscape:randomized="0" d="m 302.6459,-210.03172 -51.81035,0 25.90517,-44.86908 z" inkscape:transform-center-y="7.4781812" inkscape:label="-1"/>
<rect inkscape:label="edit" onclick="" y="95.40741" x="1.8178837" height="128" width="230.94511" id="rect438-6" style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ff00ff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"/>
<path inkscape:label="+1" inkscape:transform-center-y="-7.4781804" d="m 302.6459,111.4008 -51.81035,0 25.90517,-44.869079 z" inkscape:randomized="0" inkscape:rounded="0" inkscape:flatsided="true" sodipodi:arg2="1.5707963" sodipodi:arg1="0.52359878" sodipodi:r2="14.956361" sodipodi:r1="29.912722" sodipodi:cy="96.444443" sodipodi:cx="276.74072" sodipodi:sides="3" id="path442-2" style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#ff6600;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" sodipodi:type="star"/>
<path inkscape:label="=0" inkscape:transform-center-y="-10.828983" d="m 306.14807,189.68763 -58.37872,0.43598 -0.43597,-58.37872 58.37871,-0.43597 z" inkscape:randomized="0" inkscape:rounded="0" inkscape:flatsided="true" sodipodi:arg2="1.5633284" sodipodi:arg1="0.77793027" sodipodi:r2="21.657967" sodipodi:r1="41.281136" sodipodi:cy="160.71626" sodipodi:cx="276.74072" sodipodi:sides="4" id="path444-61" style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#ff6600;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" sodipodi:type="star" inkscape:transform-center-x="1.0089177e-06"/>
</g>
</svg><script><![CDATA[
//
//
// Early independent declarations
//
//
/* hmi-tree */
var hmi_hash = [129,53,34,212,104,24,210,166];
var heartbeat_index = 5;
var current_page_var_index = 6;
var hmitree_types = [
"NODE",
"INT",
"INT",
"INT",
"INT",
"INT",
"STRING"
];
var hmitree_paths = [
"/",
"/HMI_RELAY0",
"/HMI_RELAY1",
"/HMI_RELAY2",
"/HMI_RELAY3",
"/HEARTBEAT",
"/CURRENT_PAGE_0"
];
var hmitree_nodes = {
"/" : [0, ""]
};
/* default-page */
var default_page = "Home";
/* inline-svg */
let id = document.getElementById.bind(document);
var svg_root = id("hmi0");
/* i18n */
var langs = [ ["Default", "C"],];
var translations = [
]
/* local-variable-indexes */
let hmi_locals = {};
var last_remote_index = hmitree_types.length - 1;
var next_available_index = hmitree_types.length;
let cookies = new Map(document.cookie.split("; ").map(s=>s.split("=")));
const local_defaults = {
"lang":cookies.has("lang")?cookies.get("lang"):0
};
const persistent_locals = new Set([
"lang"
]);
var persistent_indexes = new Map();
var cache = hmitree_types.map(_ignored => undefined);
var updates = new Map();
function page_local_index(varname, pagename){
let pagevars = hmi_locals[pagename];
let new_index;
if(pagevars == undefined){
new_index = next_available_index++;
hmi_locals[pagename] = {[varname]:new_index}
} else {
let result = pagevars[varname];
if(result != undefined) {
return result;
}
new_index = next_available_index++;
pagevars[varname] = new_index;
}
let defaultval = local_defaults[varname];
if(defaultval != undefined) {
cache[new_index] = defaultval;
updates.set(new_index, defaultval);
if(persistent_locals.has(varname))
persistent_indexes.set(new_index, varname);
}
return new_index;
}
function hmi_local_index(varname){
return page_local_index(varname, "HMI_LOCAL");
}
/* widget-base-class */
var pending_widget_animates = [];
class Widget {
offset = 0;
frequency = 10; /* FIXME arbitrary default max freq. Obtain from config ? */
unsubscribable = false;
pending_animate = false;
constructor(elt_id, freq, args, indexes, minmaxes, members){
this.element_id = elt_id;
this.element = id(elt_id);
this.args = args;
this.indexes = indexes;
this.minmaxes = minmaxes;
Object.keys(members).forEach(prop => this[prop]=members[prop]);
this.lastapply = indexes.map(() => undefined);
this.inhibit = indexes.map(() => undefined);
this.pending = indexes.map(() => undefined);
this.bound_unhinibit = this.unhinibit.bind(this);
this.forced_frequency = freq;
this.clip = true;
}
do_init(){
let forced = this.forced_frequency;
if(forced !== undefined){
/*
once every 10 seconds : 10s
once per minute : 1m
once per hour : 1h
once per day : 1d
*/
let unit = forced.slice(-1);
let factor = {
"s":1,
"m":60,
"h":3600,
"d":86400}[unit];
this.frequency = factor ? 1/(factor * Number(forced.slice(0,-1)))
: Number(forced);
}
let init = this.init;
if(typeof(init) == "function"){
try {
init.call(this);
} catch(err) {
console.log(err);
}
}
}
unsub(){
/* remove subsribers */
if(!this.unsubscribable)
for(let i = 0; i < this.indexes.length; i++) {
/* flush updates pending because of inhibition */
let inhibition = this.inhibit[i];
if(inhibition != undefined){
clearTimeout(inhibition);
this.lastapply[i] = undefined;
this.unhinibit(i);
}
let index = this.indexes[i];
if(this.relativeness[i])
index += this.offset;
subscribers(index).delete(this);
}
this.offset = 0;
this.relativeness = undefined;
}
sub(new_offset=0, relativeness, container_id){
this.offset = new_offset;
this.relativeness = relativeness;
this.container_id = container_id ;
/* add this's subsribers */
if(!this.unsubscribable)
for(let i = 0; i < this.indexes.length; i++) {
let index = this.get_variable_index(i);
if(index == undefined) continue;
subscribers(index).add(this);
}
need_cache_apply.push(this);
}
apply_cache() {
if(!this.unsubscribable) for(let index in this.indexes){
/* dispatch current cache in newly opened page widgets */
let realindex = this.get_variable_index(index);
if(realindex == undefined) continue;
let cached_val = cache[realindex];
if(cached_val != undefined)
this._dispatch(cached_val, cached_val, index);
}
}
get_variable_index(varnum) {
let index = this.indexes[varnum];
if(typeof(index) == "string"){
index = page_local_index(index, this.container_id);
} else {
if(this.relativeness[varnum]){
index += this.offset;
}
}
return index;
}
overshot(new_val, max) {
}
undershot(new_val, min) {
}
clip_min_max(index, new_val) {
let minmax = this.minmaxes[index];
if(minmax !== undefined && typeof new_val == "number") {
let [min,max] = minmax;
if(new_val < min){
this.undershot(new_val, min);
return min;
}
if(new_val > max){
this.overshot(new_val, max);
return max;
}
}
return new_val;
}
change_hmi_value(index, opstr) {
let realindex = this.get_variable_index(index);
if(realindex == undefined) return undefined;
let old_val = cache[realindex];
let new_val = eval_operation_string(old_val, opstr);
if(this.clip)
new_val = this.clip_min_max(index, new_val);
return apply_hmi_value(realindex, new_val);
}
_apply_hmi_value(index, new_val) {
let realindex = this.get_variable_index(index);
if(realindex == undefined) return undefined;
if(this.clip)
new_val = this.clip_min_max(index, new_val);
return apply_hmi_value(realindex, new_val);
}
unhinibit(index){
this.inhibit[index] = undefined;
let new_val = this.pending[index];
this.pending[index] = undefined;
return this.apply_hmi_value(index, new_val);
}
apply_hmi_value(index, new_val) {
if(this.inhibit[index] == undefined){
let now = Date.now();
let min_interval = 1000/this.frequency;
let lastapply = this.lastapply[index];
if(lastapply == undefined || now > lastapply + min_interval){
this.lastapply[index] = now;
return this._apply_hmi_value(index, new_val);
}
else {
let elapsed = now - lastapply;
this.pending[index] = new_val;
this.inhibit[index] = setTimeout(this.bound_unhinibit, min_interval - elapsed, index);
}
}
else {
this.pending[index] = new_val;
return new_val;
}
}
new_hmi_value(index, value, oldval) {
// TODO avoid searching, store index at sub()
for(let i = 0; i < this.indexes.length; i++) {
let refindex = this.get_variable_index(i);
if(refindex == undefined) continue;
if(index == refindex) {
this._dispatch(value, oldval, i);
break;
}
}
}
_dispatch(value, oldval, varnum) {
let dispatch = this.dispatch;
if(dispatch != undefined){
try {
dispatch.call(this, value, oldval, varnum);
} catch(err) {
console.log(err);
}
}
}
_animate(){
this.animate();
this.pending_animate = false;
}
request_animate(){
if(!this.pending_animate){
pending_widget_animates.push(this);
this.pending_animate = true;
requestHMIAnimation();
}
}
activate_activable(eltsub) {
eltsub.inactive.style.display = "none";
eltsub.active.style.display = "";
}
inactivate_activable(eltsub) {
eltsub.active.style.display = "none";
eltsub.inactive.style.display = "";
}
}
//
//
// Declarations depending on preamble
//
//
/* detachable-elements */
var detachable_elements = {
"rect1016":[id("rect1016"), id("hmi0")],
"text35":[id("text35"), id("hmi0")],
"g446":[id("g446"), id("hmi0")],
"g446-3":[id("g446-3"), id("hmi0")],
"text35-2":[id("text35-2"), id("hmi0")],
"text35-2-3":[id("text35-2-3"), id("hmi0")],
"text35-2-7":[id("text35-2-7"), id("hmi0")],
"g446-35":[id("g446-35"), id("hmi0")],
"g446-9":[id("g446-9"), id("hmi0")]
}
/* hmi-classes */
class InputWidget extends Widget{
on_op_click(opstr) {
this.change_hmi_value(0, opstr);
}
edit_callback(new_val) {
this.apply_hmi_value(0, new_val);
}
is_inhibited = false;
alert(msg){
this.is_inhibited = true;
this.display = msg;
setTimeout(() => this.stopalert(), 1000);
this.request_animate();
}
stopalert(){
this.is_inhibited = false;
this.display = this.last_value;
this.request_animate();
}
overshot(new_val, max) {
this.alert("max");
}
undershot(new_val, min) {
this.alert("min");
}
}
/* hmi-elements */
var hmi_widgets = {
"g446": new InputWidget ("g446",undefined,[],[1],[undefined],{
value_elt: id("text432"),
edit_elt: id("rect438"),
frequency: 5,
dispatch: function(value) {
this.last_value = value;
if(!this.is_inhibited){
this.display = this.last_value;
this.request_animate();
}
},
animate: function(){
this.value_elt.textContent = String(this.display);
},
init: function() {
this.edit_elt.onclick = () => edit_value("/HMI_RELAY0", "HMI_INT", this, this.last_value);
this.value_elt.style.pointerEvents = "none";
id("path436").onclick = () => this.on_op_click("-1");
id("path442").onclick = () => this.on_op_click("+1");
id("path444").onclick = () => this.on_op_click("=0");
},
}),
"g446-3": new InputWidget ("g446-3",undefined,[],[2],[undefined],{
value_elt: id("text432-6"),
edit_elt: id("rect438-3"),
frequency: 5,
dispatch: function(value) {
this.last_value = value;
if(!this.is_inhibited){
this.display = this.last_value;
this.request_animate();
}
},
animate: function(){
this.value_elt.textContent = String(this.display);
},
init: function() {
this.edit_elt.onclick = () => edit_value("/HMI_RELAY1", "HMI_INT", this, this.last_value);
this.value_elt.style.pointerEvents = "none";
id("path436-5").onclick = () => this.on_op_click("-1");
id("path442-5").onclick = () => this.on_op_click("+1");
id("path444-6").onclick = () => this.on_op_click("=0");
},
}),
"g446-35": new InputWidget ("g446-35",undefined,[],[3],[undefined],{
value_elt: id("text432-62"),
edit_elt: id("rect438-2"),
frequency: 5,
dispatch: function(value) {
this.last_value = value;
if(!this.is_inhibited){
this.display = this.last_value;
this.request_animate();
}
},
animate: function(){
this.value_elt.textContent = String(this.display);
},
init: function() {
this.edit_elt.onclick = () => edit_value("/HMI_RELAY2", "HMI_INT", this, this.last_value);
this.value_elt.style.pointerEvents = "none";
id("path436-1").onclick = () => this.on_op_click("-1");
id("path442-7").onclick = () => this.on_op_click("+1");
id("path444-0").onclick = () => this.on_op_click("=0");
},
}),
"g446-9": new InputWidget ("g446-9",undefined,[],[4],[undefined],{
value_elt: id("text432-3"),
edit_elt: id("rect438-6"),
frequency: 5,
dispatch: function(value) {
this.last_value = value;
if(!this.is_inhibited){
this.display = this.last_value;
this.request_animate();
}
},
animate: function(){
this.value_elt.textContent = String(this.display);
},
init: function() {
this.edit_elt.onclick = () => edit_value("/HMI_RELAY3", "HMI_INT", this, this.last_value);
this.value_elt.style.pointerEvents = "none";
id("path436-0").onclick = () => this.on_op_click("-1");
id("path442-2").onclick = () => this.on_op_click("+1");
id("path444-61").onclick = () => this.on_op_click("=0");
},
})
}
/* DropDown */
function gettext(o) {
if(typeof(o) == "string"){
return o;
}
return svg_text_to_multiline(o);
};
/* jump */
var jumps_need_update = false;
var jump_history = [[default_page, undefined]];
function update_jumps() {
page_desc[current_visible_page].jumps.map(w=>w.notify_page_change(current_visible_page,current_page_index));
jumps_need_update = false;
};
/* keypad */
var keypads = {
}
//
//
// Order independent declaration and code
//
//
/* page-desc */
var page_desc = {
"Home": {
bbox: [-7.9750781, -1.9937692, 1280, 720],
widgets: [
[hmi_widgets["g446"], [false]],
[hmi_widgets["g446-3"], [false]],
[hmi_widgets["g446-35"], [false]],
[hmi_widgets["g446-9"], [false]]
],
jumps: [
],
required_detachables: {
"rect1016": detachable_elements["rect1016"],
"text35": detachable_elements["text35"],
"g446": detachable_elements["g446"],
"g446-3": detachable_elements["g446-3"],
"text35-2": detachable_elements["text35-2"],
"text35-2-3": detachable_elements["text35-2-3"],
"text35-2-7": detachable_elements["text35-2-7"],
"g446-35": detachable_elements["g446-35"],
"g446-9": detachable_elements["g446-9"]
}
}
}
//
//
// Statements that needs to be at the end
//
//
/* https://github.com/alexei/sprintf.js/blob/master/src/sprintf.js */
/* global window, exports, define */
!function() {
'use strict'
var re = {
not_string: /[^s]/,
not_bool: /[^t]/,
not_type: /[^T]/,
not_primitive: /[^v]/,
number: /[diefg]/,
numeric_arg: /[bcdiefguxX]/,
json: /[j]/,
not_json: /[^j]/,
text: /^[^%]+/,
modulo: /^%{2}/,
placeholder: /^%(?:([1-9]\d*)\$|\(([^)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-gijostTuvxXD])/,
key: /^([a-z_][a-z_\d]*)/i,
key_access: /^\.([a-z_][a-z_\d]*)/i,
index_access: /^\[(\d+)\]/,
sign: /^[+-]/
}
function sprintf(key) {
// arguments is not an array, but should be fine for this call
return sprintf_format(sprintf_parse(key), arguments)
}
function vsprintf(fmt, argv) {
return sprintf.apply(null, [fmt].concat(argv || []))
}
function sprintf_format(parse_tree, argv) {
var cursor = 1, tree_length = parse_tree.length, arg, output = '', i, k, ph, pad, pad_character, pad_length, is_positive, sign
for (i = 0; i < tree_length; i++) {
if (typeof parse_tree[i] === 'string') {
output += parse_tree[i]
}
else if (typeof parse_tree[i] === 'object') {
ph = parse_tree[i] // convenience purposes only
if (ph.keys) { // keyword argument
arg = argv[cursor]
for (k = 0; k < ph.keys.length; k++) {
if (arg == undefined) {
throw new Error(sprintf('[sprintf] Cannot access property "%s" of undefined value "%s"', ph.keys[k], ph.keys[k-1]))
}
arg = arg[ph.keys[k]]
}
}
else if (ph.param_no) { // positional argument (explicit)
arg = argv[ph.param_no]
}
else { // positional argument (implicit)
arg = argv[cursor++]
}
if (re.not_type.test(ph.type) && re.not_primitive.test(ph.type) && arg instanceof Function) {
arg = arg()
}
if (re.numeric_arg.test(ph.type) && (typeof arg !== 'number' && isNaN(arg))) {
throw new TypeError(sprintf('[sprintf] expecting number but found %T', arg))
}
if (re.number.test(ph.type)) {
is_positive = arg >= 0
}
switch (ph.type) {
case 'b':
arg = parseInt(arg, 10).toString(2)
break
case 'c':
arg = String.fromCharCode(parseInt(arg, 10))
break
case 'd':
case 'i':
arg = parseInt(arg, 10)
break
case 'D':
/*
select date format with width
select time format with precision
%D => 13:31 AM (default)
%1D => 13:31 AM
%.1D => 07/07/20
%1.1D => 07/07/20, 13:31 AM
%1.2D => 07/07/20, 13:31:55 AM
%2.2D => May 5, 2022, 9:29:16 AM
%3.3D => May 5, 2022 at 9:28:16 AM GMT+2
%4.4D => Thursday, May 5, 2022 at 9:26:59 AM Central European Summer Time
see meaning of DateTimeFormat's options "datestyle" and "timestyle" in MDN
*/
let [datestyle, timestyle] = [ph.width, ph.precision].map(val => ({
1: "short",
2: "medium",
3: "long",
4: "full"
}[val]));
if(timestyle === undefined && datestyle === undefined){
timestyle = "short";
}
let options = {
dateStyle: datestyle,
timeStyle: timestyle,
hour12: false
}
/* get lang from globals */
let lang = get_current_lang_code();
arg = Date(arg).toLocaleString('en-US', options);
/*
TODO: select with padding char
a: absolute time and date (default)
r: relative time
*/
break
case 'j':
arg = JSON.stringify(arg, null, ph.width ? parseInt(ph.width) : 0)
break
case 'e':
arg = ph.precision ? parseFloat(arg).toExponential(ph.precision) : parseFloat(arg).toExponential()
break
case 'f':
arg = ph.precision ? parseFloat(arg).toFixed(ph.precision) : parseFloat(arg)
break
case 'g':
arg = ph.precision ? String(Number(arg.toPrecision(ph.precision))) : parseFloat(arg)
break
case 'o':
arg = (parseInt(arg, 10) >>> 0).toString(8)
break
case 's':
arg = String(arg)
arg = (ph.precision ? arg.substring(0, ph.precision) : arg)
break
case 't':
arg = String(!!arg)
arg = (ph.precision ? arg.substring(0, ph.precision) : arg)
break
case 'T':
arg = Object.prototype.toString.call(arg).slice(8, -1).toLowerCase()
arg = (ph.precision ? arg.substring(0, ph.precision) : arg)
break
case 'u':
arg = parseInt(arg, 10) >>> 0
break
case 'v':
arg = arg.valueOf()
arg = (ph.precision ? arg.substring(0, ph.precision) : arg)
break
case 'x':
arg = (parseInt(arg, 10) >>> 0).toString(16)
break
case 'X':
arg = (parseInt(arg, 10) >>> 0).toString(16).toUpperCase()
break
}
if (re.json.test(ph.type)) {
output += arg
}
else {
if (re.number.test(ph.type) && (!is_positive || ph.sign)) {
sign = is_positive ? '+' : '-'
arg = arg.toString().replace(re.sign, '')
}
else {
sign = ''
}
pad_character = ph.pad_char ? ph.pad_char === '0' ? '0' : ph.pad_char.charAt(1) : ' '
pad_length = ph.width - (sign + arg).length
pad = ph.width ? (pad_length > 0 ? pad_character.repeat(pad_length) : '') : ''
output += ph.align ? sign + arg + pad : (pad_character === '0' ? sign + pad + arg : pad + sign + arg)
}
}
}
return output
}
var sprintf_cache = Object.create(null)
function sprintf_parse(fmt) {
if (sprintf_cache[fmt]) {
return sprintf_cache[fmt]
}
var _fmt = fmt, match, parse_tree = [], arg_names = 0
while (_fmt) {
if ((match = re.text.exec(_fmt)) !== null) {
parse_tree.push(match[0])
}
else if ((match = re.modulo.exec(_fmt)) !== null) {
parse_tree.push('%')
}
else if ((match = re.placeholder.exec(_fmt)) !== null) {
if (match[2]) {
arg_names |= 1
var field_list = [], replacement_field = match[2], field_match = []
if ((field_match = re.key.exec(replacement_field)) !== null) {
field_list.push(field_match[1])
while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') {
if ((field_match = re.key_access.exec(replacement_field)) !== null) {
field_list.push(field_match[1])
}
else if ((field_match = re.index_access.exec(replacement_field)) !== null) {
field_list.push(field_match[1])
}
else {
throw new SyntaxError('[sprintf] failed to parse named argument key')
}
}
}
else {
throw new SyntaxError('[sprintf] failed to parse named argument key')
}
match[2] = field_list
}
else {
arg_names |= 2
}
if (arg_names === 3) {
throw new Error('[sprintf] mixing positional and named placeholders is not (yet) supported')
}
parse_tree.push(
{
placeholder: match[0],
param_no: match[1],
keys: match[2],
sign: match[3],
pad_char: match[4],
align: match[5],
width: match[6],
precision: match[7],
type: match[8]
}
)
}
else {
throw new SyntaxError('[sprintf] unexpected placeholder')
}
_fmt = _fmt.substring(match[0].length)
}
return sprintf_cache[fmt] = parse_tree
}
/**
* export to either browser or node.js
*/
/* eslint-disable quote-props */
if (typeof exports !== 'undefined') {
exports['sprintf'] = sprintf
exports['vsprintf'] = vsprintf
}
if (typeof window !== 'undefined') {
window['sprintf'] = sprintf
window['vsprintf'] = vsprintf
if (typeof define === 'function' && define['amd']) {
define(function() {
return {
'sprintf': sprintf,
'vsprintf': vsprintf
}
})
}
}
/* eslint-enable quote-props */
}(); // eslint-disable-line
// svghmi.js
var need_cache_apply = [];
function dispatch_value(index, value) {
let widgets = subscribers(index);
let oldval = cache[index];
cache[index] = value;
if(widgets.size > 0) {
for(let widget of widgets){
widget.new_hmi_value(index, value, oldval);
}
}
};
function init_widgets() {
Object.keys(hmi_widgets).forEach(function(id) {
let widget = hmi_widgets[id];
widget.do_init();
});
};
// Open WebSocket to relative "/ws" address
var has_watchdog = window.location.hash == "#watchdog";
var ws_url =
window.location.href.replace(/^http(s?:\/\/[^\/]*)\/.*$/, 'ws$1/ws')
+ '?mode=' + (has_watchdog ? "watchdog" : "multiclient");
var ws = new WebSocket(ws_url);
ws.binaryType = 'arraybuffer';
const dvgetters = {
INT: (dv,offset) => [dv.getInt16(offset, true), 2],
BOOL: (dv,offset) => [dv.getInt8(offset, true), 1],
NODE: (dv,offset) => [dv.getInt8(offset, true), 1],
REAL: (dv,offset) => [dv.getFloat32(offset, true), 4],
STRING: (dv, offset) => {
const size = dv.getInt8(offset);
return [
String.fromCharCode.apply(null, new Uint8Array(
dv.buffer, /* original buffer */
offset + 1, /* string starts after size*/
size /* size of string */
)), size + 1]; /* total increment */
}
};
// Apply updates recieved through ws.onmessage to subscribed widgets
function apply_updates() {
updates.forEach((value, index) => {
dispatch_value(index, value);
});
updates.clear();
}
// Called on requestAnimationFrame, modifies DOM
var requestAnimationFrameID = null;
function animate() {
// Do the page swith if any one pending
if(current_subscribed_page != current_visible_page){
switch_visible_page(current_subscribed_page);
}
while(widget = need_cache_apply.pop()){
widget.apply_cache();
}
if(jumps_need_update) update_jumps();
apply_updates();
pending_widget_animates.forEach(widget => widget._animate());
pending_widget_animates = [];
requestAnimationFrameID = null;
}
function requestHMIAnimation() {
if(requestAnimationFrameID == null){
requestAnimationFrameID = window.requestAnimationFrame(animate);
}
}
// Message reception handler
// Hash is verified and HMI values updates resulting from binary parsing
// are stored until browser can compute next frame, DOM is left untouched
ws.onmessage = function (evt) {
let data = evt.data;
let dv = new DataView(data);
let i = 0;
try {
for(let hash_int of hmi_hash) {
if(hash_int != dv.getUint8(i)){
throw new Error("Hash doesn't match");
};
i++;
};
while(i < data.byteLength){
let index = dv.getUint32(i, true);
i += 4;
let iectype = hmitree_types[index];
if(iectype != undefined){
let dvgetter = dvgetters[iectype];
let [value, bytesize] = dvgetter(dv,i);
updates.set(index, value);
i += bytesize;
} else {
throw new Error("Unknown index "+index);
}
};
// register for rendering on next frame, since there are updates
requestHMIAnimation();
} catch(err) {
// 1003 is for "Unsupported Data"
// ws.close(1003, err.message);
// TODO : remove debug alert ?
alert("Error : "+err.message+"\nHMI will be reloaded.");
// force reload ignoring cache
location.reload(true);
}
};
hmi_hash_u8 = new Uint8Array(hmi_hash);
function send_blob(data) {
if(data.length > 0) {
ws.send(new Blob([hmi_hash_u8].concat(data)));
};
};
const typedarray_types = {
INT: (number) => new Int16Array([number]),
BOOL: (truth) => new Int16Array([truth]),
NODE: (truth) => new Int16Array([truth]),
REAL: (number) => new Float32Array([number]),
STRING: (str) => {
// beremiz default string max size is 128
str = str.slice(0,128);
binary = new Uint8Array(str.length + 1);
binary[0] = str.length;
for(let i = 0; i < str.length; i++){
binary[i+1] = str.charCodeAt(i);
}
return binary;
}
/* TODO */
};
function send_reset() {
send_blob(new Uint8Array([1])); /* reset = 1 */
};
var subscriptions = [];
function subscribers(index) {
let entry = subscriptions[index];
let res;
if(entry == undefined){
res = new Set();
subscriptions[index] = [res,0];
}else{
[res, _ign] = entry;
}
return res
}
function get_subscription_period(index) {
let entry = subscriptions[index];
if(entry == undefined)
return 0;
let [_ign, period] = entry;
return period;
}
function set_subscription_period(index, period) {
let entry = subscriptions[index];
if(entry == undefined){
subscriptions[index] = [new Set(), period];
} else {
entry[1] = period;
}
}
if(has_watchdog){
// artificially subscribe the watchdog widget to "/heartbeat" hmi variable
// Since dispatch directly calls change_hmi_value,
// PLC will periodically send variable at given frequency
subscribers(heartbeat_index).add({
/* type: "Watchdog", */
frequency: 1,
indexes: [heartbeat_index],
new_hmi_value: function(index, value, oldval) {
apply_hmi_value(heartbeat_index, value+1);
}
});
}
// subscribe to per instance current page hmi variable
// PLC must prefix page name with "!" for page switch to happen
subscribers(current_page_var_index).add({
frequency: 1,
indexes: [current_page_var_index],
new_hmi_value: function(index, value, oldval) {
if(value.startsWith("!"))
switch_page(value.slice(1));
}
});
function svg_text_to_multiline(elt) {
return(Array.prototype.map.call(elt.children, x=>x.textContent).join("\n"));
}
function multiline_to_svg_text(elt, str) {
str.split('\n').map((line,i) => {elt.children[i].textContent = line;});
}
function switch_langnum(langnum) {
langnum = Math.max(0, Math.min(langs.length - 1, langnum));
for (let translation of translations) {
let [objs, msgs] = translation;
let msg = msgs[langnum];
for (let obj of objs) {
multiline_to_svg_text(obj, msg);
obj.setAttribute("lang",langnum);
}
}
return langnum;
}
// backup original texts
for (let translation of translations) {
let [objs, msgs] = translation;
msgs.unshift(svg_text_to_multiline(objs[0]));
}
var lang_local_index = hmi_local_index("lang");
var langcode_local_index = hmi_local_index("lang_code");
var langname_local_index = hmi_local_index("lang_name");
subscribers(lang_local_index).add({
indexes: [lang_local_index],
new_hmi_value: function(index, value, oldval) {
let current_lang = switch_langnum(value);
let [langname,langcode] = langs[current_lang];
apply_hmi_value(langcode_local_index, langcode);
apply_hmi_value(langname_local_index, langname);
switch_page();
}
});
// returns en_US, fr_FR or en_UK depending on selected language
function get_current_lang_code(){
return cache[langcode_local_index];
}
function setup_lang(){
let current_lang = cache[lang_local_index];
let new_lang = switch_langnum(current_lang);
if(current_lang != new_lang){
apply_hmi_value(lang_local_index, new_lang);
}
}
setup_lang();
function update_subscriptions() {
let delta = [];
for(let index in subscriptions){
let widgets = subscribers(index);
// periods are in ms
let previous_period = get_subscription_period(index);
// subscribing with a zero period is unsubscribing
let new_period = 0;
if(widgets.size > 0) {
let maxfreq = 0;
for(let widget of widgets){
let wf = widget.frequency;
if(wf != undefined && maxfreq < wf)
maxfreq = wf;
}
if(maxfreq != 0)
new_period = 1000/maxfreq;
}
if(previous_period != new_period) {
set_subscription_period(index, new_period);
if(index <= last_remote_index){
delta.push(
new Uint8Array([2]), /* subscribe = 2 */
new Uint32Array([index]),
new Uint16Array([new_period]));
}
}
}
send_blob(delta);
};
function send_hmi_value(index, value) {
if(index > last_remote_index){
updates.set(index, value);
if(persistent_indexes.has(index)){
let varname = persistent_indexes.get(index);
document.cookie = varname+"="+value+"; max-age=3153600000";
}
requestHMIAnimation();
return;
}
let iectype = hmitree_types[index];
let tobinary = typedarray_types[iectype];
send_blob([
new Uint8Array([0]), /* setval = 0 */
new Uint32Array([index]),
tobinary(value)]);
// DON'T DO THAT unless read_iterator in svghmi.c modifies wbuf as well, not only rbuf
// cache[index] = value;
};
function apply_hmi_value(index, new_val) {
// Similarly to previous comment, taking decision to update based
// on cache content is bad and can lead to inconsistency
/*let old_val = cache[index];*/
if(new_val != undefined /*&& old_val != new_val*/)
send_hmi_value(index, new_val);
return new_val;
}
const quotes = {"'":null, '"':null};
function eval_operation_string(old_val, opstr) {
let op = opstr[0];
let given_val;
if(opstr.length < 2)
return undefined;
if(opstr[1] in quotes){
if(opstr.length < 3)
return undefined;
if(opstr[opstr.length-1] == opstr[1]){
given_val = opstr.slice(2,opstr.length-1);
}
} else {
given_val = Number(opstr.slice(1));
}
let new_val;
switch(op){
case "=":
new_val = given_val;
break;
case "+":
new_val = old_val + given_val;
break;
case "-":
new_val = old_val - given_val;
break;
case "*":
new_val = old_val * given_val;
break;
case "/":
new_val = old_val / given_val;
break;
}
return new_val;
}
var current_visible_page;
var current_subscribed_page;
var current_page_index;
var page_node_local_index = hmi_local_index("page_node");
function toggleFullscreen() {
let elem = document.documentElement;
if (!document.fullscreenElement) {
elem.requestFullscreen().catch(err => {
console.log("Error attempting to enable full-screen mode: "+err.message+" ("+err.name+")");
});
} else {
document.exitFullscreen();
}
}
function prepare_svg() {
// prevents context menu from appearing on right click and long touch
document.body.addEventListener('contextmenu', e => {
toggleFullscreen();
e.preventDefault();
});
for(let eltid in detachable_elements){
let [element,parent] = detachable_elements[eltid];
parent.removeChild(element);
}
};
function switch_page(page_name, page_index) {
if(current_subscribed_page != current_visible_page){
/* page switch already going */
/* TODO LOG ERROR */
return false;
}
if(page_name == undefined)
page_name = current_subscribed_page;
else if(page_index == undefined){
[page_name, page_index] = page_name.split('@')
}
let old_desc = page_desc[current_subscribed_page];
let new_desc = page_desc[page_name];
if(new_desc == undefined){
/* TODO LOG ERROR */
return false;
}
if(page_index == undefined)
page_index = new_desc.page_index;
else if(typeof(page_index) == "string") {
let hmitree_node = hmitree_nodes[page_index];
if(hmitree_node !== undefined){
let [int_index, hmiclass] = hmitree_node;
if(hmiclass == new_desc.page_class)
page_index = int_index;
else
page_index = new_desc.page_index;
} else {
page_index = new_desc.page_index;
}
}
if(old_desc){
old_desc.widgets.map(([widget,relativeness])=>widget.unsub());
}
const new_offset = page_index == undefined ? 0 : page_index - new_desc.page_index;
const container_id = page_name + (page_index != undefined ? page_index : "");
new_desc.widgets.map(([widget,relativeness])=>widget.sub(new_offset,relativeness,container_id));
update_subscriptions();
current_subscribed_page = page_name;
current_page_index = page_index;
let page_node;
if(page_index != undefined){
page_node = hmitree_paths[page_index];
}else{
page_node = "";
}
apply_hmi_value(page_node_local_index, page_node);
jumps_need_update = true;
requestHMIAnimation();
jump_history.push([page_name, page_index]);
if(jump_history.length > 42)
jump_history.shift();
apply_hmi_value(current_page_var_index, page_index == undefined
? page_name
: page_name + "@" + hmitree_paths[page_index]);
return true;
};
function switch_visible_page(page_name) {
let old_desc = page_desc[current_visible_page];
let new_desc = page_desc[page_name];
if(old_desc){
for(let eltid in old_desc.required_detachables){
if(!(eltid in new_desc.required_detachables)){
let [element, parent] = old_desc.required_detachables[eltid];
parent.removeChild(element);
}
}
for(let eltid in new_desc.required_detachables){
if(!(eltid in old_desc.required_detachables)){
let [element, parent] = new_desc.required_detachables[eltid];
parent.appendChild(element);
}
}
}else{
for(let eltid in new_desc.required_detachables){
let [element, parent] = new_desc.required_detachables[eltid];
parent.appendChild(element);
}
}
svg_root.setAttribute('viewBox',new_desc.bbox.join(" "));
current_visible_page = page_name;
};
// Once connection established
ws.onopen = function (evt) {
init_widgets();
send_reset();
// show main page
prepare_svg();
switch_page(default_page);
};
ws.onclose = function (evt) {
// TODO : add visible notification while waiting for reload
console.log("Connection closed. code:"+evt.code+" reason:"+evt.reason+" wasClean:"+evt.wasClean+" Reload in 10s.");
// TODO : re-enable auto reload when not in debug
//window.setTimeout(() => location.reload(true), 10000);
alert("Connection closed. code:"+evt.code+" reason:"+evt.reason+" wasClean:"+evt.wasClean+".");
};
const xmlns = "http://www.w3.org/2000/svg";
var edit_callback;
const localtypes = {"PAGE_LOCAL":null, "HMI_LOCAL":null}
function edit_value(path, valuetype, callback, initial) {
if(valuetype in localtypes){
valuetype = (typeof initial) == "number" ? "HMI_REAL" : "HMI_STRING";
}
let [keypadid, xcoord, ycoord] = keypads[valuetype];
edit_callback = callback;
let widget = hmi_widgets[keypadid];
widget.start_edit(path, valuetype, callback, initial);
};
var current_modal; /* TODO stack ?*/
function show_modal() {
let [element, parent] = detachable_elements[this.element.id];
tmpgrp = document.createElementNS(xmlns,"g");
tmpgrpattr = document.createAttribute("transform");
let [xcoord,ycoord] = this.coordinates;
let [xdest,ydest] = page_desc[current_visible_page].bbox;
tmpgrpattr.value = "translate("+String(xdest-xcoord)+","+String(ydest-ycoord)+")";
tmpgrp.setAttributeNode(tmpgrpattr);
tmpgrp.appendChild(element);
parent.appendChild(tmpgrp);
current_modal = [this.element.id, tmpgrp];
};
function end_modal() {
let [eltid, tmpgrp] = current_modal;
let [element, parent] = detachable_elements[this.element.id];
parent.removeChild(tmpgrp);
current_modal = undefined;
};
]]></script></body></html>
\ No newline at end of file
FUNCTION_BLOCK CounterST
VAR
Cnt0 : INT;
END_VAR
VAR_EXTERNAL
Relay0 : DINT;
Relay1 : DINT;
Relay2 : DINT;
Relay3 : DINT;
HMI_RELAY0 : HMI_INT;
HMI_RELAY1 : HMI_INT;
HMI_RELAY2 : HMI_INT;
HMI_RELAY3 : HMI_INT;
END_VAR
IF HMI_RELAY2 >= 1 THEN
Relay2:=1;
ELSE
Relay2:=0;
END_IF;
IF HMI_RELAY3 >= 1 THEN
Relay3:=1;
ELSE
Relay3:=0;
END_IF;
Cnt0 := Cnt0 + 1;
IF Cnt0 = 50 THEN
Relay0 := 1;
HMI_RELAY0:= 1;
Relay1 := 1;
HMI_RELAY1:= 1;
END_IF;
IF Cnt0 = 100 THEN
Relay0 := 0;
HMI_RELAY0:= 0;
Relay1 := 0;
HMI_RELAY1:= 0;
Cnt0 := 0;
END_IF;
END_FUNCTION_BLOCK
PROGRAM plc_prg
VAR
CounterST0 : CounterST;
END_VAR
CounterST0();
END_PROGRAM
CONFIGURATION config
VAR_GLOBAL
Relay0 AT %QD1.0 : DINT := 0;
Relay1 AT %QD1.1 : DINT := 0;
Relay2 AT %QD1.2 : DINT := 0;
Relay3 AT %QD1.3 : DINT := 0;
HMI_RELAY0 : HMI_INT := 0;
HMI_RELAY1 : HMI_INT := 0;
HMI_RELAY2 : HMI_INT := 0;
HMI_RELAY3 : HMI_INT := 0;
HMI_ROOT : HMI_NODE;
heartbeat : HMI_INT;
CURRENT_PAGE_0 : HMI_STRING;
END_VAR
RESOURCE resource1 ON PLC
TASK task0(INTERVAL := T#20ms,PRIORITY := 0);
PROGRAM instance0 WITH task0 : plc_prg;
END_RESOURCE
END_CONFIGURATION
<HMI_NODE name="" path="CONFIG.HMI_ROOT"><HMI_INT name="HMI_RELAY0" path="CONFIG.HMI_RELAY0"/><HMI_INT name="HMI_RELAY1" path="CONFIG.HMI_RELAY1"/><HMI_INT name="HMI_RELAY2" path="CONFIG.HMI_RELAY2"/><HMI_INT name="HMI_RELAY3" path="CONFIG.HMI_RELAY3"/><HMI_INT name="HEARTBEAT" path="CONFIG.HEARTBEAT"/><HMI_STRING name="CURRENT_PAGE_0" path="CONFIG.CURRENT_PAGE_0"/></HMI_NODE>
\ No newline at end of file
0d60a23d82a69794d34cd693de3cac7f
\ No newline at end of file
/* code generated by beremiz OPC-UA extension */
#include <open62541/client_config_default.h>
#include <open62541/client_highlevel.h>
#include <open62541/plugin/log_stdout.h>
static UA_Client *client;
#define DECL_VAR(ua_type, C_type, c_loc_name) \
static UA_Variant c_loc_name##_variant; \
static C_type c_loc_name##_buf = 0; \
C_type *c_loc_name = &c_loc_name##_buf;
DECL_VAR(UA_Int32, uint32_t, __ID1_0)
DECL_VAR(UA_Int32, uint32_t, __ID1_1)
DECL_VAR(UA_Int32, uint32_t, __ID1_2)
DECL_VAR(UA_Int32, uint32_t, __ID1_3)
DECL_VAR(UA_Int32, uint32_t, __QD1_0)
DECL_VAR(UA_Int32, uint32_t, __QD1_1)
DECL_VAR(UA_Int32, uint32_t, __QD1_2)
DECL_VAR(UA_Int32, uint32_t, __QD1_3)
void __cleanup_1(void)
{
UA_Client_disconnect(client);
UA_Client_delete(client);
}
#define INIT_READ_VARIANT(ua_type, c_loc_name) \
UA_Variant_init(&c_loc_name##_variant);
#define INIT_WRITE_VARIANT(ua_type, ua_type_enum, c_loc_name) \
UA_Variant_setScalar(&c_loc_name##_variant, (ua_type*)c_loc_name, &UA_TYPES[ua_type_enum]);
int __init_1(int argc,char **argv)
{
UA_StatusCode retval;
client = UA_Client_new();
UA_ClientConfig_setDefault(UA_Client_getConfig(client));
INIT_READ_VARIANT(UA_Int32, __ID1_0)
INIT_READ_VARIANT(UA_Int32, __ID1_1)
INIT_READ_VARIANT(UA_Int32, __ID1_2)
INIT_READ_VARIANT(UA_Int32, __ID1_3)
INIT_WRITE_VARIANT(UA_Int32, UA_TYPES_INT32, __QD1_0)
INIT_WRITE_VARIANT(UA_Int32, UA_TYPES_INT32, __QD1_1)
INIT_WRITE_VARIANT(UA_Int32, UA_TYPES_INT32, __QD1_2)
INIT_WRITE_VARIANT(UA_Int32, UA_TYPES_INT32, __QD1_3)
/* Connect to server */
retval = UA_Client_connect(client, "opc.tcp://192.168.0.44:4840");
if(retval != UA_STATUSCODE_GOOD) {
UA_Client_delete(client);
return EXIT_FAILURE;
}
}
#define READ_VALUE(ua_type, ua_type_enum, c_loc_name, ua_nodeid_type, ua_nsidx, ua_node_id) \
retval = UA_Client_readValueAttribute( \
client, ua_nodeid_type(ua_nsidx, ua_node_id), &c_loc_name##_variant); \
if(retval == UA_STATUSCODE_GOOD && UA_Variant_isScalar(&c_loc_name##_variant) && \
c_loc_name##_variant.type == &UA_TYPES[ua_type_enum]) { \
c_loc_name##_buf = *(ua_type*)c_loc_name##_variant.data; \
UA_Variant_clear(&c_loc_name##_variant); /* Unalloc requiered on each read ! */ \
}
void __retrieve_1(void)
{
UA_StatusCode retval;
READ_VALUE(UA_Int32, UA_TYPES_INT32, __ID1_0, UA_NODEID_STRING, 1, "i2c0.relay0")
READ_VALUE(UA_Int32, UA_TYPES_INT32, __ID1_1, UA_NODEID_STRING, 1, "i2c0.relay1")
READ_VALUE(UA_Int32, UA_TYPES_INT32, __ID1_2, UA_NODEID_STRING, 1, "i2c0.relay2")
READ_VALUE(UA_Int32, UA_TYPES_INT32, __ID1_3, UA_NODEID_STRING, 1, "i2c0.relay3")
}
#define WRITE_VALUE(ua_type, c_loc_name, ua_nodeid_type, ua_nsidx, ua_node_id) \
UA_Client_writeValueAttribute( \
client, ua_nodeid_type(ua_nsidx, ua_node_id), &c_loc_name##_variant);
void __publish_1(void)
{
WRITE_VALUE(UA_Int32, __QD1_0, UA_NODEID_STRING, 1, "i2c0.relay0")
WRITE_VALUE(UA_Int32, __QD1_1, UA_NODEID_STRING, 1, "i2c0.relay1")
WRITE_VALUE(UA_Int32, __QD1_2, UA_NODEID_STRING, 1, "i2c0.relay2")
WRITE_VALUE(UA_Int32, __QD1_3, UA_NODEID_STRING, 1, "i2c0.relay3")
}
TYPE
LOGLEVEL : (CRITICAL, WARNING, INFO, DEBUG) := INFO;
END_TYPE
FUNCTION_BLOCK LOGGER
VAR_INPUT
TRIG : BOOL;
MSG : STRING;
LEVEL : LOGLEVEL := INFO;
END_VAR
VAR
TRIG0 : BOOL;
END_VAR
IF TRIG AND NOT TRIG0 THEN
{{
LogMessage(GetFbVar(LEVEL),(char*)GetFbVar(MSG, .body),GetFbVar(MSG, .len));
}}
END_IF;
TRIG0:=TRIG;
END_FUNCTION_BLOCK
FUNCTION_BLOCK python_eval
VAR_INPUT
TRIG : BOOL;
CODE : STRING;
END_VAR
VAR_OUTPUT
ACK : BOOL;
RESULT : STRING;
END_VAR
VAR
STATE : DWORD;
BUFFER : STRING;
PREBUFFER : STRING;
TRIGM1 : BOOL;
TRIGGED : BOOL;
END_VAR
{extern void __PythonEvalFB(int, PYTHON_EVAL*);__PythonEvalFB(0, data__);}
END_FUNCTION_BLOCK
FUNCTION_BLOCK python_poll
VAR_INPUT
TRIG : BOOL;
CODE : STRING;
END_VAR
VAR_OUTPUT
ACK : BOOL;
RESULT : STRING;
END_VAR
VAR
STATE : DWORD;
BUFFER : STRING;
PREBUFFER : STRING;
TRIGM1 : BOOL;
TRIGGED : BOOL;
END_VAR
{extern void __PythonEvalFB(int, PYTHON_EVAL*);__PythonEvalFB(1,(PYTHON_EVAL*)(void*)data__);}
END_FUNCTION_BLOCK
FUNCTION_BLOCK python_gear
VAR_INPUT
N : UINT;
TRIG : BOOL;
CODE : STRING;
END_VAR
VAR_OUTPUT
ACK : BOOL;
RESULT : STRING;
END_VAR
VAR
py_eval : python_eval;
COUNTER : UINT;
_TMP_ADD10_OUT : UINT;
_TMP_EQ13_OUT : BOOL;
_TMP_SEL15_OUT : UINT;
_TMP_AND7_OUT : BOOL;
END_VAR
_TMP_ADD10_OUT := ADD(COUNTER, 1);
_TMP_EQ13_OUT := EQ(N, _TMP_ADD10_OUT);
_TMP_SEL15_OUT := SEL(_TMP_EQ13_OUT, _TMP_ADD10_OUT, 0);
COUNTER := _TMP_SEL15_OUT;
_TMP_AND7_OUT := AND(_TMP_EQ13_OUT, TRIG);
py_eval(TRIG := _TMP_AND7_OUT, CODE := CODE);
ACK := py_eval.ACK;
RESULT := py_eval.RESULT;
END_FUNCTION_BLOCK
TYPE
HMI_REAL : REAL;
HMI_NODE : BOOL;
HMI_STRING : STRING;
HMI_BOOL : BOOL;
HMI_INT : INT;
END_TYPE
FUNCTION_BLOCK CounterST
VAR
Cnt0 : INT;
END_VAR
VAR_EXTERNAL
Relay0 : DINT;
Relay1 : DINT;
Relay2 : DINT;
Relay3 : DINT;
HMI_RELAY0 : HMI_INT;
HMI_RELAY1 : HMI_INT;
HMI_RELAY2 : HMI_INT;
HMI_RELAY3 : HMI_INT;
END_VAR
IF HMI_RELAY2 >= 1 THEN
Relay2:=1;
ELSE
Relay2:=0;
END_IF;
IF HMI_RELAY3 >= 1 THEN
Relay3:=1;
ELSE
Relay3:=0;
END_IF;
Cnt0 := Cnt0 + 1;
IF Cnt0 = 50 THEN
Relay0 := 1;
HMI_RELAY0:= 1;
Relay1 := 1;
HMI_RELAY1:= 1;
END_IF;
IF Cnt0 = 100 THEN
Relay0 := 0;
HMI_RELAY0:= 0;
Relay1 := 0;
HMI_RELAY1:= 0;
Cnt0 := 0;
END_IF;
END_FUNCTION_BLOCK
PROGRAM plc_prg
VAR
CounterST0 : CounterST;
END_VAR
CounterST0();
END_PROGRAM
CONFIGURATION config
VAR_GLOBAL
Relay0 AT %QD1.0 : DINT := 0;
Relay1 AT %QD1.1 : DINT := 0;
Relay2 AT %QD1.2 : DINT := 0;
Relay3 AT %QD1.3 : DINT := 0;
HMI_RELAY0 : HMI_INT := 0;
HMI_RELAY1 : HMI_INT := 0;
HMI_RELAY2 : HMI_INT := 0;
HMI_RELAY3 : HMI_INT := 0;
HMI_ROOT : HMI_NODE;
heartbeat : HMI_INT;
CURRENT_PAGE_0 : HMI_STRING;
END_VAR
RESOURCE resource1 ON PLC
TASK task0(INTERVAL := T#20ms,PRIORITY := 0);
PROGRAM instance0 WITH task0 : plc_prg;
END_RESOURCE
END_CONFIGURATION
/*
* DEBUGGER code
*
* On "publish", when buffer is free, debugger stores arbitrary variables
* content into, and mark this buffer as filled
*
*
* Buffer content is read asynchronously, (from non real time part),
* and then buffer marked free again.
*
*
* */
#ifdef TARGET_DEBUG_AND_RETAIN_DISABLE
void __init_debug (void){}
void __cleanup_debug (void){}
void __retrieve_debug(void){}
void __publish_debug (void){}
#else
#include "iec_types_all.h"
#include "POUS.h"
/*for memcpy*/
#include <string.h>
#include <stdio.h>
typedef unsigned int dbgvardsc_index_t;
typedef unsigned short trace_buf_offset_t;
#define BUFFER_EMPTY 0
#define BUFFER_FULL 1
#ifndef TARGET_ONLINE_DEBUG_DISABLE
#define TRACE_BUFFER_SIZE 4096
#define TRACE_LIST_SIZE 1024
/* Atomically accessed variable for buffer state */
static long trace_buffer_state = BUFFER_EMPTY;
typedef struct trace_item_s {
dbgvardsc_index_t dbgvardsc_index;
} trace_item_t;
trace_item_t trace_list[TRACE_LIST_SIZE];
char trace_buffer[TRACE_BUFFER_SIZE];
/* Trace's cursor*/
static trace_item_t *trace_list_collect_cursor = trace_list;
static trace_item_t *trace_list_addvar_cursor = trace_list;
static const trace_item_t *trace_list_end =
&trace_list[TRACE_LIST_SIZE-1];
static char *trace_buffer_cursor = trace_buffer;
static const char *trace_buffer_end = trace_buffer + TRACE_BUFFER_SIZE;
#define FORCE_BUFFER_SIZE 1024
#define FORCE_LIST_SIZE 256
typedef struct force_item_s {
dbgvardsc_index_t dbgvardsc_index;
void *value_pointer_backup;
} force_item_t;
force_item_t force_list[FORCE_LIST_SIZE];
char force_buffer[FORCE_BUFFER_SIZE];
/* Force's cursor*/
static force_item_t *force_list_apply_cursor = force_list;
static force_item_t *force_list_addvar_cursor = force_list;
static const force_item_t *force_list_end =
&force_list[FORCE_LIST_SIZE-1];
static char *force_buffer_cursor = force_buffer;
static const char *force_buffer_end = force_buffer + FORCE_BUFFER_SIZE;
#endif
/***
* Declare programs
**/
extern PLC_PRG RESOURCE1__INSTANCE0;
/***
* Declare global variables from resources and conf
**/
extern __IEC_DINT_p CONFIG__RELAY0;
extern __IEC_DINT_p CONFIG__RELAY1;
extern __IEC_DINT_p CONFIG__RELAY2;
extern __IEC_DINT_p CONFIG__RELAY3;
extern __IEC_INT_t CONFIG__HMI_RELAY0;
extern __IEC_INT_t CONFIG__HMI_RELAY1;
extern __IEC_INT_t CONFIG__HMI_RELAY2;
extern __IEC_INT_t CONFIG__HMI_RELAY3;
extern __IEC_BOOL_t CONFIG__HMI_ROOT;
extern __IEC_INT_t CONFIG__HEARTBEAT;
extern __IEC_STRING_t CONFIG__CURRENT_PAGE_0;
extern PLC_PRG RESOURCE1__INSTANCE0;
typedef const struct {
void *ptr;
__IEC_types_enum type;
} dbgvardsc_t;
static const dbgvardsc_t dbgvardsc[] = {
{&(CONFIG__RELAY0), DINT_O_ENUM},
{&(CONFIG__RELAY1), DINT_O_ENUM},
{&(CONFIG__RELAY2), DINT_O_ENUM},
{&(CONFIG__RELAY3), DINT_O_ENUM},
{&(CONFIG__HMI_RELAY0), INT_ENUM},
{&(CONFIG__HMI_RELAY1), INT_ENUM},
{&(CONFIG__HMI_RELAY2), INT_ENUM},
{&(CONFIG__HMI_RELAY3), INT_ENUM},
{&(CONFIG__HMI_ROOT), BOOL_ENUM},
{&(CONFIG__HEARTBEAT), INT_ENUM},
{&(CONFIG__CURRENT_PAGE_0), STRING_ENUM},
{&(RESOURCE1__INSTANCE0.COUNTERST0.EN), BOOL_ENUM},
{&(RESOURCE1__INSTANCE0.COUNTERST0.ENO), BOOL_ENUM},
{&(RESOURCE1__INSTANCE0.COUNTERST0.CNT0), INT_ENUM},
{&(RESOURCE1__INSTANCE0.COUNTERST0.RELAY0), DINT_P_ENUM},
{&(RESOURCE1__INSTANCE0.COUNTERST0.RELAY1), DINT_P_ENUM},
{&(RESOURCE1__INSTANCE0.COUNTERST0.RELAY2), DINT_P_ENUM},
{&(RESOURCE1__INSTANCE0.COUNTERST0.RELAY3), DINT_P_ENUM},
{&(RESOURCE1__INSTANCE0.COUNTERST0.HMI_RELAY0), INT_P_ENUM},
{&(RESOURCE1__INSTANCE0.COUNTERST0.HMI_RELAY1), INT_P_ENUM},
{&(RESOURCE1__INSTANCE0.COUNTERST0.HMI_RELAY2), INT_P_ENUM},
{&(RESOURCE1__INSTANCE0.COUNTERST0.HMI_RELAY3), INT_P_ENUM}
};
static const dbgvardsc_index_t retain_list[] = {
};
static unsigned int retain_list_collect_cursor = 0;
static const unsigned int retain_list_size = sizeof(retain_list)/sizeof(dbgvardsc_index_t);
typedef void(*__for_each_variable_do_fp)(dbgvardsc_t*);
void __for_each_variable_do(__for_each_variable_do_fp fp)
{
unsigned int i;
for(i = 0; i < sizeof(dbgvardsc)/sizeof(dbgvardsc_t); i++){
dbgvardsc_t *dsc = &dbgvardsc[i];
if(dsc->type != UNKNOWN_ENUM)
(*fp)(dsc);
}
}
#define __Unpack_desc_type dbgvardsc_t
#define __Unpack_case_t(TYPENAME) \
case TYPENAME##_ENUM : \
if(flags) *flags = ((__IEC_##TYPENAME##_t *)varp)->flags; \
if(value_p) *value_p = &((__IEC_##TYPENAME##_t *)varp)->value; \
if(size) *size = sizeof(TYPENAME); \
break;
#define __Unpack_case_p(TYPENAME) \
case TYPENAME##_O_ENUM : \
case TYPENAME##_P_ENUM : \
if(flags) *flags = ((__IEC_##TYPENAME##_p *)varp)->flags; \
if(value_p) *value_p = ((__IEC_##TYPENAME##_p *)varp)->value; \
if(size) *size = sizeof(TYPENAME); \
break;
#define __Is_a_string(dsc) (dsc->type == STRING_ENUM) ||\
(dsc->type == STRING_P_ENUM) ||\
(dsc->type == STRING_O_ENUM)
static int UnpackVar(__Unpack_desc_type *dsc, void **value_p, char *flags, size_t *size)
{
void *varp = dsc->ptr;
/* find data to copy*/
switch(dsc->type){
__ANY(__Unpack_case_t)
__ANY(__Unpack_case_p)
default:
return 0; /* should never happen */
}
return 1;
}
void Remind(unsigned int offset, unsigned int count, void * p);
extern int CheckRetainBuffer(void);
extern void InitRetain(void);
void __init_debug(void)
{
/* init local static vars */
#ifndef TARGET_ONLINE_DEBUG_DISABLE
trace_buffer_cursor = trace_buffer;
trace_list_addvar_cursor = trace_list;
trace_list_collect_cursor = trace_list;
trace_buffer_state = BUFFER_EMPTY;
force_buffer_cursor = force_buffer;
force_list_addvar_cursor = force_list;
force_list_apply_cursor = force_list;
#endif
InitRetain();
/* Iterate over all variables to fill debug buffer */
if(CheckRetainBuffer()){
static unsigned int retain_offset = 0;
retain_list_collect_cursor = 0;
/* iterate over retain list */
while(retain_list_collect_cursor < retain_list_size){
void *value_p = NULL;
size_t size;
char* next_cursor;
dbgvardsc_t *dsc = &dbgvardsc[
retain_list[retain_list_collect_cursor]];
UnpackVar(dsc, &value_p, NULL, &size);
printf("Reminding %d %ld \n", retain_list_collect_cursor, size);
/* if buffer not full */
Remind(retain_offset, size, value_p);
/* increment cursor according size*/
retain_offset += size;
retain_list_collect_cursor++;
}
}else{
char mstr[] = "RETAIN memory invalid - defaults used";
LogMessage(LOG_WARNING, mstr, sizeof(mstr));
}
}
extern void InitiateDebugTransfer(void);
extern void CleanupRetain(void);
extern unsigned long __tick;
void __cleanup_debug(void)
{
#ifndef TARGET_ONLINE_DEBUG_DISABLE
trace_buffer_cursor = trace_buffer;
InitiateDebugTransfer();
#endif
CleanupRetain();
}
void __retrieve_debug(void)
{
}
void Retain(unsigned int offset, unsigned int count, void * p);
/* Return size of all retain variables */
unsigned int GetRetainSize(void)
{
unsigned int retain_size = 0;
retain_list_collect_cursor = 0;
/* iterate over retain list */
while(retain_list_collect_cursor < retain_list_size){
void *value_p = NULL;
size_t size;
char* next_cursor;
dbgvardsc_t *dsc = &dbgvardsc[
retain_list[retain_list_collect_cursor]];
UnpackVar(dsc, &value_p, NULL, &size);
retain_size += size;
retain_list_collect_cursor++;
}
printf("Retain size %d \n", retain_size);
return retain_size;
}
extern void PLC_GetTime(IEC_TIME*);
extern int TryEnterDebugSection(void);
extern long AtomicCompareExchange(long*, long, long);
extern long long AtomicCompareExchange64(long long* , long long , long long);
extern void LeaveDebugSection(void);
extern void ValidateRetainBuffer(void);
extern void InValidateRetainBuffer(void);
#define __ReForceOutput_case_p(TYPENAME) \
case TYPENAME##_P_ENUM : \
case TYPENAME##_O_ENUM : \
{ \
char *next_cursor = force_buffer_cursor + sizeof(TYPENAME); \
if(next_cursor <= force_buffer_end ){ \
/* outputs real value must be systematically forced */ \
if(vartype == TYPENAME##_O_ENUM) \
/* overwrite value pointed by backup */ \
*((TYPENAME *)force_list_apply_cursor->value_pointer_backup) = \
*((TYPENAME *)force_buffer_cursor); \
/* inc force_buffer cursor */ \
force_buffer_cursor = next_cursor; \
}else{ \
stop = 1; \
} \
} \
break;
void __publish_debug(void)
{
InValidateRetainBuffer();
#ifndef TARGET_ONLINE_DEBUG_DISABLE
/* Check there is no running debugger re-configuration */
if(TryEnterDebugSection()){
/* Lock buffer */
long latest_state = AtomicCompareExchange(
&trace_buffer_state,
BUFFER_EMPTY,
BUFFER_FULL);
/* If buffer was free */
if(latest_state == BUFFER_EMPTY)
{
int stop = 0;
/* Reset force list cursor */
force_list_apply_cursor = force_list;
/* iterate over force list */
while(!stop && force_list_apply_cursor < force_list_addvar_cursor){
dbgvardsc_t *dsc = &dbgvardsc[
force_list_apply_cursor->dbgvardsc_index];
void *varp = dsc->ptr;
__IEC_types_enum vartype = dsc->type;
switch(vartype){
__ANY(__ReForceOutput_case_p)
default:
break;
}
force_list_apply_cursor++; \
}
/* Reset buffer cursor */
trace_buffer_cursor = trace_buffer;
/* Reset trace list cursor */
trace_list_collect_cursor = trace_list;
/* iterate over trace list */
while(trace_list_collect_cursor < trace_list_addvar_cursor){
void *value_p = NULL;
size_t size;
char* next_cursor;
dbgvardsc_t *dsc = &dbgvardsc[
trace_list_collect_cursor->dbgvardsc_index];
UnpackVar(dsc, &value_p, NULL, &size);
/* copy visible variable to buffer */;
if(__Is_a_string(dsc)){
/* optimization for strings */
/* assume NULL terminated strings */
size = ((STRING*)value_p)->len + 1;
}
/* compute next cursor positon.*/
next_cursor = trace_buffer_cursor + size;
/* check for buffer overflow */
if(next_cursor < trace_buffer_end)
/* copy data to the buffer */
memcpy(trace_buffer_cursor, value_p, size);
else
/* stop looping in case of overflow */
break;
/* increment cursor according size*/
trace_buffer_cursor = next_cursor;
trace_list_collect_cursor++;
}
/* Leave debug section,
* Trigger asynchronous transmission
* (returns immediately) */
InitiateDebugTransfer(); /* size */
}
LeaveDebugSection();
}
#endif
static unsigned int retain_offset = 0;
/* when not debugging, do only retain */
retain_list_collect_cursor = 0;
/* iterate over retain list */
while(retain_list_collect_cursor < retain_list_size){
void *value_p = NULL;
size_t size;
char* next_cursor;
dbgvardsc_t *dsc = &dbgvardsc[
retain_list[retain_list_collect_cursor]];
UnpackVar(dsc, &value_p, NULL, &size);
/* if buffer not full */
Retain(retain_offset, size, value_p);
/* increment cursor according size*/
retain_offset += size;
retain_list_collect_cursor++;
}
ValidateRetainBuffer();
}
#ifndef TARGET_ONLINE_DEBUG_DISABLE
#define TRACE_LIST_OVERFLOW 1
#define FORCE_LIST_OVERFLOW 2
#define FORCE_BUFFER_OVERFLOW 3
#define __ForceVariable_case_t(TYPENAME) \
case TYPENAME##_ENUM : \
/* add to force_list*/ \
force_list_addvar_cursor->dbgvardsc_index = idx; \
((__IEC_##TYPENAME##_t *)varp)->flags |= __IEC_FORCE_FLAG; \
((__IEC_##TYPENAME##_t *)varp)->value = *((TYPENAME *)force); \
break;
#define __ForceVariable_case_p(TYPENAME) \
case TYPENAME##_P_ENUM : \
case TYPENAME##_O_ENUM : \
{ \
char *next_cursor = force_buffer_cursor + sizeof(TYPENAME); \
if(next_cursor <= force_buffer_end ){ \
/* add to force_list*/ \
force_list_addvar_cursor->dbgvardsc_index = idx; \
/* save pointer to backup */ \
force_list_addvar_cursor->value_pointer_backup = \
((__IEC_##TYPENAME##_p *)varp)->value; \
/* store forced value in force_buffer */ \
*((TYPENAME *)force_buffer_cursor) = *((TYPENAME *)force); \
/* replace pointer with pointer to force_buffer */ \
((__IEC_##TYPENAME##_p *)varp)->value = \
(TYPENAME *)force_buffer_cursor; \
/* mark variable as forced */ \
((__IEC_##TYPENAME##_p *)varp)->flags |= __IEC_FORCE_FLAG; \
/* inc force_buffer cursor */ \
force_buffer_cursor = next_cursor; \
/* outputs real value must be systematically forced */ \
if(vartype == TYPENAME##_O_ENUM) \
*(((__IEC_##TYPENAME##_p *)varp)->value) = *((TYPENAME *)force);\
} else { \
error_code = FORCE_BUFFER_OVERFLOW; \
goto error_cleanup; \
} \
} \
break;
void ResetDebugVariables(void);
int RegisterDebugVariable(dbgvardsc_index_t idx, void* force)
{
int error_code = 0;
if(idx < sizeof(dbgvardsc)/sizeof(dbgvardsc_t)){
/* add to trace_list, inc trace_list_addvar_cursor*/
if(trace_list_addvar_cursor <= trace_list_end){
trace_list_addvar_cursor->dbgvardsc_index = idx;
trace_list_addvar_cursor++;
} else {
error_code = TRACE_LIST_OVERFLOW;
goto error_cleanup;
}
if(force){
if(force_list_addvar_cursor <= force_list_end){
dbgvardsc_t *dsc = &dbgvardsc[idx];
void *varp = dsc->ptr;
__IEC_types_enum vartype = dsc->type;
switch(vartype){
__ANY(__ForceVariable_case_t)
__ANY(__ForceVariable_case_p)
default:
break;
}
/* inc force_list cursor */
force_list_addvar_cursor++;
} else {
error_code = FORCE_LIST_OVERFLOW;
goto error_cleanup;
}
}
}
return 0;
error_cleanup:
ResetDebugVariables();
trace_buffer_state = BUFFER_EMPTY;
return error_code;
}
#define ResetForcedVariable_case_t(TYPENAME) \
case TYPENAME##_ENUM : \
((__IEC_##TYPENAME##_t *)varp)->flags &= ~__IEC_FORCE_FLAG; \
/* for local variable we don't restore original value */ \
/* that can be added if needed, but it was like that since ever */ \
break;
#define ResetForcedVariable_case_p(TYPENAME) \
case TYPENAME##_P_ENUM : \
case TYPENAME##_O_ENUM : \
((__IEC_##TYPENAME##_p *)varp)->flags &= ~__IEC_FORCE_FLAG; \
/* restore backup to pointer */ \
((__IEC_##TYPENAME##_p *)varp)->value = \
force_list_apply_cursor->value_pointer_backup; \
break;
void ResetDebugVariables(void)
{
/* Reset trace list */
trace_list_addvar_cursor = trace_list;
force_list_apply_cursor = force_list;
/* Restore forced variables */
while(force_list_apply_cursor < force_list_addvar_cursor){
dbgvardsc_t *dsc = &dbgvardsc[
force_list_apply_cursor->dbgvardsc_index];
void *varp = dsc->ptr;
switch(dsc->type){
__ANY(ResetForcedVariable_case_t)
__ANY(ResetForcedVariable_case_p)
default:
break;
}
/* inc force_list cursor */
force_list_apply_cursor++;
} /* else TODO: warn user about failure to force */
/* Reset force list */
force_list_addvar_cursor = force_list;
/* Reset force buffer */
force_buffer_cursor = force_buffer;
}
void FreeDebugData(void)
{
/* atomically mark buffer as free */
AtomicCompareExchange(
&trace_buffer_state,
BUFFER_FULL,
BUFFER_EMPTY);
}
int WaitDebugData(unsigned long *tick);
/* Wait until debug data ready and return pointer to it */
int GetDebugData(unsigned long *tick, unsigned long *size, void **buffer){
int wait_error = WaitDebugData(tick);
if(!wait_error){
*size = trace_buffer_cursor - trace_buffer;
*buffer = trace_buffer;
}
return wait_error;
}
#endif
#endif
/**
* Head of code common to all C targets
**/
#include "beremiz.h"
#include <string.h>
/*
* Prototypes of functions provided by generated C softPLC
**/
void config_run__(unsigned long tick);
void config_init__(void);
/*
* Prototypes of functions provided by generated target C code
* */
long long AtomicCompareExchange64(long long*, long long, long long);
void __init_debug(void);
void __cleanup_debug(void);
/*void __retrieve_debug(void);*/
void __publish_debug(void);
/*
* Variables used by generated C softPLC and plugins
**/
IEC_TIME __CURRENT_TIME;
IEC_BOOL __DEBUG = 0;
unsigned long __tick = 0;
char *PLC_ID = NULL;
/*
* Variable generated by C softPLC and plugins
**/
extern unsigned long greatest_tick_count__;
/* Help to quit cleanly when init fail at a certain level */
static int init_level = 0;
/*
* Prototypes of functions exported by plugins
**/
int __init_py_ext(int argc,char **argv);
void __cleanup_py_ext(void);
void __retrieve_py_ext(void);
void __publish_py_ext(void);
int __init_svghmi(int argc,char **argv);
void __cleanup_svghmi(void);
void __retrieve_svghmi(void);
void __publish_svghmi(void);
int __init_1(int argc,char **argv);
void __cleanup_1(void);
void __retrieve_1(void);
void __publish_1(void);
/*
* Retrieve input variables, run PLC and publish output variables
**/
void __run(void)
{
__tick++;
if (greatest_tick_count__)
__tick %= greatest_tick_count__;
__retrieve_py_ext();
__retrieve_svghmi();
__retrieve_1();
/*__retrieve_debug();*/
config_run__(__tick);
__publish_debug();
__publish_1();
__publish_svghmi();
__publish_py_ext();
}
/*
* Initialize variables according to PLC's default values,
* and then init plugins with that values
**/
int __init(int argc,char **argv)
{
int res = 0;
init_level = 0;
/* Effective tick time with 1ms default value */
if(!common_ticktime__)
common_ticktime__ = 1000000;
config_init__();
__init_debug();
init_level=1; if((res = __init_py_ext(argc,argv))){return res;}
init_level=2; if((res = __init_svghmi(argc,argv))){return res;}
init_level=3; if((res = __init_1(argc,argv))){return res;}
return res;
}
/*
* Calls plugin cleanup proc.
**/
void __cleanup(void)
{
if(init_level >= 3) __cleanup_1();
if(init_level >= 2) __cleanup_svghmi();
if(init_level >= 1) __cleanup_py_ext();
__cleanup_debug();
}
void PLC_GetTime(IEC_TIME *CURRENT_TIME);
void PLC_SetTimer(unsigned long long next, unsigned long long period);
/**
* Linux specific code
**/
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <signal.h>
#include <stdlib.h>
#include <pthread.h>
#include <locale.h>
#include <semaphore.h>
static sem_t Run_PLC;
long AtomicCompareExchange(long* atomicvar,long compared, long exchange)
{
return __sync_val_compare_and_swap(atomicvar, compared, exchange);
}
long long AtomicCompareExchange64(long long* atomicvar, long long compared, long long exchange)
{
return __sync_val_compare_and_swap(atomicvar, compared, exchange);
}
void PLC_GetTime(IEC_TIME *CURRENT_TIME)
{
struct timespec tmp;
clock_gettime(CLOCK_REALTIME, &tmp);
CURRENT_TIME->tv_sec = tmp.tv_sec;
CURRENT_TIME->tv_nsec = tmp.tv_nsec;
}
void PLC_timer_notify(sigval_t val)
{
PLC_GetTime(&__CURRENT_TIME);
sem_post(&Run_PLC);
}
timer_t PLC_timer;
void PLC_SetTimer(unsigned long long next, unsigned long long period)
{
struct itimerspec timerValues;
/*
printf("SetTimer(%lld,%lld)\n",next, period);
*/
memset (&timerValues, 0, sizeof (struct itimerspec));
{
#ifdef __lldiv_t_defined
lldiv_t nxt_div = lldiv(next, 1000000000);
lldiv_t period_div = lldiv(period, 1000000000);
timerValues.it_value.tv_sec = nxt_div.quot;
timerValues.it_value.tv_nsec = nxt_div.rem;
timerValues.it_interval.tv_sec = period_div.quot;
timerValues.it_interval.tv_nsec = period_div.rem;
#else
timerValues.it_value.tv_sec = next / 1000000000;
timerValues.it_value.tv_nsec = next % 1000000000;
timerValues.it_interval.tv_sec = period / 1000000000;
timerValues.it_interval.tv_nsec = period % 1000000000;
#endif
}
timer_settime (PLC_timer, 0, &timerValues, NULL);
}
//
void catch_signal(int sig)
{
// signal(SIGTERM, catch_signal);
signal(SIGINT, catch_signal);
printf("Got Signal %d\n",sig);
exit(0);
}
static unsigned long __debug_tick;
pthread_t PLC_thread;
static pthread_mutex_t python_wait_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t python_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t debug_wait_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t debug_mutex = PTHREAD_MUTEX_INITIALIZER;
int PLC_shutdown = 0;
int ForceSaveRetainReq(void) {
return PLC_shutdown;
}
void PLC_thread_proc(void *arg)
{
while (!PLC_shutdown) {
sem_wait(&Run_PLC);
__run();
}
pthread_exit(0);
}
#define maxval(a,b) ((a>b)?a:b)
int startPLC(int argc,char **argv)
{
struct sigevent sigev;
setlocale(LC_NUMERIC, "C");
PLC_shutdown = 0;
sem_init(&Run_PLC, 0, 0);
pthread_create(&PLC_thread, NULL, (void*) &PLC_thread_proc, NULL);
memset (&sigev, 0, sizeof (struct sigevent));
sigev.sigev_value.sival_int = 0;
sigev.sigev_notify = SIGEV_THREAD;
sigev.sigev_notify_attributes = NULL;
sigev.sigev_notify_function = PLC_timer_notify;
pthread_mutex_init(&debug_wait_mutex, NULL);
pthread_mutex_init(&debug_mutex, NULL);
pthread_mutex_init(&python_wait_mutex, NULL);
pthread_mutex_init(&python_mutex, NULL);
pthread_mutex_lock(&debug_wait_mutex);
pthread_mutex_lock(&python_wait_mutex);
timer_create (CLOCK_MONOTONIC, &sigev, &PLC_timer);
if( __init(argc,argv) == 0 ){
PLC_SetTimer(common_ticktime__,common_ticktime__);
/* install signal handler for manual break */
signal(SIGINT, catch_signal);
}else{
return 1;
}
return 0;
}
int TryEnterDebugSection(void)
{
if (pthread_mutex_trylock(&debug_mutex) == 0){
/* Only enter if debug active */
if(__DEBUG){
return 1;
}
pthread_mutex_unlock(&debug_mutex);
}
return 0;
}
void LeaveDebugSection(void)
{
pthread_mutex_unlock(&debug_mutex);
}
int stopPLC()
{
/* Stop the PLC */
PLC_shutdown = 1;
sem_post(&Run_PLC);
PLC_SetTimer(0,0);
pthread_join(PLC_thread, NULL);
sem_destroy(&Run_PLC);
timer_delete (PLC_timer);
__cleanup();
pthread_mutex_destroy(&debug_wait_mutex);
pthread_mutex_destroy(&debug_mutex);
pthread_mutex_destroy(&python_wait_mutex);
pthread_mutex_destroy(&python_mutex);
return 0;
}
extern unsigned long __tick;
int WaitDebugData(unsigned long *tick)
{
int res;
if (PLC_shutdown) return 1;
/* Wait signal from PLC thread */
res = pthread_mutex_lock(&debug_wait_mutex);
*tick = __debug_tick;
return res;
}
/* Called by PLC thread when debug_publish finished
* This is supposed to unlock debugger thread in WaitDebugData*/
void InitiateDebugTransfer()
{
/* remember tick */
__debug_tick = __tick;
/* signal debugger thread it can read data */
pthread_mutex_unlock(&debug_wait_mutex);
}
int suspendDebug(int disable)
{
/* Prevent PLC to enter debug code */
pthread_mutex_lock(&debug_mutex);
/*__DEBUG is protected by this mutex */
__DEBUG = !disable;
if (disable)
pthread_mutex_unlock(&debug_mutex);
return 0;
}
void resumeDebug(void)
{
__DEBUG = 1;
/* Let PLC enter debug code */
pthread_mutex_unlock(&debug_mutex);
}
/* from plc_python.c */
int WaitPythonCommands(void)
{
/* Wait signal from PLC thread */
return pthread_mutex_lock(&python_wait_mutex);
}
/* Called by PLC thread on each new python command*/
void UnBlockPythonCommands(void)
{
/* signal python thread it can read data */
pthread_mutex_unlock(&python_wait_mutex);
}
int TryLockPython(void)
{
return pthread_mutex_trylock(&python_mutex) == 0;
}
void UnLockPython(void)
{
pthread_mutex_unlock(&python_mutex);
}
void LockPython(void)
{
pthread_mutex_lock(&python_mutex);
}
struct RT_to_nRT_signal_s {
pthread_cond_t WakeCond;
pthread_mutex_t WakeCondLock;
};
typedef struct RT_to_nRT_signal_s RT_to_nRT_signal_t;
#define _LogAndReturnNull(text) \
{\
char mstr[256] = text " for ";\
strncat(mstr, name, 255);\
LogMessage(LOG_CRITICAL, mstr, strlen(mstr));\
return NULL;\
}
void *create_RT_to_nRT_signal(char* name){
RT_to_nRT_signal_t *sig = (RT_to_nRT_signal_t*)malloc(sizeof(RT_to_nRT_signal_t));
if(!sig)
_LogAndReturnNull("Failed allocating memory for RT_to_nRT signal");
pthread_cond_init(&sig->WakeCond, NULL);
pthread_mutex_init(&sig->WakeCondLock, NULL);
return (void*)sig;
}
void delete_RT_to_nRT_signal(void* handle){
RT_to_nRT_signal_t *sig = (RT_to_nRT_signal_t*)handle;
pthread_cond_destroy(&sig->WakeCond);
pthread_mutex_destroy(&sig->WakeCondLock);
free(sig);
}
int wait_RT_to_nRT_signal(void* handle){
int ret;
RT_to_nRT_signal_t *sig = (RT_to_nRT_signal_t*)handle;
pthread_mutex_lock(&sig->WakeCondLock);
ret = pthread_cond_wait(&sig->WakeCond, &sig->WakeCondLock);
pthread_mutex_unlock(&sig->WakeCondLock);
return ret;
}
int unblock_RT_to_nRT_signal(void* handle){
RT_to_nRT_signal_t *sig = (RT_to_nRT_signal_t*)handle;
return pthread_cond_signal(&sig->WakeCond);
}
void nRT_reschedule(void){
sched_yield();
}
/*
This file is part of Beremiz, a Integrated Development Environment for
programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
See COPYING.runtime
Copyright (C) 2018: Sergey Surkov <surkov.sv@summatechnology.ru>
Copyright (C) 2018: Andrey Skvortsov <andrej.skvortzov@gmail.com>
*/
#ifndef HAVE_RETAIN
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include "iec_types.h"
int GetRetainSize(void);
/* Retain buffer. */
FILE *retain_buffer;
const char rb_file[] = "retain_buffer_file";
const char rb_file_bckp[] = "retain_buffer_file.bak";
/* Retain header struct. */
struct retain_info_t {
uint32_t retain_size;
uint32_t hash_size;
uint8_t* hash;
uint32_t header_offset;
uint32_t header_crc;
};
/* Init retain info structure. */
struct retain_info_t retain_info;
/* CRC lookup table and initial state. */
static const uint32_t crc32_table[256] = {
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,
0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,
0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,
0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,
0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,
0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,
0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21,
0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,
0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D,
};
uint32_t retain_crc;
/* Calculate CRC32 for len bytes from pointer buf with init starting value. */
uint32_t GenerateCRC32Sum(const void* buf, unsigned int len, uint32_t init)
{
uint32_t crc = ~init;
unsigned char* current = (unsigned char*) buf;
while (len--)
crc = crc32_table[(crc ^ *current++) & 0xFF] ^ (crc >> 8);
return ~crc;
}
/* Calc CRC32 for retain file byte by byte. */
int CheckFileCRC(FILE* file_buffer)
{
/* Set the magic constant for one-pass CRC calc according to ZIP CRC32. */
const uint32_t magic_number = 0x2144df1c;
/* CRC initial state. */
uint32_t calc_crc32 = 0;
char data_block = 0;
while(!feof(file_buffer)){
if (fread(&data_block, sizeof(data_block), 1, file_buffer))
calc_crc32 = GenerateCRC32Sum(&data_block, sizeof(char), calc_crc32);
}
/* Compare crc result with a magic number. */
return (calc_crc32 == magic_number) ? 1 : 0;
}
/* Compare current hash with hash from file byte by byte. */
int CheckFilehash(void)
{
int k,ret;
int offset = sizeof(retain_info.retain_size);
rewind(retain_buffer);
fseek(retain_buffer, offset , SEEK_SET);
uint32_t size;
ret = fread(&size, sizeof(size), 1, retain_buffer);
if (size != retain_info.hash_size)
return 0;
for(k = 0; k < retain_info.hash_size; k++){
uint8_t file_digit;
ret = fread(&file_digit, sizeof(char), 1, retain_buffer);
if (file_digit != *(retain_info.hash+k))
return 0;
}
return 1;
}
void InitRetain(void)
{
int i;
/* Get retain size in bytes */
retain_info.retain_size = GetRetainSize();
/* Hash stored in retain file as array of char in hex digits
(that's why we divide strlen in two). */
retain_info.hash_size = PLC_ID ? strlen(PLC_ID)/2 : 0;
//retain_info.hash_size = 0;
retain_info.hash = malloc(retain_info.hash_size);
/* Transform hash string into byte sequence. */
for (i = 0; i < retain_info.hash_size; i++) {
int byte = 0;
sscanf((PLC_ID + i*2), "%02X", &byte);
retain_info.hash[i] = byte;
}
/* Calc header offset. */
retain_info.header_offset = sizeof(retain_info.retain_size) + \
sizeof(retain_info.hash_size) + \
retain_info.hash_size;
/* Set header CRC initial state. */
retain_info.header_crc = 0;
/* Calc crc for header. */
retain_info.header_crc = GenerateCRC32Sum(
&retain_info.retain_size,
sizeof(retain_info.retain_size),
retain_info.header_crc);
retain_info.header_crc = GenerateCRC32Sum(
&retain_info.hash_size,
sizeof(retain_info.hash_size),
retain_info.header_crc);
retain_info.header_crc = GenerateCRC32Sum(
retain_info.hash,
retain_info.hash_size,
retain_info.header_crc);
}
void CleanupRetain(void)
{
/* Free hash memory. */
free(retain_info.hash);
}
int CheckRetainFile(const char * file)
{
retain_buffer = fopen(file, "rb");
if (retain_buffer) {
/* Check CRC32 and hash. */
if (CheckFileCRC(retain_buffer))
if (CheckFilehash())
return 1;
fclose(retain_buffer);
retain_buffer = NULL;
}
return 0;
}
int CheckRetainBuffer(void)
{
retain_buffer = NULL;
if (!retain_info.retain_size)
return 1;
/* Check latest retain file. */
if (CheckRetainFile(rb_file))
return 1;
/* Check if we have backup. */
if (CheckRetainFile(rb_file_bckp))
return 1;
/* We don't have any valid retain buffer - nothing to remind. */
return 0;
}
#ifndef FILE_RETAIN_SAVE_PERIOD_S
#define FILE_RETAIN_SAVE_PERIOD_S 1.0
#endif
static double CalcDiffSeconds(IEC_TIME* t1, IEC_TIME *t2)
{
IEC_TIME dt ={
t1->tv_sec - t2->tv_sec,
t1->tv_nsec - t2->tv_nsec
};
if ((dt.tv_nsec < -1000000000) || ((dt.tv_sec > 0) && (dt.tv_nsec < 0))){
dt.tv_sec--;
dt.tv_nsec += 1000000000;
}
if ((dt.tv_nsec > +1000000000) || ((dt.tv_sec < 0) && (dt.tv_nsec > 0))){
dt.tv_sec++;
dt.tv_nsec -= 1000000000;
}
return dt.tv_sec + 1e-9*dt.tv_nsec;
}
int RetainSaveNeeded(void)
{
int ret = 0;
static IEC_TIME last_save;
IEC_TIME now;
double diff_s;
/* no retain */
if (!retain_info.retain_size)
return 0;
/* periodic retain flush to avoid high I/O load */
PLC_GetTime(&now);
diff_s = CalcDiffSeconds(&now, &last_save);
if ((diff_s > FILE_RETAIN_SAVE_PERIOD_S) || ForceSaveRetainReq()) {
ret = 1;
last_save = now;
}
return ret;
}
void ValidateRetainBuffer(void)
{
if (!retain_buffer)
return;
/* Add retain data CRC to the end of buffer file. */
fseek(retain_buffer, 0, SEEK_END);
fwrite(&retain_crc, sizeof(uint32_t), 1, retain_buffer);
/* Sync file buffer and close file. */
#ifdef __WIN32
fflush(retain_buffer);
#else
fsync(fileno(retain_buffer));
#endif
fclose(retain_buffer);
retain_buffer = NULL;
}
void InValidateRetainBuffer(void)
{
if (!RetainSaveNeeded())
return;
/* Rename old retain file into *.bak if it exists. */
rename(rb_file, rb_file_bckp);
/* Set file CRC initial value. */
retain_crc = retain_info.header_crc;
/* Create new retain file. */
retain_buffer = fopen(rb_file, "wb+");
if (!retain_buffer) {
fprintf(stderr, "Failed to create retain file : %s\n", rb_file);
return;
}
/* Write header to the new file. */
fwrite(&retain_info.retain_size,
sizeof(retain_info.retain_size), 1, retain_buffer);
fwrite(&retain_info.hash_size,
sizeof(retain_info.hash_size), 1, retain_buffer);
fwrite(retain_info.hash ,
sizeof(char), retain_info.hash_size, retain_buffer);
}
void Retain(unsigned int offset, unsigned int count, void *p)
{
if (!retain_buffer)
return;
/* Generate CRC 32 for each data block. */
retain_crc = GenerateCRC32Sum(p, count, retain_crc);
/* Save current var in file. */
fseek(retain_buffer, retain_info.header_offset+offset, SEEK_SET);
fwrite(p, count, 1, retain_buffer);
}
void Remind(unsigned int offset, unsigned int count, void *p)
{
int ret;
/* Remind variable from file. */
fseek(retain_buffer, retain_info.header_offset+offset, SEEK_SET);
ret = fread((void *)p, count, 1, retain_buffer);
}
#endif // !HAVE_RETAIN
/**
* Tail of code common to all C targets
**/
/**
* LOGGING
**/
#ifndef TARGET_LOGGING_DISABLE
#ifndef LOG_BUFFER_SIZE
#define LOG_BUFFER_SIZE (1<<14) /*16Ko*/
#endif
#ifndef LOG_BUFFER_ATTRS
#define LOG_BUFFER_ATTRS
#endif
#define LOG_BUFFER_MASK (LOG_BUFFER_SIZE-1)
static char LogBuff[LOG_LEVELS][LOG_BUFFER_SIZE] LOG_BUFFER_ATTRS;
static void inline copy_to_log(uint8_t level, uint32_t buffpos, void* buf, uint32_t size){
if(buffpos + size < LOG_BUFFER_SIZE){
memcpy(&LogBuff[level][buffpos], buf, size);
}else{
uint32_t remaining = LOG_BUFFER_SIZE - buffpos;
memcpy(&LogBuff[level][buffpos], buf, remaining);
memcpy(LogBuff[level], (char*)buf + remaining, size - remaining);
}
}
static void inline copy_from_log(uint8_t level, uint32_t buffpos, void* buf, uint32_t size){
if(buffpos + size < LOG_BUFFER_SIZE){
memcpy(buf, &LogBuff[level][buffpos], size);
}else{
uint32_t remaining = LOG_BUFFER_SIZE - buffpos;
memcpy(buf, &LogBuff[level][buffpos], remaining);
memcpy((char*)buf + remaining, LogBuff[level], size - remaining);
}
}
/* Log buffer structure
|<-Tail1.msgsize->|<-sizeof(mTail)->|<--Tail2.msgsize-->|<-sizeof(mTail)->|...
| Message1 Body | Tail1 | Message2 Body | Tail2 |
*/
typedef struct {
uint32_t msgidx;
uint32_t msgsize;
unsigned long tick;
IEC_TIME time;
} mTail;
/* Log cursor : 64b
|63 ... 32|31 ... 0|
| Message | Buffer |
| counter | Index | */
static uint64_t LogCursor[LOG_LEVELS] LOG_BUFFER_ATTRS = {0x0,0x0,0x0,0x0};
void ResetLogCount(void) {
uint8_t level;
for(level=0;level<LOG_LEVELS;level++){
LogCursor[level] = 0;
}
}
/* Store one log message of give size */
int LogMessage(uint8_t level, char* buf, uint32_t size){
if(size < LOG_BUFFER_SIZE - sizeof(mTail)){
uint32_t buffpos;
uint64_t new_cursor, old_cursor;
mTail tail;
tail.msgsize = size;
tail.tick = __tick;
PLC_GetTime(&tail.time);
/* We cannot increment both msg index and string pointer
in a single atomic operation but we can detect having been interrupted.
So we can try with atomic compare and swap in a loop until operation
succeeds non interrupted */
do{
old_cursor = LogCursor[level];
buffpos = (uint32_t)old_cursor;
tail.msgidx = (old_cursor >> 32);
new_cursor = ((uint64_t)(tail.msgidx + 1)<<32)
| (uint64_t)((buffpos + size + sizeof(mTail)) & LOG_BUFFER_MASK);
}while(AtomicCompareExchange64(
(long long*)&LogCursor[level],
(long long)old_cursor,
(long long)new_cursor)!=(long long)old_cursor);
copy_to_log(level, buffpos, buf, size);
copy_to_log(level, (buffpos + size) & LOG_BUFFER_MASK, &tail, sizeof(mTail));
return 1; /* Success */
}else{
char mstr[] = "Logging error : message too big";
LogMessage(LOG_CRITICAL, mstr, sizeof(mstr));
}
return 0;
}
uint32_t GetLogCount(uint8_t level){
return (uint64_t)LogCursor[level] >> 32;
}
/* Return message size and content */
uint32_t GetLogMessage(uint8_t level, uint32_t msgidx, char* buf, uint32_t max_size, uint32_t* tick, uint32_t* tv_sec, uint32_t* tv_nsec){
uint64_t cursor = LogCursor[level];
if(cursor){
/* seach cursor */
uint32_t stailpos = (uint32_t)cursor;
uint32_t smsgidx;
mTail tail;
tail.msgidx = cursor >> 32;
tail.msgsize = 0;
/* Message search loop */
do {
smsgidx = tail.msgidx;
stailpos = (stailpos - sizeof(mTail) - tail.msgsize ) & LOG_BUFFER_MASK;
copy_from_log(level, stailpos, &tail, sizeof(mTail));
}while((tail.msgidx == smsgidx - 1) && (tail.msgidx > msgidx));
if(tail.msgidx == msgidx){
uint32_t sbuffpos = (stailpos - tail.msgsize ) & LOG_BUFFER_MASK;
uint32_t totalsize = tail.msgsize;
*tick = tail.tick;
*tv_sec = tail.time.tv_sec;
*tv_nsec = tail.time.tv_nsec;
copy_from_log(level, sbuffpos, buf,
totalsize > max_size ? max_size : totalsize);
return totalsize;
}
}
return 0;
}
#endif
#ifndef TARGET_EXT_SYNC_DISABLE
#define CALIBRATED -2
#define NOT_CALIBRATED -1
static int calibration_count = NOT_CALIBRATED;
static IEC_TIME cal_begin;
static long long Tsync = 0;
static long long FreqCorr = 0;
static int Nticks = 0;
static unsigned long last_tick = 0;
/*
* Called on each external periodic sync event
* make PLC tick synchronous with external sync
* ratio defines when PLC tick occurs between two external sync
* @param sync_align_ratio
* 0->100 : align ratio
* < 0 : no align, calibrate period
**/
void align_tick(int sync_align_ratio)
{
/*
printf("align_tick(%d)\n", calibrate);
*/
if(sync_align_ratio < 0){ /* Calibration */
if(calibration_count == CALIBRATED)
/* Re-calibration*/
calibration_count = NOT_CALIBRATED;
if(calibration_count == NOT_CALIBRATED)
/* Calibration start, get time*/
PLC_GetTime(&cal_begin);
calibration_count++;
}else{ /* do alignment (if possible) */
if(calibration_count >= 0){
/* End of calibration */
/* Get final time */
IEC_TIME cal_end;
PLC_GetTime(&cal_end);
/*adjust calibration_count*/
calibration_count++;
/* compute mean of Tsync, over calibration period */
Tsync = ((long long)(cal_end.tv_sec - cal_begin.tv_sec) * (long long)1000000000 +
(cal_end.tv_nsec - cal_begin.tv_nsec)) / calibration_count;
if( (Nticks = (Tsync / common_ticktime__)) > 0){
FreqCorr = (Tsync % common_ticktime__); /* to be divided by Nticks */
}else{
FreqCorr = Tsync - (common_ticktime__ % Tsync);
}
/*
printf("Tsync = %ld\n", Tsync);
printf("calibration_count = %d\n", calibration_count);
printf("Nticks = %d\n", Nticks);
*/
calibration_count = CALIBRATED;
}
if(calibration_count == CALIBRATED){
/* Get Elapsed time since last PLC tick (__CURRENT_TIME) */
IEC_TIME now;
long long elapsed;
long long Tcorr;
long long PhaseCorr;
long long PeriodicTcorr;
PLC_GetTime(&now);
elapsed = (now.tv_sec - __CURRENT_TIME.tv_sec) * 1000000000 + now.tv_nsec - __CURRENT_TIME.tv_nsec;
if(Nticks > 0){
PhaseCorr = elapsed - (common_ticktime__ + FreqCorr/Nticks)*sync_align_ratio/100; /* to be divided by Nticks */
Tcorr = common_ticktime__ + (PhaseCorr + FreqCorr) / Nticks;
if(Nticks < 2){
/* When Sync source period is near Tick time */
/* PhaseCorr may not be applied to Periodic time given to timer */
PeriodicTcorr = common_ticktime__ + FreqCorr / Nticks;
}else{
PeriodicTcorr = Tcorr;
}
}else if(__tick > last_tick){
last_tick = __tick;
PhaseCorr = elapsed - (Tsync*sync_align_ratio/100);
PeriodicTcorr = Tcorr = common_ticktime__ + PhaseCorr + FreqCorr;
}else{
/*PLC did not run meanwhile. Nothing to do*/
return;
}
/* DO ALIGNEMENT */
PLC_SetTimer(Tcorr - elapsed, PeriodicTcorr);
}
}
}
#endif
/*
* 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[1];
/* 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 < 1; 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__)
{
if(!__GET_VAR(data__->TRIG)){
/* ACK is False when TRIG is false, except a pulse when receiving result */
__SET_VAR(data__->, ACK,, 0);
}
/* 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 presence 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) % 1;
}
}
}
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) % 1;
//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;
}
/*******************************************/
/* FILE GENERATED BY iec2c */
/* Editing this file is not recommended... */
/*******************************************/
#include "iec_std_lib.h"
// RESOURCE RESOURCE1
extern unsigned long long common_ticktime__;
#include "accessor.h"
#include "POUS.h"
#include "config.h"
#include "POUS.c"
BOOL TASK0;
PLC_PRG RESOURCE1__INSTANCE0;
#define INSTANCE0 RESOURCE1__INSTANCE0
void RESOURCE1_init__(void) {
BOOL retain;
retain = 0;
TASK0 = __BOOL_LITERAL(FALSE);
PLC_PRG_init__(&INSTANCE0,retain);
}
void RESOURCE1_run__(unsigned long tick) {
TASK0 = !(tick % 1);
if (TASK0) {
PLC_PRG_body__(&INSTANCE0);
}
}
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# This file is part of Beremiz
# Copyright (C) 2019: Edouard TISSERANT
# See COPYING file for copyrights details.
from __future__ import absolute_import
import errno
from threading import RLock, Timer
try:
from runtime.spawn_subprocess import Popen
except ImportError:
from subprocess import Popen
from twisted.web.server import Site
from twisted.web.resource import Resource
from twisted.internet import reactor
from twisted.web.static import File
from autobahn.twisted.websocket import WebSocketServerFactory, WebSocketServerProtocol
from autobahn.websocket.protocol import WebSocketProtocol
from autobahn.twisted.resource import WebSocketResource
max_svghmi_sessions = None
svghmi_watchdog = None
svghmi_wait = PLCBinary.svghmi_wait
svghmi_wait.restype = ctypes.c_int # error or 0
svghmi_wait.argtypes = []
svghmi_continue_collect = ctypes.c_int.in_dll(PLCBinary, "svghmi_continue_collect")
svghmi_send_collect = PLCBinary.svghmi_send_collect
svghmi_send_collect.restype = ctypes.c_int # error or 0
svghmi_send_collect.argtypes = [
ctypes.c_uint32, # index
ctypes.POINTER(ctypes.c_uint32), # size
ctypes.POINTER(ctypes.c_void_p)] # data ptr
svghmi_reset = PLCBinary.svghmi_reset
svghmi_reset.restype = ctypes.c_int # error or 0
svghmi_reset.argtypes = [
ctypes.c_uint32] # index
svghmi_recv_dispatch = PLCBinary.svghmi_recv_dispatch
svghmi_recv_dispatch.restype = ctypes.c_int # error or 0
svghmi_recv_dispatch.argtypes = [
ctypes.c_uint32, # index
ctypes.c_uint32, # size
ctypes.c_char_p] # data ptr
class HMISessionMgr(object):
def __init__(self):
self.multiclient_sessions = set()
self.watchdog_session = None
self.session_count = 0
self.lock = RLock()
self.indexes = set()
def next_index(self):
if self.indexes:
greatest = max(self.indexes)
holes = set(range(greatest)) - self.indexes
index = min(holes) if holes else greatest+1
else:
index = 0
self.indexes.add(index)
return index
def free_index(self, index):
self.indexes.remove(index)
def register(self, session):
global max_svghmi_sessions
with self.lock:
if session.is_watchdog_session:
# Creating a new watchdog session closes pre-existing one
if self.watchdog_session is not None:
self.unregister(self.watchdog_session)
else:
assert(self.session_count < max_svghmi_sessions)
self.session_count += 1
self.watchdog_session = session
else:
assert(self.session_count < max_svghmi_sessions)
self.multiclient_sessions.add(session)
self.session_count += 1
session.session_index = self.next_index()
def unregister(self, session):
with self.lock:
if session.is_watchdog_session :
if self.watchdog_session != session:
return
self.watchdog_session = None
else:
try:
self.multiclient_sessions.remove(session)
except KeyError:
return
self.free_index(session.session_index)
self.session_count -= 1
session.kill()
def close_all(self):
for session in self.iter_sessions():
self.unregister(session)
def iter_sessions(self):
with self.lock:
lst = list(self.multiclient_sessions)
if self.watchdog_session is not None:
lst = [self.watchdog_session]+lst
for nxt_session in lst:
yield nxt_session
svghmi_session_manager = HMISessionMgr()
class HMISession(object):
def __init__(self, protocol_instance):
self.protocol_instance = protocol_instance
self._session_index = None
self.closed = False
@property
def is_watchdog_session(self):
return self.protocol_instance.has_watchdog
@property
def session_index(self):
return self._session_index
@session_index.setter
def session_index(self, value):
self._session_index = value
def reset(self):
return svghmi_reset(self.session_index)
def close(self):
if self.closed: return
self.protocol_instance.sendClose(WebSocketProtocol.CLOSE_STATUS_CODE_NORMAL)
def notify_closed(self):
self.closed = True
def kill(self):
self.close()
self.reset()
def onMessage(self, msg):
# pass message to the C side recieve_message()
if self.closed: return
return svghmi_recv_dispatch(self.session_index, len(msg), msg)
def sendMessage(self, msg):
if self.closed: return
self.protocol_instance.sendMessage(msg, True)
return 0
class Watchdog(object):
def __init__(self, initial_timeout, interval, callback):
self._callback = callback
self.lock = RLock()
self.initial_timeout = initial_timeout
self.interval = interval
self.callback = callback
with self.lock:
self._start()
def _start(self, rearm=False):
duration = self.interval if rearm else self.initial_timeout
if duration:
self.timer = Timer(duration, self.trigger)
self.timer.start()
else:
self.timer = None
def _stop(self):
if self.timer is not None:
self.timer.cancel()
self.timer = None
def cancel(self):
with self.lock:
self._stop()
def feed(self, rearm=True):
with self.lock:
self._stop()
self._start(rearm)
def trigger(self):
self._callback()
# Don't repeat trigger periodically
# # wait for initial timeout on re-start
# self.feed(rearm=False)
class HMIProtocol(WebSocketServerProtocol):
def __init__(self, *args, **kwargs):
self._hmi_session = None
self.has_watchdog = False
WebSocketServerProtocol.__init__(self, *args, **kwargs)
def onConnect(self, request):
self.has_watchdog = request.params.get("mode", [None])[0] == "watchdog"
return WebSocketServerProtocol.onConnect(self, request)
def onOpen(self):
global svghmi_session_manager
assert(self._hmi_session is None)
_hmi_session = HMISession(self)
registered = svghmi_session_manager.register(_hmi_session)
self._hmi_session = _hmi_session
def onClose(self, wasClean, code, reason):
global svghmi_session_manager
if self._hmi_session is None : return
self._hmi_session.notify_closed()
svghmi_session_manager.unregister(self._hmi_session)
self._hmi_session = None
def onMessage(self, msg, isBinary):
global svghmi_watchdog
if self._hmi_session is None : return
result = self._hmi_session.onMessage(msg)
if result == 1 and self.has_watchdog: # was heartbeat
if svghmi_watchdog is not None:
svghmi_watchdog.feed()
class HMIWebSocketServerFactory(WebSocketServerFactory):
protocol = HMIProtocol
svghmi_servers = {}
svghmi_send_thread = None
# python's errno on windows seems to have no ENODATA
ENODATA = errno.ENODATA if hasattr(errno,"ENODATA") else None
def SendThreadProc():
global svghmi_session_manager
size = ctypes.c_uint32()
ptr = ctypes.c_void_p()
res = 0
while svghmi_continue_collect:
svghmi_wait()
for svghmi_session in svghmi_session_manager.iter_sessions():
res = svghmi_send_collect(
svghmi_session.session_index,
ctypes.byref(size), ctypes.byref(ptr))
if res == 0:
svghmi_session.sendMessage(
ctypes.string_at(ptr.value,size.value))
elif res == ENODATA:
# this happens when there is no data after wakeup
# because of hmi data refresh period longer than
# PLC common ticktime
pass
else:
# this happens when finishing
break
def AddPathToSVGHMIServers(path, factory, *args, **kwargs):
for k,v in svghmi_servers.iteritems():
svghmi_root, svghmi_listener, path_list = v
svghmi_root.putChild(path, factory(*args, **kwargs))
# Called by PLCObject at start
def _runtime_00_svghmi_start():
global svghmi_send_thread
# start a thread that call the C part of SVGHMI
svghmi_send_thread = Thread(target=SendThreadProc, name="SVGHMI Send")
svghmi_send_thread.start()
# Called by PLCObject at stop
def _runtime_00_svghmi_stop():
global svghmi_send_thread, svghmi_session
svghmi_session_manager.close_all()
# plc cleanup calls svghmi_(locstring)_cleanup and unlocks send thread
svghmi_send_thread.join()
svghmi_send_thread = None
class NoCacheFile(File):
def render_GET(self, request):
request.setHeader(b"Cache-Control", b"no-cache, no-store")
return File.render_GET(self, request)
render_HEAD = render_GET
# TODO : multiple watchdog (one for each svghmi instance)
def svghmi_0_watchdog_trigger():
Popen(['echo', 'Watchdog', 'for', 'svghmi_0', '!'])
max_svghmi_sessions = 16
def _runtime_0_svghmi_start():
global svghmi_watchdog, svghmi_servers
srv = svghmi_servers.get("localhost:8008", None)
if srv is not None:
svghmi_root, svghmi_listener, path_list = srv
if 'svghmi_0' in path_list:
raise Exception("SVGHMI svghmi_0: path svghmi_0 already used on localhost:8008")
else:
svghmi_root = Resource()
factory = HMIWebSocketServerFactory()
factory.setProtocolOptions(maxConnections=16)
svghmi_root.putChild("ws", WebSocketResource(factory))
svghmi_listener = reactor.listenTCP(8008, Site(svghmi_root), interface='localhost')
path_list = []
svghmi_servers["localhost:8008"] = (svghmi_root, svghmi_listener, path_list)
svghmi_root.putChild(
'svghmi_0',
NoCacheFile('svghmi_0.xhtml',
defaultType='application/xhtml+xml'))
path_list.append("svghmi_0")
Popen(['chromium', 'http://localhost:8008/svghmi_0#watchdog'])
if True:
if svghmi_watchdog is None:
svghmi_watchdog = Watchdog(
30,
5,
svghmi_0_watchdog_trigger)
else:
raise Exception("SVGHMI svghmi_0: only one watchdog allowed")
def _runtime_0_svghmi_stop():
global svghmi_watchdog, svghmi_servers
if svghmi_watchdog is not None:
svghmi_watchdog.cancel()
svghmi_watchdog = None
svghmi_root, svghmi_listener, path_list = svghmi_servers["localhost:8008"]
svghmi_root.delEntity('svghmi_0')
path_list.remove('svghmi_0')
if len(path_list)==0:
svghmi_root.delEntity("ws")
svghmi_listener.stopListening()
svghmi_servers.pop("localhost:8008")
pass # no command given
\ No newline at end of file
#include <pthread.h>
#include <errno.h>
#include "iec_types_all.h"
#include "POUS.h"
#include "config.h"
#include "beremiz.h"
#define DEFAULT_REFRESH_PERIOD_MS 100
#define HMI_BUFFER_SIZE 138
#define HMI_ITEM_COUNT 7
#define HMI_HASH_SIZE 8
#define MAX_CONNECTIONS 16
#define MAX_CON_INDEX MAX_CONNECTIONS - 1
static uint8_t hmi_hash[HMI_HASH_SIZE] = {129,53,34,212,104,24,210,166};
/* 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;
#define heartbeat_index 5
extern DINT CONFIG__RELAY0;
extern DINT CONFIG__RELAY1;
extern DINT CONFIG__RELAY2;
extern DINT CONFIG__RELAY3;
extern INT CONFIG__HMI_RELAY0;
extern INT CONFIG__HMI_RELAY1;
extern INT CONFIG__HMI_RELAY2;
extern INT CONFIG__HMI_RELAY3;
extern BOOL CONFIG__HMI_ROOT;
extern INT CONFIG__HEARTBEAT;
extern STRING CONFIG__CURRENT_PAGE_0;
extern PLC_PRG RESOURCE1__INSTANCE0;
#define ticktime_ns 20000000
static uint16_t ticktime_ms = (ticktime_ns>1000000)?
ticktime_ns/1000000:
1;
typedef enum {
buf_free = 0,
buf_new,
buf_set,
buf_tosend
} buf_state_t;
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;
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];
/* 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) { \
&(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[] = {
HMITREE_ITEM_INITIALIZER(CONFIG__HMI_ROOT, BOOL_ENUM, 0),
HMITREE_ITEM_INITIALIZER(CONFIG__HMI_RELAY0, INT_ENUM, 1),
HMITREE_ITEM_INITIALIZER(CONFIG__HMI_RELAY1, INT_ENUM, 3),
HMITREE_ITEM_INITIALIZER(CONFIG__HMI_RELAY2, INT_ENUM, 5),
HMITREE_ITEM_INITIALIZER(CONFIG__HMI_RELAY3, INT_ENUM, 7),
HMITREE_ITEM_INITIALIZER(CONFIG__HEARTBEAT, INT_ENUM, 9),
HMITREE_ITEM_INITIALIZER(CONFIG__CURRENT_PAGE_0, STRING_ENUM, 11)
};
#define __Unpack_desc_type hmi_tree_item_t
#define __Unpack_case_t(TYPENAME) \
case TYPENAME##_ENUM : \
if(flags) *flags = ((__IEC_##TYPENAME##_t *)varp)->flags; \
if(value_p) *value_p = &((__IEC_##TYPENAME##_t *)varp)->value; \
if(size) *size = sizeof(TYPENAME); \
break;
#define __Unpack_case_p(TYPENAME) \
case TYPENAME##_O_ENUM : \
case TYPENAME##_P_ENUM : \
if(flags) *flags = ((__IEC_##TYPENAME##_p *)varp)->flags; \
if(value_p) *value_p = ((__IEC_##TYPENAME##_p *)varp)->value; \
if(size) *size = sizeof(TYPENAME); \
break;
#define __Is_a_string(dsc) (dsc->type == STRING_ENUM) ||\
(dsc->type == STRING_P_ENUM) ||\
(dsc->type == STRING_O_ENUM)
static int UnpackVar(__Unpack_desc_type *dsc, void **value_p, char *flags, size_t *size)
{
void *varp = dsc->ptr;
/* find data to copy*/
switch(dsc->type){
__ANY(__Unpack_case_t)
__ANY(__Unpack_case_p)
default:
return 0; /* should never happen */
}
return 1;
}
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;
}
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];
if(__Is_a_string(dsc)){
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;
}
else
{
printf("BUG!!! %d + %ld + %d > %ld \n", sbufidx, sizeof(uint32_t), sz, sizeof(sbuf));
return EOVERFLOW;
}
}
return 0;
}
static int read_iterator(hmi_tree_item_t *dsc)
{
if(dsc->rstate == buf_set)
{
void *src_p = &rbuf[dsc->buf_index];
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;
}
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;
}
} 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 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;
int __init_svghmi()
{
memset(rbuf,0,sizeof(rbuf));
memset(wbuf,0,sizeof(wbuf));
svghmi_continue_collect = 1;
/* create svghmi_pipe */
svghmi_handle = create_RT_to_nRT_signal("SVGHMI_pipe");
if(!svghmi_handle)
return 1;
return 0;
}
void __cleanup_svghmi()
{
svghmi_continue_collect = 0;
SVGHMI_WakeupFromRTThread();
delete_RT_to_nRT_signal(svghmi_handle);
}
void __retrieve_svghmi()
{
if(AtomicCompareExchange(&hmitree_rlock, 0, 1) == 0) {
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);
}
}
void __publish_svghmi()
{
global_write_dirty = 0;
if(AtomicCompareExchange(&hmitree_wlock, 0, 1) == 0) {
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();
}
}
/* PYTHON CALLS */
int svghmi_wait(void){
SVGHMI_SuspendFromPythonThread();
}
int svghmi_send_collect(uint32_t session_index, uint32_t *size, char **ptr){
if(svghmi_continue_collect) {
int res;
sbufidx = HMI_HASH_SIZE;
while(AtomicCompareExchange(&hmitree_wlock, 0, 1)){
nRT_reschedule();
}
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);
*ptr = &sbuf[0];
*size = sbufidx;
AtomicCompareExchange(&hmitree_wlock, 1, 0);
return 0;
}
AtomicCompareExchange(&hmitree_wlock, 1, 0);
return ENODATA;
}
AtomicCompareExchange(&hmitree_wlock, 1, 0);
return res;
}
else
{
return EINTR;
}
}
typedef enum {
unset = -1,
setval = 0,
reset = 1,
subscribe = 2
} cmd_from_JS;
int svghmi_reset(uint32_t session_index){
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;
}
// Returns :
// 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;
int was_hearbeat = 0;
/* match hmitree fingerprint */
if(size <= HMI_HASH_SIZE || memcmp(ptr, hmi_hash, HMI_HASH_SIZE) != 0)
{
printf("svghmi_recv_dispatch MISMATCH !!\n");
return -EINVAL;
}
int ret;
int got_wlock = 0;
int got_rlock = 0;
cmd_from_JS cmd_old = unset;
cmd_from_JS cmd = unset;
while(cursor < end)
{
uint32_t progress;
cmd_old = cmd;
cmd = *(cursor++);
if(cmd_old != cmd){
if(got_wlock){
AtomicCompareExchange(&hmitree_wlock, 1, 0);
got_wlock = 0;
}
if(got_rlock){
AtomicCompareExchange(&hmitree_rlock, 1, 0);
got_rlock = 0;
}
}
switch(cmd)
{
case setval:
{
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_items[index];
size_t sz = 0;
void *dst_p = &rbuf[dsc->buf_index];
if(__Is_a_string(dsc)){
sz = ((STRING*)valptr)->len + 1;
} else {
UnpackVar(dsc, NULL, NULL, &sz);
}
if((valptr + sz) <= end)
{
// rescheduling spinlock until free
if(!got_rlock){
while(AtomicCompareExchange(&hmitree_rlock, 0, 1)){
nRT_reschedule();
}
got_rlock=1;
}
memcpy(dst_p, valptr, sz);
/* 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 */;
}
else
{
ret = -EINVAL;
goto exit_free;
}
}
else
{
ret = -EINVAL;
goto exit_free;
}
}
break;
case reset:
{
progress = 0;
if(!got_wlock){
while(AtomicCompareExchange(&hmitree_wlock, 0, 1)){
nRT_reschedule();
}
got_wlock = 1;
}
{
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;
case subscribe:
{
uint32_t index = *(uint32_t*)(cursor);
uint16_t refresh_period_ms = *(uint32_t*)(cursor + sizeof(uint32_t));
if(index < HMI_ITEM_COUNT)
{
if(!got_wlock){
while(AtomicCompareExchange(&hmitree_wlock, 0, 1)){
nRT_reschedule();
}
got_wlock = 1;
}
hmi_tree_item_t *dsc = &hmi_tree_items[index];
update_refresh_period(dsc, session_index, refresh_period_ms);
}
else
{
ret = -EINVAL;
goto exit_free;
}
progress = sizeof(uint32_t) /* index */ +
sizeof(uint16_t) /* refresh period */;
}
break;
default:
printf("svghmi_recv_dispatch unknown %d\n",cmd);
}
cursor += progress;
}
ret = was_hearbeat;
exit_free:
if(got_wlock){
AtomicCompareExchange(&hmitree_wlock, 1, 0);
got_wlock = 0;
}
if(got_rlock){
AtomicCompareExchange(&hmitree_rlock, 1, 0);
got_rlock = 0;
}
return ret;
}
1e07dadd9175d71c49bda502ce3d71b8
\ No newline at end of file
<!--Made with SVGHMI. https://beremiz.org--><html xmlns="http://www.w3.org/1999/xhtml" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:xhtml="http://www.w3.org/1999/xhtml"><head><style type="text/css" media="screen"/></head><body style="margin:0;overflow:hidden;user-select:none;touch-action:none;"><!-- Created with Inkscape (http://www.inkscape.org/) --><svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="none" height="100vh" width="100vw" inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)" sodipodi:docname="svghmi.svg" id="hmi0" version="1.1" viewBox="0 0 1280 720">
<defs id="defs6"/>
<sodipodi:namedview pagecolor="#ffffff" bordercolor="#666666" borderopacity="1" objecttolerance="10" gridtolerance="10" guidetolerance="10" inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:window-width="1920" inkscape:window-height="1015" id="namedview4" showgrid="false" inkscape:zoom="1.418633" inkscape:cx="625.37152" inkscape:cy="419.21141" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1" inkscape:current-layer="hmi0" showguides="false"/>
<rect inkscape:label="HMI:Page:Home" y="-1.9937692" x="-7.9750781" height="720" width="1280" id="rect1016" style="color:#000000;opacity:1;fill:#d6d6d6;fill-opacity:1"/>
<text xml:space="preserve" style="font-style:normal;font-weight:normal;font-size:39.52233505px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.98805833" x="552.8136" y="109.08164" id="text35" transform="scale(0.98805833,1.012086)"><tspan sodipodi:role="line" id="tspan33" x="552.8136" y="109.08164" style="stroke-width:0.98805833">Relay 0</tspan></text>
<g id="g446" transform="matrix(0.2859027,0,0,0.2859027,709.48026,45.693658)" inkscape:label="HMI:Input@/HMI_RELAY0">
<text xml:space="preserve" style="font-style:normal;font-weight:normal;font-size:160px;line-height:125%;font-family:sans-serif;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#ff8c00;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" x="216.32812" y="218.24219" id="text432" inkscape:label="value"><tspan sodipodi:role="line" id="tspan430" x="216.32812" y="218.24219" style="text-align:end;text-anchor:end;fill:#ff8c00;stroke-width:1px">8</tspan></text>
<path transform="scale(1,-1)" sodipodi:type="star" style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#ff6600;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" id="path436" sodipodi:sides="3" sodipodi:cx="276.74072" sodipodi:cy="-224.98808" sodipodi:r1="29.912722" sodipodi:r2="14.956361" sodipodi:arg1="0.52359878" sodipodi:arg2="1.5707963" inkscape:flatsided="true" inkscape:rounded="0" inkscape:randomized="0" d="m 302.6459,-210.03172 -51.81035,0 25.90517,-44.86908 z" inkscape:transform-center-y="7.4781812" inkscape:label="-1"/>
<rect inkscape:label="edit" onclick="" y="95.40741" x="1.8178837" height="128" width="230.94511" id="rect438" style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ff00ff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"/>
<path inkscape:label="+1" inkscape:transform-center-y="-7.4781804" d="m 302.6459,111.4008 -51.81035,0 25.90517,-44.869079 z" inkscape:randomized="0" inkscape:rounded="0" inkscape:flatsided="true" sodipodi:arg2="1.5707963" sodipodi:arg1="0.52359878" sodipodi:r2="14.956361" sodipodi:r1="29.912722" sodipodi:cy="96.444443" sodipodi:cx="276.74072" sodipodi:sides="3" id="path442" style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#ff6600;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" sodipodi:type="star"/>
<path inkscape:label="=0" inkscape:transform-center-y="-10.828983" d="m 306.14807,189.68763 -58.37872,0.43598 -0.43597,-58.37872 58.37871,-0.43597 z" inkscape:randomized="0" inkscape:rounded="0" inkscape:flatsided="true" sodipodi:arg2="1.5633284" sodipodi:arg1="0.77793027" sodipodi:r2="21.657967" sodipodi:r1="41.281136" sodipodi:cy="160.71626" sodipodi:cx="276.74072" sodipodi:sides="4" id="path444" style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#ff6600;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" sodipodi:type="star" inkscape:transform-center-x="1.0089177e-06"/>
</g>
<g id="g446-3" transform="matrix(0.2859027,0,0,0.2859027,709.48026,145.69366)" inkscape:label="HMI:Input@/HMI_RELAY1">
<text xml:space="preserve" style="font-style:normal;font-weight:normal;font-size:160px;line-height:125%;font-family:sans-serif;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#ff8c00;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" x="216.32812" y="218.24219" id="text432-6" inkscape:label="value"><tspan sodipodi:role="line" id="tspan430-7" x="216.32812" y="218.24219" style="text-align:end;text-anchor:end;fill:#ff8c00;stroke-width:1px">8</tspan></text>
<path transform="scale(1,-1)" sodipodi:type="star" style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#ff6600;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" id="path436-5" sodipodi:sides="3" sodipodi:cx="276.74072" sodipodi:cy="-224.98808" sodipodi:r1="29.912722" sodipodi:r2="14.956361" sodipodi:arg1="0.52359878" sodipodi:arg2="1.5707963" inkscape:flatsided="true" inkscape:rounded="0" inkscape:randomized="0" d="m 302.6459,-210.03172 -51.81035,0 25.90517,-44.86908 z" inkscape:transform-center-y="7.4781812" inkscape:label="-1"/>
<rect inkscape:label="edit" onclick="" y="95.40741" x="1.8178837" height="128" width="230.94511" id="rect438-3" style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ff00ff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"/>
<path inkscape:label="+1" inkscape:transform-center-y="-7.4781804" d="m 302.6459,111.4008 -51.81035,0 25.90517,-44.869079 z" inkscape:randomized="0" inkscape:rounded="0" inkscape:flatsided="true" sodipodi:arg2="1.5707963" sodipodi:arg1="0.52359878" sodipodi:r2="14.956361" sodipodi:r1="29.912722" sodipodi:cy="96.444443" sodipodi:cx="276.74072" sodipodi:sides="3" id="path442-5" style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#ff6600;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" sodipodi:type="star"/>
<path inkscape:label="=0" inkscape:transform-center-y="-10.828983" d="m 306.14807,189.68763 -58.37872,0.43598 -0.43597,-58.37872 58.37871,-0.43597 z" inkscape:randomized="0" inkscape:rounded="0" inkscape:flatsided="true" sodipodi:arg2="1.5633284" sodipodi:arg1="0.77793027" sodipodi:r2="21.657967" sodipodi:r1="41.281136" sodipodi:cy="160.71626" sodipodi:cx="276.74072" sodipodi:sides="4" id="path444-6" style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#ff6600;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" sodipodi:type="star" inkscape:transform-center-x="1.0089177e-06"/>
</g>
<text xml:space="preserve" style="font-style:normal;font-weight:normal;font-size:40.60216904px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.01505423" x="537.90454" y="213.56741" id="text35-2" transform="scale(1.0150542,0.98516905)"><tspan sodipodi:role="line" id="tspan33-9" x="537.90454" y="213.56741" style="stroke-width:1.01505423">Relay 1</tspan></text>
<text xml:space="preserve" style="font-style:normal;font-weight:normal;font-size:39.54086685px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.9885217" x="552.5509" y="306.83713" id="text35-2-3" transform="scale(0.98852168,1.0116116)"><tspan sodipodi:role="line" id="tspan33-9-6" x="552.5509" y="306.83713" style="stroke-width:0.9885217">Relay 2</tspan></text>
<text xml:space="preserve" style="font-style:normal;font-weight:normal;font-size:39.57181549px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.98929536" x="552.11279" y="406.00681" id="text35-2-7" transform="scale(0.98929534,1.0108205)"><tspan sodipodi:role="line" id="tspan33-9-5" x="552.11279" y="406.00681" style="stroke-width:0.98929536">Relay 3</tspan></text>
<g id="g446-35" transform="matrix(0.2859027,0,0,0.2859027,709.48026,245.69366)" inkscape:label="HMI:Input@/HMI_RELAY2">
<text xml:space="preserve" style="font-style:normal;font-weight:normal;font-size:160px;line-height:125%;font-family:sans-serif;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#ff8c00;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" x="216.32812" y="218.24219" id="text432-62" inkscape:label="value"><tspan sodipodi:role="line" id="tspan430-9" x="216.32812" y="218.24219" style="text-align:end;text-anchor:end;fill:#ff8c00;stroke-width:1px">8</tspan></text>
<path transform="scale(1,-1)" sodipodi:type="star" style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#ff6600;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" id="path436-1" sodipodi:sides="3" sodipodi:cx="276.74072" sodipodi:cy="-224.98808" sodipodi:r1="29.912722" sodipodi:r2="14.956361" sodipodi:arg1="0.52359878" sodipodi:arg2="1.5707963" inkscape:flatsided="true" inkscape:rounded="0" inkscape:randomized="0" d="m 302.6459,-210.03172 -51.81035,0 25.90517,-44.86908 z" inkscape:transform-center-y="7.4781812" inkscape:label="-1"/>
<rect inkscape:label="edit" onclick="" y="95.40741" x="1.8178837" height="128" width="230.94511" id="rect438-2" style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ff00ff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"/>
<path inkscape:label="+1" inkscape:transform-center-y="-7.4781804" d="m 302.6459,111.4008 -51.81035,0 25.90517,-44.869079 z" inkscape:randomized="0" inkscape:rounded="0" inkscape:flatsided="true" sodipodi:arg2="1.5707963" sodipodi:arg1="0.52359878" sodipodi:r2="14.956361" sodipodi:r1="29.912722" sodipodi:cy="96.444443" sodipodi:cx="276.74072" sodipodi:sides="3" id="path442-7" style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#ff6600;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" sodipodi:type="star"/>
<path inkscape:label="=0" inkscape:transform-center-y="-10.828983" d="m 306.14807,189.68763 -58.37872,0.43598 -0.43597,-58.37872 58.37871,-0.43597 z" inkscape:randomized="0" inkscape:rounded="0" inkscape:flatsided="true" sodipodi:arg2="1.5633284" sodipodi:arg1="0.77793027" sodipodi:r2="21.657967" sodipodi:r1="41.281136" sodipodi:cy="160.71626" sodipodi:cx="276.74072" sodipodi:sides="4" id="path444-0" style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#ff6600;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" sodipodi:type="star" inkscape:transform-center-x="1.0089177e-06"/>
</g>
<g id="g446-9" transform="matrix(0.2859027,0,0,0.2859027,709.48026,345.69366)" inkscape:label="HMI:Input@/HMI_RELAY3">
<text xml:space="preserve" style="font-style:normal;font-weight:normal;font-size:160px;line-height:125%;font-family:sans-serif;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#ff8c00;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" x="216.32812" y="218.24219" id="text432-3" inkscape:label="value"><tspan sodipodi:role="line" id="tspan430-6" x="216.32812" y="218.24219" style="text-align:end;text-anchor:end;fill:#ff8c00;stroke-width:1px">8</tspan></text>
<path transform="scale(1,-1)" sodipodi:type="star" style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#ff6600;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" id="path436-0" sodipodi:sides="3" sodipodi:cx="276.74072" sodipodi:cy="-224.98808" sodipodi:r1="29.912722" sodipodi:r2="14.956361" sodipodi:arg1="0.52359878" sodipodi:arg2="1.5707963" inkscape:flatsided="true" inkscape:rounded="0" inkscape:randomized="0" d="m 302.6459,-210.03172 -51.81035,0 25.90517,-44.86908 z" inkscape:transform-center-y="7.4781812" inkscape:label="-1"/>
<rect inkscape:label="edit" onclick="" y="95.40741" x="1.8178837" height="128" width="230.94511" id="rect438-6" style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ff00ff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"/>
<path inkscape:label="+1" inkscape:transform-center-y="-7.4781804" d="m 302.6459,111.4008 -51.81035,0 25.90517,-44.869079 z" inkscape:randomized="0" inkscape:rounded="0" inkscape:flatsided="true" sodipodi:arg2="1.5707963" sodipodi:arg1="0.52359878" sodipodi:r2="14.956361" sodipodi:r1="29.912722" sodipodi:cy="96.444443" sodipodi:cx="276.74072" sodipodi:sides="3" id="path442-2" style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#ff6600;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" sodipodi:type="star"/>
<path inkscape:label="=0" inkscape:transform-center-y="-10.828983" d="m 306.14807,189.68763 -58.37872,0.43598 -0.43597,-58.37872 58.37871,-0.43597 z" inkscape:randomized="0" inkscape:rounded="0" inkscape:flatsided="true" sodipodi:arg2="1.5633284" sodipodi:arg1="0.77793027" sodipodi:r2="21.657967" sodipodi:r1="41.281136" sodipodi:cy="160.71626" sodipodi:cx="276.74072" sodipodi:sides="4" id="path444-61" style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#ff6600;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" sodipodi:type="star" inkscape:transform-center-x="1.0089177e-06"/>
</g>
</svg><script><![CDATA[
//
//
// Early independent declarations
//
//
/* hmi-tree */
var hmi_hash = [129,53,34,212,104,24,210,166];
var heartbeat_index = 5;
var current_page_var_index = 6;
var hmitree_types = [
"NODE",
"INT",
"INT",
"INT",
"INT",
"INT",
"STRING"
];
var hmitree_paths = [
"/",
"/HMI_RELAY0",
"/HMI_RELAY1",
"/HMI_RELAY2",
"/HMI_RELAY3",
"/HEARTBEAT",
"/CURRENT_PAGE_0"
];
var hmitree_nodes = {
"/" : [0, ""]
};
/* default-page */
var default_page = "Home";
/* inline-svg */
let id = document.getElementById.bind(document);
var svg_root = id("hmi0");
/* i18n */
var langs = [ ["Default", "C"],];
var translations = [
]
/* local-variable-indexes */
let hmi_locals = {};
var last_remote_index = hmitree_types.length - 1;
var next_available_index = hmitree_types.length;
let cookies = new Map(document.cookie.split("; ").map(s=>s.split("=")));
const local_defaults = {
"lang":cookies.has("lang")?cookies.get("lang"):0
};
const persistent_locals = new Set([
"lang"
]);
var persistent_indexes = new Map();
var cache = hmitree_types.map(_ignored => undefined);
var updates = new Map();
function page_local_index(varname, pagename){
let pagevars = hmi_locals[pagename];
let new_index;
if(pagevars == undefined){
new_index = next_available_index++;
hmi_locals[pagename] = {[varname]:new_index}
} else {
let result = pagevars[varname];
if(result != undefined) {
return result;
}
new_index = next_available_index++;
pagevars[varname] = new_index;
}
let defaultval = local_defaults[varname];
if(defaultval != undefined) {
cache[new_index] = defaultval;
updates.set(new_index, defaultval);
if(persistent_locals.has(varname))
persistent_indexes.set(new_index, varname);
}
return new_index;
}
function hmi_local_index(varname){
return page_local_index(varname, "HMI_LOCAL");
}
/* widget-base-class */
var pending_widget_animates = [];
class Widget {
offset = 0;
frequency = 10; /* FIXME arbitrary default max freq. Obtain from config ? */
unsubscribable = false;
pending_animate = false;
constructor(elt_id, freq, args, indexes, minmaxes, members){
this.element_id = elt_id;
this.element = id(elt_id);
this.args = args;
this.indexes = indexes;
this.minmaxes = minmaxes;
Object.keys(members).forEach(prop => this[prop]=members[prop]);
this.lastapply = indexes.map(() => undefined);
this.inhibit = indexes.map(() => undefined);
this.pending = indexes.map(() => undefined);
this.bound_unhinibit = this.unhinibit.bind(this);
this.forced_frequency = freq;
this.clip = true;
}
do_init(){
let forced = this.forced_frequency;
if(forced !== undefined){
/*
once every 10 seconds : 10s
once per minute : 1m
once per hour : 1h
once per day : 1d
*/
let unit = forced.slice(-1);
let factor = {
"s":1,
"m":60,
"h":3600,
"d":86400}[unit];
this.frequency = factor ? 1/(factor * Number(forced.slice(0,-1)))
: Number(forced);
}
let init = this.init;
if(typeof(init) == "function"){
try {
init.call(this);
} catch(err) {
console.log(err);
}
}
}
unsub(){
/* remove subsribers */
if(!this.unsubscribable)
for(let i = 0; i < this.indexes.length; i++) {
/* flush updates pending because of inhibition */
let inhibition = this.inhibit[i];
if(inhibition != undefined){
clearTimeout(inhibition);
this.lastapply[i] = undefined;
this.unhinibit(i);
}
let index = this.indexes[i];
if(this.relativeness[i])
index += this.offset;
subscribers(index).delete(this);
}
this.offset = 0;
this.relativeness = undefined;
}
sub(new_offset=0, relativeness, container_id){
this.offset = new_offset;
this.relativeness = relativeness;
this.container_id = container_id ;
/* add this's subsribers */
if(!this.unsubscribable)
for(let i = 0; i < this.indexes.length; i++) {
let index = this.get_variable_index(i);
if(index == undefined) continue;
subscribers(index).add(this);
}
need_cache_apply.push(this);
}
apply_cache() {
if(!this.unsubscribable) for(let index in this.indexes){
/* dispatch current cache in newly opened page widgets */
let realindex = this.get_variable_index(index);
if(realindex == undefined) continue;
let cached_val = cache[realindex];
if(cached_val != undefined)
this._dispatch(cached_val, cached_val, index);
}
}
get_variable_index(varnum) {
let index = this.indexes[varnum];
if(typeof(index) == "string"){
index = page_local_index(index, this.container_id);
} else {
if(this.relativeness[varnum]){
index += this.offset;
}
}
return index;
}
overshot(new_val, max) {
}
undershot(new_val, min) {
}
clip_min_max(index, new_val) {
let minmax = this.minmaxes[index];
if(minmax !== undefined && typeof new_val == "number") {
let [min,max] = minmax;
if(new_val < min){
this.undershot(new_val, min);
return min;
}
if(new_val > max){
this.overshot(new_val, max);
return max;
}
}
return new_val;
}
change_hmi_value(index, opstr) {
let realindex = this.get_variable_index(index);
if(realindex == undefined) return undefined;
let old_val = cache[realindex];
let new_val = eval_operation_string(old_val, opstr);
if(this.clip)
new_val = this.clip_min_max(index, new_val);
return apply_hmi_value(realindex, new_val);
}
_apply_hmi_value(index, new_val) {
let realindex = this.get_variable_index(index);
if(realindex == undefined) return undefined;
if(this.clip)
new_val = this.clip_min_max(index, new_val);
return apply_hmi_value(realindex, new_val);
}
unhinibit(index){
this.inhibit[index] = undefined;
let new_val = this.pending[index];
this.pending[index] = undefined;
return this.apply_hmi_value(index, new_val);
}
apply_hmi_value(index, new_val) {
if(this.inhibit[index] == undefined){
let now = Date.now();
let min_interval = 1000/this.frequency;
let lastapply = this.lastapply[index];
if(lastapply == undefined || now > lastapply + min_interval){
this.lastapply[index] = now;
return this._apply_hmi_value(index, new_val);
}
else {
let elapsed = now - lastapply;
this.pending[index] = new_val;
this.inhibit[index] = setTimeout(this.bound_unhinibit, min_interval - elapsed, index);
}
}
else {
this.pending[index] = new_val;
return new_val;
}
}
new_hmi_value(index, value, oldval) {
// TODO avoid searching, store index at sub()
for(let i = 0; i < this.indexes.length; i++) {
let refindex = this.get_variable_index(i);
if(refindex == undefined) continue;
if(index == refindex) {
this._dispatch(value, oldval, i);
break;
}
}
}
_dispatch(value, oldval, varnum) {
let dispatch = this.dispatch;
if(dispatch != undefined){
try {
dispatch.call(this, value, oldval, varnum);
} catch(err) {
console.log(err);
}
}
}
_animate(){
this.animate();
this.pending_animate = false;
}
request_animate(){
if(!this.pending_animate){
pending_widget_animates.push(this);
this.pending_animate = true;
requestHMIAnimation();
}
}
activate_activable(eltsub) {
eltsub.inactive.style.display = "none";
eltsub.active.style.display = "";
}
inactivate_activable(eltsub) {
eltsub.active.style.display = "none";
eltsub.inactive.style.display = "";
}
}
//
//
// Declarations depending on preamble
//
//
/* detachable-elements */
var detachable_elements = {
"rect1016":[id("rect1016"), id("hmi0")],
"text35":[id("text35"), id("hmi0")],
"g446":[id("g446"), id("hmi0")],
"g446-3":[id("g446-3"), id("hmi0")],
"text35-2":[id("text35-2"), id("hmi0")],
"text35-2-3":[id("text35-2-3"), id("hmi0")],
"text35-2-7":[id("text35-2-7"), id("hmi0")],
"g446-35":[id("g446-35"), id("hmi0")],
"g446-9":[id("g446-9"), id("hmi0")]
}
/* hmi-classes */
class InputWidget extends Widget{
on_op_click(opstr) {
this.change_hmi_value(0, opstr);
}
edit_callback(new_val) {
this.apply_hmi_value(0, new_val);
}
is_inhibited = false;
alert(msg){
this.is_inhibited = true;
this.display = msg;
setTimeout(() => this.stopalert(), 1000);
this.request_animate();
}
stopalert(){
this.is_inhibited = false;
this.display = this.last_value;
this.request_animate();
}
overshot(new_val, max) {
this.alert("max");
}
undershot(new_val, min) {
this.alert("min");
}
}
/* hmi-elements */
var hmi_widgets = {
"g446": new InputWidget ("g446",undefined,[],[1],[undefined],{
value_elt: id("text432"),
edit_elt: id("rect438"),
frequency: 5,
dispatch: function(value) {
this.last_value = value;
if(!this.is_inhibited){
this.display = this.last_value;
this.request_animate();
}
},
animate: function(){
this.value_elt.textContent = String(this.display);
},
init: function() {
this.edit_elt.onclick = () => edit_value("/HMI_RELAY0", "HMI_INT", this, this.last_value);
this.value_elt.style.pointerEvents = "none";
id("path436").onclick = () => this.on_op_click("-1");
id("path442").onclick = () => this.on_op_click("+1");
id("path444").onclick = () => this.on_op_click("=0");
},
}),
"g446-3": new InputWidget ("g446-3",undefined,[],[2],[undefined],{
value_elt: id("text432-6"),
edit_elt: id("rect438-3"),
frequency: 5,
dispatch: function(value) {
this.last_value = value;
if(!this.is_inhibited){
this.display = this.last_value;
this.request_animate();
}
},
animate: function(){
this.value_elt.textContent = String(this.display);
},
init: function() {
this.edit_elt.onclick = () => edit_value("/HMI_RELAY1", "HMI_INT", this, this.last_value);
this.value_elt.style.pointerEvents = "none";
id("path436-5").onclick = () => this.on_op_click("-1");
id("path442-5").onclick = () => this.on_op_click("+1");
id("path444-6").onclick = () => this.on_op_click("=0");
},
}),
"g446-35": new InputWidget ("g446-35",undefined,[],[3],[undefined],{
value_elt: id("text432-62"),
edit_elt: id("rect438-2"),
frequency: 5,
dispatch: function(value) {
this.last_value = value;
if(!this.is_inhibited){
this.display = this.last_value;
this.request_animate();
}
},
animate: function(){
this.value_elt.textContent = String(this.display);
},
init: function() {
this.edit_elt.onclick = () => edit_value("/HMI_RELAY2", "HMI_INT", this, this.last_value);
this.value_elt.style.pointerEvents = "none";
id("path436-1").onclick = () => this.on_op_click("-1");
id("path442-7").onclick = () => this.on_op_click("+1");
id("path444-0").onclick = () => this.on_op_click("=0");
},
}),
"g446-9": new InputWidget ("g446-9",undefined,[],[4],[undefined],{
value_elt: id("text432-3"),
edit_elt: id("rect438-6"),
frequency: 5,
dispatch: function(value) {
this.last_value = value;
if(!this.is_inhibited){
this.display = this.last_value;
this.request_animate();
}
},
animate: function(){
this.value_elt.textContent = String(this.display);
},
init: function() {
this.edit_elt.onclick = () => edit_value("/HMI_RELAY3", "HMI_INT", this, this.last_value);
this.value_elt.style.pointerEvents = "none";
id("path436-0").onclick = () => this.on_op_click("-1");
id("path442-2").onclick = () => this.on_op_click("+1");
id("path444-61").onclick = () => this.on_op_click("=0");
},
})
}
/* DropDown */
function gettext(o) {
if(typeof(o) == "string"){
return o;
}
return svg_text_to_multiline(o);
};
/* jump */
var jumps_need_update = false;
var jump_history = [[default_page, undefined]];
function update_jumps() {
page_desc[current_visible_page].jumps.map(w=>w.notify_page_change(current_visible_page,current_page_index));
jumps_need_update = false;
};
/* keypad */
var keypads = {
}
//
//
// Order independent declaration and code
//
//
/* page-desc */
var page_desc = {
"Home": {
bbox: [-7.9750781, -1.9937692, 1280, 720],
widgets: [
[hmi_widgets["g446"], [false]],
[hmi_widgets["g446-3"], [false]],
[hmi_widgets["g446-35"], [false]],
[hmi_widgets["g446-9"], [false]]
],
jumps: [
],
required_detachables: {
"rect1016": detachable_elements["rect1016"],
"text35": detachable_elements["text35"],
"g446": detachable_elements["g446"],
"g446-3": detachable_elements["g446-3"],
"text35-2": detachable_elements["text35-2"],
"text35-2-3": detachable_elements["text35-2-3"],
"text35-2-7": detachable_elements["text35-2-7"],
"g446-35": detachable_elements["g446-35"],
"g446-9": detachable_elements["g446-9"]
}
}
}
//
//
// Statements that needs to be at the end
//
//
/* https://github.com/alexei/sprintf.js/blob/master/src/sprintf.js */
/* global window, exports, define */
!function() {
'use strict'
var re = {
not_string: /[^s]/,
not_bool: /[^t]/,
not_type: /[^T]/,
not_primitive: /[^v]/,
number: /[diefg]/,
numeric_arg: /[bcdiefguxX]/,
json: /[j]/,
not_json: /[^j]/,
text: /^[^%]+/,
modulo: /^%{2}/,
placeholder: /^%(?:([1-9]\d*)\$|\(([^)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-gijostTuvxXD])/,
key: /^([a-z_][a-z_\d]*)/i,
key_access: /^\.([a-z_][a-z_\d]*)/i,
index_access: /^\[(\d+)\]/,
sign: /^[+-]/
}
function sprintf(key) {
// arguments is not an array, but should be fine for this call
return sprintf_format(sprintf_parse(key), arguments)
}
function vsprintf(fmt, argv) {
return sprintf.apply(null, [fmt].concat(argv || []))
}
function sprintf_format(parse_tree, argv) {
var cursor = 1, tree_length = parse_tree.length, arg, output = '', i, k, ph, pad, pad_character, pad_length, is_positive, sign
for (i = 0; i < tree_length; i++) {
if (typeof parse_tree[i] === 'string') {
output += parse_tree[i]
}
else if (typeof parse_tree[i] === 'object') {
ph = parse_tree[i] // convenience purposes only
if (ph.keys) { // keyword argument
arg = argv[cursor]
for (k = 0; k < ph.keys.length; k++) {
if (arg == undefined) {
throw new Error(sprintf('[sprintf] Cannot access property "%s" of undefined value "%s"', ph.keys[k], ph.keys[k-1]))
}
arg = arg[ph.keys[k]]
}
}
else if (ph.param_no) { // positional argument (explicit)
arg = argv[ph.param_no]
}
else { // positional argument (implicit)
arg = argv[cursor++]
}
if (re.not_type.test(ph.type) && re.not_primitive.test(ph.type) && arg instanceof Function) {
arg = arg()
}
if (re.numeric_arg.test(ph.type) && (typeof arg !== 'number' && isNaN(arg))) {
throw new TypeError(sprintf('[sprintf] expecting number but found %T', arg))
}
if (re.number.test(ph.type)) {
is_positive = arg >= 0
}
switch (ph.type) {
case 'b':
arg = parseInt(arg, 10).toString(2)
break
case 'c':
arg = String.fromCharCode(parseInt(arg, 10))
break
case 'd':
case 'i':
arg = parseInt(arg, 10)
break
case 'D':
/*
select date format with width
select time format with precision
%D => 13:31 AM (default)
%1D => 13:31 AM
%.1D => 07/07/20
%1.1D => 07/07/20, 13:31 AM
%1.2D => 07/07/20, 13:31:55 AM
%2.2D => May 5, 2022, 9:29:16 AM
%3.3D => May 5, 2022 at 9:28:16 AM GMT+2
%4.4D => Thursday, May 5, 2022 at 9:26:59 AM Central European Summer Time
see meaning of DateTimeFormat's options "datestyle" and "timestyle" in MDN
*/
let [datestyle, timestyle] = [ph.width, ph.precision].map(val => ({
1: "short",
2: "medium",
3: "long",
4: "full"
}[val]));
if(timestyle === undefined && datestyle === undefined){
timestyle = "short";
}
let options = {
dateStyle: datestyle,
timeStyle: timestyle,
hour12: false
}
/* get lang from globals */
let lang = get_current_lang_code();
arg = Date(arg).toLocaleString('en-US', options);
/*
TODO: select with padding char
a: absolute time and date (default)
r: relative time
*/
break
case 'j':
arg = JSON.stringify(arg, null, ph.width ? parseInt(ph.width) : 0)
break
case 'e':
arg = ph.precision ? parseFloat(arg).toExponential(ph.precision) : parseFloat(arg).toExponential()
break
case 'f':
arg = ph.precision ? parseFloat(arg).toFixed(ph.precision) : parseFloat(arg)
break
case 'g':
arg = ph.precision ? String(Number(arg.toPrecision(ph.precision))) : parseFloat(arg)
break
case 'o':
arg = (parseInt(arg, 10) >>> 0).toString(8)
break
case 's':
arg = String(arg)
arg = (ph.precision ? arg.substring(0, ph.precision) : arg)
break
case 't':
arg = String(!!arg)
arg = (ph.precision ? arg.substring(0, ph.precision) : arg)
break
case 'T':
arg = Object.prototype.toString.call(arg).slice(8, -1).toLowerCase()
arg = (ph.precision ? arg.substring(0, ph.precision) : arg)
break
case 'u':
arg = parseInt(arg, 10) >>> 0
break
case 'v':
arg = arg.valueOf()
arg = (ph.precision ? arg.substring(0, ph.precision) : arg)
break
case 'x':
arg = (parseInt(arg, 10) >>> 0).toString(16)
break
case 'X':
arg = (parseInt(arg, 10) >>> 0).toString(16).toUpperCase()
break
}
if (re.json.test(ph.type)) {
output += arg
}
else {
if (re.number.test(ph.type) && (!is_positive || ph.sign)) {
sign = is_positive ? '+' : '-'
arg = arg.toString().replace(re.sign, '')
}
else {
sign = ''
}
pad_character = ph.pad_char ? ph.pad_char === '0' ? '0' : ph.pad_char.charAt(1) : ' '
pad_length = ph.width - (sign + arg).length
pad = ph.width ? (pad_length > 0 ? pad_character.repeat(pad_length) : '') : ''
output += ph.align ? sign + arg + pad : (pad_character === '0' ? sign + pad + arg : pad + sign + arg)
}
}
}
return output
}
var sprintf_cache = Object.create(null)
function sprintf_parse(fmt) {
if (sprintf_cache[fmt]) {
return sprintf_cache[fmt]
}
var _fmt = fmt, match, parse_tree = [], arg_names = 0
while (_fmt) {
if ((match = re.text.exec(_fmt)) !== null) {
parse_tree.push(match[0])
}
else if ((match = re.modulo.exec(_fmt)) !== null) {
parse_tree.push('%')
}
else if ((match = re.placeholder.exec(_fmt)) !== null) {
if (match[2]) {
arg_names |= 1
var field_list = [], replacement_field = match[2], field_match = []
if ((field_match = re.key.exec(replacement_field)) !== null) {
field_list.push(field_match[1])
while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') {
if ((field_match = re.key_access.exec(replacement_field)) !== null) {
field_list.push(field_match[1])
}
else if ((field_match = re.index_access.exec(replacement_field)) !== null) {
field_list.push(field_match[1])
}
else {
throw new SyntaxError('[sprintf] failed to parse named argument key')
}
}
}
else {
throw new SyntaxError('[sprintf] failed to parse named argument key')
}
match[2] = field_list
}
else {
arg_names |= 2
}
if (arg_names === 3) {
throw new Error('[sprintf] mixing positional and named placeholders is not (yet) supported')
}
parse_tree.push(
{
placeholder: match[0],
param_no: match[1],
keys: match[2],
sign: match[3],
pad_char: match[4],
align: match[5],
width: match[6],
precision: match[7],
type: match[8]
}
)
}
else {
throw new SyntaxError('[sprintf] unexpected placeholder')
}
_fmt = _fmt.substring(match[0].length)
}
return sprintf_cache[fmt] = parse_tree
}
/**
* export to either browser or node.js
*/
/* eslint-disable quote-props */
if (typeof exports !== 'undefined') {
exports['sprintf'] = sprintf
exports['vsprintf'] = vsprintf
}
if (typeof window !== 'undefined') {
window['sprintf'] = sprintf
window['vsprintf'] = vsprintf
if (typeof define === 'function' && define['amd']) {
define(function() {
return {
'sprintf': sprintf,
'vsprintf': vsprintf
}
})
}
}
/* eslint-enable quote-props */
}(); // eslint-disable-line
// svghmi.js
var need_cache_apply = [];
function dispatch_value(index, value) {
let widgets = subscribers(index);
let oldval = cache[index];
cache[index] = value;
if(widgets.size > 0) {
for(let widget of widgets){
widget.new_hmi_value(index, value, oldval);
}
}
};
function init_widgets() {
Object.keys(hmi_widgets).forEach(function(id) {
let widget = hmi_widgets[id];
widget.do_init();
});
};
// Open WebSocket to relative "/ws" address
var has_watchdog = window.location.hash == "#watchdog";
var ws_url =
window.location.href.replace(/^http(s?:\/\/[^\/]*)\/.*$/, 'ws$1/ws')
+ '?mode=' + (has_watchdog ? "watchdog" : "multiclient");
var ws = new WebSocket(ws_url);
ws.binaryType = 'arraybuffer';
const dvgetters = {
INT: (dv,offset) => [dv.getInt16(offset, true), 2],
BOOL: (dv,offset) => [dv.getInt8(offset, true), 1],
NODE: (dv,offset) => [dv.getInt8(offset, true), 1],
REAL: (dv,offset) => [dv.getFloat32(offset, true), 4],
STRING: (dv, offset) => {
const size = dv.getInt8(offset);
return [
String.fromCharCode.apply(null, new Uint8Array(
dv.buffer, /* original buffer */
offset + 1, /* string starts after size*/
size /* size of string */
)), size + 1]; /* total increment */
}
};
// Apply updates recieved through ws.onmessage to subscribed widgets
function apply_updates() {
updates.forEach((value, index) => {
dispatch_value(index, value);
});
updates.clear();
}
// Called on requestAnimationFrame, modifies DOM
var requestAnimationFrameID = null;
function animate() {
// Do the page swith if any one pending
if(current_subscribed_page != current_visible_page){
switch_visible_page(current_subscribed_page);
}
while(widget = need_cache_apply.pop()){
widget.apply_cache();
}
if(jumps_need_update) update_jumps();
apply_updates();
pending_widget_animates.forEach(widget => widget._animate());
pending_widget_animates = [];
requestAnimationFrameID = null;
}
function requestHMIAnimation() {
if(requestAnimationFrameID == null){
requestAnimationFrameID = window.requestAnimationFrame(animate);
}
}
// Message reception handler
// Hash is verified and HMI values updates resulting from binary parsing
// are stored until browser can compute next frame, DOM is left untouched
ws.onmessage = function (evt) {
let data = evt.data;
let dv = new DataView(data);
let i = 0;
try {
for(let hash_int of hmi_hash) {
if(hash_int != dv.getUint8(i)){
throw new Error("Hash doesn't match");
};
i++;
};
while(i < data.byteLength){
let index = dv.getUint32(i, true);
i += 4;
let iectype = hmitree_types[index];
if(iectype != undefined){
let dvgetter = dvgetters[iectype];
let [value, bytesize] = dvgetter(dv,i);
updates.set(index, value);
i += bytesize;
} else {
throw new Error("Unknown index "+index);
}
};
// register for rendering on next frame, since there are updates
requestHMIAnimation();
} catch(err) {
// 1003 is for "Unsupported Data"
// ws.close(1003, err.message);
// TODO : remove debug alert ?
alert("Error : "+err.message+"\nHMI will be reloaded.");
// force reload ignoring cache
location.reload(true);
}
};
hmi_hash_u8 = new Uint8Array(hmi_hash);
function send_blob(data) {
if(data.length > 0) {
ws.send(new Blob([hmi_hash_u8].concat(data)));
};
};
const typedarray_types = {
INT: (number) => new Int16Array([number]),
BOOL: (truth) => new Int16Array([truth]),
NODE: (truth) => new Int16Array([truth]),
REAL: (number) => new Float32Array([number]),
STRING: (str) => {
// beremiz default string max size is 128
str = str.slice(0,128);
binary = new Uint8Array(str.length + 1);
binary[0] = str.length;
for(let i = 0; i < str.length; i++){
binary[i+1] = str.charCodeAt(i);
}
return binary;
}
/* TODO */
};
function send_reset() {
send_blob(new Uint8Array([1])); /* reset = 1 */
};
var subscriptions = [];
function subscribers(index) {
let entry = subscriptions[index];
let res;
if(entry == undefined){
res = new Set();
subscriptions[index] = [res,0];
}else{
[res, _ign] = entry;
}
return res
}
function get_subscription_period(index) {
let entry = subscriptions[index];
if(entry == undefined)
return 0;
let [_ign, period] = entry;
return period;
}
function set_subscription_period(index, period) {
let entry = subscriptions[index];
if(entry == undefined){
subscriptions[index] = [new Set(), period];
} else {
entry[1] = period;
}
}
if(has_watchdog){
// artificially subscribe the watchdog widget to "/heartbeat" hmi variable
// Since dispatch directly calls change_hmi_value,
// PLC will periodically send variable at given frequency
subscribers(heartbeat_index).add({
/* type: "Watchdog", */
frequency: 1,
indexes: [heartbeat_index],
new_hmi_value: function(index, value, oldval) {
apply_hmi_value(heartbeat_index, value+1);
}
});
}
// subscribe to per instance current page hmi variable
// PLC must prefix page name with "!" for page switch to happen
subscribers(current_page_var_index).add({
frequency: 1,
indexes: [current_page_var_index],
new_hmi_value: function(index, value, oldval) {
if(value.startsWith("!"))
switch_page(value.slice(1));
}
});
function svg_text_to_multiline(elt) {
return(Array.prototype.map.call(elt.children, x=>x.textContent).join("\n"));
}
function multiline_to_svg_text(elt, str) {
str.split('\n').map((line,i) => {elt.children[i].textContent = line;});
}
function switch_langnum(langnum) {
langnum = Math.max(0, Math.min(langs.length - 1, langnum));
for (let translation of translations) {
let [objs, msgs] = translation;
let msg = msgs[langnum];
for (let obj of objs) {
multiline_to_svg_text(obj, msg);
obj.setAttribute("lang",langnum);
}
}
return langnum;
}
// backup original texts
for (let translation of translations) {
let [objs, msgs] = translation;
msgs.unshift(svg_text_to_multiline(objs[0]));
}
var lang_local_index = hmi_local_index("lang");
var langcode_local_index = hmi_local_index("lang_code");
var langname_local_index = hmi_local_index("lang_name");
subscribers(lang_local_index).add({
indexes: [lang_local_index],
new_hmi_value: function(index, value, oldval) {
let current_lang = switch_langnum(value);
let [langname,langcode] = langs[current_lang];
apply_hmi_value(langcode_local_index, langcode);
apply_hmi_value(langname_local_index, langname);
switch_page();
}
});
// returns en_US, fr_FR or en_UK depending on selected language
function get_current_lang_code(){
return cache[langcode_local_index];
}
function setup_lang(){
let current_lang = cache[lang_local_index];
let new_lang = switch_langnum(current_lang);
if(current_lang != new_lang){
apply_hmi_value(lang_local_index, new_lang);
}
}
setup_lang();
function update_subscriptions() {
let delta = [];
for(let index in subscriptions){
let widgets = subscribers(index);
// periods are in ms
let previous_period = get_subscription_period(index);
// subscribing with a zero period is unsubscribing
let new_period = 0;
if(widgets.size > 0) {
let maxfreq = 0;
for(let widget of widgets){
let wf = widget.frequency;
if(wf != undefined && maxfreq < wf)
maxfreq = wf;
}
if(maxfreq != 0)
new_period = 1000/maxfreq;
}
if(previous_period != new_period) {
set_subscription_period(index, new_period);
if(index <= last_remote_index){
delta.push(
new Uint8Array([2]), /* subscribe = 2 */
new Uint32Array([index]),
new Uint16Array([new_period]));
}
}
}
send_blob(delta);
};
function send_hmi_value(index, value) {
if(index > last_remote_index){
updates.set(index, value);
if(persistent_indexes.has(index)){
let varname = persistent_indexes.get(index);
document.cookie = varname+"="+value+"; max-age=3153600000";
}
requestHMIAnimation();
return;
}
let iectype = hmitree_types[index];
let tobinary = typedarray_types[iectype];
send_blob([
new Uint8Array([0]), /* setval = 0 */
new Uint32Array([index]),
tobinary(value)]);
// DON'T DO THAT unless read_iterator in svghmi.c modifies wbuf as well, not only rbuf
// cache[index] = value;
};
function apply_hmi_value(index, new_val) {
// Similarly to previous comment, taking decision to update based
// on cache content is bad and can lead to inconsistency
/*let old_val = cache[index];*/
if(new_val != undefined /*&& old_val != new_val*/)
send_hmi_value(index, new_val);
return new_val;
}
const quotes = {"'":null, '"':null};
function eval_operation_string(old_val, opstr) {
let op = opstr[0];
let given_val;
if(opstr.length < 2)
return undefined;
if(opstr[1] in quotes){
if(opstr.length < 3)
return undefined;
if(opstr[opstr.length-1] == opstr[1]){
given_val = opstr.slice(2,opstr.length-1);
}
} else {
given_val = Number(opstr.slice(1));
}
let new_val;
switch(op){
case "=":
new_val = given_val;
break;
case "+":
new_val = old_val + given_val;
break;
case "-":
new_val = old_val - given_val;
break;
case "*":
new_val = old_val * given_val;
break;
case "/":
new_val = old_val / given_val;
break;
}
return new_val;
}
var current_visible_page;
var current_subscribed_page;
var current_page_index;
var page_node_local_index = hmi_local_index("page_node");
function toggleFullscreen() {
let elem = document.documentElement;
if (!document.fullscreenElement) {
elem.requestFullscreen().catch(err => {
console.log("Error attempting to enable full-screen mode: "+err.message+" ("+err.name+")");
});
} else {
document.exitFullscreen();
}
}
function prepare_svg() {
// prevents context menu from appearing on right click and long touch
document.body.addEventListener('contextmenu', e => {
toggleFullscreen();
e.preventDefault();
});
for(let eltid in detachable_elements){
let [element,parent] = detachable_elements[eltid];
parent.removeChild(element);
}
};
function switch_page(page_name, page_index) {
if(current_subscribed_page != current_visible_page){
/* page switch already going */
/* TODO LOG ERROR */
return false;
}
if(page_name == undefined)
page_name = current_subscribed_page;
else if(page_index == undefined){
[page_name, page_index] = page_name.split('@')
}
let old_desc = page_desc[current_subscribed_page];
let new_desc = page_desc[page_name];
if(new_desc == undefined){
/* TODO LOG ERROR */
return false;
}
if(page_index == undefined)
page_index = new_desc.page_index;
else if(typeof(page_index) == "string") {
let hmitree_node = hmitree_nodes[page_index];
if(hmitree_node !== undefined){
let [int_index, hmiclass] = hmitree_node;
if(hmiclass == new_desc.page_class)
page_index = int_index;
else
page_index = new_desc.page_index;
} else {
page_index = new_desc.page_index;
}
}
if(old_desc){
old_desc.widgets.map(([widget,relativeness])=>widget.unsub());
}
const new_offset = page_index == undefined ? 0 : page_index - new_desc.page_index;
const container_id = page_name + (page_index != undefined ? page_index : "");
new_desc.widgets.map(([widget,relativeness])=>widget.sub(new_offset,relativeness,container_id));
update_subscriptions();
current_subscribed_page = page_name;
current_page_index = page_index;
let page_node;
if(page_index != undefined){
page_node = hmitree_paths[page_index];
}else{
page_node = "";
}
apply_hmi_value(page_node_local_index, page_node);
jumps_need_update = true;
requestHMIAnimation();
jump_history.push([page_name, page_index]);
if(jump_history.length > 42)
jump_history.shift();
apply_hmi_value(current_page_var_index, page_index == undefined
? page_name
: page_name + "@" + hmitree_paths[page_index]);
return true;
};
function switch_visible_page(page_name) {
let old_desc = page_desc[current_visible_page];
let new_desc = page_desc[page_name];
if(old_desc){
for(let eltid in old_desc.required_detachables){
if(!(eltid in new_desc.required_detachables)){
let [element, parent] = old_desc.required_detachables[eltid];
parent.removeChild(element);
}
}
for(let eltid in new_desc.required_detachables){
if(!(eltid in old_desc.required_detachables)){
let [element, parent] = new_desc.required_detachables[eltid];
parent.appendChild(element);
}
}
}else{
for(let eltid in new_desc.required_detachables){
let [element, parent] = new_desc.required_detachables[eltid];
parent.appendChild(element);
}
}
svg_root.setAttribute('viewBox',new_desc.bbox.join(" "));
current_visible_page = page_name;
};
// Once connection established
ws.onopen = function (evt) {
init_widgets();
send_reset();
// show main page
prepare_svg();
switch_page(default_page);
};
ws.onclose = function (evt) {
// TODO : add visible notification while waiting for reload
console.log("Connection closed. code:"+evt.code+" reason:"+evt.reason+" wasClean:"+evt.wasClean+" Reload in 10s.");
// TODO : re-enable auto reload when not in debug
//window.setTimeout(() => location.reload(true), 10000);
alert("Connection closed. code:"+evt.code+" reason:"+evt.reason+" wasClean:"+evt.wasClean+".");
};
const xmlns = "http://www.w3.org/2000/svg";
var edit_callback;
const localtypes = {"PAGE_LOCAL":null, "HMI_LOCAL":null}
function edit_value(path, valuetype, callback, initial) {
if(valuetype in localtypes){
valuetype = (typeof initial) == "number" ? "HMI_REAL" : "HMI_STRING";
}
let [keypadid, xcoord, ycoord] = keypads[valuetype];
edit_callback = callback;
let widget = hmi_widgets[keypadid];
widget.start_edit(path, valuetype, callback, initial);
};
var current_modal; /* TODO stack ?*/
function show_modal() {
let [element, parent] = detachable_elements[this.element.id];
tmpgrp = document.createElementNS(xmlns,"g");
tmpgrpattr = document.createAttribute("transform");
let [xcoord,ycoord] = this.coordinates;
let [xdest,ydest] = page_desc[current_visible_page].bbox;
tmpgrpattr.value = "translate("+String(xdest-xcoord)+","+String(ydest-ycoord)+")";
tmpgrp.setAttributeNode(tmpgrpattr);
tmpgrp.appendChild(element);
parent.appendChild(tmpgrp);
current_modal = [this.element.id, tmpgrp];
};
function end_modal() {
let [eltid, tmpgrp] = current_modal;
let [element, parent] = detachable_elements[this.element.id];
parent.removeChild(tmpgrp);
current_modal = undefined;
};
]]></script></body></html>
\ No newline at end of file
<?xml version='1.0' encoding='utf-8'?>
<BaseParams xmlns:xsd="http://www.w3.org/2001/XMLSchema" IEC_Channel="1" Name="opcua_0"/>
<?xml version='1.0' encoding='utf-8'?>
<OPCUAClient xmlns:xsd="http://www.w3.org/2001/XMLSchema" Server_URI="opc.tcp://192.168.0.44:4840"/>
input,"LocalizedText(Encoding:3, Locale:en-US, Text:Relay 0)",1,str,i2c0.relay0,Int32,0
input,"LocalizedText(Encoding:3, Locale:en-US, Text:Relay 1)",1,str,i2c0.relay1,Int32,1
input,"LocalizedText(Encoding:3, Locale:en-US, Text:Relay 2)",1,str,i2c0.relay2,Int32,2
input,"LocalizedText(Encoding:3, Locale:en-US, Text:Relay 3)",1,str,i2c0.relay3,Int32,3
output,"LocalizedText(Encoding:3, Locale:en-US, Text:Relay 0)",1,str,i2c0.relay0,Int32,0
output,"LocalizedText(Encoding:3, Locale:en-US, Text:Relay 1)",1,str,i2c0.relay1,Int32,1
output,"LocalizedText(Encoding:3, Locale:en-US, Text:Relay 2)",1,str,i2c0.relay2,Int32,2
output,"LocalizedText(Encoding:3, Locale:en-US, Text:Relay 3)",1,str,i2c0.relay3,Int32,3
<?xml version='1.0' encoding='utf-8'?>
<project xmlns:ns1="http://www.plcopen.org/xml/tc6_0201" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.plcopen.org/xml/tc6_0201">
<fileHeader companyName="Unknown" productName="Unnamed" productVersion="1" creationDateTime="2021-05-14T14:33:11"/>
<contentHeader name="Counter (OSIE)" modificationDateTime="2022-07-09T08:44:59">
<coordinateInfo>
<fbd>
<scaling x="0" y="0"/>
</fbd>
<ld>
<scaling x="0" y="0"/>
</ld>
<sfc>
<scaling x="0" y="0"/>
</sfc>
</coordinateInfo>
</contentHeader>
<types>
<dataTypes/>
<pous>
<pou name="plc_prg" pouType="program">
<interface>
<localVars>
<variable name="CounterST0">
<type>
<derived name="CounterST"/>
</type>
</variable>
</localVars>
</interface>
<body>
<FBD>
<comment localId="1" height="143" width="201">
<position x="556" y="168"/>
<content>
<xhtml:p><![CDATA[This PLC will switch ON / OFF attached over OPC UA relays {0..3} of a Lime2 coupler.]]></xhtml:p>
</content>
</comment>
<block localId="2" typeName="CounterST" instanceName="CounterST0" executionOrderId="0" height="109" width="99">
<position x="288" y="192"/>
<inputVariables>
<variable formalParameter="Reset">
<connectionPointIn>
<relPosition x="0" y="42"/>
<connection refLocalId="3">
<position x="288" y="234"/>
<position x="258" y="234"/>
<position x="258" y="218"/>
<position x="231" y="218"/>
</connection>
</connectionPointIn>
</variable>
</inputVariables>
<inOutVariables/>
<outputVariables>
<variable formalParameter="Out0">
<connectionPointOut>
<relPosition x="99" y="42"/>
</connectionPointOut>
</variable>
<variable formalParameter="Out1">
<connectionPointOut>
<relPosition x="99" y="86"/>
</connectionPointOut>
</variable>
</outputVariables>
</block>
</FBD>
</body>
</pou>
<pou name="CounterST" pouType="functionBlock">
<interface>
<localVars>
<variable name="Cnt0">
<type>
<INT/>
</type>
</variable>
</localVars>
<externalVars>
<variable name="Relay0">
<type>
<DINT/>
</type>
</variable>
<variable name="Relay1">
<type>
<DINT/>
</type>
</variable>
<variable name="Relay2">
<type>
<DINT/>
</type>
</variable>
<variable name="Relay3">
<type>
<DINT/>
</type>
</variable>
<variable name="HMI_RELAY0">
<type>
<derived name="HMI_INT"/>
</type>
</variable>
<variable name="HMI_RELAY1">
<type>
<derived name="HMI_INT"/>
</type>
</variable>
<variable name="HMI_RELAY2">
<type>
<derived name="HMI_INT"/>
</type>
</variable>
<variable name="HMI_RELAY3">
<type>
<derived name="HMI_INT"/>
</type>
</variable>
</externalVars>
</interface>
<body>
<ST>
<xhtml:p><![CDATA[IF HMI_RELAY2 >= 1 THEN
Relay2:=1;
ELSE
Relay2:=0;
END_IF;
IF HMI_RELAY3 >= 1 THEN
Relay3:=1;
ELSE
Relay3:=0;
END_IF;
Cnt0 := Cnt0 + 1;
IF Cnt0 = 50 THEN
Relay0 := 1;
HMI_RELAY0:= 1;
Relay1 := 1;
HMI_RELAY1:= 1;
END_IF;
IF Cnt0 = 100 THEN
Relay0 := 0;
HMI_RELAY0:= 0;
Relay1 := 0;
HMI_RELAY1:= 0;
Cnt0 := 0;
END_IF;
]]></xhtml:p>
</ST>
</body>
</pou>
</pous>
</types>
<instances>
<configurations>
<configuration name="config">
<resource name="resource1">
<task name="task0" priority="0" interval="T#20ms">
<pouInstance name="instance0" typeName="plc_prg"/>
</task>
</resource>
<globalVars>
<variable name="Relay0" address="%QD1.0">
<type>
<DINT/>
</type>
<initialValue>
<simpleValue value="0"/>
</initialValue>
<documentation>
<xhtml:p><![CDATA[[I2C0] OPC-UA relay 0]]></xhtml:p>
</documentation>
</variable>
<variable name="Relay1" address="%QD1.1">
<type>
<DINT/>
</type>
<initialValue>
<simpleValue value="0"/>
</initialValue>
<documentation>
<xhtml:p><![CDATA[[I2C0] OPC-UA relay 1]]></xhtml:p>
</documentation>
</variable>
<variable name="Relay2" address="%QD1.2">
<type>
<DINT/>
</type>
<initialValue>
<simpleValue value="0"/>
</initialValue>
<documentation>
<xhtml:p><![CDATA[[I2C0] OPC-UA relay 2]]></xhtml:p>
</documentation>
</variable>
<variable name="Relay3" address="%QD1.3">
<type>
<DINT/>
</type>
<initialValue>
<simpleValue value="0"/>
</initialValue>
<documentation>
<xhtml:p><![CDATA[[I2C0] OPC-UA relay 2]]></xhtml:p>
</documentation>
</variable>
<variable name="HMI_RELAY0">
<type>
<derived name="HMI_INT"/>
</type>
<initialValue>
<simpleValue value="0"/>
</initialValue>
<documentation>
<xhtml:p><![CDATA[HMI Relay 0]]></xhtml:p>
</documentation>
</variable>
<variable name="HMI_RELAY1">
<type>
<derived name="HMI_INT"/>
</type>
<initialValue>
<simpleValue value="0"/>
</initialValue>
<documentation>
<xhtml:p><![CDATA[HMI Relay 1]]></xhtml:p>
</documentation>
</variable>
<variable name="HMI_RELAY2">
<type>
<derived name="HMI_INT"/>
</type>
<initialValue>
<simpleValue value="0"/>
</initialValue>
<documentation>
<xhtml:p><![CDATA[HMI Relay 2]]></xhtml:p>
</documentation>
</variable>
<variable name="HMI_RELAY3">
<type>
<derived name="HMI_INT"/>
</type>
<initialValue>
<simpleValue value="0"/>
</initialValue>
<documentation>
<xhtml:p><![CDATA[HMI Relay 3]]></xhtml:p>
</documentation>
</variable>
</globalVars>
</configuration>
</configurations>
</instances>
</project>
<?xml version='1.0' encoding='utf-8'?>
<BaseParams xmlns:xsd="http://www.w3.org/2001/XMLSchema" IEC_Channel="0" Name="svghmi_0"/>
<?xml version='1.0' encoding='utf-8'?>
<SVGHMI xmlns:xsd="http://www.w3.org/2001/XMLSchema" EnableWatchdog="true"/>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"
sodipodi:docname="svghmi.svg"
id="hmi0"
version="1.1"
viewBox="0 0 1280 720"
height="720"
width="1280">
<metadata
id="metadata8">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs6" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1015"
id="namedview4"
showgrid="false"
inkscape:zoom="1.418633"
inkscape:cx="625.37152"
inkscape:cy="419.21141"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="hmi0"
showguides="false" />
<rect
inkscape:label="HMI:Page:Home"
y="-1.9937692"
x="-7.9750781"
height="720"
width="1280"
id="rect1016"
style="color:#000000;opacity:1;fill:#d6d6d6;fill-opacity:1" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:39.52233505px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.98805833"
x="552.8136"
y="109.08164"
id="text35"
transform="scale(0.98805833,1.012086)"><tspan
sodipodi:role="line"
id="tspan33"
x="552.8136"
y="109.08164"
style="stroke-width:0.98805833">Relay 0</tspan></text>
<g
id="g446"
transform="matrix(0.2859027,0,0,0.2859027,709.48026,45.693658)"
inkscape:label="HMI:Input@/HMI_RELAY0">
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:160px;line-height:125%;font-family:sans-serif;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#ff8c00;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="216.32812"
y="218.24219"
id="text432"
inkscape:label="value"><tspan
sodipodi:role="line"
id="tspan430"
x="216.32812"
y="218.24219"
style="text-align:end;text-anchor:end;fill:#ff8c00;stroke-width:1px">8</tspan></text>
<path
transform="scale(1,-1)"
sodipodi:type="star"
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#ff6600;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
id="path436"
sodipodi:sides="3"
sodipodi:cx="276.74072"
sodipodi:cy="-224.98808"
sodipodi:r1="29.912722"
sodipodi:r2="14.956361"
sodipodi:arg1="0.52359878"
sodipodi:arg2="1.5707963"
inkscape:flatsided="true"
inkscape:rounded="0"
inkscape:randomized="0"
d="m 302.6459,-210.03172 -51.81035,0 25.90517,-44.86908 z"
inkscape:transform-center-y="7.4781812"
inkscape:label="-1" />
<rect
inkscape:label="edit"
onclick=""
y="95.40741"
x="1.8178837"
height="128"
width="230.94511"
id="rect438"
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ff00ff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
<path
inkscape:label="+1"
inkscape:transform-center-y="-7.4781804"
d="m 302.6459,111.4008 -51.81035,0 25.90517,-44.869079 z"
inkscape:randomized="0"
inkscape:rounded="0"
inkscape:flatsided="true"
sodipodi:arg2="1.5707963"
sodipodi:arg1="0.52359878"
sodipodi:r2="14.956361"
sodipodi:r1="29.912722"
sodipodi:cy="96.444443"
sodipodi:cx="276.74072"
sodipodi:sides="3"
id="path442"
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#ff6600;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
sodipodi:type="star" />
<path
inkscape:label="=0"
inkscape:transform-center-y="-10.828983"
d="m 306.14807,189.68763 -58.37872,0.43598 -0.43597,-58.37872 58.37871,-0.43597 z"
inkscape:randomized="0"
inkscape:rounded="0"
inkscape:flatsided="true"
sodipodi:arg2="1.5633284"
sodipodi:arg1="0.77793027"
sodipodi:r2="21.657967"
sodipodi:r1="41.281136"
sodipodi:cy="160.71626"
sodipodi:cx="276.74072"
sodipodi:sides="4"
id="path444"
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#ff6600;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
sodipodi:type="star"
inkscape:transform-center-x="1.0089177e-06" />
</g>
<g
id="g446-3"
transform="matrix(0.2859027,0,0,0.2859027,709.48026,145.69366)"
inkscape:label="HMI:Input@/HMI_RELAY1">
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:160px;line-height:125%;font-family:sans-serif;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#ff8c00;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="216.32812"
y="218.24219"
id="text432-6"
inkscape:label="value"><tspan
sodipodi:role="line"
id="tspan430-7"
x="216.32812"
y="218.24219"
style="text-align:end;text-anchor:end;fill:#ff8c00;stroke-width:1px">8</tspan></text>
<path
transform="scale(1,-1)"
sodipodi:type="star"
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#ff6600;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
id="path436-5"
sodipodi:sides="3"
sodipodi:cx="276.74072"
sodipodi:cy="-224.98808"
sodipodi:r1="29.912722"
sodipodi:r2="14.956361"
sodipodi:arg1="0.52359878"
sodipodi:arg2="1.5707963"
inkscape:flatsided="true"
inkscape:rounded="0"
inkscape:randomized="0"
d="m 302.6459,-210.03172 -51.81035,0 25.90517,-44.86908 z"
inkscape:transform-center-y="7.4781812"
inkscape:label="-1" />
<rect
inkscape:label="edit"
onclick=""
y="95.40741"
x="1.8178837"
height="128"
width="230.94511"
id="rect438-3"
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ff00ff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
<path
inkscape:label="+1"
inkscape:transform-center-y="-7.4781804"
d="m 302.6459,111.4008 -51.81035,0 25.90517,-44.869079 z"
inkscape:randomized="0"
inkscape:rounded="0"
inkscape:flatsided="true"
sodipodi:arg2="1.5707963"
sodipodi:arg1="0.52359878"
sodipodi:r2="14.956361"
sodipodi:r1="29.912722"
sodipodi:cy="96.444443"
sodipodi:cx="276.74072"
sodipodi:sides="3"
id="path442-5"
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#ff6600;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
sodipodi:type="star" />
<path
inkscape:label="=0"
inkscape:transform-center-y="-10.828983"
d="m 306.14807,189.68763 -58.37872,0.43598 -0.43597,-58.37872 58.37871,-0.43597 z"
inkscape:randomized="0"
inkscape:rounded="0"
inkscape:flatsided="true"
sodipodi:arg2="1.5633284"
sodipodi:arg1="0.77793027"
sodipodi:r2="21.657967"
sodipodi:r1="41.281136"
sodipodi:cy="160.71626"
sodipodi:cx="276.74072"
sodipodi:sides="4"
id="path444-6"
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#ff6600;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
sodipodi:type="star"
inkscape:transform-center-x="1.0089177e-06" />
</g>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:40.60216904px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.01505423"
x="537.90454"
y="213.56741"
id="text35-2"
transform="scale(1.0150542,0.98516905)"><tspan
sodipodi:role="line"
id="tspan33-9"
x="537.90454"
y="213.56741"
style="stroke-width:1.01505423">Relay 1</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:39.54086685px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.9885217"
x="552.5509"
y="306.83713"
id="text35-2-3"
transform="scale(0.98852168,1.0116116)"><tspan
sodipodi:role="line"
id="tspan33-9-6"
x="552.5509"
y="306.83713"
style="stroke-width:0.9885217">Relay 2</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:39.57181549px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.98929536"
x="552.11279"
y="406.00681"
id="text35-2-7"
transform="scale(0.98929534,1.0108205)"><tspan
sodipodi:role="line"
id="tspan33-9-5"
x="552.11279"
y="406.00681"
style="stroke-width:0.98929536">Relay 3</tspan></text>
<g
id="g446-35"
transform="matrix(0.2859027,0,0,0.2859027,709.48026,245.69366)"
inkscape:label="HMI:Input@/HMI_RELAY2">
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:160px;line-height:125%;font-family:sans-serif;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#ff8c00;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="216.32812"
y="218.24219"
id="text432-62"
inkscape:label="value"><tspan
sodipodi:role="line"
id="tspan430-9"
x="216.32812"
y="218.24219"
style="text-align:end;text-anchor:end;fill:#ff8c00;stroke-width:1px">8</tspan></text>
<path
transform="scale(1,-1)"
sodipodi:type="star"
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#ff6600;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
id="path436-1"
sodipodi:sides="3"
sodipodi:cx="276.74072"
sodipodi:cy="-224.98808"
sodipodi:r1="29.912722"
sodipodi:r2="14.956361"
sodipodi:arg1="0.52359878"
sodipodi:arg2="1.5707963"
inkscape:flatsided="true"
inkscape:rounded="0"
inkscape:randomized="0"
d="m 302.6459,-210.03172 -51.81035,0 25.90517,-44.86908 z"
inkscape:transform-center-y="7.4781812"
inkscape:label="-1" />
<rect
inkscape:label="edit"
onclick=""
y="95.40741"
x="1.8178837"
height="128"
width="230.94511"
id="rect438-2"
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ff00ff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
<path
inkscape:label="+1"
inkscape:transform-center-y="-7.4781804"
d="m 302.6459,111.4008 -51.81035,0 25.90517,-44.869079 z"
inkscape:randomized="0"
inkscape:rounded="0"
inkscape:flatsided="true"
sodipodi:arg2="1.5707963"
sodipodi:arg1="0.52359878"
sodipodi:r2="14.956361"
sodipodi:r1="29.912722"
sodipodi:cy="96.444443"
sodipodi:cx="276.74072"
sodipodi:sides="3"
id="path442-7"
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#ff6600;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
sodipodi:type="star" />
<path
inkscape:label="=0"
inkscape:transform-center-y="-10.828983"
d="m 306.14807,189.68763 -58.37872,0.43598 -0.43597,-58.37872 58.37871,-0.43597 z"
inkscape:randomized="0"
inkscape:rounded="0"
inkscape:flatsided="true"
sodipodi:arg2="1.5633284"
sodipodi:arg1="0.77793027"
sodipodi:r2="21.657967"
sodipodi:r1="41.281136"
sodipodi:cy="160.71626"
sodipodi:cx="276.74072"
sodipodi:sides="4"
id="path444-0"
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#ff6600;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
sodipodi:type="star"
inkscape:transform-center-x="1.0089177e-06" />
</g>
<g
id="g446-9"
transform="matrix(0.2859027,0,0,0.2859027,709.48026,345.69366)"
inkscape:label="HMI:Input@/HMI_RELAY3">
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:160px;line-height:125%;font-family:sans-serif;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#ff8c00;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="216.32812"
y="218.24219"
id="text432-3"
inkscape:label="value"><tspan
sodipodi:role="line"
id="tspan430-6"
x="216.32812"
y="218.24219"
style="text-align:end;text-anchor:end;fill:#ff8c00;stroke-width:1px">8</tspan></text>
<path
transform="scale(1,-1)"
sodipodi:type="star"
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#ff6600;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
id="path436-0"
sodipodi:sides="3"
sodipodi:cx="276.74072"
sodipodi:cy="-224.98808"
sodipodi:r1="29.912722"
sodipodi:r2="14.956361"
sodipodi:arg1="0.52359878"
sodipodi:arg2="1.5707963"
inkscape:flatsided="true"
inkscape:rounded="0"
inkscape:randomized="0"
d="m 302.6459,-210.03172 -51.81035,0 25.90517,-44.86908 z"
inkscape:transform-center-y="7.4781812"
inkscape:label="-1" />
<rect
inkscape:label="edit"
onclick=""
y="95.40741"
x="1.8178837"
height="128"
width="230.94511"
id="rect438-6"
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ff00ff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
<path
inkscape:label="+1"
inkscape:transform-center-y="-7.4781804"
d="m 302.6459,111.4008 -51.81035,0 25.90517,-44.869079 z"
inkscape:randomized="0"
inkscape:rounded="0"
inkscape:flatsided="true"
sodipodi:arg2="1.5707963"
sodipodi:arg1="0.52359878"
sodipodi:r2="14.956361"
sodipodi:r1="29.912722"
sodipodi:cy="96.444443"
sodipodi:cx="276.74072"
sodipodi:sides="3"
id="path442-2"
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#ff6600;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
sodipodi:type="star" />
<path
inkscape:label="=0"
inkscape:transform-center-y="-10.828983"
d="m 306.14807,189.68763 -58.37872,0.43598 -0.43597,-58.37872 58.37871,-0.43597 z"
inkscape:randomized="0"
inkscape:rounded="0"
inkscape:flatsided="true"
sodipodi:arg2="1.5633284"
sodipodi:arg1="0.77793027"
sodipodi:r2="21.657967"
sodipodi:r1="41.281136"
sodipodi:cy="160.71626"
sodipodi:cx="276.74072"
sodipodi:sides="4"
id="path444-61"
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:#ff6600;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
sodipodi:type="star"
inkscape:transform-center-x="1.0089177e-06" />
</g>
</svg>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment