From 71722127f1d69cb4673f0b5b9e79c01586921bcf Mon Sep 17 00:00:00 2001 From: Joppe Blondel Date: Wed, 11 Mar 2026 11:57:48 +0100 Subject: [PATCH] Start met EmbeddedControl lib --- .gitignore | 3 +- EmbeddedControl.mo | 121 +++++++++++++++++++++++++++++++ exporttest/export_controller.log | 14 ++++ exporttest/export_controller.mos | 11 +++ exporttest/run_mos_docker.sh | 19 +++++ test.mo | 27 +++++++ 6 files changed, 194 insertions(+), 1 deletion(-) create mode 100644 EmbeddedControl.mo create mode 100644 exporttest/export_controller.log create mode 100644 exporttest/export_controller.mos create mode 100755 exporttest/run_mos_docker.sh create mode 100644 test.mo diff --git a/.gitignore b/.gitignore index ad776c5..c462269 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ build/ -__pycache__* \ No newline at end of file +__pycache__* +exporttest/out \ No newline at end of file diff --git a/EmbeddedControl.mo b/EmbeddedControl.mo new file mode 100644 index 0000000..785c0ab --- /dev/null +++ b/EmbeddedControl.mo @@ -0,0 +1,121 @@ +package EmbeddedControl + extends Modelica.Icons.Package; + + package Boundary + model ADC + extends Modelica.Blocks.Icons.DiscreteBlock; + parameter Real stepSize = 0.1; + parameter Real scale = 1; + Modelica.Blocks.Interfaces.RealInput u annotation( + Placement(transformation(origin = {-122, 2}, extent = {{-20, -20}, {20, 20}}), iconTransformation(origin = {-120, 0}, extent = {{-20, -20}, {20, 20}}))); + Modelica.Blocks.Interfaces.IntegerOutput y annotation( + Placement(transformation(origin = {86, -34}, extent = {{-10, -10}, {10, 10}}), iconTransformation(origin = {110, 0}, extent = {{-10, -10}, {10, 10}}))); + protected + discrete Integer yInt(start=0, fixed=true); + + algorithm + yInt := integer(floor((scale*u)/stepSize+ 0.5)); + + equation + y = yInt; + + annotation( + Documentation(info = " +

Integer analog-to-digital conversion block.

+

The real-valued input u is quantized to an integer code using:

+

y = floor((scale*u)/stepSize + 0.5)

+

This produces a rounded integer representation of the scaled input, intended for embedded-style control chains that operate on integer counts.

+

Parameters:

+ +"), + Icon(graphics = {Line(origin = {5, 10}, points = {{-61, -68}, {-35, -68}, {-35, -22}, {-21, -22}, {-21, 38}, {7, 38}, {7, 68}, {41, 68}, {41, 24}, {61, 24}, {61, 24}}, color = {255, 85, 0}, thickness = 1.25), Text(origin = {49, -50}, extent = {{-51, 50}, {51, -50}}, textString = "AD")})); + end ADC; + + model DAC + extends Modelica.Blocks.Icons.DiscreteBlock; + parameter Real stepSize = 0.1; + parameter Real scale = 1; + Modelica.Blocks.Interfaces.IntegerInput u annotation( + Placement(transformation(origin = {-136, -2}, extent = {{-20, -20}, {20, 20}}), iconTransformation(origin = {-120, 0}, extent = {{-20, -20}, {20, 20}}))); + Modelica.Blocks.Interfaces.RealOutput y annotation( + Placement(transformation(origin = {126, 10}, extent = {{-10, -10}, {10, 10}}), iconTransformation(origin = {110, 0}, extent = {{-10, -10}, {10, 10}}))); + equation + // Convert integer DAC code back to physical Real value. + y = (stepSize * scale) * u; + + annotation( + Documentation(info = " +

Integer digital-to-analog conversion block.

+

The integer input u is mapped back to a real-valued physical signal using:

+

y = (stepSize*scale)*u

+

When used with matching parameter values, this block acts as the inverse conversion of the ADC block.

+

Parameters:

+ +"), + Diagram(graphics), + Icon(graphics = {Line(origin = {5, 10}, points = {{-61, -68}, {-35, -68}, {-35, -22}, {-21, -22}, {-21, 38}, {7, 38}, {7, 68}, {41, 68}, {41, 24}, {61, 24}, {61, 24}}, color = {0, 0, 127}, thickness = 1.25, smooth = Smooth.Bezier), Text(origin = {49, -50}, extent = {{-51, 50}, {51, -50}}, textString = "DA")})); + end DAC; + extends Modelica.Icons.Package; + annotation( + Icon(graphics = {Line(origin = {-48.92, -4.33}, points = {{-35.0829, -47.6744}, {-17.0829, 18.3256}, {-3.08288, -9.6744}, {10.9171, 48.3256}, {34.9171, 14.3256}, {34.9171, 14.3256}}, color = {0, 0, 127}, thickness = 1.5, smooth = Smooth.Bezier), Line( points = {{0, 81}, {0, -81}}, pattern = LinePattern.Dash, thickness = 2.75), Line(origin = {54, -10}, points = {{-36, -42}, {-36, -20}, {-30, -20}, {-30, -10}, {-20, -10}, {-20, 10}, {-6, 10}, {-6, 4}, {6, 4}, {6, 42}, {28, 42}, {28, 18}, {36, 18}, {36, 6}, {36, 6}}, color = {255, 85, 0}, thickness = 1.5)})); + end Boundary; + + package Math + extends Modelica.Icons.Package; + + model DDifference + extends EmbeddedControl.icons.discreteIntegerSISO; + + discrete Integer x(start=0, fixed=true) "Initial or guess value of state"; + protected + discrete Integer y_internal(start=0, fixed=true); + algorithm + x := u; + if x <> pre(x) then + y_internal := x - pre(x); + else + y_internal := pre(y_internal); + end if; + equation + y = y_internal; + + annotation( + Documentation(info = " +

Discrete integer difference block.

+

Whenever the integer input changes, the block outputs the increment between the current and previous sample:

+

y = u[k] - u[k-1]

+

This is a raw discrete difference, not a time-scaled derivative. If a discrete-time derivative is required, include the factor 1/Ts in a downstream gain or in the controller gain.

+

The output updates only when u changes.

+ "), + Diagram(graphics), + Icon(graphics = {Text(textColor = {156, 156, 156}, extent = {{-30, 14}, {86, 60}}, textString = "DT1"), Line(points = {{-90, -80}, {82, -80}}, color = {156, 156, 156}), Line(points = {{-80, 78}, {-80, -90}}, color = {156, 156, 156}), Polygon(lineColor = {156, 156, 156}, fillColor = {156, 156, 156}, fillPattern = FillPattern.Solid, points = {{-80, 90}, {-88, 68}, {-72, 68}, {-80, 90}}), Polygon(lineColor = {156, 156, 156}, fillColor = {156, 156, 156}, fillPattern = FillPattern.Solid, points = {{90, -80}, {68, -72}, {68, -88}, {90, -80}}), Line(origin = {-9, -4}, points = {{-63, 66}, {-63, 40}, {-53, 40}, {-53, -20}, {-39, -20}, {-39, -54}, {3, -54}, {3, -66}, {63, -66}, {63, -66}}, color = {255, 85, 0}, thickness = 0.5)})); + end DDifference; + annotation( + Icon(graphics = {Line(origin = {-0.95, 0.62}, points = {{-75.0496, -68.6246}, {-61.0496, 1.37545}, {-29.0496, 69.3754}, {-5.04956, 1.37545}, {20.9504, -64.6246}, {50.9504, -2.62455}, {74.9504, 65.3754}, {74.9504, 65.3754}}, color = {255, 85, 0}, thickness = 2, smooth = Smooth.Bezier)})); + end Math; + + model icons + extends Modelica.Icons.IconsPackage; + + model discreteIntegerSISO + extends Modelica.Blocks.Icons.DiscreteBlock; + Modelica.Blocks.Interfaces.IntegerInput u annotation( + Placement(transformation(origin = {-194, 2}, extent = {{-20, -20}, {20, 20}}), iconTransformation(origin = {-120, 0}, extent = {{-20, -20}, {20, 20}}))); + Modelica.Blocks.Interfaces.IntegerOutput y annotation( + Placement(transformation(origin = {128, 0}, extent = {{-10, -10}, {10, 10}}), iconTransformation(origin = {110, 0}, extent = {{-10, -10}, {10, 10}}))); + equation + + end discreteIntegerSISO; + equation + + end icons; + annotation( + Icon(graphics = {Text(origin = {1, 1}, textColor = {255, 170, 0}, extent = {{-99, 99}, {99, -99}}, textString = "EC", textStyle = {TextStyle.Bold})}), + uses(Modelica(version = "4.1.0"))); +end EmbeddedControl; \ No newline at end of file diff --git a/exporttest/export_controller.log b/exporttest/export_controller.log new file mode 100644 index 0000000..d7873b9 --- /dev/null +++ b/exporttest/export_controller.log @@ -0,0 +1,14 @@ +true +true +"Check of EmbeddedControl.Math.DDifference completed successfully. +Class EmbeddedControl.Math.DDifference has 4 equation(s) and 4 variable(s). +1 of these are trivial equation(s)." + +Notification: Automatically loaded package Modelica 4.1.0 due to uses annotation from EmbeddedControl. +Notification: Automatically loaded package Complex 4.1.0 due to uses annotation from Modelica. +Notification: Automatically loaded package ModelicaServices 4.1.0 due to uses annotation from Modelica. + +true +translateModel(test2d.Dcontroller) = true + + diff --git a/exporttest/export_controller.mos b/exporttest/export_controller.mos new file mode 100644 index 0000000..7bbc471 --- /dev/null +++ b/exporttest/export_controller.mos @@ -0,0 +1,11 @@ +setCommandLineOptions("--simCodeTarget=ExperimentalEmbeddedC"); + +loadFile("/work/EmbeddedControl.mo"); + +okCheck := checkModel(EmbeddedControl.Math.DDifference); +print("checkModel(EmbeddedControl.Math.DDifference) = " + String(okCheck) + "\n"); +print(getErrorString()); + +okTranslate := translateModel(EmbeddedControl.Math.DDifference, fileNamePrefix="difference"); +print("translateModel(test2d.Dcontroller) = " + String(okTranslate) + "\n"); +print(getErrorString()); diff --git a/exporttest/run_mos_docker.sh b/exporttest/run_mos_docker.sh new file mode 100755 index 0000000..7425f91 --- /dev/null +++ b/exporttest/run_mos_docker.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" +MOS_FILE="/work/exporttest/export_controller.mos" +LOG_FILE="$ROOT_DIR/exporttest/export_controller.log" + +docker run --rm \ + -v "$ROOT_DIR:/work" \ + -v "$HOME/.openmodelica/libraries:/home/ubuntu/.openmodelica/libraries:ro" \ + -w /work/exporttest/out \ + openmodelica omc "$MOS_FILE" 2>&1 | tee "$LOG_FILE" + +if grep -E "Error:|= false" "$LOG_FILE" >/dev/null; then + echo "Failed. See: $LOG_FILE" >&2 + exit 1 +fi + +echo "Success. Log: $LOG_FILE" diff --git a/test.mo b/test.mo new file mode 100644 index 0000000..74b0f80 --- /dev/null +++ b/test.mo @@ -0,0 +1,27 @@ +model test + Modelica.Blocks.Sources.Sine sine(amplitude = 1) annotation( + Placement(transformation(origin = {-100, 0}, extent = {{-10, -10}, {10, 10}}))); + EmbeddedControl.Math.DDifference dDifferentiate(x(start = 0)) annotation( + Placement(transformation(origin = {20, 0}, extent = {{-10, -10}, {10, 10}}))); + EmbeddedControl.Boundary.DAC dac(stepSize = 0.02) annotation( + Placement(transformation(origin = {60, 0}, extent = {{-10, -10}, {10, 10}}))); + EmbeddedControl.Boundary.ADC adc(stepSize = 0.02) annotation( + Placement(transformation(origin = {-20, 0}, extent = {{-10, -10}, {10, 10}}))); + Modelica.Blocks.Discrete.Sampler sampler(samplePeriod(displayUnit = "ms") = 0.01) annotation( + Placement(transformation(origin = {-60, 0}, extent = {{-10, -10}, {10, 10}}))); + Modelica.Blocks.Discrete.ZeroOrderHold zeroOrderHold(samplePeriod(displayUnit = "ms") = 0.01) annotation( + Placement(transformation(origin = {100, 0}, extent = {{-10, -10}, {10, 10}}))); +equation + connect(adc.y, dDifferentiate.u) annotation( + Line(points = {{-9, 0}, {7, 0}}, color = {255, 127, 0})); + connect(sine.y, sampler.u) annotation( + Line(points = {{-89, 0}, {-73, 0}}, color = {0, 0, 127})); + connect(sampler.y, adc.u) annotation( + Line(points = {{-49, 0}, {-33, 0}}, color = {0, 0, 127})); + connect(dDifferentiate.y, dac.u) annotation( + Line(points = {{31, 0}, {47, 0}}, color = {255, 127, 0})); + connect(dac.y, zeroOrderHold.u) annotation( + Line(points = {{71, 0}, {87, 0}}, color = {0, 0, 127})); + annotation( + uses(Modelica(version = "4.1.0"))); +end test; \ No newline at end of file