diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 000000000..919ce1f1f
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
index 7e340a776..9a55c2de1 100644
--- a/.idea/kotlinc.xml
+++ b/.idea/kotlinc.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/.idea/libraries/ant_antlr.xml b/.idea/libraries/ant_antlr.xml
new file mode 100644
index 000000000..59652667b
--- /dev/null
+++ b/.idea/libraries/ant_antlr.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/libraries/ant_launcher.xml b/.idea/libraries/ant_launcher.xml
new file mode 100644
index 000000000..ef0794b5f
--- /dev/null
+++ b/.idea/libraries/ant_launcher.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/libraries/apache_ant_junit.xml b/.idea/libraries/apache_ant_junit.xml
new file mode 100644
index 000000000..9b11bfcbb
--- /dev/null
+++ b/.idea/libraries/apache_ant_junit.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.mps/encodings.xml b/.mps/encodings.xml
deleted file mode 100644
index 15a15b218..000000000
--- a/.mps/encodings.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/.mps/modules.xml b/.mps/modules.xml
index 995e49edc..a08ecb86d 100644
--- a/.mps/modules.xml
+++ b/.mps/modules.xml
@@ -31,6 +31,7 @@
+
\ No newline at end of file
diff --git a/.mps/runConfigurations/MPS_with_IEC_61499.xml b/.mps/runConfigurations/MPS_with_IEC_61499.xml
index 20ed40dd2..d787223d3 100644
--- a/.mps/runConfigurations/MPS_with_IEC_61499.xml
+++ b/.mps/runConfigurations/MPS_with_IEC_61499.xml
@@ -4,15 +4,13 @@
-
+
diff --git a/build-bootstrap.xml b/build-bootstrap.xml
index 4f552809e..2416f0bd9 100644
--- a/build-bootstrap.xml
+++ b/build-bootstrap.xml
@@ -148,6 +148,9 @@
+
+
+
diff --git a/code/4diac-integration/solutions/org.fbme.ide.integration.fordiac/models/org.fbme.ide.integration.fordiac.mps b/code/4diac-integration/solutions/org.fbme.ide.integration.fordiac/models/org.fbme.ide.integration.fordiac.mps
index aa5d31040..85d87ef57 100644
--- a/code/4diac-integration/solutions/org.fbme.ide.integration.fordiac/models/org.fbme.ide.integration.fordiac.mps
+++ b/code/4diac-integration/solutions/org.fbme.ide.integration.fordiac/models/org.fbme.ide.integration.fordiac.mps
@@ -474,6 +474,7 @@
+
@@ -487,6 +488,7 @@
+
@@ -3052,6 +3054,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/code/4diac-integration/solutions/stdlib/models/iec61499.4diac.stdlib/hmi-blocks/JSON_DESERIALIZER.fbt b/code/4diac-integration/solutions/stdlib/models/iec61499.4diac.stdlib/hmi-blocks/JSON_DESERIALIZER.fbt
new file mode 100644
index 000000000..9d8cc093e
--- /dev/null
+++ b/code/4diac-integration/solutions/stdlib/models/iec61499.4diac.stdlib/hmi-blocks/JSON_DESERIALIZER.fbt
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/code/4diac-integration/solutions/stdlib/models/iec61499.4diac.stdlib/hmi-blocks/JSON_FORMER.fbt b/code/4diac-integration/solutions/stdlib/models/iec61499.4diac.stdlib/hmi-blocks/JSON_FORMER.fbt
new file mode 100644
index 000000000..c4d253659
--- /dev/null
+++ b/code/4diac-integration/solutions/stdlib/models/iec61499.4diac.stdlib/hmi-blocks/JSON_FORMER.fbt
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/code/4diac-integration/solutions/stdlib/models/iec61499.4diac.stdlib/hmi-blocks/JSON_PARSER.fbt b/code/4diac-integration/solutions/stdlib/models/iec61499.4diac.stdlib/hmi-blocks/JSON_PARSER.fbt
new file mode 100644
index 000000000..0ec15d196
--- /dev/null
+++ b/code/4diac-integration/solutions/stdlib/models/iec61499.4diac.stdlib/hmi-blocks/JSON_PARSER.fbt
@@ -0,0 +1,108 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/code/4diac-integration/solutions/stdlib/models/iec61499.4diac.stdlib/hmi-blocks/JSON_SERIALIZER.fbt b/code/4diac-integration/solutions/stdlib/models/iec61499.4diac.stdlib/hmi-blocks/JSON_SERIALIZER.fbt
new file mode 100644
index 000000000..280f68cd2
--- /dev/null
+++ b/code/4diac-integration/solutions/stdlib/models/iec61499.4diac.stdlib/hmi-blocks/JSON_SERIALIZER.fbt
@@ -0,0 +1,89 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/code/4diac-integration/solutions/stdlib/models/iec61499.4diac.stdlib/hmi-blocks/TYPE_DETECTOR.fbt b/code/4diac-integration/solutions/stdlib/models/iec61499.4diac.stdlib/hmi-blocks/TYPE_DETECTOR.fbt
new file mode 100644
index 000000000..ea9ffb7af
--- /dev/null
+++ b/code/4diac-integration/solutions/stdlib/models/iec61499.4diac.stdlib/hmi-blocks/TYPE_DETECTOR.fbt
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/code/4diac-integration/solutions/stdlib/models/iec61499.4diac.stdlib/hmi-blocks/TYPE_SERIALIZER.fbt b/code/4diac-integration/solutions/stdlib/models/iec61499.4diac.stdlib/hmi-blocks/TYPE_SERIALIZER.fbt
new file mode 100644
index 000000000..bdc97c05d
--- /dev/null
+++ b/code/4diac-integration/solutions/stdlib/models/iec61499.4diac.stdlib/hmi-blocks/TYPE_SERIALIZER.fbt
@@ -0,0 +1,94 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/code/cat_visual/README.md b/code/cat_visual/README.md
new file mode 100644
index 000000000..a2cb49d34
--- /dev/null
+++ b/code/cat_visual/README.md
@@ -0,0 +1,14 @@
+# compose_template
+
+Виды примитивных блоков для поддержки
+* checkbox
+* radio
+* text choice
+* different toggles
+
+Interfaces:
+* Zoomable
+* Positioned
+* ZIndexed
+* Grouped
+*
\ No newline at end of file
diff --git a/code/cat_visual/build.gradle.kts b/code/cat_visual/build.gradle.kts
new file mode 100644
index 000000000..c92353dac
--- /dev/null
+++ b/code/cat_visual/build.gradle.kts
@@ -0,0 +1,40 @@
+import org.jetbrains.compose.desktop.application.dsl.TargetFormat
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+plugins {
+ kotlin("jvm") version "1.8.0"
+ id("org.jetbrains.compose") version "1.3.1"
+ kotlin("plugin.serialization") version "1.8.0"
+}
+
+group = "fbme"
+version = "1.0"
+
+repositories {
+ google()
+ mavenCentral()
+ maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
+}
+
+dependencies {
+ implementation(compose.desktop.currentOs)
+ implementation("com.hierynomus:asn-one:0.5.0")
+ implementation(kotlin("stdlib-jdk8"))
+ implementation("io.github.pdvrieze.xmlutil:serialization-jvm:0.84.3")
+ implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
+}
+
+tasks.withType {
+ kotlinOptions.jvmTarget = "11"
+}
+
+compose.desktop {
+ application {
+ mainClass = "MainKt"
+ nativeDistributions {
+ targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
+ packageName = "compose"
+ packageVersion = "1.0.0"
+ }
+ }
+}
\ No newline at end of file
diff --git a/code/cat_visual/gradle.properties b/code/cat_visual/gradle.properties
new file mode 100644
index 000000000..7fc6f1ff2
--- /dev/null
+++ b/code/cat_visual/gradle.properties
@@ -0,0 +1 @@
+kotlin.code.style=official
diff --git a/code/cat_visual/gradlew b/code/cat_visual/gradlew
new file mode 100755
index 000000000..744e882ed
--- /dev/null
+++ b/code/cat_visual/gradlew
@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MSYS* | MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=`expr $i + 1`
+ done
+ case $i in
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/code/cat_visual/gradlew.bat b/code/cat_visual/gradlew.bat
new file mode 100644
index 000000000..ac1b06f93
--- /dev/null
+++ b/code/cat_visual/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/code/cat_visual/settings.gradle.kts b/code/cat_visual/settings.gradle.kts
new file mode 100644
index 000000000..bbba57708
--- /dev/null
+++ b/code/cat_visual/settings.gradle.kts
@@ -0,0 +1,10 @@
+pluginManagement {
+ repositories {
+ google()
+ gradlePluginPortal()
+ maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
+ }
+
+}
+rootProject.name = "compose"
+
diff --git a/code/cat_visual/src/main/kotlin/Main.kt b/code/cat_visual/src/main/kotlin/Main.kt
new file mode 100644
index 000000000..f5710a74d
--- /dev/null
+++ b/code/cat_visual/src/main/kotlin/Main.kt
@@ -0,0 +1,79 @@
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Scaffold
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.snapshots.SnapshotStateList
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Window
+import androidx.compose.ui.window.application
+import androidx.compose.ui.window.rememberWindowState
+import canvas.items.CustomItem
+import connection.*
+import connection.field.BoolField
+import connection.field.TYPE_ID
+import example.COUNTER.CounterLampHMI
+import example.WATER_TANK.System
+import example.WATER_TANK.WaterTank
+import serializer.PlainMapping
+import serializer.getConf
+import serializer.getMapping
+import serializer.getPlainMapping
+import java.io.BufferedReader
+import java.io.File
+import java.io.InputStreamReader
+import java.net.DatagramPacket
+import java.net.InetAddress
+import java.net.ServerSocket
+
+@Composable
+fun CanvasContext(listFigures: SnapshotStateList) {
+ Scaffold { innerPadding ->
+ Box(Modifier.fillMaxHeight().fillMaxWidth().background(Color.Gray)) {
+ listFigures.forEach { f -> f.create() }
+ }
+ }
+}
+
+fun buildMappingClient(modelFile: String, configFile: String = "", mode: String = ""): AbstractClient {
+ if (mode.equals("plain")) {
+ val modelText = File(modelFile).readText()
+ val mapping = getPlainMapping(modelText)
+
+ return PlainClient(mapping)
+ }
+
+ val modelText = File(modelFile).readText()
+ val mapping = getMapping(modelText)
+ val configText = File(configFile).readText()
+ val conf = getConf(configText)
+
+ if (mode.equals("json")) {
+ return JSONClient(mapping, conf)
+ }
+ return NamedClient(mapping, conf)
+}
+
+//val client = buildMappingClient("src/main/kotlin/example/COUNTER/COUNTER.xml", "src/main/kotlin/example/COUNTER/COUNTER_CONF.xml", "")
+val client = buildMappingClient("src/main/kotlin/example/COUNTER/COUNTER.xml", "src/main/kotlin/example/COUNTER/COMMON_CONF.xml", "json")
+//val client = buildMappingClient("src/main/kotlin/example/WATER_TANK/WATER_TANK.xml", "src/main/kotlin/example/WATER_TANK/WATER_TANK_CONF.xml", "")
+//val client = buildMappingClient("src/main/kotlin/example/WATER_TANK/WATER_TANK.xml", "src/main/kotlin/example/WATER_TANK/COMMON_CONF.xml", "json")
+
+
+fun main() = application {
+ Window(
+ onCloseRequest = ::exitApplication,
+ title = "Canvas for CAT",
+ state = rememberWindowState(width = 600.dp, height = 600.dp)
+ ) {
+ MaterialTheme {
+ CounterLampHMI(client, "1")
+ client.retrieveValues()
+ }
+ }
+}
\ No newline at end of file
diff --git a/code/cat_visual/src/main/kotlin/canvas/Canvas.kt b/code/cat_visual/src/main/kotlin/canvas/Canvas.kt
new file mode 100644
index 000000000..1688219d8
--- /dev/null
+++ b/code/cat_visual/src/main/kotlin/canvas/Canvas.kt
@@ -0,0 +1,16 @@
+import androidx.compose.desktop.ui.tooling.preview.Preview
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material.Button
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.runtime.*
+import androidx.compose.ui.Modifier
+
+@Composable
+fun CustomCanvas() {
+ Canvas(modifier = Modifier.fillMaxSize()) {
+ }
+}
+
diff --git a/code/cat_visual/src/main/kotlin/canvas/UpperPlate.kt b/code/cat_visual/src/main/kotlin/canvas/UpperPlate.kt
new file mode 100644
index 000000000..12a011b5c
--- /dev/null
+++ b/code/cat_visual/src/main/kotlin/canvas/UpperPlate.kt
@@ -0,0 +1,91 @@
+import androidx.compose.desktop.ui.tooling.preview.Preview
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material.Button
+import androidx.compose.material.Checkbox
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.runtime.*
+import androidx.compose.ui.unit.dp
+
+@Composable
+fun UpperPlate() {
+ Row(horizontalArrangement = Arrangement.spacedBy(5.dp)) {
+ PointerButton()
+ }
+}
+@Composable
+@Preview
+fun PointerButton() {
+ var text by remember { mutableStateOf("P") }
+
+ MaterialTheme {
+ Button(onClick = {
+ text = "O"
+ }) {
+ Text(text)
+ }
+ }
+}
+
+@Composable
+@Preview
+fun RectangleButton(onClickFun: () -> Unit) {
+ var text by remember { mutableStateOf("R") }
+
+ MaterialTheme {
+ Button(onClick = onClickFun) {
+ Text(text)
+ }
+ }
+}
+
+
+@Composable
+@Preview
+fun CheckboxButton(onClickFun: () -> Unit) {
+ var text by remember { mutableStateOf("CB") }
+
+ MaterialTheme {
+ Button(onClick = onClickFun) {
+ Text(text)
+ }
+ }
+}
+
+@Composable
+@Preview
+fun RadioButton(onClickFun: () -> Unit) {
+ var text by remember { mutableStateOf("RB") }
+
+ MaterialTheme {
+ Button(onClick = onClickFun) {
+ Text(text)
+ }
+ }
+}
+
+@Composable
+@Preview
+fun ToggleButton(onClickFun: () -> Unit) {
+ var text by remember { mutableStateOf("TB") }
+
+ MaterialTheme {
+ Button(onClick = onClickFun) {
+ Text(text)
+ }
+ }
+}
+
+@Composable
+@Preview
+fun DropdownButton(onClickFun: () -> Unit) {
+ var text by remember { mutableStateOf("DB") }
+
+ MaterialTheme {
+ Button(onClick = onClickFun) {
+ Text(text)
+ }
+ }
+}
diff --git a/code/cat_visual/src/main/kotlin/canvas/items/CustomItem.kt b/code/cat_visual/src/main/kotlin/canvas/items/CustomItem.kt
new file mode 100644
index 000000000..86447e4cf
--- /dev/null
+++ b/code/cat_visual/src/main/kotlin/canvas/items/CustomItem.kt
@@ -0,0 +1,182 @@
+package canvas.items
+
+import androidx.compose.animation.animateContentSize
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.background
+import androidx.compose.foundation.gestures.*
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.selection.selectable
+import androidx.compose.material.RadioButton
+import androidx.compose.material.Text
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.input.pointer.PointerEventPass
+import androidx.compose.ui.input.pointer.PointerEventType
+import androidx.compose.ui.input.pointer.onPointerEvent
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.dp
+
+enum class ItemType {
+ RECTANGLE, RADIO, CHECKBOX
+}
+
+@Composable
+fun ZoomableBox(
+ modifier: Modifier = Modifier,
+ minScale: Float = 0.1f,
+ maxScale: Float = 5f,
+ content: @Composable ZoomableBoxScope.() -> Unit
+) {
+ var scale by remember { mutableStateOf(1f) }
+ var offsetX by remember { mutableStateOf(0f) }
+ var offsetY by remember { mutableStateOf(0f) }
+ var size by remember { mutableStateOf(IntSize(50, 50)) }
+ Box(
+ modifier = modifier
+// .clip(RectangleShape)
+ .onSizeChanged { size = it }
+ .pointerInput(Unit) {
+ detectTransformGestures { _, pan, zoom, _ ->
+ scale = maxOf(minScale, minOf(scale * zoom, maxScale))
+ val maxX = (size.width * (scale - 1)) / 2
+ val minX = -maxX
+ offsetX = maxOf(minX, minOf(maxX, offsetX + pan.x))
+ val maxY = (size.height * (scale - 1)) / 2
+ val minY = -maxY
+ offsetY = maxOf(minY, minOf(maxY, offsetY + pan.y))
+ }
+ }
+ ) {
+ val scope = ZoomableBoxScopeImpl(scale, offsetX, offsetY)
+ scope.content()
+ println(scale)
+ println(offsetX)
+ println(offsetY)
+ println(size)
+ }
+}
+
+interface ZoomableBoxScope {
+ val scale: Float
+ val offsetX: Float
+ val offsetY: Float
+}
+
+private data class ZoomableBoxScopeImpl(
+ override val scale: Float,
+ override val offsetX: Float,
+ override val offsetY: Float
+) : ZoomableBoxScope
+
+
+abstract interface CustomItem {
+ @Composable
+ fun create();
+}
+
+class RectangleCustomItem : CustomItem {
+ var c = Color.Blue
+ @Composable
+ fun RectangleItem() {
+ var color by remember { mutableStateOf(c) }
+ ZoomableBox {
+ Box(
+ modifier = Modifier
+ .graphicsLayer(
+ scaleX = scale,
+ scaleY = scale,
+ translationX = offsetX,
+ translationY = offsetY
+ )
+ .background(color = color)
+ .width(600.dp)
+ .height(600.dp)
+ ) {
+ }
+ }
+
+ }
+
+ @Composable
+ override fun create() {
+ return RectangleItem();
+ }
+}
+
+class CheckboxCustomItem : CustomItem {
+ var c = Color.Blue
+ @Composable
+ fun RectangleItem() {
+ var color by remember { mutableStateOf(c) }
+ ZoomableBox {
+ Box(
+ modifier = Modifier
+ .graphicsLayer(
+ scaleX = scale,
+ scaleY = scale,
+ translationX = offsetX,
+ translationY = offsetY
+ )
+ .background(color = color)
+ .width(600.dp)
+ .height(600.dp)
+ ) {
+ }
+ }
+
+ }
+
+ @Composable
+ override fun create() {
+ return RectangleItem();
+ }
+}
+
+class RadioCustomItem : CustomItem {
+ var c = Color.Blue
+ @Composable
+ fun RadioItem(items: List) {
+ var color by remember { mutableStateOf(c) }
+ var selectedValue by remember { mutableStateOf("") }
+ val isSelectedItem: (String) -> Boolean = { selectedValue == it }
+ val onChangeState: (String) -> Unit = { selectedValue = it }
+ ZoomableBox {
+ items.forEach { item ->
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier.selectable(
+ selected = isSelectedItem(item),
+ onClick = { onChangeState(item) },
+ role = Role.RadioButton
+ ).padding(8.dp)
+ ) {
+ RadioButton(
+ selected = isSelectedItem(item),
+ onClick = null
+ )
+ Text(
+ text = item,
+ modifier = Modifier.fillMaxWidth()
+ )
+ }
+ }
+ }
+
+ }
+
+ @Composable
+ override fun create() {
+ val items = listOf("Item1", "Item2")
+ return RadioItem(items);
+ }
+}
diff --git a/code/cat_visual/src/main/kotlin/connection/ConnectionFieldRegistry.kt b/code/cat_visual/src/main/kotlin/connection/ConnectionFieldRegistry.kt
new file mode 100644
index 000000000..96b524370
--- /dev/null
+++ b/code/cat_visual/src/main/kotlin/connection/ConnectionFieldRegistry.kt
@@ -0,0 +1,45 @@
+package connection
+
+import connection.field.ConnectionField
+import connection.field.TYPE_ID
+import connection.provider.ConnectionProvider
+import connection.provider.UDPConnectionProvider
+
+
+class ConnectionFieldRegistry {
+ private val providerRegistry: MutableMap, ConnectionProvider> = mutableMapOf()
+ private val fieldRegistry: MutableMap,ConnectionProvider> > = mutableMapOf()
+
+ fun getConnection(name: String, type: TYPE_ID, host: String, port: Int): Pair,ConnectionProvider> {
+ if (name in fieldRegistry) {
+ return fieldRegistry[name]!!
+ }
+ val providerAddress = Pair(host, port)
+ val provider = providerRegistry.computeIfAbsent(providerAddress, { UDPConnectionProvider(port, host) })
+ val field = ConnectionField.create(type)
+ val connection = Pair(field, provider)
+ fieldRegistry[name] = connection
+ return connection
+ }
+
+ fun getConnection(name: String): Pair,ConnectionProvider>? {
+ if (name in fieldRegistry) {
+ return fieldRegistry[name]!!
+ }
+ return null
+ }
+
+ fun getField(name: String): ConnectionField? {
+ if (name in fieldRegistry) {
+ return fieldRegistry[name]!!.first
+ }
+ return null
+ }
+
+ fun getConnector(hostPort: Pair): ConnectionProvider? {
+ if (hostPort in providerRegistry) {
+ return providerRegistry[hostPort]!!
+ }
+ return null
+ }
+}
\ No newline at end of file
diff --git a/code/cat_visual/src/main/kotlin/connection/clients.kt b/code/cat_visual/src/main/kotlin/connection/clients.kt
new file mode 100644
index 000000000..99a76bee1
--- /dev/null
+++ b/code/cat_visual/src/main/kotlin/connection/clients.kt
@@ -0,0 +1,174 @@
+package connection
+
+import connection.field.ConnectionField
+import connection.field.StringField
+import connection.field.TYPE_ID
+import connection.provider.ConnectionProvider
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.decodeFromString
+import kotlinx.serialization.encodeToString
+import kotlinx.serialization.json.Json
+import serializer.Conf
+import serializer.Mapping
+import serializer.PlainMapping
+import java.nio.ByteBuffer
+
+abstract class AbstractClient() {
+ abstract fun getField(name: String): ConnectionField?;
+ abstract fun sendValue(name: String)
+ abstract fun retrieveValues(callbacks: Map Unit> = mapOf(), isInput: Boolean = true)
+}
+
+abstract class UDPClient(): AbstractClient() {
+ protected val registry: ConnectionFieldRegistry = ConnectionFieldRegistry()
+ val inputs: MutableSet = mutableSetOf()
+ val outputs: MutableSet = mutableSetOf()
+ val inputConnections: MutableList = mutableListOf()
+ val outputConnections: MutableList = mutableListOf()
+
+ override fun getField(name: String): ConnectionField? {
+ return registry.getConnection(name)?.first
+ }
+}
+
+class PlainClient(mapping: PlainMapping): UDPClient() {
+ init {
+ mapping.inputs.inputs.forEach({
+ registry.getConnection(it.name, TYPE_ID.valueOf(it.type), it.host, it.port)
+ inputs.add(it.name)
+ inputConnections.add(registry.getConnection(it.name)!!.second)
+ })
+ mapping.outputs.outputs.forEach({
+ registry.getConnection(it.name, TYPE_ID.valueOf(it.type), it.host, it.port)
+ outputs.add(it.name)
+ outputConnections.add(registry.getConnection(it.name)!!.second)
+ })
+ }
+
+ override fun sendValue(name: String) {
+ val fieldConnector = registry.getConnection(name)
+ val field = fieldConnector!!.first
+ val connector = fieldConnector.second
+ connector.request(field, ByteArray(0))
+ }
+
+ override fun retrieveValues(callbacks: Map Unit>, isInput: Boolean) {
+ val units = if (isInput) inputs else outputs
+ units.forEach{
+ val fieldConnector = registry.getConnection(it)
+ val field = fieldConnector!!.first
+ val connector = fieldConnector.second
+ connector.response({ba -> Pair(Pair(field, ba), it)}, callbacks=callbacks)
+ }
+ }
+}
+
+class NamedClient(mapping: Mapping, conf: Conf): UDPClient() {
+ val nameField: ConnectionField = StringField()
+ val inputsTypes: MutableMap> = mutableMapOf()
+ val outputsTypes: MutableMap> = mutableMapOf()
+
+ init {
+ conf.inputs.inputs.forEach({
+ inputsTypes.put(TYPE_ID.valueOf(it.type), Pair(it.host, it.port))
+ })
+ conf.outputs.outputs.forEach({
+ outputsTypes.put(TYPE_ID.valueOf(it.type), Pair(it.host, it.port))
+ })
+ mapping.inputs.inputs.forEach {
+ val hostPort = inputsTypes.get(TYPE_ID.valueOf(it.type))
+ registry.getConnection(it.name, TYPE_ID.valueOf(it.type), hostPort!!.first, hostPort.second)
+ inputs.add(it.name)
+ }
+ mapping.outputs.outputs.forEach({
+ val hostPort = outputsTypes.get(TYPE_ID.valueOf(it.type))
+ registry.getConnection(it.name, TYPE_ID.valueOf(it.type), hostPort!!.first, hostPort.second)
+ outputs.add(it.name)
+ })
+ inputsTypes.values.forEach({
+ inputConnections.add(registry.getConnector(it)!!)
+ })
+ outputsTypes.values.forEach({
+ outputConnections.add(registry.getConnector(it)!!)
+ })
+ }
+
+ override fun sendValue(name: String) {
+ val fieldConnector = registry.getConnection(name)
+ val field = fieldConnector!!.first
+ val connector = fieldConnector.second
+ nameField.setValue(name)
+ connector.request(field, nameField.getFromFBValue())
+ }
+
+ override fun retrieveValues(callbacks: Map Unit>, isInput: Boolean) {
+ val units = if (isInput) inputConnections else outputConnections
+ units.forEach {
+ it.response({ ba ->
+ var size = ByteBuffer.wrap(ba).getShort(1)
+ val name = String(ba.copyOfRange(3, 3 + size.toInt()))
+ val offset = 3 + size.toInt()
+ registry.getConnection(name)
+ val field = registry.getConnection(name)!!.first
+ Pair(Pair(field, ba.copyOfRange(offset, ba.size)), name)
+ }, callbacks= callbacks)
+ }
+ }
+}
+
+class JSONClient(mapping: Mapping, conf: Conf): UDPClient() {
+ val MSG_TYPE: TYPE_ID = TYPE_ID.STRING
+ val nameField: ConnectionField = StringField()
+ val inputsTypes: MutableMap> = mutableMapOf()
+ val outputsTypes: MutableMap> = mutableMapOf()
+
+ @Serializable
+ data class Msg(val NAME: String, val TYPE: String, val VALUE: String)
+ init {
+ conf.inputs.inputs.forEach({
+ inputsTypes.put(TYPE_ID.valueOf(it.type), Pair(it.host, it.port))
+ })
+ conf.outputs.outputs.forEach({
+ outputsTypes.put(TYPE_ID.valueOf(it.type), Pair(it.host, it.port))
+ })
+ mapping.inputs.inputs.forEach {
+ val hostPort = inputsTypes.get(MSG_TYPE)
+ registry.getConnection("${mapping.id}#${it.name}", TYPE_ID.valueOf(it.type), hostPort!!.first, hostPort.second)
+ inputs.add(it.name)
+ }
+ mapping.outputs.outputs.forEach({
+ val hostPort = outputsTypes.get(MSG_TYPE)
+ registry.getConnection("${mapping.id}#${it.name}", TYPE_ID.valueOf(it.type), hostPort!!.first, hostPort.second)
+ outputs.add(it.name)
+ })
+ conf.inputs.inputs.forEach({
+ inputConnections.add(registry.getConnector(Pair(it.host, it.port))!!)
+ })
+ conf.outputs.outputs.forEach({
+ outputConnections.add(registry.getConnector(Pair(it.host, it.port))!!)
+ })
+ }
+
+ override fun sendValue(name: String) {
+ val fieldConnector = registry.getConnection(name)
+ val field = fieldConnector!!.first
+ val connector = fieldConnector.second
+ val data = Msg(name, field.getTypeID().name, field.getMsgValue())
+ val msg = Json.encodeToString(data)
+ connector.request(msg)
+ }
+
+ override fun retrieveValues(callbacks: Map Unit>, isInput: Boolean) {
+ val units = if (isInput) inputConnections else outputConnections
+ units.forEach {
+ it.response({
+ val msg = Json.decodeFromString(it)
+ val field = registry.getField(msg.NAME)!!
+ println("GETTING ${field.getTypeID()}")
+ field.getFBValue(msg.VALUE)
+ println("GOT ${field.getValue()}")
+ msg.NAME
+ }, true, callbacks= callbacks)
+ }
+ }
+}
\ No newline at end of file
diff --git a/code/cat_visual/src/main/kotlin/connection/field/BoolField.kt b/code/cat_visual/src/main/kotlin/connection/field/BoolField.kt
new file mode 100644
index 000000000..a2384a409
--- /dev/null
+++ b/code/cat_visual/src/main/kotlin/connection/field/BoolField.kt
@@ -0,0 +1,37 @@
+package connection.field
+
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+
+data class BoolField(override var content:Boolean = false, override val contentState: MutableState = mutableStateOf(content)): ConnectionField(content, contentState) {
+
+ val TRUE_VALUE:Byte = 65
+ val FALSE_VALUE:Byte = 64
+
+
+ override fun getFromFBValue():ByteArray {
+ val res = ByteArray(1)
+ res[0] = if (content) TRUE_VALUE else FALSE_VALUE
+ return res
+ }
+
+ override fun getFBValue(d: ByteArray) {
+ setValue(d[0] == TRUE_VALUE)
+ }
+
+ override fun getFBValue(d: String) {
+ setValue(d.equals("TRUE"));
+ }
+
+ override fun getMsgValue(): String {
+ if (content) {
+ return "TRUE";
+ }
+ return "FALSE";
+ }
+
+ override fun getTypeID(): TYPE_ID {
+ return TYPE_ID.BOOL
+ }
+
+}
\ No newline at end of file
diff --git a/code/cat_visual/src/main/kotlin/connection/field/ConnectionField.kt b/code/cat_visual/src/main/kotlin/connection/field/ConnectionField.kt
new file mode 100644
index 000000000..4d539ee6e
--- /dev/null
+++ b/code/cat_visual/src/main/kotlin/connection/field/ConnectionField.kt
@@ -0,0 +1,61 @@
+package connection.field
+
+import androidx.compose.runtime.MutableState
+import kotlinx.coroutines.Dispatchers
+import java.lang.UnsupportedOperationException
+
+
+abstract class ConnectionField(open var content: V, open val contentState: MutableState) {
+ companion object {
+ fun create(type: TYPE_ID):ConnectionField {
+// println(type)
+ return when (type) {
+ TYPE_ID.BOOL -> BoolField()
+ TYPE_ID.REAL -> FloatField(0f)
+ TYPE_ID.LREAL -> DoubleField(0.0)
+ TYPE_ID.STRING -> StringField()
+ TYPE_ID.SINT -> SIntField(0)
+ TYPE_ID.USINT -> USIntField(0u)
+ TYPE_ID.INT -> IntField(0)
+ TYPE_ID.UINT -> UIntField(0u)
+ TYPE_ID.DINT -> DIntField(0)
+ TYPE_ID.UDINT -> UDIntField(0u)
+ TYPE_ID.LINT -> LIntField(0)
+ TYPE_ID.ULINT -> ULIntField(0u)
+ else -> {throw UnsupportedOperationException("Unsupported type literal")}
+ }
+ }
+ }
+ abstract fun getFromFBValue():ByteArray
+ abstract fun getMsgValue():String
+ abstract fun getFBValue(d: ByteArray)
+ abstract fun getFBValue(d: String)
+ abstract fun getTypeID():TYPE_ID
+ fun getValue():V {
+ return content
+ }
+ fun setValue(v:V) {
+ println("!!! BEFORE SET ${content}")
+ content = v
+ contentState.value = content
+ println("!!! SET ${content}")
+ }
+}
+
+enum class TYPE_ID(val code: Int) {
+ BOOL(65),
+ SINT(66),
+ INT(67),
+ DINT(68),
+ LINT(69),
+ USINT(70),
+ UINT(71),
+ UDINT(72),
+ ULINT(73),
+ REAL(74),
+ LREAL(75),
+ STRING(80),
+ WSTRING(85),
+ DATE_AND_TIME(79),
+ ARRAY(118)
+}
\ No newline at end of file
diff --git a/code/cat_visual/src/main/kotlin/connection/field/DoubleField.kt b/code/cat_visual/src/main/kotlin/connection/field/DoubleField.kt
new file mode 100644
index 000000000..4c456030b
--- /dev/null
+++ b/code/cat_visual/src/main/kotlin/connection/field/DoubleField.kt
@@ -0,0 +1,36 @@
+package connection.field;
+
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.sourceInformation
+import java.nio.ByteBuffer
+import java.util.*
+
+data class DoubleField(var defaultValue:Double, override var content:Double = 0.0, override val contentState: MutableState = mutableStateOf(defaultValue)): ConnectionField(content, contentState) {
+
+ override fun getFromFBValue(): ByteArray {
+ val bytes: ByteArray = ByteBuffer.allocate(5).put(getTypeID().code.toByte()).putDouble(content).array()
+ return bytes
+ }
+
+ override fun getMsgValue(): String {
+ return getValue().toString()
+ }
+
+ override fun getFBValue(d: ByteArray) {
+ var buf = ByteBuffer.wrap(d)
+
+ if (buf[0] == TYPE_ID.LREAL.code.toByte()) {
+ setValue(ByteBuffer.wrap(d).getDouble(1))
+ }
+ }
+
+ override fun getFBValue(d: String) {
+ setValue(d.toDouble())
+ }
+
+ override fun getTypeID(): TYPE_ID {
+// TODO("add support of just SINT")
+ return TYPE_ID.LREAL
+ }
+}
diff --git a/code/cat_visual/src/main/kotlin/connection/field/FloatField.kt b/code/cat_visual/src/main/kotlin/connection/field/FloatField.kt
new file mode 100644
index 000000000..e44ab1d30
--- /dev/null
+++ b/code/cat_visual/src/main/kotlin/connection/field/FloatField.kt
@@ -0,0 +1,33 @@
+package connection.field;
+
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+import java.nio.ByteBuffer
+
+data class FloatField(var defaultValue:Float, override var content:Float = 0f, override val contentState: MutableState = mutableStateOf(defaultValue)): ConnectionField(content, contentState) {
+
+ override fun getFromFBValue(): ByteArray {
+ val bytes: ByteArray = ByteBuffer.allocate(5).put(getTypeID().code.toByte()).putFloat(content).array()
+ return bytes
+ }
+
+ override fun getMsgValue(): String {
+ return getValue().toString()
+ }
+
+ override fun getFBValue(d: ByteArray) {
+ var buf = ByteBuffer.wrap(d)
+
+ if (buf[0] == TYPE_ID.REAL.code.toByte()) {
+ setValue(ByteBuffer.wrap(d).getFloat(1))
+ }
+ }
+
+ override fun getFBValue(d: String) {
+ setValue(d.toFloat())
+ }
+
+ override fun getTypeID(): TYPE_ID {
+ return TYPE_ID.REAL
+ }
+}
diff --git a/code/cat_visual/src/main/kotlin/connection/field/IntField.kt b/code/cat_visual/src/main/kotlin/connection/field/IntField.kt
new file mode 100644
index 000000000..4ba3719ca
--- /dev/null
+++ b/code/cat_visual/src/main/kotlin/connection/field/IntField.kt
@@ -0,0 +1,238 @@
+package connection.field;
+
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+import java.nio.ByteBuffer
+
+data class SIntField(var defaultValue:Byte, override var content:Byte = 0, override val contentState: MutableState = mutableStateOf(defaultValue)): ConnectionField(content, contentState) {
+
+ override fun getFromFBValue(): ByteArray {
+ val bytes: ByteArray = ByteBuffer.allocate(5).put(getTypeID().code.toByte()).put(content).array()
+ return bytes
+ }
+
+ override fun getMsgValue(): String {
+ return getValue().toString()
+ }
+
+ override fun getFBValue(d: ByteArray) {
+ var buf = ByteBuffer.wrap(d)
+
+ if (buf[0] == TYPE_ID.SINT.code.toByte()) {
+ setValue(ByteBuffer.wrap(d).get(1))
+ }
+ println(d.contentToString())
+ }
+
+ override fun getFBValue(d: String) {
+ setValue(d.toByte())
+ }
+
+ override fun getTypeID(): TYPE_ID {
+ return TYPE_ID.SINT
+ }
+}
+
+data class USIntField(var defaultValue:UByte, override var content:UByte = 0u, override val contentState: MutableState = mutableStateOf(defaultValue)): ConnectionField(content, contentState) {
+
+ override fun getFromFBValue(): ByteArray {
+ val bytes: ByteArray = ByteBuffer.allocate(5).put(getTypeID().code.toByte()).put(content.toByte()).array()
+ return bytes
+ }
+
+ override fun getMsgValue(): String {
+ return getValue().toString()
+ }
+
+ override fun getFBValue(d: ByteArray) {
+ var buf = ByteBuffer.wrap(d)
+
+ if (buf[0] == TYPE_ID.UINT.code.toByte()) {
+ setValue(ByteBuffer.wrap(d).get(1).toUByte())
+ }
+ println(d.contentToString())
+ }
+
+ override fun getFBValue(d: String) {
+ setValue(d.toUByte())
+ }
+
+ override fun getTypeID(): TYPE_ID {
+ return TYPE_ID.USINT
+ }
+}
+
+data class IntField(var defaultValue:Short, override var content:Short = 0, override val contentState: MutableState = mutableStateOf(defaultValue)): ConnectionField(content, contentState) {
+
+ override fun getFromFBValue(): ByteArray {
+ val bytes: ByteArray = ByteBuffer.allocate(5).put(getTypeID().code.toByte()).putShort(content).array()
+ return bytes
+ }
+
+ override fun getMsgValue(): String {
+ return getValue().toString()
+ }
+
+ override fun getFBValue(d: ByteArray) {
+ var buf = ByteBuffer.wrap(d)
+
+ if (buf[0] == TYPE_ID.INT.code.toByte()) {
+ setValue(ByteBuffer.wrap(d).getShort(1))
+ }
+ println(d.contentToString())
+ }
+
+ override fun getFBValue(d: String) {
+ setValue(d.toShort())
+ }
+
+ override fun getTypeID(): TYPE_ID {
+ return TYPE_ID.INT
+ }
+}
+
+data class UIntField(var defaultValue:UShort, override var content:UShort = 0u, override val contentState: MutableState = mutableStateOf(defaultValue)): ConnectionField(content, contentState) {
+
+ override fun getFromFBValue(): ByteArray {
+ val bytes: ByteArray = ByteBuffer.allocate(5).put(getTypeID().code.toByte()).putShort(content.toShort()).array()
+ return bytes
+ }
+
+ override fun getMsgValue(): String {
+ return getValue().toString()
+ }
+
+ override fun getFBValue(d: ByteArray) {
+ var buf = ByteBuffer.wrap(d)
+
+ if (buf[0] == TYPE_ID.UINT.code.toByte()) {
+ setValue(ByteBuffer.wrap(d).getShort(1).toUShort())
+ }
+ println(d.contentToString())
+ }
+
+ override fun getFBValue(d: String) {
+ setValue(d.toUShort())
+ }
+
+ override fun getTypeID(): TYPE_ID {
+ return TYPE_ID.UINT
+ }
+}
+
+data class DIntField(var defaultValue:Int, override var content:Int = 0, override val contentState: MutableState = mutableStateOf(defaultValue)): ConnectionField(content, contentState) {
+
+ override fun getFromFBValue(): ByteArray {
+ val bytes: ByteArray = ByteBuffer.allocate(5).put(getTypeID().code.toByte()).putInt(content).array()
+ return bytes
+ }
+
+ override fun getMsgValue(): String {
+ return getValue().toString()
+ }
+
+ override fun getFBValue(d: ByteArray) {
+ var buf = ByteBuffer.wrap(d)
+
+ if (buf[0] == TYPE_ID.DINT.code.toByte()) {
+ setValue(ByteBuffer.wrap(d).getInt(1))
+ }
+ println(d.contentToString())
+ }
+
+ override fun getFBValue(d: String) {
+ setValue(d.toInt())
+ }
+
+ override fun getTypeID(): TYPE_ID {
+ return TYPE_ID.DINT
+ }
+}
+
+data class UDIntField(var defaultValue:UInt, override var content:UInt = 0u, override val contentState: MutableState = mutableStateOf(defaultValue)): ConnectionField(content, contentState) {
+
+ override fun getFromFBValue(): ByteArray {
+ val bytes: ByteArray = ByteBuffer.allocate(9).put(getTypeID().code.toByte()).putInt(content.toInt()).array()
+ return bytes
+ }
+
+ override fun getMsgValue(): String {
+ return getValue().toString()
+ }
+
+ override fun getFBValue(d: ByteArray) {
+ var buf = ByteBuffer.wrap(d)
+
+ if (buf[0] == TYPE_ID.UDINT.code.toByte()) {
+ setValue(ByteBuffer.wrap(d).getInt(1).toUInt())
+ }
+ println(d.contentToString())
+ }
+
+ override fun getFBValue(d: String) {
+ setValue(d.toUInt())
+ }
+
+ override fun getTypeID(): TYPE_ID {
+ return TYPE_ID.UDINT
+ }
+}
+
+
+data class LIntField(var defaultValue:Long, override var content:Long = 0, override val contentState: MutableState = mutableStateOf(defaultValue)): ConnectionField(content, contentState) {
+
+ override fun getFromFBValue(): ByteArray {
+ val bytes: ByteArray = ByteBuffer.allocate(5).put(getTypeID().code.toByte()).putLong(content).array()
+ return bytes
+ }
+
+ override fun getMsgValue(): String {
+ return getValue().toString()
+ }
+
+ override fun getFBValue(d: ByteArray) {
+ var buf = ByteBuffer.wrap(d)
+
+ if (buf[0] == TYPE_ID.LINT.code.toByte()) {
+ setValue(ByteBuffer.wrap(d).getLong(1))
+ }
+ println(d.contentToString())
+ }
+
+ override fun getFBValue(d: String) {
+ setValue(d.toLong())
+ }
+
+ override fun getTypeID(): TYPE_ID {
+ return TYPE_ID.LINT
+ }
+}
+
+data class ULIntField(var defaultValue:ULong, override var content:ULong = 0uL, override val contentState: MutableState = mutableStateOf(defaultValue)): ConnectionField(content, contentState) {
+
+ override fun getFromFBValue(): ByteArray {
+ val bytes: ByteArray = ByteBuffer.allocate(9).put(getTypeID().code.toByte()).putLong(content.toLong()).array()
+ return bytes
+ }
+
+ override fun getMsgValue(): String {
+ return getValue().toString()
+ }
+
+ override fun getFBValue(d: ByteArray) {
+ var buf = ByteBuffer.wrap(d)
+
+ if (buf[0] == TYPE_ID.UDINT.code.toByte()) {
+ setValue(ByteBuffer.wrap(d).getLong(1).toULong())
+ }
+ println(d.contentToString())
+ }
+
+ override fun getFBValue(d: String) {
+ setValue(d.toULong())
+ }
+
+ override fun getTypeID(): TYPE_ID {
+ return TYPE_ID.ULINT
+ }
+}
diff --git a/code/cat_visual/src/main/kotlin/connection/field/StringField.kt b/code/cat_visual/src/main/kotlin/connection/field/StringField.kt
new file mode 100644
index 000000000..d57e7afff
--- /dev/null
+++ b/code/cat_visual/src/main/kotlin/connection/field/StringField.kt
@@ -0,0 +1,31 @@
+package connection.field
+
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+import java.nio.ByteBuffer
+
+data class StringField(override var content:String = "", override val contentState: MutableState = mutableStateOf(content)): ConnectionField(content, contentState) {
+ override fun getFromFBValue(): ByteArray {
+ val contentBytes: ByteArray = content.toByteArray()
+ return ByteBuffer.allocate(contentBytes.size + 3).put(TYPE_ID.STRING.code.toByte()).putShort(content.length.toShort()).put(contentBytes).array()
+ }
+
+ override fun getMsgValue(): String {
+ return content
+ }
+
+ override fun getFBValue(d: ByteArray) {
+ var size = ByteBuffer.wrap(d).getShort(1)
+ setValue(String(d.copyOfRange(3, 3 + size.toInt())))
+ println(d.contentToString())
+ }
+
+ override fun getFBValue(d: String) {
+ setValue(d)
+ }
+
+ override fun getTypeID(): TYPE_ID {
+ return TYPE_ID.STRING
+ }
+
+}
\ No newline at end of file
diff --git a/code/cat_visual/src/main/kotlin/connection/field/WStringField.kt b/code/cat_visual/src/main/kotlin/connection/field/WStringField.kt
new file mode 100644
index 000000000..41ef84f6d
--- /dev/null
+++ b/code/cat_visual/src/main/kotlin/connection/field/WStringField.kt
@@ -0,0 +1,31 @@
+package connection.field
+
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+import java.nio.ByteBuffer
+
+data class WStringField(override var content:String = "", override val contentState: MutableState = mutableStateOf(content)): ConnectionField(content, contentState) {
+ override fun getFromFBValue(): ByteArray {
+ val contentBytes: ByteArray = content.toByteArray(Charsets.UTF_16)
+ return ByteBuffer.allocate(contentBytes.size * 2 + 3).put(getTypeID().code.toByte()).putShort((content.length.toShort() * 2).toShort()).put(contentBytes).array()
+ }
+
+ override fun getMsgValue(): String {
+ TODO("Not yet implemented")
+ }
+
+ override fun getFBValue(d: ByteArray) {
+ var size = ByteBuffer.wrap(d).getShort(1)
+ setValue(String(d.copyOfRange(3, 3 + size.toInt()), Charsets.UTF_16))
+ println(d.contentToString())
+ }
+
+ override fun getFBValue(d: String) {
+ TODO("Not yet implemented")
+ }
+
+ override fun getTypeID(): TYPE_ID {
+ return TYPE_ID.WSTRING
+ }
+
+}
\ No newline at end of file
diff --git a/code/cat_visual/src/main/kotlin/connection/provider/ConnectionProvider.kt b/code/cat_visual/src/main/kotlin/connection/provider/ConnectionProvider.kt
new file mode 100644
index 000000000..a81d45400
--- /dev/null
+++ b/code/cat_visual/src/main/kotlin/connection/provider/ConnectionProvider.kt
@@ -0,0 +1,89 @@
+package connection.provider
+
+import connection.ConnectionFieldRegistry
+import connection.field.ConnectionField
+import connection.field.TYPE_ID
+import java.io.BufferedReader
+import java.io.InputStreamReader
+import java.io.PrintWriter
+import java.net.DatagramPacket
+import java.net.InetAddress
+import java.net.MulticastSocket
+import java.net.ServerSocket
+import java.net.Socket
+import java.nio.ByteBuffer
+import kotlin.concurrent.thread
+
+abstract class ConnectionProvider(private val port: Int, private val host: String = "225.0.0.1") {
+ abstract fun response(fieldGetter: (ByteArray) -> Pair, ByteArray>, String>, ping:Long = 500, callbacks: Map Unit> = mapOf())
+ abstract fun response(fieldSetter: (String) -> String, log: Boolean, ping:Long = 500, callbacks: Map Unit> = mapOf())
+ abstract fun request(field: ConnectionField, prefix: ByteArray)
+ abstract fun request(msg: String)
+}
+
+class UDPConnectionProvider(private val port: Int, private val host: String = "225.0.0.1"): ConnectionProvider(port, host) {
+ val group = InetAddress.getByName(host);
+ val socket = MulticastSocket(port);
+
+
+ init {
+ socket.joinGroup(group)
+ }
+
+ var ind = 0
+
+ override fun response(fieldGetter: (ByteArray) -> Pair, ByteArray>, String>, ping:Long, callbacks: Map Unit>) {
+ thread {
+ while (true) {
+ val buf = ByteArray(1024);
+ val recv = DatagramPacket(buf, buf.size);
+ socket.receive(recv)
+ val fieldData = fieldGetter(recv.data)
+ val field = fieldData.first.first
+ val data = fieldData.first.second
+ val name = fieldData.second
+ println("GETTING ${field.getTypeID()}")
+ field.getFBValue(data)
+ callbacks.getOrDefault(name, {})()
+ println("GOT ${field.getValue()}")
+ Thread.sleep(ping)
+ }
+ }
+ }
+
+ override fun request(field: ConnectionField, prefix: ByteArray) {
+ println("SENDING ${field.getValue()}")
+ var msg = field.getFromFBValue()
+ msg = prefix.plus(msg)
+ val hi = DatagramPacket(msg, msg.size,
+ group, port);
+ socket.send(hi)
+ }
+
+ override fun request(msgJ: String) {
+ println("SENDING ${msgJ}")
+ val contentBytes = msgJ.toByteArray()
+ val msg = ByteBuffer.allocate(contentBytes.size + 3).put(TYPE_ID.STRING.code.toByte()).putShort(msgJ.length.toShort()).put(contentBytes).array()
+ val hi = DatagramPacket(msg, msg.size,
+ group, port);
+ socket.send(hi)
+ }
+
+ override fun response(fieldSetter: (String) -> String, log: Boolean, ping:Long, callbacks: Map Unit>) {
+ thread {
+ while (true) {
+
+ val buf = ByteArray(1024);
+ val recv = DatagramPacket(buf, buf.size);
+ socket.receive(recv)
+ var size = ByteBuffer.wrap(recv.data).getShort(1)
+ var msg = String(recv.data.copyOfRange(3, 3 + size.toInt()))
+ if (log) println("MESSAGE $msg")
+ val name = fieldSetter(msg)
+ callbacks.getOrDefault(name, {})()
+ Thread.sleep(ping)
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/code/cat_visual/src/main/kotlin/example/COUNTER/COMMON_CONF.xml b/code/cat_visual/src/main/kotlin/example/COUNTER/COMMON_CONF.xml
new file mode 100644
index 000000000..b887ebd85
--- /dev/null
+++ b/code/cat_visual/src/main/kotlin/example/COUNTER/COMMON_CONF.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/code/cat_visual/src/main/kotlin/example/COUNTER/COUNT.xml b/code/cat_visual/src/main/kotlin/example/COUNTER/COUNT.xml
new file mode 100644
index 000000000..e69de29bb
diff --git a/code/cat_visual/src/main/kotlin/example/COUNTER/COUNTER.kt b/code/cat_visual/src/main/kotlin/example/COUNTER/COUNTER.kt
new file mode 100644
index 000000000..309fe7173
--- /dev/null
+++ b/code/cat_visual/src/main/kotlin/example/COUNTER/COUNTER.kt
@@ -0,0 +1,44 @@
+package example.COUNTER
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.width
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+import connection.AbstractClient
+import connection.ConnectionFieldRegistry
+import lib.elements.getters.Bulb
+import lib.elements.getters.TextBox
+import lib.elements.setters.Checkbox
+import lib.elements.setters.Toggle
+import lib.visual.PositionedBox
+
+
+@Composable
+fun LampHMI(client: AbstractClient, id: String) {
+ Bulb("$id#LAMP", client)
+}
+@Composable
+fun ToggleHMI(client: AbstractClient, id: String) {
+ Toggle("$id#TOGGLE", client)
+}
+
+@Composable
+fun CounterHMI(client: AbstractClient, id: String) {
+ Row {
+ Text("Count of changes: ")
+ TextBox("$id#COUNT", client, Modifier.width(40.dp).height(30.dp).background(Color.LightGray))
+// PositionedBox( children = {Indicator("count", "225.0.0.2", 65003, registry, 0, 100, 1000.dp)}, x = 100, y = 100)
+ }
+}
+
+@Composable
+fun CounterLampHMI(client: AbstractClient, id: String) {
+ PositionedBox(children = {CounterHMI(client, "1")}, x = 200)
+ LampHMI(client, "1")
+ PositionedBox(children = {example.COUNTER.ToggleHMI(client, "1")}, x = 300, y = 100)
+}
\ No newline at end of file
diff --git a/code/cat_visual/src/main/kotlin/example/COUNTER/COUNTER.xml b/code/cat_visual/src/main/kotlin/example/COUNTER/COUNTER.xml
new file mode 100644
index 000000000..869c96923
--- /dev/null
+++ b/code/cat_visual/src/main/kotlin/example/COUNTER/COUNTER.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/code/cat_visual/src/main/kotlin/example/COUNTER/COUNTER_CONF.xml b/code/cat_visual/src/main/kotlin/example/COUNTER/COUNTER_CONF.xml
new file mode 100644
index 000000000..bb9432301
--- /dev/null
+++ b/code/cat_visual/src/main/kotlin/example/COUNTER/COUNTER_CONF.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/code/cat_visual/src/main/kotlin/example/COUNTER/COUNTER_PLAIN.xml b/code/cat_visual/src/main/kotlin/example/COUNTER/COUNTER_PLAIN.xml
new file mode 100644
index 000000000..8998ad64d
--- /dev/null
+++ b/code/cat_visual/src/main/kotlin/example/COUNTER/COUNTER_PLAIN.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/code/cat_visual/src/main/kotlin/example/COUNTER/LAMP.xml b/code/cat_visual/src/main/kotlin/example/COUNTER/LAMP.xml
new file mode 100644
index 000000000..869c96923
--- /dev/null
+++ b/code/cat_visual/src/main/kotlin/example/COUNTER/LAMP.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/code/cat_visual/src/main/kotlin/example/COUNTER/TOGGLE.xml b/code/cat_visual/src/main/kotlin/example/COUNTER/TOGGLE.xml
new file mode 100644
index 000000000..e69de29bb
diff --git a/code/cat_visual/src/main/kotlin/example/INCHOICE/INCHOICE.kt b/code/cat_visual/src/main/kotlin/example/INCHOICE/INCHOICE.kt
new file mode 100644
index 000000000..b835fd35d
--- /dev/null
+++ b/code/cat_visual/src/main/kotlin/example/INCHOICE/INCHOICE.kt
@@ -0,0 +1,75 @@
+//package example.INCHOICE
+//
+//import androidx.compose.foundation.background
+//import androidx.compose.foundation.layout.Box
+//import androidx.compose.foundation.layout.Column
+//import androidx.compose.foundation.layout.height
+//import androidx.compose.foundation.layout.width
+//import androidx.compose.material.Switch
+//import androidx.compose.material.Text
+//import androidx.compose.runtime.*
+//import androidx.compose.ui.Modifier
+//import androidx.compose.ui.graphics.Color
+//import androidx.compose.ui.unit.dp
+//import connection.field.BoolField
+//import connection.field.ConnectionField
+//import connection.field.StringField
+//import connection.provider.ConnectionProvider
+//
+//val field: ConnectionField = BoolField()
+//val toggleField: ConnectionField = BoolField()
+//val ipField: ConnectionField = StringField()
+//
+//var connectionProvider: ConnectionProvider = ConnectionProvider(field, 65001)
+//var connectionProviderToggle: ConnectionProvider = ConnectionProvider(toggleField, 65000)
+//var connectionProviderIpText: ConnectionProvider = ConnectionProvider(ipField, 65002)
+//
+//
+//val ping = 1
+//
+//@Composable
+//fun LampToggle() {
+// val checkedState = remember { toggleField }
+// Switch(
+// checked = checkedState.contentState.value,
+// onCheckedChange = {
+// checkedState.setValue(it)
+// connectionProviderToggle.request()}
+// )
+//}
+//
+//
+//val trueColor = Color.Yellow
+//val falseColor = Color.Black
+//
+//
+//@Composable
+//fun Lamp_HMI() {
+// fun getColor(s: Boolean): Color {
+// if (s) {
+// return trueColor
+// } else {
+// return falseColor
+// }
+// }
+//
+//
+// var checkedState = remember{ field }
+// Column {
+// Box(
+// modifier = Modifier.background(color = if (checkedState.contentState.value) trueColor else falseColor)
+// .width(600.dp)
+// .height(600.dp)
+// ) {
+// connectionProvider.response()
+// }
+// Box(
+// modifier = Modifier.background(color = Color.Gray)
+// .width(200.dp)
+// .height(200.dp)
+// ) {
+// Text(ipField.contentState.value)
+// connectionProviderIpText.response()
+// }
+// }
+//}
diff --git a/code/cat_visual/src/main/kotlin/example/INCHOICE/INCHOICE_HMI.cnv.xml b/code/cat_visual/src/main/kotlin/example/INCHOICE/INCHOICE_HMI.cnv.xml
new file mode 100644
index 000000000..5297eeccc
--- /dev/null
+++ b/code/cat_visual/src/main/kotlin/example/INCHOICE/INCHOICE_HMI.cnv.xml
@@ -0,0 +1,17 @@
+
+
+
+ CHOICES
+
+
+ I
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/code/cat_visual/src/main/kotlin/example/WATER_TANK/WATER_TANK.kt b/code/cat_visual/src/main/kotlin/example/WATER_TANK/WATER_TANK.kt
new file mode 100644
index 000000000..3d66990ed
--- /dev/null
+++ b/code/cat_visual/src/main/kotlin/example/WATER_TANK/WATER_TANK.kt
@@ -0,0 +1,121 @@
+package example.WATER_TANK
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.zIndex
+import connection.AbstractClient
+import connection.ConnectionFieldRegistry
+import lib.elements.*
+import lib.elements.getters.Bulb
+import lib.elements.getters.LightingText
+import lib.elements.getters.VerticalIndicator
+import lib.elements.setters.RoundKnob
+import lib.visual.PositionedBox
+
+@Composable
+fun WaterTank(client: AbstractClient) {
+
+ Box {
+ PositionedBox(@Composable { LampIndicator("lampIndicator1", client) }, 120, 190)
+ PositionedBox(@Composable { LampIndicator("lampIndicator2", client) }, 120, 380)
+ PositionedBox(@Composable { PipeConnector(Color.Gray, 100.dp, 30.dp) }, 150, 100)
+ PositionedBox(@Composable { Figure(RoundedCornerShape(100.dp),
+ modifier = Modifier.size(200.dp, 300.dp)
+ .background(brush = METALLIC_BRUSH, shape = RoundedCornerShape(20.dp))
+ )
+ }, 200, 150, zIndex = 2f)
+ PositionedBox(@Composable { Pipe(Color.Gray, 100.dp, 30.dp) }, 400, 400)
+ PositionedBox(@Composable {
+ VerticalIndicator(
+ "tankIndicator", client,
+ color = Color.Blue, step = 20f, minTemperature = 0f, maxTemperature = 100f,
+ trackHeight = 200.dp, indicatorWidth = 20.dp
+ )
+ }, 300, 200, zIndex = 3f)
+ }
+}
+
+@Composable
+fun LampIndicator(name: String, client: AbstractClient, pipeSize: Dp = 30.dp) {
+ Row {
+ PositionedBox(@Composable {
+ Bulb(
+ name,
+ client,
+ size = pipeSize,
+ borderWidth = 4.dp,
+ modifier = Modifier.zIndex(2f)
+ )
+ }, pipeSize.value / 2)
+ Pipe(Color.Gray, 100.dp, pipeSize)
+ }
+}
+
+
+@Composable
+fun System(client: AbstractClient) {
+ PositionedBox(@Composable {
+ RoundKnob(
+ "knob1",
+ client,
+ 0..100,
+ 0f,
+ knobSize = 60.dp,
+ knobColor = Color.Red
+ )
+ }, 10, 50)
+ PositionedBox(@Composable {
+ RoundKnob(
+ "knob2",
+ client,
+ 0..100,
+ 0f,
+ knobSize = 60.dp,
+ knobColor = Color.Blue
+ )
+ }, 10, 120)
+ WaterTank(client)
+ PositionedBox(@Composable {
+ VerticalIndicator(
+ "outputIndicator", client,
+ color = Color.Red, step = 20f, minTemperature = 0f, maxTemperature = 100f,
+ trackHeight = 200.dp, indicatorWidth = 20.dp
+ )
+ }, 440, 150, zIndex = 3f)
+ PositionedBox(@Composable {
+ LightingText(
+ "stateInlet",
+ client,
+ "Inlet",
+ width = 70.dp,
+ height = 25.dp,
+ borderWidth = 5.dp
+ )
+ }, 500, 150)
+ PositionedBox(@Composable {
+ LightingText(
+ "stateHeat",
+ client,
+ "Heat",
+ width = 70.dp,
+ height = 25.dp,
+ borderWidth = 5.dp
+ )
+ }, 500, 200)
+ PositionedBox(@Composable {
+ LightingText(
+ "stateOutlet",
+ client,
+ "Outlet",
+ width = 70.dp,
+ height = 25.dp,
+ borderWidth = 5.dp
+ )
+ }, 500, 250)
+}
\ No newline at end of file
diff --git a/code/cat_visual/src/main/kotlin/example/WATER_TANK/WATER_TANK.xml b/code/cat_visual/src/main/kotlin/example/WATER_TANK/WATER_TANK.xml
new file mode 100644
index 000000000..bba095874
--- /dev/null
+++ b/code/cat_visual/src/main/kotlin/example/WATER_TANK/WATER_TANK.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/code/cat_visual/src/main/kotlin/example/WATER_TANK/WATER_TANK_CONF.xml b/code/cat_visual/src/main/kotlin/example/WATER_TANK/WATER_TANK_CONF.xml
new file mode 100644
index 000000000..b7461d6df
--- /dev/null
+++ b/code/cat_visual/src/main/kotlin/example/WATER_TANK/WATER_TANK_CONF.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/code/cat_visual/src/main/kotlin/example/WATER_TANK/WATER_TANK_PLAIN.xml b/code/cat_visual/src/main/kotlin/example/WATER_TANK/WATER_TANK_PLAIN.xml
new file mode 100644
index 000000000..f59dbb5dc
--- /dev/null
+++ b/code/cat_visual/src/main/kotlin/example/WATER_TANK/WATER_TANK_PLAIN.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/code/cat_visual/src/main/kotlin/lib/elements/figures/elements.kt b/code/cat_visual/src/main/kotlin/lib/elements/figures/elements.kt
new file mode 100644
index 000000000..d23a47a84
--- /dev/null
+++ b/code/cat_visual/src/main/kotlin/lib/elements/figures/elements.kt
@@ -0,0 +1,212 @@
+package lib.elements
+
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.CutCornerShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.rotate
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.*
+import androidx.compose.ui.graphics.drawscope.Stroke
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+
+
+@Composable
+fun Rectangle(color: Color, width: Dp, height: Dp) {
+ Box(
+ modifier = Modifier.background(color = color)
+ .width(width)
+ .height(height)
+ ) {
+ }
+}
+
+@Composable
+fun Rectangle(color: Color, size: Dp) {
+ Box(
+ modifier = Modifier.background(color = color)
+ .size(size)
+ ) {
+ }
+}
+
+@Composable
+fun Round(color: Color, width: Dp, height: Dp) {
+ Box(
+ modifier = Modifier.background(color = color)
+ .clip(CircleShape)
+ .width(width)
+ .height(height)
+ ) {
+ }
+}
+
+@Composable
+fun Round(color: Color, size: Dp) {
+ Box(
+ modifier = Modifier.background(color = color)
+ .clip(CircleShape)
+ .size(size)
+ ) {
+ }
+}
+
+@Composable
+fun Round(modifier: Modifier, width: Dp, height: Dp) {
+ Box(
+ modifier = Modifier
+ .clip(CircleShape)
+ .width(width)
+ .height(height)
+ .then(modifier)
+ ) {
+ }
+}
+
+@Composable
+fun RoundedRectangle(color: Color, width: Dp, height: Dp, radius: Dp) {
+ Box(
+ modifier = Modifier.background(color = color)
+ .clip(RoundedCornerShape(radius))
+ .width(width)
+ .height(height)
+ ) {
+ }
+}
+
+@Composable
+fun RoundedRectangle(color: Color, size: Dp, radius: Dp) {
+ Box(
+ modifier = Modifier.background(color = color)
+ .clip(RoundedCornerShape(radius))
+ .size(size)
+ ) {
+ }
+}
+
+
+@Composable
+fun RoundedRectangle(modifier: Modifier, width: Dp, height: Dp, radius: Dp) {
+ Box(
+ modifier = modifier
+ .clip(RoundedCornerShape(radius))
+ .width(width)
+ .height(height)
+ ) {
+ }
+}
+
+@Composable
+fun CutRectangle(color: Color, width: Dp, height: Dp, radius: Dp) {
+ Figure(shape = CutCornerShape(radius),
+ modifier = Modifier.background(color = color)
+ .width(width)
+ .height(height)
+ )
+}
+
+@Composable
+fun CutRectangle(color: Color, size: Dp, radius: Dp) {
+ Figure(
+ shape = CutCornerShape(radius),
+ modifier = Modifier.background(color = color)
+ .clip(CutCornerShape(radius))
+ .size(size)
+ )
+}
+
+
+@Composable
+fun Figure(shape: Shape = RectangleShape, modifier: Modifier = Modifier) {
+ Box(modifier = modifier.clip(shape)){
+ }
+}
+
+@Composable
+fun OneDAnimatedComposable(
+ content: @Composable () -> Unit,
+ isAtTheEnd: Boolean,
+ targetValue: Float,
+ horizontally: Boolean = true,
+ animationSpec: AnimationSpec = tween(durationMillis = 1000),
+) {
+ val animatedValue by animateFloatAsState(
+ targetValue = if (isAtTheEnd) targetValue else 0f,
+ animationSpec = animationSpec
+ )
+
+ var modifier:Modifier = Modifier;
+ if (horizontally) {
+ modifier = modifier.offset(x = animatedValue.dp)
+ } else {
+ modifier = modifier.offset(y = animatedValue.dp)
+ }
+ Box(
+ modifier = modifier
+ ) {
+ content()
+ }
+}
+
+@Composable
+fun Pipe(
+ color: Color,
+ pipeWidth: Dp,
+ pipeHeight: Dp,
+ modifier: Modifier = Modifier
+) {
+ Box(
+ modifier = modifier
+ .width(pipeWidth)
+ .height(pipeHeight)
+ .background(brush = Brush.verticalGradient(listOf(color, color.copy(alpha = 0.2f), color)))
+ ){}
+}
+
+@Composable
+fun PipeConnector(
+ color: Color,
+ connectorWidth: Dp,
+ connectorHeight: Dp,
+ modifier: Modifier = Modifier
+) {
+ Box(modifier = modifier) {
+ Box(
+ modifier = Modifier
+ .offset()
+ .width(connectorWidth)
+ .height(connectorHeight)
+ .background(
+ brush = Brush.verticalGradient(listOf(color, color.copy(alpha = 0.2f), color)),
+ shape = RoundedCornerShape(0.dp, connectorHeight, connectorHeight, 0.dp)
+ )
+ ) {}
+ val rotatedBoxOffset = connectorWidth / 2 - connectorHeight /2
+ Box(
+ modifier = Modifier
+ .offset(x = rotatedBoxOffset, y = rotatedBoxOffset)
+ .rotate(-90f)
+ .width(connectorWidth)
+ .height(connectorHeight)
+ .background(
+ brush = Brush.verticalGradient(listOf(color, color.copy(alpha = 0.2f), color)),
+ shape = RoundedCornerShape(0.dp, connectorHeight, connectorHeight, 0.dp)
+ )
+ ) {}
+ }
+
+}
\ No newline at end of file
diff --git a/code/cat_visual/src/main/kotlin/lib/elements/getters/elements.kt b/code/cat_visual/src/main/kotlin/lib/elements/getters/elements.kt
new file mode 100644
index 000000000..3be2f0045
--- /dev/null
+++ b/code/cat_visual/src/main/kotlin/lib/elements/getters/elements.kt
@@ -0,0 +1,221 @@
+package lib.elements.getters
+
+import androidx.compose.foundation.*
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.graphics.StrokeCap
+import androidx.compose.ui.graphics.drawscope.Stroke
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.font.SystemFontFamily
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.TextUnit
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.compose.ui.zIndex
+import connection.AbstractClient
+import connection.ConnectionFieldRegistry
+import connection.field.ConnectionField
+import connection.field.TYPE_ID
+import connection.provider.ConnectionProvider
+import lib.elements.*
+
+@Composable
+fun Bulb(name: String, client: AbstractClient,
+ size: Dp = 60.dp, borderWidth:Dp = 10.dp,
+ modifier: Modifier = Modifier
+ ) {
+
+ val field = client.getField(name) as ConnectionField
+
+ var checkedStateBulb = remember { field }
+ Round(
+ modifier.background(brush = if (checkedStateBulb.contentState.value) LIGHT_RADIAL_BRUSH else DARK_RADIAL_BRUSH)
+ .border(width = borderWidth, brush = METALLIC_BRUSH, shape = CircleShape),
+ size,
+ size
+ )
+}
+
+
+@Composable
+fun LightingText(name: String, client: AbstractClient,
+ text:String,
+ width: Dp = 60.dp, height: Dp = 60.dp, borderWidth:Dp = 10.dp,
+ modifier: Modifier = Modifier, lightBrush: Brush = LIGHT_RADIAL_BRUSH
+ ) {
+
+ val field = client.getField(name) as ConnectionField
+
+ var checkedStateBulb = remember { field }
+
+ Box(
+ modifier = modifier.background(brush = if (checkedStateBulb.contentState.value) lightBrush else DARK_RADIAL_BRUSH, shape = RoundedCornerShape(height / 4))
+ .border(width = borderWidth, brush = METALLIC_BRUSH, shape = RoundedCornerShape(height / 4))
+ .widthIn(min = width)
+ .heightIn(min = height)
+ .clip(RoundedCornerShape(height / 4)),
+ ) {
+ Text(text, modifier = Modifier.align(Alignment.Center))
+ }
+}
+
+@Composable
+fun TextBox(
+ name: String,
+ client: AbstractClient,
+ modifier: Modifier = Modifier.focusable(true)
+) {
+ val field = client.getField(name)
+
+ var checkedState = remember { field }
+ Box(
+ modifier = modifier.then(Modifier.fillMaxWidth().fillMaxHeight())
+ ) {
+ Text(checkedState!!.contentState.value.toString())
+ }
+}
+
+@Composable
+fun Indicator(
+ name: String, client: AbstractClient,
+ minIndicatorValue: Int, maxIndicatorValue: Int, size: Dp,
+ indicatorThickness: Dp = 28.dp,
+ animationDuration: Int = 1000,
+ animationDelay: Int = 0
+) {
+ val field = client.getField(name) as ConnectionField
+
+ var checkedState = remember { field }
+ Box(
+ contentAlignment = Alignment.Center,
+ modifier = Modifier.wrapContentSize().padding(indicatorThickness)
+ ) {
+ Canvas(
+ modifier = Modifier
+ .height(size + indicatorThickness)
+ .width(size + indicatorThickness)
+ ) {
+ drawArc(
+ color = Color.LightGray,
+ startAngle = 0f,
+ sweepAngle = -180f,
+ useCenter = false,
+ style = Stroke(width = indicatorThickness.toPx(), cap = StrokeCap.Round)
+ )
+
+ var sweepAngle = 0f;
+ if (checkedState.contentState.value.toInt() != 0) {
+ sweepAngle = (checkedState.contentState.value.toInt() / (maxIndicatorValue - minIndicatorValue).toFloat()) * 180f
+ }
+
+ // Foreground circle
+ drawArc(
+ color = Color.Red,
+ startAngle = 180f,
+ sweepAngle = sweepAngle,
+ useCenter = false,
+ style = Stroke(indicatorThickness.toPx(), cap = StrokeCap.Round)
+ )
+
+
+ }
+ Text(
+ text = checkedState.contentState.value.toString(),
+ color = Color.Black,
+ style = MaterialTheme.typography.body1,
+ textAlign = TextAlign.Center,
+ modifier = Modifier
+ .align(Alignment.Center),
+ fontSize = 30.sp
+ )
+ }
+}
+
+
+@Composable
+fun VerticalIndicator(
+ name: String, client: AbstractClient,
+ temperature: Float = 0f,
+ maxTemperature: Float,
+ minTemperature: Float,
+ color: Color,
+ step: Float,
+ modifier: Modifier = Modifier,
+ trackHeight: Dp = 400.dp,
+ indicatorWidth: Dp = 48.dp,
+ legendFontSize: TextUnit = 12.sp,
+ legendFontColor: Color = Color.Black,
+ font: SystemFontFamily = FontFamily.Default,
+ showCurrentState: Boolean = false
+) {
+ val field = client.getField(name) as ConnectionField
+ var checkedState = remember { field }
+
+ val stepCount = ((maxTemperature - minTemperature) / step).toInt()
+ val temperatureRange = maxTemperature - minTemperature
+
+ Box(
+ modifier = modifier
+ .width(indicatorWidth)
+ .height(trackHeight)
+ .background(Color.LightGray)
+ .border(BorderStroke(3.dp, METALLIC_BRUSH), RectangleShape)
+ ) {
+ val temperatureOffset = (checkedState.contentState.value.toFloat() - minTemperature) / temperatureRange
+ val indicatorHeight = (temperatureOffset * trackHeight.value).dp
+
+
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(indicatorHeight)
+ .background(color)
+ .align(Alignment.BottomCenter)
+ ) {
+
+ if (showCurrentState) {
+ Text(
+ text = "${checkedState.contentState.value}",
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Bold,
+ color = legendFontColor,
+ modifier = Modifier.offset(y = trackHeight)
+ )
+ }
+ }
+
+ }
+ for (i in (minTemperature.toInt()..maxTemperature.toInt()).reversed() step step.toInt()) {
+ val halfLegendHeight = legendFontSize.value.dp / 2f
+ val labelOffset = ((i - minTemperature) / temperatureRange).coerceIn(0f, 1f)
+ val labelTop = (((1f - labelOffset) * (trackHeight.value)) - halfLegendHeight.value).coerceIn(0f, trackHeight.value).dp
+ Box(modifier = Modifier.width(indicatorWidth * 2)) {
+ Text(
+ modifier = Modifier.offset(x = indicatorWidth, y = labelTop),
+ text = "$i",
+ fontSize = legendFontSize,
+ fontWeight = FontWeight.Light,
+ fontFamily = font,
+ color = legendFontColor,
+ softWrap = false,
+ lineHeight = legendFontSize
+ )
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/code/cat_visual/src/main/kotlin/lib/elements/setters/elements.kt b/code/cat_visual/src/main/kotlin/lib/elements/setters/elements.kt
new file mode 100644
index 000000000..a13e073dd
--- /dev/null
+++ b/code/cat_visual/src/main/kotlin/lib/elements/setters/elements.kt
@@ -0,0 +1,549 @@
+package lib.elements.setters
+
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.foundation.*
+import androidx.compose.foundation.gestures.*
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.lazy.itemsIndexed
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.material.*
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ArrowDropDown
+import androidx.compose.material.icons.filled.Check
+import androidx.compose.material.icons.filled.Done
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.rotate
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.*
+import androidx.compose.ui.graphics.drawscope.Stroke
+import androidx.compose.ui.input.pointer.consumeAllChanges
+import androidx.compose.ui.input.pointer.consumePositionChange
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.input.pointer.positionChange
+import androidx.compose.ui.layout.boundsInWindow
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.text.input.TextFieldValue
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.*
+import androidx.compose.ui.zIndex
+import connection.AbstractClient
+import connection.ConnectionFieldRegistry
+import connection.field.*
+import connection.provider.ConnectionProvider
+import lib.elements.METALLIC_BRUSH
+import java.lang.UnsupportedOperationException
+import kotlin.concurrent.thread
+import kotlin.math.PI
+import kotlin.math.atan2
+import kotlin.math.roundToInt
+
+@Composable
+fun CustomButton(
+ text: String,
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier,
+ buttonColors: ButtonColors = ButtonDefaults.buttonColors(),
+ textColor: Color = Color.Black,
+ backgroundColor: Color = Color.White,
+ borderColor: Color = Color.Transparent,
+ borderWidth: Int = 0,
+ cornerRadius: Int = 0
+) {
+ Button(
+ onClick = onClick,
+ modifier = modifier,
+ colors = buttonColors,
+ shape = RoundedCornerShape(cornerRadius.dp),
+ border = BorderStroke(width = borderWidth.dp, color = borderColor),
+ contentPadding = PaddingValues(16.dp),
+ elevation = ButtonDefaults.elevation(defaultElevation = 0.dp, pressedElevation = 2.dp)
+ ) {
+ Text(
+ text = text,
+ color = textColor,
+ modifier = Modifier.padding(4.dp)
+ )
+ }
+}
+
+@Composable
+fun Toggle(
+ name: String, client: AbstractClient,
+ modifier: Modifier = Modifier,
+ onCheckedChange: (Boolean) -> Unit = {}
+) {
+ val toggleField = client.getField(name) as ConnectionField
+
+ val checkedStateToggle = remember { toggleField }
+ Switch(
+ modifier = modifier.size(24.dp),
+ checked = checkedStateToggle.contentState.value,
+ onCheckedChange = {
+ checkedStateToggle.setValue(it)
+ client.sendValue(name)
+ onCheckedChange(it)
+ }
+ )
+}
+
+@Composable
+fun Checkbox(
+ name: String, client: AbstractClient,
+ modifier: Modifier = Modifier,
+ onCheckedChange: (Boolean) -> Unit = {},
+ checkboxColor: Color = MaterialTheme.colors.primary,
+ checkmarkColor: Color = Color.White,
+ disabledColor: Color = MaterialTheme.colors.onSurface.copy(alpha = 0.38f),
+ contentDescription: String? = null
+) {
+ val checkboxField = client.getField(name) as ConnectionField
+
+ val checkedStateCheckbox = remember { checkboxField }
+
+ Box(
+ modifier = modifier
+ .clickable(
+ onClick = {
+ val changedValue = !checkedStateCheckbox.contentState.value
+ checkedStateCheckbox.setValue(changedValue)
+ client.sendValue(name)
+ onCheckedChange(changedValue)
+ })
+ .size(24.dp),
+ contentAlignment = Alignment.Center
+ ) {
+ val backgroundColor = if (checkedStateCheckbox.contentState.value) checkboxColor else disabledColor
+ Box(
+ modifier = Modifier
+ .size(20.dp)
+ .background(color = backgroundColor)
+ .border(width = 2.dp, brush = METALLIC_BRUSH, shape = RectangleShape),
+ contentAlignment = Alignment.Center
+ ) {
+ if (checkedStateCheckbox.contentState.value) {
+ Icon(
+ imageVector = Icons.Default.Check,
+ contentDescription = contentDescription,
+ tint = checkmarkColor,
+ modifier = Modifier.size(12.dp)
+ )
+ }
+ }
+ }
+}
+
+@Composable
+fun ListBox(
+ name: String, client: AbstractClient,
+ modifier: Modifier = Modifier,
+ items: List,
+ onItemSelected: (String) -> Unit = {},
+ dropdownMaxHeight: Dp = 240.dp,
+ content: @Composable (String) -> Unit
+) {
+ val comboBoxField = client.getField(name) as ConnectionField
+
+ val checkedStateComboBox = remember { comboBoxField }
+ var expanded by remember { mutableStateOf(false) }
+ var dropdownHeight by remember { mutableStateOf(0.dp) }
+ val density = LocalDensity.current
+ val dropdownHeightPx = with(density) { dropdownHeight.toPx() }
+ var selectedItem: String by remember { mutableStateOf("") }
+ var textfieldSize by remember { mutableStateOf(0) }
+ val interactionSource = remember {
+ MutableInteractionSource()
+ }
+
+ Column(
+ modifier = modifier.wrapContentSize().clickable(
+ interactionSource = interactionSource,
+ indication = null,
+ onClick = {
+ expanded = false
+ }
+ ),
+ horizontalAlignment = Alignment.Start
+ ) {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .background(color = Color.White, shape = MaterialTheme.shapes.medium)
+ .padding(horizontal = 16.dp, vertical = 12.dp)
+ ) {
+ Row(verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier.onSizeChanged {
+ textfieldSize = it.width
+// print(textfieldSize)
+ }) {
+
+ Box(modifier = Modifier.weight(1f)) {
+ content(selectedItem)
+ }
+ Icon(imageVector = Icons.Default.ArrowDropDown,
+ contentDescription = null,
+ Modifier.size(24.dp).alignByBaseline().clickable { expanded = !expanded })
+
+ }
+ }
+ DropdownMenu(
+ expanded = expanded,
+ onDismissRequest = { expanded = false },
+ modifier = Modifier
+ .heightIn(max = dropdownMaxHeight)
+ .width(with(LocalDensity.current) { textfieldSize.toDp() })
+ .onSizeChanged { dropdownHeight = it.height.dp }
+ ) {
+ items.forEach { item ->
+ DropdownMenuItem(
+ onClick = {
+ checkedStateComboBox.setValue(item)
+ client.sendValue(name)
+ onItemSelected(item)
+ selectedItem = item
+ expanded = false
+ },
+ modifier = Modifier
+ .fillMaxWidth()
+ .heightIn(min = 48.dp)
+ ) {
+ content(item)
+ }
+ }
+ }
+ }
+}
+
+
+@Composable
+fun ComboBox(
+ name: String, client: AbstractClient,
+ modifier: Modifier = Modifier,
+ items: List,
+ onItemSelected: (String) -> Unit = {},
+ dropdownMaxHeight: Dp = 240.dp,
+ content: @Composable (String) -> Unit
+) {
+ val comboBoxField = client.getField(name) as ConnectionField
+
+ val checkedStateComboBox = remember { comboBoxField }
+ var expanded by remember { mutableStateOf(false) }
+ var dropdownHeight by remember { mutableStateOf(0.dp) }
+ var selectedItem by remember { mutableStateOf("") }
+ var textfieldSize by remember { mutableStateOf(0) }
+ val scrollState = rememberScrollState()
+
+ Column(
+ modifier = modifier.wrapContentSize(),
+ horizontalAlignment = Alignment.Start
+ ) {
+
+ Box(modifier = Modifier.fillMaxWidth()) {
+ OutlinedTextField(
+ value = selectedItem,
+ onValueChange = {
+ selectedItem = it
+ expanded = true
+ },
+ modifier = Modifier.fillMaxWidth(),
+ label = { Text("Select an item") },
+ keyboardOptions = KeyboardOptions(
+ keyboardType = KeyboardType.Text,
+ imeAction = ImeAction.Done
+ ),
+ trailingIcon = {
+ Icon(
+ Icons.Default.ArrowDropDown,
+ "Drop Down",
+ Modifier.size(24.dp).clickable { expanded = !expanded }
+ )
+ }
+ )
+ }
+ AnimatedVisibility(visible = expanded) {
+ Card(
+ modifier = Modifier
+ .padding(horizontal = 5.dp)
+ .fillMaxWidth(),
+ elevation = 15.dp
+ ) {
+ var currItems: List = items.sorted()
+ if (selectedItem.isNotEmpty()) {
+ currItems = items.filter { it.lowercase().contains(selectedItem.lowercase()) }
+ }
+ LazyColumn {
+ items(currItems){ item ->
+ Box(
+ modifier = Modifier.clickable {
+ checkedStateComboBox.setValue(item)
+ client.sendValue(name)
+ onItemSelected(item)
+ selectedItem = item
+ expanded = false
+ }
+ .fillMaxWidth()
+ .heightIn(min = 48.dp)
+ ) {
+ content(item)
+ }
+ }
+ }
+
+ }
+ }
+ }
+}
+
+@Composable
+fun InputTextBox(
+ name: String, client: AbstractClient,
+ modifier: Modifier = Modifier,
+ onItemChanged: (String) -> Unit = {},
+ hintText: String = "Write something"
+) {
+ val textBoxField = client.getField(name) as ConnectionField
+ val checkedStateTextBox = remember { textBoxField }
+ var selectedItem: String by remember { mutableStateOf("") }
+ Box(modifier = Modifier.fillMaxWidth()) {
+ OutlinedTextField(
+ value = selectedItem,
+ onValueChange = {
+ selectedItem = it
+ },
+ modifier = modifier.fillMaxWidth(),
+ label = { Text(hintText) },
+ keyboardOptions = KeyboardOptions(
+ keyboardType = KeyboardType.Text,
+ imeAction = ImeAction.Done
+ ),
+ trailingIcon = {
+ Icon(
+ Icons.Default.Check,
+ "Drop Down",
+ Modifier.size(24.dp).clickable {
+ checkedStateTextBox.setValue(selectedItem)
+ client.sendValue(name)
+ onItemChanged(checkedStateTextBox.getValue())
+ }
+ )
+ }
+ )
+ }
+}
+
+
+
+@Composable
+fun HorizontalTracker(
+ name: String, client: AbstractClient,
+ range: ClosedFloatingPointRange,
+ initialValue: Float,
+ onValueSelected: (Float) -> Unit = {},
+ modifier: Modifier = Modifier,
+ thumbColor: Color = Color.Magenta,
+ trackColor: Color = Color.LightGray,
+ borderWidth: Dp = 3.dp,
+ thumbSize: Dp = 24.dp,
+ trackWidth: Dp = 100.dp,
+ trackHeight: Dp = 24.dp,
+ legend: Boolean = true,
+ steps: Int = 0,
+ tickSize: Dp = 1.dp,
+ legendFontSize: TextUnit = 10.sp
+) {
+ val trackerField = client.getField(name) as ConnectionField
+ val checkedStateTracker = remember { trackerField }
+
+ var offsetX by remember { mutableStateOf(0f) }
+ var position by remember { mutableStateOf(getThumbPosition(initialValue, range, thumbSize, trackWidth)) }
+ var selectedValue by remember { mutableStateOf(initialValue) }
+
+ Box(modifier = modifier.padding(thumbSize / 2)) {
+ Spacer(
+ modifier = Modifier
+ .width(trackWidth)
+ .height(trackHeight)
+ .background(trackColor)
+ .zIndex(1f)
+ .border(BorderStroke(borderWidth, METALLIC_BRUSH))
+ )
+
+ Box(
+ modifier = Modifier
+ .offset(x = position)
+ .size(thumbSize)
+ .background(thumbColor, CircleShape)
+ .zIndex(2f)
+ .pointerInput(Unit) {
+ detectDragGestures(
+ onDragStart = {
+ },
+ onDragEnd = {
+ val newValue = ((position.value + thumbSize.value / 2) / trackWidth.value) * (range.endInclusive - range.start)
+ selectedValue = newValue.coerceIn(range)
+ checkedStateTracker.setValue(selectedValue)
+ client.sendValue(name)
+ onValueSelected(selectedValue)
+ },
+ onDragCancel = {
+ },
+ onDrag = { change, _ ->
+ offsetX = change.positionChange().x
+ position = max(0.dp - thumbSize / 2, min(position + offsetX.toDp(), trackWidth - thumbSize / 2))
+ change.consumePositionChange()
+ }
+ )
+ }
+ )
+
+ if (legend && steps > 0) {
+ val tickPositions =
+ (0..steps).map { Pair(range.start + it.toFloat() / steps * (range.endInclusive - range.start), it) }
+ tickPositions.forEach { position ->
+ val tickPosition = getTickPosition(position.first, range, thumbSize)
+ Column(modifier = Modifier.offset(x = tickPosition.dp).zIndex(0f)) {
+ Spacer(
+ modifier = Modifier
+ .height(trackHeight + tickSize)
+ .width(1.dp)
+ .background(Color.Black)
+
+ )
+ Text(position.second.toString(), modifier = Modifier.align(Alignment.CenterHorizontally), fontSize = legendFontSize)
+ }
+ }
+ }
+ }
+}
+
+fun getThumbPosition(value: Float, range: ClosedFloatingPointRange, thumbWidth: Dp, trackWidth: Dp): Dp {
+ val normalizedValue = (value - range.start) / (range.endInclusive - range.start)
+ return trackWidth * normalizedValue + thumbWidth / 2
+}
+
+fun getTickPosition(value: Float, range: ClosedFloatingPointRange, thumbWidth: Dp): Float {
+ val normalizedValue = (value - range.start) / (range.endInclusive - range.start)
+ val availableWidth = (range.endInclusive - range.start)
+ return availableWidth * normalizedValue
+}
+
+
+@Composable
+fun RoundKnob(
+ name: String, client: AbstractClient,
+ range: IntRange,
+ value: Float,
+ onValueSelected: (Number) -> Unit = {},
+ knobSize: Dp = 48.dp,
+ strokeWidth: Dp = 4.dp,
+ knobColor: Color = MaterialTheme.colors.primary,
+ legendColor: Color = MaterialTheme.colors.onBackground
+) {
+ val knobField = client.getField(name) as ConnectionField
+ val checkedStateKnob = remember { knobField }
+
+ var rotationAngle by remember { mutableStateOf(calculateRotationAngle(value, range))}
+ var currValue by remember { mutableStateOf(value) }
+ var offsetX by remember { mutableStateOf(0f) }
+ var offsetY by remember { mutableStateOf(0f) }
+ var centerX by remember { mutableStateOf(0f) }
+ var centerY by remember { mutableStateOf(0f) }
+ Box(
+ contentAlignment = Alignment.Center,
+ modifier = Modifier.size(knobSize)
+ ) {
+ Box(modifier = Modifier.pointerInput(Unit) {
+ detectDragGestures(
+ onDragStart = {
+ },
+ onDragEnd = {
+ var angle = atan2(centerY - offsetY, centerX - offsetX) * (180f / PI).toFloat() - 90
+ if (angle < -180) {
+ angle = (angle + 360) % 360
+ }
+ rotationAngle = (angle).coerceIn(-150f, 150f)
+ currValue = calculateValue(rotationAngle, range)
+
+ checkedStateKnob.setValue(currValue)
+ client.sendValue(name)
+ onValueSelected(currValue)
+ },
+ onDrag = { change, dragAmount ->
+ offsetX = change.position.x
+ offsetY = change.position.y
+ var angle = atan2(centerY - offsetY, centerX - offsetX) * (180f / PI).toFloat() - 90
+ if (angle < -180) {
+ angle = (angle + 360) % 360
+ }
+ rotationAngle = (angle).coerceIn(-150f , 150f)
+ currValue = calculateValue(rotationAngle, range)
+ change.consumePositionChange()
+ }
+ )
+ }) {
+ Canvas(modifier = Modifier.matchParentSize().onGloballyPositioned {
+ val windowBounds = it.boundsInWindow()
+ centerX = windowBounds.size.width / 2f
+ centerY = windowBounds.size.height / 2f
+ }) {
+ drawCircle(
+ brush = METALLIC_BRUSH,
+ radius = size.minDimension / 2,
+ style = Stroke(width = strokeWidth.toPx())
+ )
+ }
+
+ Canvas(
+ modifier = Modifier
+ .size(knobSize)
+ .rotate(rotationAngle)
+ ) {
+ drawLine(
+ color = knobColor,
+ start = Offset(x = size.width / 2, y = 0f),
+ end = Offset(x = size.width / 2, y = size.height / 2),
+ strokeWidth = strokeWidth.toPx(),
+ cap = StrokeCap.Round
+ )
+ }
+ }
+
+ Text(
+ text = currValue.toString(),
+ color = legendColor,
+ style = MaterialTheme.typography.body1,
+ textAlign = TextAlign.Center,
+ modifier = Modifier
+ .align(Alignment.BottomCenter)
+ .padding(bottom = 8.dp)
+ )
+ }
+}
+
+private fun calculateRotationAngle(value: Float, range: IntRange):Float {
+ val ratio = (value - range.start) / (range.endInclusive - range.start)
+ return ratio * 300 - 150
+}
+
+private fun calculateValue(rotationAngle: Float, range: IntRange): Float {
+ val ratio = (rotationAngle + 150) / 300
+ val value = ratio * (range.endInclusive - range.start) + range.start
+ return value
+}
diff --git a/code/cat_visual/src/main/kotlin/lib/visual/Positioning.kt b/code/cat_visual/src/main/kotlin/lib/visual/Positioning.kt
new file mode 100644
index 000000000..7f8ae0082
--- /dev/null
+++ b/code/cat_visual/src/main/kotlin/lib/visual/Positioning.kt
@@ -0,0 +1,32 @@
+package lib.visual
+
+import androidx.compose.foundation.layout.*
+import androidx.compose.runtime.Composable
+
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.zIndex
+
+@Composable
+fun PositionedBox(children: @Composable () -> Unit, x: Int = 0, y:Int = 0, zIndex: Float = 1f) {
+ Box(modifier = Modifier.
+ zIndex(zIndex)
+ .offset {IntOffset(x, y)}
+ .wrapContentSize()) {
+ children()
+ }
+}
+
+@Composable
+fun PositionedBox(children: @Composable () -> Unit, x: Float = 0f, y:Float = 0f, zIndex: Float = 1f) {
+ Box(modifier = Modifier.
+ zIndex(zIndex)
+ .offset(x.dp, y.dp)
+ .wrapContentSize()) {
+ children()
+ }
+}
+
+
diff --git a/code/cat_visual/src/main/kotlin/lib/visual/colors.kt b/code/cat_visual/src/main/kotlin/lib/visual/colors.kt
new file mode 100644
index 000000000..f67d03cf3
--- /dev/null
+++ b/code/cat_visual/src/main/kotlin/lib/visual/colors.kt
@@ -0,0 +1,51 @@
+package lib.elements
+
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+
+
+
+
+val LIGHT_RADIAL_BRUSH = Brush.radialGradient(
+ colors = listOf(
+ Color.Yellow,
+ Color(0xFFFFA500)
+ )
+)
+
+val GREEN_RADIAL_BRUSH = Brush.radialGradient(
+ colors = listOf(
+ Color.Gray,
+ Color.Yellow
+ )
+)
+
+val DARK_RADIAL_BRUSH = Brush.radialGradient(
+ colors = listOf(
+ Color.DarkGray,
+ Color.Gray
+ )
+)
+
+val LIGHT_HORIZONTAL_BRUSH = Brush.horizontalGradient(
+ colors = listOf(
+ Color.Yellow,
+ Color(0xFFA500FF)
+ )
+)
+
+val DARK_HORIZONTAL_BRUSH = Brush.horizontalGradient(
+ colors = listOf(
+ Color.Black,
+ Color.Gray
+ )
+)
+
+val METALLIC_BRUSH = Brush.linearGradient(
+ colors = listOf(
+ Color.White,
+ Color.Gray,
+ Color.DarkGray
+ )
+)
+
diff --git a/code/cat_visual/src/main/kotlin/serializer/serialization.kt b/code/cat_visual/src/main/kotlin/serializer/serialization.kt
new file mode 100644
index 000000000..9b4923cea
--- /dev/null
+++ b/code/cat_visual/src/main/kotlin/serializer/serialization.kt
@@ -0,0 +1,155 @@
+package serializer
+
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.modules.SerializersModule
+import kotlinx.serialization.serializer
+import nl.adaptivity.xmlutil.XmlDeclMode
+import nl.adaptivity.xmlutil.serialization.XML
+import nl.adaptivity.xmlutil.serialization.XmlElement
+import nl.adaptivity.xmlutil.serialization.XmlSerialName
+
+@Serializable
+@XmlSerialName("Mapping", "", "")
+data class PlainMapping(
+ val inputs: PlainInputs,
+ val outputs: PlainOutputs,
+)
+
+@Serializable
+@XmlSerialName("Inputs", "", "")
+data class PlainInputs(
+ val inputs: List
+)
+
+@Serializable
+@XmlSerialName("Input", "", "")
+data class PlainInput(
+ val name: String,
+ val type: String,
+ val host: String,
+ val port: Int
+)
+
+@Serializable
+@XmlSerialName("Outputs", "", "")
+data class PlainOutputs(
+ val outputs: List
+
+)
+
+@Serializable
+@XmlSerialName("Output", "", "")
+data class PlainOutput(
+ val name: String,
+ val type: String,
+ val host: String,
+ val port: Int
+)
+
+
+fun getPlainMapping(modelText: String): PlainMapping {
+ val module = SerializersModule {}
+ val xml = XML(module) {
+ indentString = " "
+ xmlDeclMode = XmlDeclMode.Minimal
+ autoPolymorphic = true
+ }
+
+ val serializer = serializer()
+ return xml.decodeFromString(serializer, modelText)
+}
+
+@Serializable
+@XmlSerialName("Mapping", "", "")
+data class Mapping(
+ val id: String,
+ val inputs: Inputs,
+ val outputs: Outputs,
+)
+
+@Serializable
+@XmlSerialName("Inputs", "", "")
+data class Inputs(
+ val inputs: List
+)
+
+@Serializable
+@XmlSerialName("Input", "", "")
+data class Input(
+ val name: String,
+ val type: String
+)
+
+@Serializable
+@XmlSerialName("Outputs", "", "")
+data class Outputs(
+ val outputs: List
+
+
+
diff --git a/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.editor.mps b/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.editor.mps
index e652db39a..543b3e73c 100644
--- a/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.editor.mps
+++ b/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.editor.mps
@@ -170,6 +170,7 @@
+
@@ -845,15 +846,8 @@
-
+
-
-
-
-
-
-
-
@@ -4381,5 +4375,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.intentions.mps b/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.intentions.mps
new file mode 100644
index 000000000..81075bdb0
--- /dev/null
+++ b/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.intentions.mps
@@ -0,0 +1,268 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.structure.mps b/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.structure.mps
index 2bfd4b611..ebc3e744a 100644
--- a/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.structure.mps
+++ b/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.structure.mps
@@ -1557,5 +1557,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/code/language/languages/org.fbme.ide.iec61499.lang/org.fbme.ide.iec61499.lang.mpl b/code/language/languages/org.fbme.ide.iec61499.lang/org.fbme.ide.iec61499.lang.mpl
index 9304586c9..5e19666f5 100644
--- a/code/language/languages/org.fbme.ide.iec61499.lang/org.fbme.ide.iec61499.lang.mpl
+++ b/code/language/languages/org.fbme.ide.iec61499.lang/org.fbme.ide.iec61499.lang.mpl
@@ -73,6 +73,7 @@
1db6de07-b355-4c0f-9979-75b4ac1e8215(org.fbme.lib)
ce018f97-56b9-4ee7-9b5f-2d462b6628bf(org.fbme.platform.lib)
d7eb0a2a-bd50-4576-beae-e4a89db35f20(jetbrains.mps.lang.scopes.runtime)
+ 1ed103c3-3aa6-49b7-9c21-6765ee11f224(MPS.Editor)
@@ -125,7 +126,10 @@
+
+
+
diff --git a/code/language/languages/org.fbme.ide.st.lang/models/org.fbme.ide.st.lang.editor.mps b/code/language/languages/org.fbme.ide.st.lang/models/org.fbme.ide.st.lang.editor.mps
index 3e6ce0ea7..5a947bc38 100644
--- a/code/language/languages/org.fbme.ide.st.lang/models/org.fbme.ide.st.lang.editor.mps
+++ b/code/language/languages/org.fbme.ide.st.lang/models/org.fbme.ide.st.lang.editor.mps
@@ -40,9 +40,6 @@
-
-
-
@@ -201,7 +198,6 @@
-
@@ -316,7 +312,6 @@
-
@@ -417,9 +412,6 @@
-
-
-
@@ -714,7 +706,7 @@
-
+
@@ -846,12 +838,8 @@
-
+
-
-
-
-
@@ -9716,7 +9704,7 @@
-
+
@@ -9725,12 +9713,12 @@
-
+
-
+
@@ -9886,111 +9874,5 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/code/language/solutions/org.fbme.ide.iec61499.adapter/models/org.fbme.ide.iec61499.adapter.interfacepart.mps b/code/language/solutions/org.fbme.ide.iec61499.adapter/models/org.fbme.ide.iec61499.adapter.interfacepart.mps
index 5a040de71..27717cb54 100644
--- a/code/language/solutions/org.fbme.ide.iec61499.adapter/models/org.fbme.ide.iec61499.adapter.interfacepart.mps
+++ b/code/language/solutions/org.fbme.ide.iec61499.adapter/models/org.fbme.ide.iec61499.adapter.interfacepart.mps
@@ -107,6 +107,7 @@
+
@@ -7660,5 +7661,395 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/code/language/solutions/org.fbme.ide.iec61499.adapter/models/org.fbme.ide.iec61499.repository.mps b/code/language/solutions/org.fbme.ide.iec61499.adapter/models/org.fbme.ide.iec61499.repository.mps
index 04d80e36f..7b30eee98 100644
--- a/code/language/solutions/org.fbme.ide.iec61499.adapter/models/org.fbme.ide.iec61499.repository.mps
+++ b/code/language/solutions/org.fbme.ide.iec61499.adapter/models/org.fbme.ide.iec61499.repository.mps
@@ -93,6 +93,9 @@
+
+
+
@@ -307,6 +310,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -4029,6 +4119,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/code/language/solutions/org.fbme.ide.iec61499.adapter/org.fbme.ide.iec61499.adapter.msd b/code/language/solutions/org.fbme.ide.iec61499.adapter/org.fbme.ide.iec61499.adapter.msd
index 269285bc8..ff7367ae2 100644
--- a/code/language/solutions/org.fbme.ide.iec61499.adapter/org.fbme.ide.iec61499.adapter.msd
+++ b/code/language/solutions/org.fbme.ide.iec61499.adapter/org.fbme.ide.iec61499.adapter.msd
@@ -17,7 +17,7 @@
8865b7a8-5271-43d3-884c-6fd1d9cfdd34(MPS.OpenAPI)
3f233e7f-b8a6-46d2-a57f-795d56775243(Annotations)
6594f340-4d73-4027-b7d3-c6ca2e70a53b(org.fbme.ide.iec61499.lang)
- 1db6de07-b355-4c0f-9979-75b4ac1e8215(org.fbme.lib)
+ 1db6de07-b355-4c0f-9979-75b4ac1e8215(org.fbme.lib)
14f8fb68-9526-41ae-a986-e33a7382fe12(org.fbme.ide.util.lang)
6ed54515-acc8-4d1e-a16c-9fd6cfe951ea(MPS.Core)
491a88b6-52bd-4c4e-a61a-8496046b69aa(org.fbme.ide.attributes)
diff --git a/code/language/src/main/kotlin/org/fbme/ide/iec61499/repository/PlatformDeclarationsScope.kt b/code/language/src/main/kotlin/org/fbme/ide/iec61499/repository/PlatformDeclarationsScope.kt
index 82038c66c..6e04737b8 100644
--- a/code/language/src/main/kotlin/org/fbme/ide/iec61499/repository/PlatformDeclarationsScope.kt
+++ b/code/language/src/main/kotlin/org/fbme/ide/iec61499/repository/PlatformDeclarationsScope.kt
@@ -18,6 +18,10 @@ internal class PlatformDeclarationsScope(
return findNode(identifier)?.let { repository.adapterOrNull(it) }
}
+ override fun findCATBlockTypeDeclaration(identifier: Identifier): CATBlockTypeDeclaration? {
+ return findNode(identifier)?.let {repository.getAdapter(it, CATBlockTypeDeclaration::class.java)}
+ }
+
override fun findBasicFBTypeDeclaration(identifier: Identifier): BasicFBTypeDeclaration? {
return findNode(identifier)?.let { repository.adapterOrNull(it) }
}
diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/DeclarationsScope.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/DeclarationsScope.kt
index 3e35d940a..73f634785 100644
--- a/code/library/src/main/kotlin/org/fbme/lib/iec61499/DeclarationsScope.kt
+++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/DeclarationsScope.kt
@@ -6,6 +6,7 @@ import org.fbme.lib.iec61499.fbnetwork.FunctionBlockDeclaration
interface DeclarationsScope {
fun findCompositeFBTypeDeclaration(identifier: Identifier): CompositeFBTypeDeclaration?
+ fun findCATBlockTypeDeclaration(identifier: Identifier): CATBlockTypeDeclaration?
fun findBasicFBTypeDeclaration(identifier: Identifier): BasicFBTypeDeclaration?
fun findServiceFBTypeDeclaration(identifier: Identifier): ServiceInterfaceFBTypeDeclaration?
fun findAdapterTypeDeclaration(identifier: Identifier): AdapterTypeDeclaration?
diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/IEC61499Factory.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/IEC61499Factory.kt
index 5cf2d46dc..99f791479 100644
--- a/code/library/src/main/kotlin/org/fbme/lib/iec61499/IEC61499Factory.kt
+++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/IEC61499Factory.kt
@@ -18,6 +18,8 @@ interface IEC61499Factory {
fun createApplicationDeclaration(identifier: Identifier?): ApplicationDeclaration
fun createBasicFBTypeDeclaration(identifier: Identifier?): BasicFBTypeDeclaration
fun createCompositeFBTypeDeclaration(identifier: Identifier?): CompositeFBTypeDeclaration
+ fun createCATBlockTypeDeclaration(identifier: Identifier?): CATBlockTypeDeclaration
+ fun createHMIBlockTypeDeclaration(identifier: Identifier?): HMIInterfaceTypeDeclaration
fun createDeviceDeclaration(identifier: Identifier?): DeviceDeclaration
fun createDeviceTypeDeclaration(identifier: Identifier?): DeviceTypeDeclaration
fun createParameterAssignment(): ParameterAssignment
diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/CATBlockTypeDeclaration.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/CATBlockTypeDeclaration.kt
new file mode 100644
index 000000000..84632a1d4
--- /dev/null
+++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/CATBlockTypeDeclaration.kt
@@ -0,0 +1,12 @@
+package org.fbme.lib.iec61499.declarations
+
+import org.fbme.lib.common.Declaration
+import org.fbme.lib.common.RootElement
+import org.fbme.lib.common.Reference
+
+
+interface CATBlockTypeDeclaration : Declaration, RootElement {
+ val hmiInterface: Reference
+ val blockDeclaration: Reference
+ var interfaceFileName: String
+}
\ No newline at end of file
diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/DependentFilesDefinition.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/DependentFilesDefinition.kt
new file mode 100644
index 000000000..4de6a8b2e
--- /dev/null
+++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/DependentFilesDefinition.kt
@@ -0,0 +1,5 @@
+package org.fbme.lib.iec61499.declarations
+
+interface DependentFilesDefinition {
+ var value: String
+}
\ No newline at end of file
diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/HMIInterfaceTypeDeclaration.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/HMIInterfaceTypeDeclaration.kt
new file mode 100644
index 000000000..8a4908a4f
--- /dev/null
+++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/HMIInterfaceTypeDeclaration.kt
@@ -0,0 +1,5 @@
+package org.fbme.lib.iec61499.declarations
+
+
+interface HMIInterfaceTypeDeclaration : FBTypeDeclaration{
+}
\ No newline at end of file
diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/SubCATDeclaration.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/SubCATDeclaration.kt
new file mode 100644
index 000000000..e123165a4
--- /dev/null
+++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/SubCATDeclaration.kt
@@ -0,0 +1,8 @@
+package org.fbme.lib.iec61499.declarations
+
+import org.fbme.lib.common.ContainedElement
+import org.fbme.lib.common.Declaration
+
+interface SubCATDeclaration: Declaration, ContainedElement {
+
+}
diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/SymbolDefinition.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/SymbolDefinition.kt
new file mode 100644
index 000000000..da071c7f3
--- /dev/null
+++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/declarations/SymbolDefinition.kt
@@ -0,0 +1,5 @@
+package org.fbme.lib.iec61499.declarations
+
+interface SymbolDefinition {
+ var children: List
+}
\ No newline at end of file
diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/parser/CATBlockTypeConverter.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/parser/CATBlockTypeConverter.kt
new file mode 100644
index 000000000..29a275664
--- /dev/null
+++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/parser/CATBlockTypeConverter.kt
@@ -0,0 +1,19 @@
+package org.fbme.lib.iec61499.parser
+
+import org.fbme.lib.common.Identifier
+import org.fbme.lib.common.Reference
+import org.fbme.lib.iec61499.declarations.CATBlockTypeDeclaration
+import org.fbme.lib.iec61499.declarations.CompositeFBTypeDeclaration
+import org.fbme.lib.iec61499.fbnetwork.FunctionBlockDeclaration
+
+class CATBlockTypeConverter(arguments: ConverterArguments) :
+ DeclarationConverterBase(arguments) {
+ override fun extractDeclarationBody(identifier: Identifier?): CATBlockTypeDeclaration {
+ checkNotNull(element)
+ val fbtd = factory.createCATBlockTypeDeclaration(identifier)
+ fbtd.blockDeclaration.setTargetName(element.getChild("Composite").getAttributeValue("Type"))
+ fbtd.hmiInterface.setTargetName(element.getChild("HMI").getAttributeValue("Type"))
+ fbtd.interfaceFileName = element.getChild("HMI").getAttributeValue("InterfaceFile")
+ return fbtd
+ }
+}
\ No newline at end of file
diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/parser/HMIInterfaceConverter.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/parser/HMIInterfaceConverter.kt
new file mode 100644
index 000000000..08b3bcab6
--- /dev/null
+++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/parser/HMIInterfaceConverter.kt
@@ -0,0 +1,27 @@
+package org.fbme.lib.iec61499.parser
+
+import org.fbme.lib.common.Identifier
+import org.fbme.lib.common.Reference
+import org.fbme.lib.iec61499.declarations.CATBlockTypeDeclaration
+import org.fbme.lib.iec61499.declarations.CompositeFBTypeDeclaration
+import org.fbme.lib.iec61499.declarations.HMIInterfaceTypeDeclaration
+import org.fbme.lib.iec61499.fbnetwork.FunctionBlockDeclaration
+
+class HMIInterfaceConverter(arguments: ConverterArguments) :
+ DeclarationConverterBase(arguments) {
+ override fun extractDeclarationBody(identifier: Identifier?): HMIInterfaceTypeDeclaration {
+ checkNotNull(element)
+ val fbtd = factory.createHMIBlockTypeDeclaration(identifier)
+ val interfaceListElement = element.getChild("InterfaceList")
+ ParameterDeclarationConverter.extractAll(
+ with(interfaceListElement.getChild("InputVars")),
+ fbtd.inputParameters
+ )
+ ParameterDeclarationConverter.extractAll(
+ with(interfaceListElement.getChild("OutputVars")),
+ fbtd.outputParameters
+ )
+
+ return fbtd
+ }
+}
\ No newline at end of file
diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/parser/RootConverter.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/parser/RootConverter.kt
index d6d5e1139..cbbff2411 100644
--- a/code/library/src/main/kotlin/org/fbme/lib/iec61499/parser/RootConverter.kt
+++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/parser/RootConverter.kt
@@ -10,6 +10,9 @@ class RootConverter(
) {
fun convertFBType(): FBTypeDeclaration {
val root = myDocument.rootElement
+ if (root.getAttribute("UsedInCAT") != null && root.getAttribute("UsedInCAT").value == "True") {
+ return HMIInterfaceConverter(arguments()).extract()
+ }
if (root.getChild("FBNetwork") != null) {
return myConfiguration.createCompositeFbTypeConverter(arguments()).extract()
}
@@ -44,6 +47,10 @@ class RootConverter(
return SystemConverter(arguments()).extract()
}
+ fun convertCATConfiguration(): CATBlockTypeDeclaration {
+ return CATBlockTypeConverter(arguments()).extract()
+ }
+
private fun arguments(): ConverterArgumentsHolder {
return ConverterArgumentsHolder(
myConfiguration.entryFactory,
diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/stringify/CATBlockTypePrinter.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/stringify/CATBlockTypePrinter.kt
new file mode 100644
index 000000000..15e06b1d6
--- /dev/null
+++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/stringify/CATBlockTypePrinter.kt
@@ -0,0 +1,30 @@
+package org.fbme.lib.iec61499.stringify
+
+import org.fbme.lib.iec61499.declarations.CATBlockTypeDeclaration
+import org.fbme.lib.iec61499.declarations.CompositeFBTypeDeclaration
+import org.fbme.lib.iec61499.fbnetwork.FunctionBlockDeclaration
+import org.jdom.Element
+
+class CATBlockTypePrinter(declaration: CATBlockTypeDeclaration) :
+ DeclarationPrinterBase(declaration, "CAT") {
+ override fun printDeclarationBody(element: Element) {
+ element.setAttribute("name", this.element.name)
+ element.addContent(CompositePrinter(this.element).print())
+ element.addContent(HMIPrinter(this.element).print())
+ }
+
+ private class CompositePrinter(fb: CATBlockTypeDeclaration) :
+ DeclarationPrinterBase(fb, "Composite") {
+ override fun printDeclarationBody(element: Element) {
+ element.setAttribute("Type", this.element.blockDeclaration.presentation)
+ }
+ }
+
+ private class HMIPrinter(fb: CATBlockTypeDeclaration) :
+ DeclarationPrinterBase(fb, "HMI") {
+ override fun printDeclarationBody(element: Element) {
+ element.setAttribute("Type", this.element.hmiInterface.presentation)
+ element.setAttribute("InterfaceFile", this.element.interfaceFileName)
+ }
+ }
+}
\ No newline at end of file
diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/stringify/DependentDeclarationGenerator.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/stringify/DependentDeclarationGenerator.kt
new file mode 100644
index 000000000..d66920d59
--- /dev/null
+++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/stringify/DependentDeclarationGenerator.kt
@@ -0,0 +1,39 @@
+package org.fbme.lib.iec61499.stringify
+
+import org.fbme.lib.common.Declaration
+import org.fbme.lib.common.RootElement
+import org.fbme.lib.iec61499.declarations.CATBlockTypeDeclaration
+import org.fbme.lib.iec61499.declarations.FBTypeDeclaration
+import org.fbme.lib.iec61499.declarations.HMIInterfaceTypeDeclaration
+import org.fbme.lib.iec61499.parser.ConverterArguments
+import org.fbme.lib.iec61499.parser.Iec61499ConverterConfiguration
+
+class DependentDeclarationGenerator(private val myDeclaration: Declaration, private val converterArguments: Iec61499ConverterConfiguration) {
+
+
+ fun generate(): List {
+ val rootElements: List = when (myDeclaration) {
+ is HMIInterfaceTypeDeclaration -> HMIInterfaceTypeGenerator(myDeclaration, converterArguments).generateDependents()
+ else -> listOf()
+ }
+
+
+ return rootElements
+ }
+
+ fun getName(): String {
+ val name = when (myDeclaration) {
+ is HMIInterfaceTypeDeclaration -> HMIInterfaceTypeGenerator.getDeclarationName(myDeclaration.name)
+ else -> myDeclaration.name
+ }
+ return name
+ }
+
+ fun getIdentifier(): String {
+ val name = when (myDeclaration) {
+ is HMIInterfaceTypeDeclaration -> HMIInterfaceTypeGenerator.getDeclarationName(myDeclaration.identifier.toString())
+ else -> myDeclaration.name
+ }
+ return name
+ }
+}
\ No newline at end of file
diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/stringify/HMIBlockPrinter.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/stringify/HMIBlockPrinter.kt
new file mode 100644
index 000000000..7eaea2f42
--- /dev/null
+++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/stringify/HMIBlockPrinter.kt
@@ -0,0 +1,23 @@
+package org.fbme.lib.iec61499.stringify
+
+import org.fbme.lib.iec61499.declarations.HMIInterfaceTypeDeclaration
+import org.fbme.lib.iec61499.parser.Iec61499ConverterConfiguration
+import org.jdom.Element
+
+class HMIBlockPrinter(declaration: HMIInterfaceTypeDeclaration, val converterArguments: Iec61499ConverterConfiguration) :
+ DeclarationPrinterBase(declaration, "HMIDeclaration") {
+
+ val factory = converterArguments.entryFactory
+ val stFactory = converterArguments.stEntryFactory
+ override fun printDeclarationBody(element: Element) {
+ element.addContent(FBInterfacePrinterWithAdapters(this.element).print())
+// val cFB = CompositeFBTypePrinter(HMIInterfaceTypeGenerator.generateComposite(factory, stFactory, this.element)).print().children
+// for (c in cFB) {
+// element.addContent(c.clone().detach())
+// }
+ addNullableContent(element, ParameterDeclarationPrinter.printAll("InputVars", this.element.inputParameters))
+ addNullableContent(element, ParameterDeclarationPrinter.printAll("OutputVars", this.element.outputParameters))
+ element.setAttribute("UsedInCAT", "True")
+ }
+
+}
\ No newline at end of file
diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/stringify/HMIInterfaceTypeGenerator.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/stringify/HMIInterfaceTypeGenerator.kt
new file mode 100644
index 000000000..6224a7243
--- /dev/null
+++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/stringify/HMIInterfaceTypeGenerator.kt
@@ -0,0 +1,469 @@
+package org.fbme.lib.iec61499.stringify
+
+import org.fbme.lib.common.Identifier
+import org.fbme.lib.iec61499.IEC61499Factory
+import org.fbme.lib.iec61499.declarations.*
+import org.fbme.lib.iec61499.fbnetwork.EntryKind
+import org.fbme.lib.iec61499.fbnetwork.FunctionBlockDeclaration
+import org.fbme.lib.iec61499.parser.Iec61499ConverterConfiguration
+import org.fbme.lib.iec61499.parser.STConverter
+import org.fbme.lib.st.STFactory
+import org.fbme.lib.st.expressions.Expression
+import org.fbme.lib.st.expressions.Literal
+import org.fbme.lib.st.statements.Statement
+import org.fbme.lib.st.types.DataType
+import org.fbme.lib.st.types.ElementaryType
+import java.util.function.BiFunction
+
+class HMIInterfaceTypeGenerator(val declaration: HMIInterfaceTypeDeclaration, val converterArguments: Iec61499ConverterConfiguration) {
+
+ val factory = converterArguments.entryFactory
+ val stFactory = converterArguments.stEntryFactory
+
+ fun generateDependents(): List {
+ val elements = mutableListOf()
+ val name = getDeclarationName(declaration.name)
+ val identifier = declaration.identifier.toString()
+ if (declaration.inputParameters.size > 0) {
+ val outFb = generateDispatchOut(factory, stFactory, declaration.inputParameters, name, identifier)
+ elements.add(outFb)
+ }
+ if (declaration.outputParameters.size > 0) {
+ val inFb = generateDispatchIn(
+ factory,
+ stFactory,
+ declaration.outputParameters,
+ name,
+ identifier
+ )
+ elements.add(inFb)
+ }
+ val compositeHMI = generateComposite(factory, stFactory, declaration, identifier)
+ elements.add(compositeHMI)
+ return elements
+ }
+
+
+companion object {
+ val CONNECTION_TYPES = listOf(
+ ElementaryType.BOOL,
+ ElementaryType.INT,
+ ElementaryType.REAL,
+ ElementaryType.LREAL,
+ ElementaryType.STRING
+ )
+
+ fun generateDispatchOut(factory: IEC61499Factory, stFactory: STFactory, outputVars: List, name: String = "", identifier: String = ""): FBTypeDeclaration {
+ val bfb = factory.createBasicFBTypeDeclaration(null)
+ bfb.name = "DISPATCH_OUT_${name}"
+
+ val startState = factory.createStateDeclaration(null)
+ startState.name = "START"
+ bfb.ecc.states.add(startState)
+
+ val nameDeclaration = generateParameterDeclaration(factory, "NAME", ElementaryType.STRING, listOf())
+ val mappingDeclaration = generateParameterDeclaration(factory, "MAPPING", ElementaryType.STRING, listOf())
+ mappingDeclaration.initialValue = STConverter.parseLiteral(stFactory, identifier)
+ bfb.inputParameters.add(mappingDeclaration)
+ bfb.outputParameters.add(nameDeclaration)
+
+ val outConnections = generateTypedConnections(factory, listOf(), CONNECTION_TYPES)
+ bfb.outputParameters.addAll(outConnections)
+ val typedEventsMap = mutableMapOf()
+ outConnections.forEach {
+ val typedEvent = factory.createEventDeclaration(null)
+ typedEvent.name = "IS_${it.type}"
+ val assoc = factory.createEventAssociation()
+ assoc.parameterReference.setTarget(it)
+ typedEvent.associations.add(assoc)
+
+ val nameAssoc = factory.createEventAssociation()
+ nameAssoc.parameterReference.setTarget(nameDeclaration)
+ typedEvent.associations.add(nameAssoc)
+ bfb.outputEvents.add(typedEvent)
+ typedEventsMap.put(it.type!!, typedEvent)
+ }
+
+ for (iV in outputVars) {
+ val state = factory.createStateDeclaration(null)
+ state.name = "SET_${iV.name}"
+ val action = factory.createStateAction()
+ val event = factory.createEventDeclaration(null)
+ event.name = "IS_${iV.name}"
+ val mappingAssoc = factory.createEventAssociation()
+ mappingAssoc.parameterReference.setTarget(mappingDeclaration)
+ event.associations.add(mappingAssoc)
+ val currParameter = generateParameterDeclaration(factory, iV.name, iV.type, listOf(event))
+ currParameter.initialValue = iV.initialValue
+ bfb.inputParameters.add(currParameter)
+ bfb.inputEvents.add(event)
+ val algorithm = factory.createAlgorithmDeclaration(null)
+ algorithm.name = "set${iV.name}"
+ val algorithmBody = factory.createAlgorithmBody(AlgorithmLanguage.ST)
+ val outCode = generateOutCode(iV, algorithm, factory, stFactory)
+ algorithmBody.statements.addAll(outCode)
+ algorithm.body = algorithmBody
+ action.algorithm.setTarget(algorithm)
+ bfb.algorithms.add(algorithm)
+ state.actions.add(action)
+ bfb.ecc.states.add(state)
+
+ val fromStartTransition = factory.createStateTransition()
+ fromStartTransition.condition.eventReference.setFQName("IS_${iV.name}")
+ fromStartTransition.sourceReference.setTarget(startState)
+ fromStartTransition.targetReference.setTarget(state)
+ bfb.ecc.transitions.add(fromStartTransition)
+ val toStartTransition = factory.createStateTransition()
+ toStartTransition.sourceReference.setTarget(state)
+ toStartTransition.targetReference.setTarget(startState)
+ bfb.ecc.transitions.add(toStartTransition)
+ }
+
+ return bfb
+ }
+
+
+ fun generateDispatchIn(factory: IEC61499Factory, stFactory: STFactory, inputVars: List, name: String = "", identifier: String = ""): FBTypeDeclaration {
+ val bfb = factory.createBasicFBTypeDeclaration(null)
+ bfb.name = "DISPATCH_IN_${name}"
+ val startState = factory.createStateDeclaration(null)
+ startState.name = "START"
+ bfb.ecc.states.add(startState)
+ val reqEvent = factory.createEventDeclaration(null)
+ reqEvent.name = "REQ"
+ bfb.inputEvents.add(reqEvent)
+ val inConnections = generateTypedConnections(factory, listOf(reqEvent), CONNECTION_TYPES)
+ val nameDeclaration = generateParameterDeclaration(factory, "NAME", ElementaryType.STRING, listOf(reqEvent))
+ val mappingDeclaration = generateParameterDeclaration(factory, "MAPPING", ElementaryType.STRING, listOf(reqEvent))
+ mappingDeclaration.initialValue = STConverter.parseLiteral(stFactory, identifier)
+ bfb.inputParameters.addAll(inConnections)
+ bfb.inputParameters.add(nameDeclaration)
+ bfb.inputParameters.add(mappingDeclaration)
+ for (iV in inputVars) {
+ if (iV.name == "MAPPING") {
+ continue
+ }
+
+ val event = factory.createEventDeclaration(null)
+ val eventAssociation = factory.createEventAssociation()
+
+ val state = factory.createStateDeclaration(null)
+ state.name = "SET_${iV.name}"
+ val action = factory.createStateAction()
+ val currParameter = factory.createParameterDeclaration(null)
+ currParameter.name = iV.name
+ currParameter.type = iV.type
+ currParameter.initialValue = iV.initialValue
+ bfb.outputParameters.add(currParameter)
+ eventAssociation.parameterReference.setTarget(currParameter)
+ event.name = "IS_${iV.name}"
+ event.associations.add(eventAssociation)
+ bfb.outputEvents.add(event)
+ val algorithm = factory.createAlgorithmDeclaration(null)
+ algorithm.name = "set${iV.name}"
+ val algorithmBody = factory.createAlgorithmBody(AlgorithmLanguage.ST)
+ val outCode = generateInCode(iV, algorithm, factory, stFactory)
+ algorithmBody.statements.addAll(outCode)
+ algorithm.body = algorithmBody
+ action.algorithm.setTarget(algorithm)
+ bfb.algorithms.add(algorithm)
+ state.actions.add(action)
+ bfb.ecc.states.add(state)
+ val fromStartTransition = factory.createStateTransition()
+ fromStartTransition.condition.setGuardCondition(generateInGuard(iV, stFactory))
+ fromStartTransition.sourceReference.setTarget(startState)
+ fromStartTransition.targetReference.setTarget(state)
+ bfb.ecc.transitions.add(fromStartTransition)
+ val toStartTransition = factory.createStateTransition()
+ toStartTransition.sourceReference.setTarget(state)
+ toStartTransition.targetReference.setTarget(startState)
+ bfb.ecc.transitions.add(toStartTransition)
+ }
+
+ return bfb
+ }
+
+ fun generateTypedConnections(factory: IEC61499Factory, events: List, types: List): List {
+ val resTypes = mutableListOf()
+ types.forEach {
+ val p = factory.createParameterDeclaration(null)
+ p.type = it
+ p.name = "${p.type}_VALUE"
+ resTypes.add(p)
+ events.forEach {
+ val assoc = factory.createEventAssociation()
+ assoc.parameterReference.setTarget(p)
+ it.associations.add(assoc)
+ }
+ }
+ return resTypes
+ }
+
+ fun generateParameterDeclaration(factory: IEC61499Factory, name: String, type: DataType?, associatedEvents: List): ParameterDeclaration {
+ val p_ = factory.createParameterDeclaration(null)
+ p_.type = type
+ p_.name = name
+ associatedEvents.forEach {
+ val assoc = factory.createEventAssociation()
+ assoc.parameterReference.setTarget(p_)
+ it.associations.add(assoc)
+ }
+ return p_
+ }
+
+
+ fun generateCode(code: String, factory: IEC61499Factory, stFactory: STFactory, algorithmDeclaration: AlgorithmDeclaration): List {
+ val parameterCollector =
+ BiFunction { name: Identifier?, type: DataType? ->
+ val parameterDeclaration: ParameterDeclaration =
+ factory.createParameterDeclaration(name)
+ parameterDeclaration.type = type
+ algorithmDeclaration.temporaryVariables.add(parameterDeclaration)
+ Unit
+ }
+ return STConverter.parseStatementListWithDeclarations(
+ stFactory,
+ code,
+ { t, u -> parameterCollector.apply(t, u) }
+ )
+ }
+
+ fun generateOutCode(p: ParameterDeclaration, algorithmDeclaration: AlgorithmDeclaration, factory: IEC61499Factory, stFactory: STFactory): List {
+ val code = "VAR \n" +
+ "TAG: STRING;\n" +
+ "END_VAR;" +
+ "TAG:= '#${p.name}';\n" +
+ "NAME:=CONCAT(MAPPING, TAG);\n" +
+ "${p.type}_VALUE:=${p.name};]]>"
+ val statementList = generateCode(code, factory, stFactory, algorithmDeclaration)
+ return statementList
+ }
+
+ fun generateInCode(p: ParameterDeclaration, algorithmDeclaration: AlgorithmDeclaration, factory: IEC61499Factory, stFactory: STFactory): List {
+ val code = "${p.name}:= ${p.type!!.stringify()}_VALUE;"
+
+ return generateCode(code, factory, stFactory, algorithmDeclaration)
+ }
+
+ fun generateInGuard(p: ParameterDeclaration, stFactory: STFactory): Expression {
+ val code = "(LEFT(NAME, LEN(NAME) - ${p.name.length + 2}) = MAPPING) AND (RIGHT(NAME, LEN(NAME) - LEN(NAME) + ${p.name.length + 1}) = '#${p.name}')"
+ return STConverter.parseExpression(stFactory, code)!!
+ }
+
+ fun getDeclarationName(name: String): String {
+ if (name.length < 5) {
+ return name
+ }
+ if (name.endsWith("_HMI")) {
+ return name.take(name.length - 4)
+ }
+ if (name.length < 9) {
+ return name
+ }
+ if (name.endsWith("_HMI_CONF")) {
+ return name.take(name.length - 9)
+ }
+ return name
+ }
+
+ fun generateComposite(factory: IEC61499Factory, stFactory: STFactory, declaration: HMIInterfaceTypeDeclaration, identifier: String = ""): CompositeFBTypeDeclaration {
+ var cFB = factory.createCompositeFBTypeDeclaration(null)
+ val targetBlockName = getDeclarationName(declaration.name)
+ cFB.name = targetBlockName + "_HMI"
+ declaration.inputParameters.forEach {
+ if (it.name != "MAPPING") {
+ val pD = factory.createParameterDeclaration(null)
+ pD.name = it.name
+ pD.type = it.type
+ pD.initialValue = it.initialValue
+ cFB.inputParameters.add(pD)
+ }
+ }
+ declaration.outputParameters.forEach {
+ val pD = factory.createParameterDeclaration(null)
+ pD.name = it.name
+ pD.type = it.type
+ pD.initialValue = it.initialValue
+ cFB.outputParameters.add(pD)
+ }
+
+ val initEvent = factory.createEventDeclaration(null)
+ initEvent.name = "INIT"
+ initEvent.associations
+ val mappingInput = factory.createParameterDeclaration(null)
+ mappingInput.name = "MAPPING"
+ mappingInput.type = ElementaryType.STRING
+ mappingInput.initialValue = STConverter.parseLiteral(stFactory, identifier)
+ cFB.inputParameters.add(mappingInput)
+ cFB.inputEvents.add(initEvent)
+
+ if (cFB.inputParameters.size - 1 > 0) {
+// NEEDED FUNCTION BLOCKS GENERATION
+
+ cFB.network.functionBlocks.add(generateCommunicationBlock("PUBLISH_1", factory, stFactory))
+ cFB.network.functionBlocks.add(generateFunctionDeclarationNoParam("JSON_SERIALIZER", factory))
+ cFB.network.functionBlocks.add(generateFunctionDeclarationNoParam("DISPATCH_OUT_$targetBlockName", factory))
+
+// EVENTS GENERATION
+
+ val cEInitPublish = factory.createFBNetworkConnection(EntryKind.EVENT)
+ cEInitPublish.sourceReference.setFQName("INIT")
+ cEInitPublish.targetReference.setFQName("PUBLISH_1.INIT")
+ cFB.network.eventConnections.add(cEInitPublish)
+
+ val cESerializerPublish = factory.createFBNetworkConnection(EntryKind.EVENT)
+ cESerializerPublish.sourceReference.setFQName("JSON_SERIALIZER.RES")
+ cESerializerPublish.targetReference.setFQName("PUBLISH_1.REQ")
+ cFB.network.eventConnections.add(cESerializerPublish)
+
+ declaration.inputParameters.stream().filter{it.name != "MAPPING"}
+ .forEach{
+ val event = generateEventForParameter(it, factory)
+ cFB.inputEvents.add(event)
+ val cEDispatch = factory.createFBNetworkConnection(EntryKind.EVENT)
+ cEDispatch.sourceReference.setFQName(event.name)
+ cEDispatch.targetReference.setFQName("DISPATCH_OUT_$targetBlockName.${event.name}")
+ cFB.network.eventConnections.add(cEDispatch)
+ }
+
+// DATA CONNECTIONS GENERATION
+
+ val cDSerializerPublish = factory.createFBNetworkConnection(EntryKind.DATA)
+ cDSerializerPublish.sourceReference.setFQName("JSON_SERIALIZER.MSG")
+ cDSerializerPublish.targetReference.setFQName("PUBLISH_1.SD_1")
+ cFB.network.dataConnections.add(cDSerializerPublish)
+
+ declaration.inputParameters.stream()
+ .forEach{
+ val cDDispatch = factory.createFBNetworkConnection(EntryKind.DATA)
+ cDDispatch.sourceReference.setFQName(it.name)
+ cDDispatch.targetReference.setFQName("DISPATCH_OUT_$targetBlockName.${it.name}")
+ cFB.network.dataConnections.add(cDDispatch)
+ }
+
+ val cDNameSerialize = factory.createFBNetworkConnection(EntryKind.DATA)
+ cDNameSerialize.sourceReference.setFQName("DISPATCH_OUT_$targetBlockName.NAME")
+ cDNameSerialize.targetReference.setFQName("JSON_SERIALIZER.NAME")
+ cFB.network.dataConnections.add(cDNameSerialize)
+
+// CONNECTIONS FOR DIFFERENT DATA TYPES
+
+ HMIInterfaceTypeGenerator.CONNECTION_TYPES.forEach {
+ val cEDispatch = factory.createFBNetworkConnection(EntryKind.EVENT)
+ cEDispatch.sourceReference.setFQName("DISPATCH_OUT_$targetBlockName.IS_${it.name}")
+ cEDispatch.targetReference.setFQName("JSON_SERIALIZER.IS_${it.name}")
+ cFB.network.eventConnections.add(cEDispatch)
+ val cDDispatch = factory.createFBNetworkConnection(EntryKind.DATA)
+ cDDispatch.sourceReference.setFQName("DISPATCH_OUT_$targetBlockName.${it.name}_VALUE")
+ cDDispatch.targetReference.setFQName("JSON_SERIALIZER.${it.name}_VALUE")
+ cFB.network.dataConnections.add(cDDispatch)
+ }
+
+ }
+
+ if (declaration.outputParameters.size > 0) {
+// NEEDED FUNCTION BLOCKS GENERATION
+
+ cFB.network.functionBlocks.add(generateCommunicationBlock("SUBSCRIBE_1", factory, stFactory, 65012))
+ cFB.network.functionBlocks.add(generateFunctionDeclarationNoParam("JSON_DESERIALIZER", factory))
+ cFB.network.functionBlocks.add(generateFunctionDeclarationNoParam("DISPATCH_IN_$targetBlockName", factory))
+
+// EVENTS GENERATION
+
+ val cEInitSubscribe = factory.createFBNetworkConnection(EntryKind.EVENT)
+ cEInitSubscribe.sourceReference.setFQName("INIT")
+ cEInitSubscribe.targetReference.setFQName("SUBSCRIBE_1.INIT")
+ cFB.network.eventConnections.add(cEInitSubscribe)
+
+ val cESubscribeDeserialize = factory.createFBNetworkConnection(EntryKind.EVENT)
+ cESubscribeDeserialize.sourceReference.setFQName("SUBSCRIBE_1.IND")
+ cESubscribeDeserialize.targetReference.setFQName("JSON_DESERIALIZER.REQ")
+ cFB.network.eventConnections.add(cESubscribeDeserialize)
+
+ val cEDeserializeDispatch = factory.createFBNetworkConnection(EntryKind.EVENT)
+ cEDeserializeDispatch.sourceReference.setFQName("JSON_DESERIALIZER.RES")
+ cEDeserializeDispatch.targetReference.setFQName("DISPATCH_IN_$targetBlockName.REQ")
+ cFB.network.eventConnections.add(cEDeserializeDispatch)
+
+ declaration.outputParameters.forEach {
+ val event = generateEventForParameter(it, factory)
+ cFB.outputEvents.add(event)
+
+ val cEDispatchRes = factory.createFBNetworkConnection(EntryKind.EVENT)
+ cEDispatchRes.sourceReference.setFQName("DISPATCH_IN_$targetBlockName.${event.name}")
+ cEDispatchRes.targetReference.setFQName(event.name)
+ cFB.network.eventConnections.add(cEDispatchRes)
+ }
+
+// DATA GENERATION
+
+ val cDSubscribeDeserialize = factory.createFBNetworkConnection(EntryKind.DATA)
+ cDSubscribeDeserialize.sourceReference.setFQName("SUBSCRIBE_1.RD_1")
+ cDSubscribeDeserialize.targetReference.setFQName("JSON_DESERIALIZER.DATA")
+ cFB.network.dataConnections.add(cDSubscribeDeserialize)
+
+ val cDDeserializeName = factory.createFBNetworkConnection(EntryKind.DATA)
+ cDDeserializeName.sourceReference.setFQName("JSON_DESERIALIZER.NAME")
+ cDDeserializeName.targetReference.setFQName("DISPATCH_IN_$targetBlockName.NAME")
+ cFB.network.dataConnections.add(cDDeserializeName)
+
+ val cDMappingDispatch = factory.createFBNetworkConnection(EntryKind.DATA)
+ cDMappingDispatch.sourceReference.setFQName("MAPPING")
+ cDMappingDispatch.targetReference.setFQName("DISPATCH_IN_$targetBlockName.MAPPING")
+ cFB.network.dataConnections.add(cDMappingDispatch)
+
+ declaration.outputParameters.forEach {
+ val cEDispatchRes = factory.createFBNetworkConnection(EntryKind.DATA)
+ cEDispatchRes.sourceReference.setFQName("DISPATCH_IN_$targetBlockName.${it.name}")
+ cEDispatchRes.targetReference.setFQName(it.name)
+ cFB.network.dataConnections.add(cEDispatchRes)
+ }
+
+ HMIInterfaceTypeGenerator.CONNECTION_TYPES.forEach {
+ val cDDispatch = factory.createFBNetworkConnection(EntryKind.DATA)
+ cDDispatch.sourceReference.setFQName("JSON_DESERIALIZER.${it.name}_VALUE")
+ cDDispatch.targetReference.setFQName("DISPATCH_IN_$targetBlockName.${it.name}_VALUE")
+ cFB.network.dataConnections.add(cDDispatch)
+ }
+ }
+
+
+ return cFB
+ }
+
+ fun generateEventForParameter(parameter: ParameterDeclaration, factory: IEC61499Factory): EventDeclaration {
+ val currEvent = factory.createEventDeclaration(null)
+ currEvent.name = "IS_${parameter.name}"
+ val assoc = factory.createEventAssociation()
+ assoc.parameterReference.setTarget(parameter)
+ currEvent.associations.add(assoc)
+ return currEvent
+ }
+
+ fun generateCommunicationBlock(name: String, factory: IEC61499Factory, stFactory: STFactory, port: Int = 65011): FunctionBlockDeclaration {
+ val fB = factory.createFunctionBlockDeclaration(null)
+ fB.name = name
+ val host = factory.createParameterAssignment()
+ host.parameterReference.setTargetName("ID")
+ host.value = STConverter.parseLiteral(stFactory, "\"225.0.0.2:${port}\"")
+ fB.parameters.add(host)
+
+
+ val qi = factory.createParameterAssignment()
+ qi.parameterReference.setTargetName("QI")
+ qi.value = STConverter.parseLiteral(stFactory, "1")
+ fB.parameters.add(qi)
+
+ fB.typeReference.setTargetName(name)
+ return fB
+ }
+
+ fun generateFunctionDeclarationNoParam(name: String, factory: IEC61499Factory): FunctionBlockDeclaration {
+ val fB = factory.createFunctionBlockDeclaration(null)
+ fB.name = name
+ fB.typeReference.setTargetName(name)
+ return fB
+ }
+}
+
+}
\ No newline at end of file
diff --git a/code/library/src/main/kotlin/org/fbme/lib/iec61499/stringify/RootDeclarationPrinter.kt b/code/library/src/main/kotlin/org/fbme/lib/iec61499/stringify/RootDeclarationPrinter.kt
index 948987ec4..bda80f13a 100644
--- a/code/library/src/main/kotlin/org/fbme/lib/iec61499/stringify/RootDeclarationPrinter.kt
+++ b/code/library/src/main/kotlin/org/fbme/lib/iec61499/stringify/RootDeclarationPrinter.kt
@@ -2,16 +2,20 @@ package org.fbme.lib.iec61499.stringify
import org.fbme.lib.common.Declaration
import org.fbme.lib.iec61499.declarations.*
+import org.fbme.lib.iec61499.parser.ConverterArguments
+import org.fbme.lib.iec61499.parser.Iec61499ConverterConfiguration
import org.jdom.DocType
import org.jdom.Document
import org.jdom.Element
-class RootDeclarationPrinter(private val myDeclaration: Declaration) {
+class RootDeclarationPrinter(private val myDeclaration: Declaration, val converterArguments: Iec61499ConverterConfiguration) {
fun print(): Document {
val rootElement: Element = when (myDeclaration) {
is AdapterTypeDeclaration -> AdapterTypePrinter(myDeclaration).print()
is BasicFBTypeDeclaration -> BasicFBTypePrinter(myDeclaration).print()
is CompositeFBTypeDeclaration -> CompositeFBTypePrinter(myDeclaration).print()
+ is CATBlockTypeDeclaration -> CATBlockTypePrinter(myDeclaration).print()
+ is HMIInterfaceTypeDeclaration -> HMIBlockPrinter(myDeclaration, converterArguments).print()
is DeviceTypeDeclaration -> DeviceTypePrinter(myDeclaration).print()
is ResourceTypeDeclaration -> ResourceTypePrinter(myDeclaration).print()
is ServiceInterfaceFBTypeDeclaration -> ServiceInterfaceFBTypePrinter(myDeclaration).print()
diff --git a/code/platform/src/main/kotlin/org/fbme/ide/platform/persistence/Iec61499ModelFactory.kt b/code/platform/src/main/kotlin/org/fbme/ide/platform/persistence/Iec61499ModelFactory.kt
index 02e5e0ebc..3df6479dc 100644
--- a/code/platform/src/main/kotlin/org/fbme/ide/platform/persistence/Iec61499ModelFactory.kt
+++ b/code/platform/src/main/kotlin/org/fbme/ide/platform/persistence/Iec61499ModelFactory.kt
@@ -14,6 +14,12 @@ import jetbrains.mps.persistence.DataLocationAwareModelFactory
import jetbrains.mps.smodel.SModel.ImportElement
import jetbrains.mps.smodel.SModelId
import jetbrains.mps.smodel.SNodeUtil
+import jetbrains.mps.smodel.adapter.ids.SContainmentLinkId
+import jetbrains.mps.smodel.adapter.ids.SPropertyId
+import jetbrains.mps.smodel.adapter.structure.MetaAdapterFactory
+import jetbrains.mps.smodel.adapter.structure.link.SContainmentLinkAdapter
+import jetbrains.mps.smodel.adapter.structure.link.SContainmentLinkAdapterById
+import jetbrains.mps.smodel.adapter.structure.property.SPropertyAdapter
import jetbrains.mps.util.FileUtil
import jetbrains.mps.util.JDOMUtil
import jetbrains.mps.util.NameUtil
@@ -23,10 +29,14 @@ import org.fbme.ide.iec61499.repository.PlatformElement
import org.fbme.ide.iec61499.repository.PlatformElementsOwner
import org.fbme.ide.iec61499.repository.PlatformRepository
import org.fbme.ide.platform.MpsLanguages
+import org.fbme.ide.platform.converter.PlatformConverter
import org.fbme.ide.platform.converter.PlatformConverter.create
import org.fbme.lib.common.Declaration
import org.fbme.lib.common.RootElement
import org.fbme.lib.iec61499.declarations.*
+import org.fbme.lib.iec61499.parser.Iec61499ConverterConfiguration
+import org.fbme.lib.iec61499.parser.StandardIec61499ConverterConfiguration
+import org.fbme.lib.iec61499.stringify.DependentDeclarationGenerator
import org.fbme.lib.iec61499.stringify.RootDeclarationPrinter
import org.jdom.Document
import org.jetbrains.mps.openapi.model.SModel
@@ -44,6 +54,7 @@ import java.io.InputStreamReader
import java.io.OutputStream
import java.util.*
import java.util.stream.Collectors
+import kotlin.io.path.Path
class Iec61499ModelFactory : ModelFactory, DataLocationAwareModelFactory {
private fun supportedFileExtension(fileExt: String): Boolean {
@@ -54,6 +65,7 @@ class Iec61499ModelFactory : ModelFactory, DataLocationAwareModelFactory {
|| fileExt == DEV_FILE_EXT
|| fileExt == SYS_FILE_EXT
|| fileExt == SEG_FILE_EXT
+ || fileExt == CFG_FILE_EXT
}
override fun supports(dataSource: DataSource): Boolean {
@@ -262,12 +274,50 @@ class Iec61499ModelFactory : ModelFactory, DataLocationAwareModelFactory {
.getComponent(MPSCoreComponents::class.java).moduleRepository
val platformRepository = PlatformRepository(repository)
+// val nodesToDelete = mutableListOf()
+// val dependents = mutableListOf()
+// for (rootNode in model.rootNodes) {
+// if (rootNode.getProperty(SNodeUtil.property_BaseConcept_virtualPackage).orEmpty().endsWith("_dependent")) {
+//// c.dropReference(it.link)
+// nodesToDelete.add(rootNode)
+// continue
+// }
+// val owner = PlatformElementsOwner()
+// val conf = PlatformConverter.STANDARD_CONFIG_FACTORY.createConfiguration(owner)
+// val declaration = platformRepository.getAdapter(rootNode, Declaration::class.java)
+//
+// val dependentGenerator = DependentDeclarationGenerator(declaration, conf)
+// val declarationName = dependentGenerator.getName()
+// val els = dependentGenerator.generate()
+// els.forEach { element ->
+// val node = (element as PlatformElement).node
+// node.setProperty(SNodeUtil.property_BaseConcept_virtualPackage, rootNode.getProperty(SNodeUtil.property_BaseConcept_virtualPackage).orEmpty() + ".${declarationName}_dependent")
+// dependents.add(node)
+// }
+// }
+//
+// LOG.info("Deleting nodes size: {}", nodesToDelete.size)
+// nodesToDelete.forEach {
+// model.enterUpdateMode()
+// model.removeRootNode(it)
+// it.delete()
+// model.leaveUpdateMode()
+// }
+// LOG.info("Dependents size: {}", dependents.size)
+// dependents.forEach {
+// model.enterUpdateMode()
+// model.addRootNode(it)
+// model.leaveUpdateMode()
+// }
+
val errors = arrayListOf()
// Write nodes to xml files
for (rootNode in model.rootNodes) {
val document = try {
+ val conf = StandardIec61499ConverterConfiguration(platformRepository.iec61499Factory, platformRepository.stFactory)
+
val declaration = platformRepository.getAdapter(rootNode, Declaration::class.java)
- RootDeclarationPrinter(declaration).print()
+ RootDeclarationPrinter(declaration, conf).print()
} catch (e: Exception) {
errors += PersistenceProblem(SModel.Problem.Kind.Save, e.message, rootNode.name, true)
continue
@@ -301,6 +351,8 @@ class Iec61499ModelFactory : ModelFactory, DataLocationAwareModelFactory {
}
}
+
+
companion object {
private val LOG = LoggerFactory.getLogger(Iec61499ModelFactory::class.java)
@@ -311,6 +363,7 @@ class Iec61499ModelFactory : ModelFactory, DataLocationAwareModelFactory {
const val DEV_FILE_EXT = "dev"
const val SEG_FILE_EXT = "seg"
const val SYS_FILE_EXT = "sys"
+ const val CFG_FILE_EXT = "cfg"
const val HEADER_FILE_EXT = "iec61499"
const val HEADER_FILE = "header.iec61499"
@@ -344,6 +397,7 @@ class Iec61499ModelFactory : ModelFactory, DataLocationAwareModelFactory {
DEV_FILE_EXT -> (converter.convertDeviceType() as PlatformElement).node
SEG_FILE_EXT -> (converter.convertSegmentType() as PlatformElement).node
SYS_FILE_EXT -> (converter.convertSystemConfiguration() as PlatformElement).node
+ CFG_FILE_EXT -> (converter.convertCATConfiguration() as PlatformElement).node
else -> null
}
}
@@ -358,6 +412,7 @@ class Iec61499ModelFactory : ModelFactory, DataLocationAwareModelFactory {
is DeviceTypeDeclaration -> DEV_FILE_EXT
is SegmentTypeDeclaration -> SEG_FILE_EXT
is SystemDeclaration -> SYS_FILE_EXT
+ is CATBlockTypeDeclaration -> CFG_FILE_EXT
else -> null
}
}
diff --git a/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.mpsPersistence.mps b/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.mpsPersistence.mps
index 67b9e3804..d0501c7f4 100644
--- a/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.mpsPersistence.mps
+++ b/samples/sandbox/solutions/org.fbme.ide.sandbox/models/org.fbme.ide.iec61499.lang.sandbox.mpsPersistence.mps
@@ -1532,5 +1532,11 @@
+
+
+
+
+
+
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 14f191ebb..979cf2ffc 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -1,20 +1,21 @@
rootProject.name = "FBME"
include(
- "code:4diac-integration",
- "code:enas",
- "code:language",
- "code:library",
- "code:nxt-integration",
- "code:platform",
- "code:richediting",
- "code:scenes",
- "code:smv-debugger",
- "code:debugger",
+"code:4diac-integration",
+"code:enas",
+"code:language",
+"code:library",
+"code:nxt-integration",
+"code:platform",
+"code:richediting",
+"code:scenes",
+"code:smv-debugger",
+"code:debugger",
- "docs",
+"docs",
- "samples:statistics-plugin",
- "samples:sandbox",
- "samples:smv-debugger"
+"samples:statistics-plugin",
+"samples:sandbox",
+"samples:smv-debugger",
+"cat_visual"
)
\ No newline at end of file
diff --git a/solutions/WaterTank/WaterTank.msd b/solutions/WaterTank/WaterTank.msd
new file mode 100644
index 000000000..c098be686
--- /dev/null
+++ b/solutions/WaterTank/WaterTank.msd
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ b8a7e14f-52ea-4ee2-b17e-26c27da8084c(IEC-61499)
+ 5aff85f5-c1e8-49b6-a1f1-66d79702cceb(org.fbme.ide.iec61499.adapter)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/solutions/WaterTank/models/WaterTank.water_tank.mps b/solutions/WaterTank/models/WaterTank.water_tank.mps
new file mode 100644
index 000000000..29796e363
--- /dev/null
+++ b/solutions/WaterTank/models/WaterTank.water_tank.mps
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+