Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
osie
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Nikola Balog
osie
Commits
e07b66ab
Commit
e07b66ab
authored
Jul 12, 2022
by
Ivan Tyagov
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Cleanup.
parent
fe8d7440
Changes
24
Hide whitespace changes
Inline
Side-by-side
Showing
24 changed files
with
0 additions
and
6931 deletions
+0
-6931
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/LOCATED_VARIABLES.h
.../beremiz_tutorial_svghmi_opc_ua/build/LOCATED_VARIABLES.h
+0
-4
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/POUS.c
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/POUS.c
+0
-283
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/POUS.h
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/POUS.h
+0
-144
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/VARIABLES.csv
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/VARIABLES.csv
+0
-32
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/beremiz.h
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/beremiz.h
+0
-35
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/config.c
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/config.c
+0
-58
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/config.h
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/config.h
+0
-13
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/extra_files/runtime_00_svghmi.py
...rial_svghmi_opc_ua/build/extra_files/runtime_00_svghmi.py
+0
-301
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/extra_files/runtime_0_svghmi.py
...orial_svghmi_opc_ua/build/extra_files/runtime_0_svghmi.py
+0
-65
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/extra_files/svghmi_0.xhtml
...z_tutorial_svghmi_opc_ua/build/extra_files/svghmi_0.xhtml
+0
-1439
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/generated_plc.st
...miz/beremiz_tutorial_svghmi_opc_ua/build/generated_plc.st
+0
-74
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/hmitree.xml
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/hmitree.xml
+0
-1
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/lastbuildPLC.md5
...miz/beremiz_tutorial_svghmi_opc_ua/build/lastbuildPLC.md5
+0
-1
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/opcua_client__1.c
...iz/beremiz_tutorial_svghmi_opc_ua/build/opcua_client__1.c
+0
-91
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/plc.st
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/plc.st
+0
-178
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/plc_debugger.c
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/plc_debugger.c
+0
-565
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/plc_main.c
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/plc_main.c
+0
-959
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/py_ext.c
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/py_ext.c
+0
-221
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/resource1.c
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/resource1.c
+0
-37
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/runtime_00_svghmi.py
...beremiz_tutorial_svghmi_opc_ua/build/runtime_00_svghmi.py
+0
-301
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/runtime_0_svghmi_.py
...beremiz_tutorial_svghmi_opc_ua/build/runtime_0_svghmi_.py
+0
-65
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/svghmi.c
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/svghmi.c
+0
-624
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/svghmi_0.md5
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/svghmi_0.md5
+0
-1
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/svghmi_0.xhtml
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/svghmi_0.xhtml
+0
-1439
No files found.
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/LOCATED_VARIABLES.h
deleted
100644 → 0
View file @
fe8d7440
__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
)
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/POUS.c
deleted
100644 → 0
View file @
fe8d7440
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__()
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/POUS.h
deleted
100644 → 0
View file @
fe8d7440
#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
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/VARIABLES.csv
deleted
100644 → 0
View file @
fe8d7440
// 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
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/beremiz.h
deleted
100644 → 0
View file @
fe8d7440
#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
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/config.c
deleted
100644 → 0
View file @
fe8d7440
/*******************************************/
/* 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*/
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/config.h
deleted
100644 → 0
View file @
fe8d7440
#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
)
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/extra_files/runtime_00_svghmi.py
deleted
100644 → 0
View file @
fe8d7440
#!/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
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/extra_files/runtime_0_svghmi.py
deleted
100644 → 0
View file @
fe8d7440
# 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
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/extra_files/svghmi_0.xhtml
deleted
100644 → 0
View file @
fe8d7440
<!--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
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/generated_plc.st
deleted
100644 → 0
View file @
fe8d7440
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
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/hmitree.xml
deleted
100644 → 0
View file @
fe8d7440
<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
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/lastbuildPLC.md5
deleted
100644 → 0
View file @
fe8d7440
0d60a23d82a69794d34cd693de3cac7f
\ No newline at end of file
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/opcua_client__1.c
deleted
100644 → 0
View file @
fe8d7440
/* 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"
)
}
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/plc.st
deleted
100644 → 0
View file @
fe8d7440
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
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/plc_debugger.c
deleted
100644 → 0
View file @
fe8d7440
/*
* 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
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/plc_main.c
deleted
100644 → 0
View file @
fe8d7440
/**
* 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
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/py_ext.c
deleted
100644 → 0
View file @
fe8d7440
/*
* 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
;
}
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/resource1.c
deleted
100644 → 0
View file @
fe8d7440
/*******************************************/
/* 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
);
}
}
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/runtime_00_svghmi.py
deleted
100644 → 0
View file @
fe8d7440
#!/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
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/runtime_0_svghmi_.py
deleted
100644 → 0
View file @
fe8d7440
# 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
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/svghmi.c
deleted
100644 → 0
View file @
fe8d7440
#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
;
}
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/svghmi_0.md5
deleted
100644 → 0
View file @
fe8d7440
1e07dadd9175d71c49bda502ce3d71b8
\ No newline at end of file
Beremiz/beremiz_tutorial_svghmi_opc_ua/build/svghmi_0.xhtml
deleted
100644 → 0
View file @
fe8d7440
<!--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
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment