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 +) + +@Serializable +@XmlSerialName("Output", "", "") +data class Output( + val name: String, + val type: String +) + + +fun getMapping(modelText: String): Mapping { + val module = SerializersModule {} + val xml = XML(module) { + indentString = " " + xmlDeclMode = XmlDeclMode.Minimal + autoPolymorphic = true + } + + val serializer = serializer() + return xml.decodeFromString(serializer, modelText) +} + + +@Serializable +@XmlSerialName("Conf", "", "") +data class Conf( + val inputs: ConfInputs, + val outputs: ConfOutputs, +) + +@Serializable +@XmlSerialName("Inputs", "", "") +data class ConfInputs( + val inputs: List +) +@Serializable +@XmlSerialName("Input", "", "") +data class ConfInput( + val type: String, + val host: String, + val port: Int +) + +@Serializable +@XmlSerialName("Outputs", "", "") +data class ConfOutputs( + val outputs: List +) + +@Serializable +@XmlSerialName("Output", "", "") +data class ConfOutput( + val type: String, + val host: String, + val port: Int +) + + +fun getConf(modelText: String): Conf { + val module = SerializersModule {} + val xml = XML(module) { + indentString = " " + xmlDeclMode = XmlDeclMode.Minimal + autoPolymorphic = true + } + + val serializer = serializer() + return xml.decodeFromString(serializer, modelText) +} \ No newline at end of file diff --git a/code/cat_visual/src/test/kotlin/MockBackend.kt b/code/cat_visual/src/test/kotlin/MockBackend.kt new file mode 100644 index 000000000..079444c19 --- /dev/null +++ b/code/cat_visual/src/test/kotlin/MockBackend.kt @@ -0,0 +1,13 @@ +import connection.AbstractClient +import connection.ConnectionFieldRegistry +import connection.field.ConnectionField +import connection.field.TYPE_ID +import serializer.Mapping +import serializer.PlainMapping +import serializer.getMapping +import serializer.getPlainMapping +import java.io.File + +class FieldWithCallback(val field: ConnectionField, val callback: (V) -> Unit) + + diff --git a/code/cat_visual/src/test/kotlin/WaterTankTest.kt b/code/cat_visual/src/test/kotlin/WaterTankTest.kt new file mode 100644 index 000000000..a3559bc1e --- /dev/null +++ b/code/cat_visual/src/test/kotlin/WaterTankTest.kt @@ -0,0 +1,90 @@ +import connection.field.ConnectionField +import kotlin.concurrent.thread + +val client = buildMappingClient("src/main/kotlin/example/WATER_TANK/WATER_TANK.xml", "src/main/kotlin/example/WATER_TANK/WATER_TANK_CONF.xml", mode = "") + + +val TANK_VOLUME = 100 +val MAX_TEMP = 100 + +var isInlet: FieldWithCallback = FieldWithCallback(client.getField("stateInlet") as ConnectionField, + {client.sendValue("stateInlet")}) +var isHeat: FieldWithCallback = FieldWithCallback(client.getField("stateHeat") as ConnectionField, + {client.sendValue("stateHeat")}) +var isOutlet: FieldWithCallback = FieldWithCallback(client.getField("stateOutlet") as ConnectionField, + {client.sendValue("stateOutlet")}) +var lamp1: FieldWithCallback = FieldWithCallback(client.getField("lampIndicator1") as ConnectionField, + {client.sendValue("lampIndicator1")}) +var lamp2: FieldWithCallback = FieldWithCallback(client.getField("lampIndicator2") as ConnectionField, + {client.sendValue("lampIndicator2")}) +var tankIndicator: FieldWithCallback = FieldWithCallback(client.getField("tankIndicator") as ConnectionField, + {client.sendValue("tankIndicator")}) +var outputIndicator: FieldWithCallback = FieldWithCallback(client.getField("outputIndicator") as ConnectionField, + {client.sendValue("outputIndicator")}) +var knob1: FieldWithCallback = FieldWithCallback(client.getField("knob1") as ConnectionField, { + runUpdate() +}) +var knob2: FieldWithCallback = FieldWithCallback(client.getField("knob2") as ConnectionField, { + runUpdate() +}) + + +fun runSimulation(){ + client.retrieveValues(mapOf(Pair("knob1", {knob1.callback(0)}), Pair("knob2", {knob2.callback(0)})), false) +} + +fun runUpdate() { + if (knob1.field.getValue() == 0) { + return + } + if (isInlet.field.getValue() || isHeat.field.getValue() || isOutlet.field.getValue()) { + return + } + isInlet.field.setValue(true) + isInlet.callback(true) + lamp1.field.setValue(true) + lamp1.callback(true) + var currTankState = 0 + while (currTankState < TANK_VOLUME) { + currTankState += knob1.field.getValue() / 2 + tankIndicator.field.setValue(currTankState.toFloat()) + tankIndicator.callback(currTankState.toFloat()) + Thread.sleep( 500) + } + isInlet.field.setValue(false) + isInlet.callback(false) + lamp1.field.setValue(false) + lamp1.callback(false) + isHeat.field.setValue(true) + isHeat.callback(true) + var currTemp = knob2.field.getValue() + while (currTemp < MAX_TEMP) { + currTemp += 10 + outputIndicator.field.setValue(currTemp.toFloat()) + outputIndicator.callback(currTemp.toFloat()) + Thread.sleep(500) + } + isHeat.field.setValue(false) + isHeat.callback(false) + isOutlet.field.setValue(true) + isOutlet.callback(true) + lamp2.field.setValue(true) + lamp2.callback(true) + currTankState = TANK_VOLUME + while (currTankState > 0) { + currTankState -= knob1.field.getValue() / 2 + tankIndicator.field.setValue(currTankState.toFloat()) + tankIndicator.callback(currTankState.toFloat()) + Thread.sleep( 500) + } + isOutlet.field.setValue(false) + isOutlet.callback(false) + lamp2.field.setValue(false) + lamp2.callback(false) + outputIndicator.field.setValue(0f) + outputIndicator.callback(0f) +} + +fun main() { + runSimulation() +} \ No newline at end of file diff --git a/code/language/buildsolution/models/org.fbme.language.build.mps b/code/language/buildsolution/models/org.fbme.language.build.mps index 06464b2e0..b318f8383 100644 --- a/code/language/buildsolution/models/org.fbme.language.build.mps +++ b/code/language/buildsolution/models/org.fbme.language.build.mps @@ -83,6 +83,7 @@ + @@ -167,9 +168,9 @@ - + @@ -259,6 +260,7 @@ + @@ -415,6 +417,11 @@ + + + + + diff --git a/code/language/languages/org.fbme.ide.iec61499.lang/generator/template/org.fbme.ide.iec61499.lang.generator.main@generator.mps b/code/language/languages/org.fbme.ide.iec61499.lang/generator/template/org.fbme.ide.iec61499.lang.generator.main@generator.mps index 0d483cb3c..3cd1cdb50 100644 --- a/code/language/languages/org.fbme.ide.iec61499.lang/generator/template/org.fbme.ide.iec61499.lang.generator.main@generator.mps +++ b/code/language/languages/org.fbme.ide.iec61499.lang/generator/template/org.fbme.ide.iec61499.lang.generator.main@generator.mps @@ -3,6 +3,7 @@ + diff --git a/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.actions.mps b/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.actions.mps index 957e003e7..299199ef6 100644 --- a/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.actions.mps +++ b/code/language/languages/org.fbme.ide.iec61499.lang/models/org.fbme.ide.iec61499.lang.actions.mps @@ -114,5 +114,8 @@ + + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +