Makers Blog
16 views 0 comments

openEMS goes PLCnext

Motivation

Phoenix Contact strongly believes in open automation and open-source technologies. At the same time, we are firmly committed to our vision of the All Electric Society. These are the key reasons for participating in the openEMS project. openEMS enables a wide range of energy management use cases. The idea was not merely to run openEMS as an application on another embedded device. Instead, the goal was to extend the engineering possibilities of openEMS by integrating it with PLCnext Technology.

Abstract

This blog post describes an approach for using openEMS integrated into PLCnext Technology. To enable the use of openEMS on PLCnext, this article covers the following aspects:

Deployment of openEMS on PLCnext targets Setup of a development environment for openEMS with PLCnext

Basic Concept

openEMS is an application with a wide variety of drivers that cover a broad range of devices and use cases. In the system architecture, we see two separate applications: openEMS, which includes communication and algorithmic functionality, and the UI, which generates the user interface for the end user.

2026-03-30-OnPremise-OpenEMS.png

Source: Figure 1. OpenEMS system architecture

Extended Concept

In industrial projects, it is common that changes need to be made at the last minute (often in customer working areas). The idea is to extend this architecture with a PLC context that provides a generic REST interface to cover different use cases. This enables a wide range of opportunities for software engineers:

  • Enabling additional software components (e.g. PCU, drivers, HMI options)
  • Fast and robust software development
  • Interdisciplinary development between Java and PLC developers
2026-03-30-Focus-OpenEMS_with_PLCnext.png

Further Information about PLCnext Technology: plcnext.help

Deployment of openEMS Edge for PLCnext targets

Controller Preparations

Note: We recommend the AXC F 3152 as the minimum hardware requirement for using openEMS. The AXC F 2152 is usable, but its performance is limited depending on the size and complexity of the IEC project.

Installation of the Java Runtime Environment on the controller.

JRE for your target. Following requirements must be chosen:
  - JRE Version:21
  - OS: Linux 
  - architecture: AXC F 2152 -> ARM or ARM32, AXC F 3152 -> x86
   (For other targets just check the processor architecture.) 

Download the JRE and copy the files to the controller.

scp bellsoft-jre21.0.10+10-linux-amd64.tar.gz  admin@:/opt/plcnext

Note: You can also use WinSCP or FileZilla to transfer the files and to make the folders.

Use the following script to create the openEMS environment and install the Java JRE on the target system:

openEMS Environment Script Copy the script to the controller:

scp plcNext_install_jre.sh admin@:/opt/plcnext

Execute the script with:

admin@axcf3152:/opt/plcnext$ chmod +x plcNext_install_jre.sh
admin@axcf3152:/opt/plcnext$ ./plcNext_install_jre.sh

You should get as result:

Start JRE installation...
Create folders ...
Unzip the JRE package...
Add Java to PATH.
Set environment variable for JAVA_Home
Check if the installation was successful...
openjdk version "21.0.10" 2026-01-20 LTS
OpenJDK Runtime Environment (build 21.0.10+10-LTS)
OpenJDK 64-Bit Server VM (build 21.0.10+10-LTS, mixed mode, sharing)

If you receive the output, the system is ready to deploy openEMS on the target.

Deployment

Set SAN Parameter on the PLCnext target

Java strictly checks the SAN (Subject Alternative Name) attribute in the ca certificate. It is important to set these settings to your controller configuration. Please add in the settings the controller IP-Address and optional: 127.0.0.1. Navigate in the WBM: Configuration -> Web Services -> scroll down to the end.

2026-04-08_CA_Cert_Setting.png

Add the binary and startup script

Copy the openEMS-edge.jar and the PLCnext-Start-OpenEMS-Edge.sh in the bin folder:

scp openems-edge.jar admin@:/opt/plcnext/apps/openems-edge/bin
scp PLCnext-Start-OpenEMS-Edge.sh admin@:/opt/plcnext/apps/openems-edge/bin

Note: Please check the JRE version and the path in the PLCnext-Start-OpenEMS-Edge.sh script and edit the VARs in the script.

Last step: Execute the PLCnext-start-OpenEMS-Edge.sh script.

admin@axcf3152:/opt/plcnext/apps/openems-edge/bin$ chmod +x PLCnext-Start-OpenEMS-Edge.sh
admin@axcf3152:/opt/plcnext/apps/openems-edge/bin$ ./PLCnext-Start-OpenEMS-Edge.sh

The output should look like:

JRE truststore found :)
Adding PxC PLCnext self-signed certificate to OpenEMS truststore
Certificate was added to keystore
Starting OpenEMS Edge with custom truststore
2026-04-07T06:27:24,781 [logging)] INFO  [ventAdminConfigurationNotifier] Sending Event Admin notification (configuration successful) to org/ops4j/pax/logging/Configuration
2026-04-07T06:27:28,201 [artLevel] INFO  [onent.AbstractOpenemsComponent] [_serialNumber] Activate Core.SerialNumber
2026-04-07T06:27:28,385 [lNumber)] INFO  [onent.AbstractOpenemsComponent] [_serialNumber] Modified Core.SerialNumber
2026-04-07T06:27:28,673 [artLevel] INFO  [onent.AbstractOpenemsComponent] [_power] Activate Ess.Power

Done. openEMS is running on the target. Next Step is to configure openEMS and build up the connection to the PLCnext GDS.

openEMS Configuration

Start to configure via:

http://YourControlerIPAddress:8080/system/console/configMgr

Then you could read openEMS Docs to configure system for your project. openEMS Docs: Getting Started

Usage of the PLCnext driver

The communication between openEMS and the PLC is based on REST and uses a standard data structures for the data transfer. On the openEMS side, the driver is ready to use; you only need to configure the credentials and the path to your data structure.

On the PLCnext controller, it is necessary to create a project and add the required data structures for each device. In the following example, we configure a PLCnext meter.

  1. Lets start with the PLC.
  • Create a PLCnext Engineer Project. Further information: Creating a new project.
  • Copy the data struct in the your datatype definition.
  T_OE_METER_DATA : STRUCT
    VoltageL1N          : DINT;   // [mV]   
	VoltageL2N          : DINT;   // [mV]
	VoltageL3N          : DINT;   // [mV]
	VoltageL12          : DINT;   // [mV]
	VoltageL23          : DINT;   // [mV]
	VoltageL31          : DINT;   // [mV]
	CurrentL1           : DINT;   // [mA]
	CurrentL2           : DINT;   // [mA]
	CurrentL3           : DINT;   // [mA]
	CurrentNeutral      : DINT;   // [mA]
	ActivePowerL123     : DINT;   // [W]
	ActivePowerL1       : DINT;   // [W]
	ActivePowerL2       : DINT;   // [W]
	ActivePowerL3       : DINT;   // [W]
	ReactivePowerL123   : DINT;   // [var]
	ReactivePowerL1     : DINT;   // [var]
	ReactivePowerL2     : DINT;   // [var]
	ReactivePowerL3     : DINT;   // [var]
	ApparentPowerL123   : DINT;   // [VA]
	ApparentPowerL1     : DINT;   // [VA]
	ApparentPowerL2     : DINT;   // [VA]
	ApparentPowerL3     : DINT;   // [VA]
	PowerFactor         : DINT;   // 
    Frequency           : DINT;   // [mHz]
	EnergyImport        : LINT;   // [Wh]
	EnergyExport        : LINT;   // [Wh]
    END_STRUCT
  • Create a variable named meter1 and tag the variable as an HMI variable.
Datatype_Definition_IEC.png
  • Activate the REST communication on the PLC controller:
REST_communication.png
  1. Configure the meter in openEMS. The parameters in the green box are the PLCnext specific.
  • Base-URL: The default path of the PLCnext REST interface. Change the IP address to match your controller.
  • Username: Refers to the user management of the PLCnext controller. The default user is admin.
  • Password: Refers to the user management of the PLCnext controller. You can find the default password printed on the controller. Further information: PLCnext User management
  • Data instance name: Name of the data structure in the IEC context that represents the meter device in the PLC project. The openEMS driver expects specific fields (see below). The default prefix Arp.Plc.Eclr/ refers to a standard component of your PLC program. Meter1 is the name of the variable defined in your program. Note: The name of the component is displayed in the Task and Event Overview in PLCnext Engineer.
2026-04-14_Meter_Settings.png

openEMS UI on PLCnext

If you want to work with the openEMS UI directly on the controller, we recommend using the Docker image with Podman.

Preparations in openEMS Edge

  1. Add the Controller API WebSocket component to your configuration.
  2. Ensure that the configured ports match your environment (the default settings should work).
Controller_API_WebSocket.png

openEMS UI with Podman

  1. Establish an SSH connection to your target.
  2. On the controller, pull the image from Docker HUB
podman pull docker.io/openems/ui-edge:latest
  1. Start the Container using Podman:
podman run --name openems_ui --replace -p 8500:80 -p 8443:443 -e WEBSOCKET_HOST=host.docker.internal -e WEBSOCKET_PORT=8085 -d openems/ui-edge

Finally: Connecting the parts

Open a terminal and start openEMS edge via the startup script:

admin@axcf3152:/opt/plcnext/apps/openems-edge/bin$ ./PLCnext-Start-OpenEMS-Edge.sh

Open an additional terminal to start the UI container:

podman run --name openems_ui --replace -p 8500:80 -p 8443:443 -e WEBSOCKET_HOST=host.docker.internal -e WEBSOCKET_PORT=8085 -d openems/ui-edge

Make sure that your PLCnext project is running and the REST interface is activated:

  • check the Web based management
  • Use the Cockpit in PLCnext Engineer

Result

openEMS Edge Log:

admin@axcf3152:/opt/plcnext/apps/openems-edge/bin$ ./PLCnext-Start-OpenEMS-Edge.sh
JRE truststore found :)
OpenEMS truststore found, recreating it
Adding PxC PLCnext self-signed certificate to OpenEMS truststore
Certificate was added to keystore
Starting OpenEMS Edge with custom truststore
2026-04-24T09:46:21,879 [ogging])] INFO  [ventAdminConfigurationNotifier] Sending Event Admin notification (configuration successful) to org/ops4j/pax/logging/Configuration
2026-04-24T09:46:22,025 [artLevel] INFO  [enems.edge.application.EdgeApp]

Open the congfiguration site via: http://YourControlerIPAddress:8080/system/console/configMgr

openEMS UI:

admin@axcf3152:~$  podman run --name openems_ui --replace -p 8500:80 -p 8443:443 -e WEBSOCKET_HOST=host.docker.internal -e WEBSOCKET_PORT=8085 -d openems/ui-edge
fb7c2c310cf3751f5a3857e60f764bb436dcd71ce286783b7b5728f34d388369

Open the UI via: http://YourControlerIPAddress:8500

If you experience any issues with your setup or have questions about this article, please do not hesitate to contact us.

Appendix

plcNext_install_jre.sh : Installtion script for Java on the PLCnext target.


#/bin/sh

# Setup for JRE on PLCnext 
JRE_PATH="bellsoft-jre21.0.10+10-linux-amd64.tar.gz"
JRE_VERSION="jre-21.0.10"
echo "Start JRE installation... "
echo "Create folders ..."
mkdir -p /opt/plcnext/apps/openems-edge/lib/jre
mkdir -p /opt/plcnext/apps/openems-edge/conf
mkdir -p /opt/plcnext/apps/openems-edge/bin
echo "Unzip the JRE package..." 
tar zxf $JRE_PATH -C /opt/plcnext/apps/openems-edge/lib/jre

echo "Add Java to PATH." 
export PATH="/opt/plcnext/apps/openems-edge/lib/jre/$JRE_VERSION/bin:$PATH"
echo "Set environment variable for JAVA_Home"
export JAVA_HOME="/opt/plcnext/apps/openems-edge/lib/jre/$JRE_VERSION"
echo "Check if the installation was successful..."
java -version

PLCnext-Start-OpenEMS-Edge.sh : Start up script for openEMS on the PLCnext target.


#/bin/sh

### Config ###
JRE_VERSION="jre-21.0.10"
JAVA_HOME="/opt/plcnext/apps/openems-edge/lib/jre/$JRE_VERSION"
PATH="$JAVA_HOME/bin:$PATH"
JVM_OPTIONS=""
OPENEMS_TRUSTSTORE_PASSWD="changeit"

PATH_JRE_TRUSTSTORE="$JAVA_HOME/lib/security/cacerts"
PATH_OPENEMS_EDGE_CONFIG="/opt/plcnext/apps/openems-edge/conf"
PATH_OPENEMS_EDGE_JAR="/opt/plcnext/apps/openems-edge/bin/openems-edge.jar"
PATH_OPENEMS_TRUSTSTORE="$PATH_OPENEMS_EDGE_CONFIG/plcnext-truststore.jks"
PATH_PXC_PLCNEXT_CERT="/opt/plcnext/Security/IdentityStores/HTTPS-self-signed/certificate.pem"

### Prepare custom truststore ###
if [ -e $PATH_JRE_TRUSTSTORE ]; then
	echo "JRE truststore found :)"
else
	echo "JRE truststore not found!"
	ls -l "$PATH_JRE_TRUSTSTORE"
	exit 1
fi
if [ -f $PATH_OPENEMS_TRUSTSTORE ]; then
	echo "OpenEMS truststore found, recreating it"
	rm "$PATH_OPENEMS_TRUSTSTORE"
fi
cp "$PATH_JRE_TRUSTSTORE" "$PATH_OPENEMS_TRUSTSTORE"

echo "Adding PxC PLCnext self-signed certificate to OpenEMS truststore"
keytool -import -alias PxC:PLCnext-SlfSgndCert.pem \
	-file "$PATH_PXC_PLCNEXT_CERT" \
	-noprompt \
	-storepass "$OPENEMS_TRUSTSTORE_PASSWD" \
	-keystore "$PATH_OPENEMS_TRUSTSTORE"

if [ $? -ne 0 ]; then
	echo "Adding PxC PLCnext self-signed certificate to OpenEMS truststore FAILED! Skipping start of OpenEMS-Edge"
else 
	echo "Starting OpenEMS Edge with custom truststore"
	java -Djavax.net.ssl.trustStore="$PATH_OPENEMS_TRUSTSTORE" \
		-Djavax.net.ssl.trustStorePassword="OPENEMS_TRUSTSTORE_PASSWD" \
		-Dfelix.cm.dir="$PATH_OPENEMS_EDGE_CONFIG" \
		$JVM_OPTIONS \
		-jar "$PATH_OPENEMS_EDGE_JAR"
fi


Data types for PLCnext Engineer :


TYPE
     
  T_OE_METER_DATA : STRUCT
   //*** to open ems  ****************  
    VoltageL1N          : DINT;   // [mV]   
	VoltageL2N          : DINT;   // [mV]
	VoltageL3N          : DINT;   // [mV]
	VoltageL12          : DINT;   // [mV]
	VoltageL23          : DINT;   // [mV]
	VoltageL31          : DINT;   // [mV]
	CurrentL1           : DINT;   // [mA]
	CurrentL2           : DINT;   // [mA]
	CurrentL3           : DINT;   // [mA]
	CurrentNeutral      : DINT;   // [mA]
	ActivePowerL123     : DINT;   // [W]
	ActivePowerL1       : DINT;   // [W]
	ActivePowerL2       : DINT;   // [W]
	ActivePowerL3       : DINT;   // [W]
	ReactivePowerL123   : DINT;   // [var]
	ReactivePowerL1     : DINT;   // [var]
	ReactivePowerL2     : DINT;   // [var]
	ReactivePowerL3     : DINT;   // [var]
	ApparentPowerL123   : DINT;   // [VA]
	ApparentPowerL1     : DINT;   // [VA]
	ApparentPowerL2     : DINT;   // [VA]
	ApparentPowerL3     : DINT;   // [VA]
	PowerFactor         : DINT;   // 
    Frequency           : DINT;   // [mHz]
	EnergyImport        : LINT;   // [Wh]
	EnergyExport        : LINT;   // [Wh]
    END_STRUCT
 
  T_DATA_OE_INV : STRUCT
    //*** to open ems  ****************  
    VoltageL1N          : DINT;   // [mV]   
	VoltageL2N          : DINT;   // [mV]
	VoltageL3N          : DINT;   // [mV]
	VoltageL12          : DINT;   // [mV]
	VoltageL23          : DINT;   // [mV]
	VoltageL31          : DINT;   // [mV]
	CurrentL1           : DINT;   // [mA]
	CurrentL2           : DINT;   // [mA]
	CurrentL3           : DINT;   // [mA]
	CurrentNeutral      : DINT;   // [mA]
	ActivePowerL123     : DINT;   // [W]
	ActivePowerL1       : DINT;   // [W]
	ActivePowerL2       : DINT;   // [W]
	ActivePowerL3       : DINT;   // [W]
	ReactivePowerL123   : DINT;   // [var]
	ReactivePowerL1     : DINT;   // [var]
	ReactivePowerL2     : DINT;   // [var]
	ReactivePowerL3     : DINT;   // [var]
	ApparentPowerL123   : DINT;   // [VA]
	ApparentPowerL1     : DINT;   // [VA]
	ApparentPowerL2     : DINT;   // [VA]
	ApparentPowerL3     : DINT;   // [VA]
	PowerFactor         : DINT;   // 
    Frequency           : DINT;   // [mHz]
	EnergyImport        : LINT;   // [Wh]
	EnergyExport        : LINT;   // [Wh]
    //*** from open ems  ****************  
    SetActivePower      : DINT;   // [W]
    END_STRUCT
 
 T_OE_BATTERY_DATA : STRUCT
    //*** to open ems  ****************  
    AllowedChargePower    : DINT;
	AllowedDischargePower : DINT;
	Soc                   : DINT;
	Capacity              : DINT;
    GridMode              : DINT;
	ActiveChargeEnergy    : DINT;
	ActiveDischargeEnergy : DINT;
	MinCellVoltage        : DINT;
	MaxCellVoltage        : DINT;
	MinCellTemperature    : DINT;
	MaxCellTemperature    : DINT;
	ActivePowerL123       : DINT;
	ReactivePowerL123     : DINT;
    //*** from open ems  ****************  
    SetActivePowerEquals            : DINT;  // [w]
    SetReactivePowerEquals          : DINT;  // [w]
    SetActivePowerLessOrEquals      : DINT;  // [w]
    SetActivePowerGreaterOrEquals   : DINT;  // [w]
    SetReactivePowerLessOrEquals    : DINT;  // [w]
    SetReactivePowerGreaterOrEquals : DINT;  // [w]
 END_STRUCT
 
  T_OE_LOAD_Circuit_DATA : STRUCT
    //*** to open ems  ****************  
    MaxPowerExport    : DINT;  // [w]
	MaxPowerImport    : DINT;  // [w]
	MaxReactivePower  : DINT;  // [w]
 END_STRUCT
END_TYPE


Something helpful

Build openEMS edge

In the openEMS docs are two way how to build the projects. I recommend to use option with Gradle.

  1. Please install the JRE Version 21
  2. Clone the repository on your local machine.
  3. Enter the folder with a terminal
  4. Build the project with
    .\gradlew.bat cleanEdge buildEdge

If the process finished with successful, you should see something like this:

BUILD SUCCESSFUL in 3m 45s
805 actionable tasks: 625 executed, 179 from cache, 1 up-to-date

Import the CA cert

In Windows 11 use the Key Store Explorer

  1. Run the app as Administrator
  2. Choose your Java JDK
  3. Add the modified cert from the controller. (SAN must be set)

Note:

The Makers Blog shows applications and user stories of community members that are not tested or reviewed by Phoenix Contact. Use them at your own risk.

Discussion

Please login/register to comment

Login/Register

Leave a Reply

Newsletter
Never miss a new article
Sign up for the newsletter
Never miss news about PLCnext Technology
Get interesting content via newsletter four times a year
Receive exclusive information before all other users