diff --git a/about/release-notes/index.html b/about/release-notes/index.html index fbcb94d1..6023024d 100644 --- a/about/release-notes/index.html +++ b/about/release-notes/index.html @@ -772,6 +772,13 @@
easy-rscp is an INOFFICIAL library written in Kotlin (fully Java compatible) for accessing E3DC home power plants.
E3DC is a brand of HagerEnergy Gmbh (website). I have nothing to do with the company, except that I own a home power plant from E3DC and wanted to include it in my SmartHome. This \"desire\" gave birth to easy-rscp.
"},{"location":"#what-is-easy-rscp","title":"What is easy-rscp?","text":"E3DC's home power plants offer a TCP/IP interface for read and write access. The protocol used for the content is proprietary and is called RSCP. The RSCP structure is well documented by E3DC, but in terms of content there is almost no documentation at all. In addition, the documentation is incomplete, partly incorrect and not publicly available. The E3DC service portal, to which every customer has access, contains the RSCP documentation. easy-rscp and this documentation have the goal to make RSCP more easily accessible for the open source world.
"},{"location":"#what-do-i-get","title":"What do I get?","text":""},{"location":"#as-a-kotlin-developer","title":"As a Kotlin developer","text":"A library written entirely in Kotlin to access home power plants from E3DC. Easy to use, well documented -> Have fun.
"},{"location":"#as-a-java-developer","title":"As a Java developer","text":"Basically the same as a Kotlin developer. The bytecode is Java compatible and in the examples you can choose between Kotlin and Java.
Small disadvantage for you (besides the Java syntax, but you should be used to that), as a dependency smaller Kotlin libraries will be necessary.
"},{"location":"#as-ja-typescript-or-javascript-developer","title":"As ja TypeScript or JavaScript developer","text":"You will find what you are looking for in the sister project easy-rscp-js. The core functionality is identical. The documentation of both projects is summarized here.
"},{"location":"#as-xyz-developer","title":"As XYZ Developer","text":"Ok, you can't use the library directly. But in the course of the project this documentation was created. Especially the chapter RSCP should be interesting for you. Here you find a description of the RSCP protocol and all tags and meanings known to me. Maybe this will help you.
"},{"location":"#where-do-i-start","title":"Where do I start?","text":"Best in the Getting started section.
"},{"location":"#can-i-participate","title":"Can I participate","text":"Absolutely!
"},{"location":"#i-have-found-a-bug","title":"I have found a bug","text":"Too bad, but that can happen. Create a new Bug and we'll take a look.
You already know the solution? Very cool! Post a PR and we'll bring it to the main branch.
"},{"location":"#i-have-cool-extensions-improvements-fixes","title":"I have cool extensions, improvements fixes","text":"The best thing to do is to create a PR.
You can also participate in the project as a maintainer. Create a ticket here and we will see how you can support best.
"},{"location":"#i-have-further-knowledge-about-the-rscp-protocol","title":"I have further knowledge about the RSCP protocol","text":"Every drop helps to decode this protocol completely. You can create a PR for the doc, or create a ticket.
If you want to describe a specific RSCP tag, it is best to go to the API description and the corresponding tag class. At each entry you will find a link to create a ticket for exactly this tag.
"},{"location":"#special-thanks","title":"Special thanks","text":"The RSCP protocol is virtually undocumented and there is no help available from E3DC. This library would not have been possible without numerous other projects. I would like to highlight two projects and two people in particular.
First of all, a big \"thank you\" to Stefan. The developer of the incredible iOS my4E. Although he himself had no benefit from answering my questions about the RSCP protocol, he did it anyway and was a great help! Not a matter of course.
A lot of inspiration and also help in understanding the RSCP protocol came from Uli. The developer of the E3DC plugin for ioBroker. Many thanks for the support.
"},{"location":"#license","title":"License","text":"The library is licensed under the MIT License. License
"},{"location":"about/license/","title":"License","text":""},{"location":"about/license/#_1","title":"License","text":"MIT License\n\nCopyright (c) 2023 Jochen Zink\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n
"},{"location":"about/license/#included-projects","title":"Included projects","text":""},{"location":"about/license/#this-documentation","title":"This documentation","text":"This documentation was created using the great MkDocs tool and the wonderful Material for MkDocs theme:
Without all the work of many, many OpenSource developers, this library would not have been possible. Many thanks to the authors of these fantastic projects:
Lists of 30 third-party dependencies.\n (The Apache Software License, Version 2.0) kotlin-logging (io.github.microutils:kotlin-logging-jvm:3.0.5 - https://github.com/oshai/kotlin-logging)\n (Apache License, Version 2.0) MockK Agent API (io.mockk:mockk-agent-api-jvm:1.13.9 - https://mockk.io)\n (Apache License, Version 2.0) MockK (io.mockk:mockk-agent-jvm:1.13.9 - https://mockk.io)\n (Apache License, Version 2.0) MockK Core (io.mockk:mockk-core-jvm:1.13.9 - https://mockk.io)\n (Apache License, Version 2.0) MockK DSL (io.mockk:mockk-dsl-jvm:1.13.9 - https://mockk.io)\n (Apache License, Version 2.0) MockK (io.mockk:mockk-jvm:1.13.9 - https://mockk.io)\n (Eclipse Public License 1.0) JUnit (junit:junit:4.13.2 - http://junit.org)\n (Apache License, Version 2.0) Byte Buddy (without dependencies) (net.bytebuddy:byte-buddy:1.14.6 - https://bytebuddy.net/byte-buddy)\n (Apache License, Version 2.0) Byte Buddy agent (net.bytebuddy:byte-buddy-agent:1.14.6 - https://bytebuddy.net/byte-buddy-agent)\n (The Apache License, Version 2.0) org.apiguardian:apiguardian-api (org.apiguardian:apiguardian-api:1.1.2 - https://github.com/apiguardian-team/apiguardian)\n (Bouncy Castle Licence) Bouncy Castle Provider (org.bouncycastle:bcprov-jdk18on:1.77 - https://www.bouncycastle.org/java.html)\n (New BSD License) Hamcrest Core (org.hamcrest:hamcrest-core:1.3 - https://github.com/hamcrest/JavaHamcrest/hamcrest-core)\n (The Apache Software License, Version 2.0) IntelliJ IDEA Annotations (org.jetbrains:annotations:13.0 - http://www.jetbrains.org)\n (The Apache License, Version 2.0) Kotlin Reflect (org.jetbrains.kotlin:kotlin-reflect:1.9.10 - https://kotlinlang.org/)\n (The Apache License, Version 2.0) Kotlin Stdlib (org.jetbrains.kotlin:kotlin-stdlib:1.9.10 - https://kotlinlang.org/)\n (The Apache License, Version 2.0) Kotlin Stdlib Common (org.jetbrains.kotlin:kotlin-stdlib-common:1.9.10 - https://kotlinlang.org/)\n (The Apache License, Version 2.0) Kotlin Stdlib Jdk7 (org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.10 - https://kotlinlang.org/)\n (The Apache License, Version 2.0) Kotlin Stdlib Jdk8 (org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.10 - https://kotlinlang.org/)\n (The Apache Software License, Version 2.0) kotlinx-coroutines-core (org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4 - https://github.com/Kotlin/kotlinx.coroutines)\n (The Apache Software License, Version 2.0) kotlinx-coroutines-core (org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.6.4 - https://github.com/Kotlin/kotlinx.coroutines)\n (Eclipse Public License v2.0) JUnit Jupiter (Aggregator) (org.junit.jupiter:junit-jupiter:5.10.1 - https://junit.org/junit5/)\n (Eclipse Public License v2.0) JUnit Jupiter API (org.junit.jupiter:junit-jupiter-api:5.10.1 - https://junit.org/junit5/)\n (Eclipse Public License v2.0) JUnit Jupiter Engine (org.junit.jupiter:junit-jupiter-engine:5.10.1 - https://junit.org/junit5/)\n (Eclipse Public License v2.0) JUnit Jupiter Params (org.junit.jupiter:junit-jupiter-params:5.10.1 - https://junit.org/junit5/)\n (Eclipse Public License v2.0) JUnit Platform Commons (org.junit.platform:junit-platform-commons:1.10.1 - https://junit.org/junit5/)\n (Eclipse Public License v2.0) JUnit Platform Engine API (org.junit.platform:junit-platform-engine:1.10.1 - https://junit.org/junit5/)\n (Apache License, Version 2.0) Objenesis (org.objenesis:objenesis:3.3 - http://objenesis.org/objenesis)\n (The Apache License, Version 2.0) org.opentest4j:opentest4j (org.opentest4j:opentest4j:1.3.0 - https://github.com/ota4j-team/opentest4j)\n (MIT License) SLF4J API Module (org.slf4j:slf4j-api:2.0.11 - http://www.slf4j.org)\n (MIT License) SLF4J Simple Provider (org.slf4j:slf4j-simple:2.0.11 - http://www.slf4j.org)\n
"},{"location":"about/release-notes/","title":"Release Notes","text":""},{"location":"about/release-notes/#version-222-2024-02-07","title":"Version 2.2.2 (2024-02-07)","text":"NONE
, the function none()
must no longer be called explicitly. This behavior is now default.In this example we will establish a connection to the home power plant and read out some system information.
"},{"location":"getting-started/hello-world/#provide-connection-data","title":"Provide connection data","text":"For connection building it is easiest to use the ConnectionBuilder()
class. For TypeScript you can use the factory classes. Here is an example with the minimum necessary configuration:
val connectionBuilder = ConnectionBuilder()\n.withAddress(host)\n.withPortalUser(portalUser)\n.withPortalPassword(portalPassword)\n.withRSCPPassword(rscpPassword)\n
ConnectionBuilder connectionBuilder = new ConnectionBuilder()\n.withAddress(host)\n.withPortalUser(portalUser)\n.withPortalPassword(portalPassword)\n.withRSCPPassword(rscpPassword);\n
const connectionData: E3dcConnectionData = {\naddress: host,\nport: 5033,\nportalUser: portalUser,\nportalPassword: portalPassword,\nrscpPassword: rscpPassword\n}\nconst factory = new DefaultHomePowerPlantConnectionFactory(connectionData)\nconst connection = await factory.openConnection()\n
Parameter Description address IP address or DNS name of the home power plant portalUser Username. Corresponds to the username from the E3DC portal portalPassword Password. Corresponds to the password on the E3DC portal rscpPassword Encryption password. This value is configured directly at the home power plant and must be identical here"},{"location":"getting-started/hello-world/#create-service-builder","title":"Create Service Builder","text":"The access itself is done via one of the services. Here in the example we use the InfoService
. The easiest way to create it is to use the InfoServiceBuilder()
class. In TypeScript you can easily create a service if you have created an RSCP connection:
val serviceBuilder = InfoServiceBuilder()\n.withConnectionBuilder(connectionBuilder)\n
InfoServiceBuilder infoServiceBuilder = new InfoServiceBuilder()\n.withConnectionBuilder(connectionBuilder);\n
const service = new DefaultInfoService(connection)\n
"},{"location":"getting-started/hello-world/#create-and-use-service","title":"Create and use service","text":"And now ... have fun:
KotlinJavaTypeScriptval service = serviceBuilder.buildService()\nval systemInfos = service.readSystemInfo()\nprintln(systemInfos)\n
InfoService service = infoServiceBuilder.buildService();\nSystemInfo infos = service.readSystemInfo();\nSystem.out.println(infos);\n
const service = new DefaultInfoService(connection)\nservice.readSystemInfo()\n.then(systemInfos => console.log(systemInfos)\n
"},{"location":"getting-started/hello-world/#all-together","title":"All together","text":"KotlinJavaTypeScript import de.jnkconsulting.e3dc.easyrscp.connection.ConnectionBuilder\nimport de.jnkconsulting.e3dc.easyrscp.service.builder.InfoServiceBuilder\nval host = \"192.168.1.2\" // IP address of your home power plant\nval portalUser = \"my_e3dc_username\" // The username you use to log in to the E3DC portal\nval portalPassword = \"my_e3dc_password\" // The password to your E3DC portal access\nval rscpPassword = \"my_rscp_password\" // The password you set on your home power plant \nfun main() {\nval connectionBuilder = ConnectionBuilder()\n.withAddress(host)\n.withPortalUser(portalUser)\n.withPortalPassword(portalPassword)\n.withRSCPPassword(rscpPassword)\nval serviceBuilder = InfoServiceBuilder()\n.withConnectionBuilder(connectionBuilder)\nval service = serviceBuilder.buildService()\nval systemInfos = service.readSystemInfo()\nprintln(systemInfos)\n}\n
import de.jnkconsulting.e3dc.easyrscp.api.service.InfoService;\nimport de.jnkconsulting.e3dc.easyrscp.api.service.model.SystemInfo;\nimport de.jnkconsulting.e3dc.easyrscp.connection.ConnectionBuilder;\nimport de.jnkconsulting.e3dc.easyrscp.service.builder.InfoServiceBuilder;\npublic class ReadSystemInfos {\nprivate static String host = \"192.168.1.2\"; // IP address of your home power plant\nprivate static String portalUser = \"my_e3dc_username\"; // The username you use to log in to the E3DC portal\nprivate static String portalPassword = \"my_e3dc_password\"; // The password to your E3DC portal access\nprivate static String rscpPassword = \"my_rscp_password\"; // The password you set on your home power plant\npublic static void main(String[] args) {\nConnectionBuilder connectionBuilder = new ConnectionBuilder()\n.withAddress(host)\n.withPortalUser(portalUser)\n.withPortalPassword(portalPassword)\n.withRSCPPassword(rscpPassword);\nInfoServiceBuilder infoServiceBuilder = new InfoServiceBuilder()\n.withConnectionBuilder(connectionBuilder);\nInfoService service = infoServiceBuilder.buildService();\nSystemInfo infos = service.readSystemInfo();\nSystem.out.println(infos);\n}\n}\n
import {E3dcConnectionData, DefaultHomePowerPlantConnectionFactory, DefaultInfoService} from 'easy-rscp';\nconst connectionData: E3dcConnectionData = {\naddress: host,\nport: 5033,\nportalUser: portalUser,\nportalPassword: portalPassword,\nrscpPassword: rscpPassword\n}\nconst factory = new DefaultHomePowerPlantConnectionFactory(connectionData)\nconst connection = await factory.openConnection()\nconst service = new DefaultInfoService(connection)\nservice.readSystemInfo()\n.then(systemInfos => console.log(systemInfos)\n
If everything worked, you should see something like the following result (The serialNumber is hidden here):
SystemInfo(serialNumber=***, softwareVersion=H20_2023_024, productionDate=ProductionDate(rscpValue='KW27 2022', calendarWeek=27, year=2022, localDate=2022-07-04))\n
That's it. Your first RSCP request to the home power plant is done!
"},{"location":"getting-started/hello-world/#whats-next","title":"What's next?","text":"Either you read the documentation of the Service API or, if you want to code more, you look at the integration tests in the service module. These are normal unit tests that are only executed when certain environment variables are set. There is one test for each service function. You can find the tests in the service module under src/test/kotlin/de/jnkconsulting/e3dc/easyrscp/service
. The variables are visible in the code and correspond to the connection data to the home power plant.
By default, the RSCP interface is disabled on the home power plant. Therefore you have to go to the home power plant once and activate the RSCP interface. Details can be found in the manual of your home power plant.
As an example I show the setup on my S10X (On other models it should be similar). The pictures are in german (sorry for that), but it should be easy to adapt.
"},{"location":"getting-started/setup/#setup-on-the-s10x-home-power-plant","title":"Setup on the S10X home power plant","text":"Switch from the main page to the main menu Select \"Personalize\" in the main menu Select \"Profile\" here Go to the next page Set password and confirm -> The small lamp must now light up green"},{"location":"getting-started/setup/#using-the-library","title":"Using the library","text":"Warning of damage to your home power plantThis library is provided absolutely without any warranty. You can use it for write access to E3DC home power plants. The library does not validate in any way whether the requests make sense. The usage is completely in the responsibility of the user!
The library is divided into three sections
artifact-id Description jnk-easy-rscp-api Mainly contains interfaces and descriptions of the library. The other modules implement the interfaces. jnk-easy-rscp-lowlevel RSCP interface at the bottom. Here you can compile an RSCP request down to the byte, send it and interpret the response. jnk-easy-rscp-service Higher level that hides RSCP subtleties behind services. Most users should use this module. The scope will be extended in future version. Maven centralThe artifacts are available in the maven central repository and for easy-rscp-js on npm
MavenGradle (groovy)Gradle (kotlin)npm<dependency>\n<groupId>de.jnk-consulting.e3dc.easyrscp</groupId>\n<artifactId>jnk-easy-rscp-service</artifactId>\n<version>2.0.0</version>\n</dependency>\n
implementation 'de.jnk-consulting.e3dc.easyrscp:jnk-easy-rscp-service:2.0.0'\n
implementation(\"de.jnk-consulting.e3dc.easyrscp:jnk-easy-rscp-service:2.0.0\")\n
npm i easy-rscp\n
"},{"location":"lowlevel/connection/","title":"Connection setup","text":"The connection
package contains all classes that are needed to establish a connection to the home power plant. The class DefaultHomePowerPlantConnection
serves as connection point. Here frames are sent to the home power plant and the answers are parsed.
To create a connection the class DefaultHomePowerPlantConectionFactory
is available.
As ConnectionPool
the implementation SingleInstanceConnectionPool
is available, which keeps Thread Save open a connection to the home power plant.
To simplify the creation and dependencies, there is the ConnectionBuilder
which can create the instances with little configuration effort.
In the TypeScript version there is no ConnectionPool and no ConnectionBuilder. The connection ensures that all requests are sent serialized
"},{"location":"lowlevel/connection/#connectionbuilder-usage","title":"ConnectionBuilder usage","text":"In the following example we create a ConnectionBuilder instance, get a ConnectionPool and request the current live production data. To be able to read the answer we use the StringFrameConverter
to output the answer frame:
import de.jnkconsulting.e3dc.easyrscp.api.frame.tags.EMSTag\nimport de.jnkconsulting.e3dc.easyrscp.frame.DataBuilder\nimport de.jnkconsulting.e3dc.easyrscp.frame.FrameBuilder\nimport de.jnkconsulting.e3dc.easyrscp.connection.ConnectionBuilder\nimport de.jnkconsulting.e3dc.easyrscp.service.converter.StringFrameConverter\nval host = \"192.168.1.2\" // IP address of your home power plant\nval portalUser = \"my_e3dc_username\" // The username you use to log in to the E3DC portal\nval portalPassword = \"my_e3dc_password\" // The password to your E3DC portal access\nval rscpPassword = \"my_rscp_password\" // The password you set on your home power plant\nfun main() {\nval connectionBuilder = ConnectionBuilder()\n.withAddress(host)\n.withPortalUser(portalUser)\n.withPortalPassword(portalPassword)\n.withRSCPPassword(rscpPassword)\nval connectionPool = connectionBuilder.buildConnectionPool()\nval answer = connectionPool.executeAndRelease {\nit.send(FrameBuilder()\n.addData(\nDataBuilder().tag(EMSTag.REQ_POWER_PV).build(),\nDataBuilder().tag(EMSTag.REQ_POWER_BAT).build(),\nDataBuilder().tag(EMSTag.REQ_POWER_GRID).build(),\nDataBuilder().tag(EMSTag.REQ_POWER_HOME).build(),\nDataBuilder().tag(EMSTag.REQ_BAT_SOC).build(),\n).build()\n)\n}\nprintln(StringFrameConverter().invoke(answer))\n}\n
import de.jnkconsulting.e3dc.easyrscp.api.connection.ConnectionPool;\nimport de.jnkconsulting.e3dc.easyrscp.api.frame.Frame;\nimport de.jnkconsulting.e3dc.easyrscp.api.frame.tags.EMSTag;\nimport de.jnkconsulting.e3dc.easyrscp.connection.ConnectionBuilder;\nimport de.jnkconsulting.e3dc.easyrscp.frame.DataBuilder;\nimport de.jnkconsulting.e3dc.easyrscp.frame.FrameBuilder;\nimport de.jnkconsulting.e3dc.easyrscp.service.converter.StringFrameConverter;\npublic class ConnectionBuilderExample {\nprivate static String host = \"192.168.1.2\"; // IP address of your home power plant\nprivate static String portalUser = \"my_e3dc_username\"; // The username you use to log in to the E3DC portal\nprivate static String portalPassword = \"my_e3dc_password\"; // The password to your E3DC portal access\nprivate static String rscpPassword = \"my_rscp_password\"; // The password you set on your home power plant\npublic static void main(String[] args) {\nConnectionBuilder connectionBuilder = new ConnectionBuilder()\n.withAddress(host)\n.withPortalUser(portalUser)\n.withPortalPassword(portalPassword)\n.withRSCPPassword(rscpPassword);\nConnectionPool connectionPool = connectionBuilder.buildConnectionPool();\nFrame answer = connectionPool.executeAndRelease(connection -> {\nFrame requestFrame = new FrameBuilder()\n.addData(\nnew DataBuilder().tag(EMSTag.REQ_POWER_PV).build(),\nnew DataBuilder().tag(EMSTag.REQ_POWER_BAT).build(),\nnew DataBuilder().tag(EMSTag.REQ_POWER_GRID).build(),\nnew DataBuilder().tag(EMSTag.REQ_POWER_HOME).build(),\nnew DataBuilder().tag(EMSTag.REQ_BAT_SOC).build()\n).build();\nreturn connection.send(requestFrame);\n});\nSystem.out.println(new StringFrameConverter().invoke(answer));\n}\n}\n
import {\nE3dcConnectionData, DefaultHomePowerPlantConnectionFactory, FrameBuilder,\nDataBuilder,\nStringFrameConverter} from 'easy-rscp';\nconst connectionData: E3dcConnectionData = {\naddress: host,\nport: 5033,\nportalUser: portalUser,\nportalPassword: portalPassword,\nrscpPassword: rscpPassword\n}\nconst factory = new DefaultHomePowerPlantConnectionFactory(connectionData)\nconst connection = await factory.openConnection()\nconst request = new FrameBuilder()\n.addData(\nnew DataBuilder().tag(EMSTag.REQ_POWER_PV).build(),\nnew DataBuilder().tag(EMSTag.REQ_POWER_BAT).build(),\nnew DataBuilder().tag(EMSTag.REQ_POWER_GRID).build(),\nnew DataBuilder().tag(EMSTag.REQ_POWER_HOME).build(),\nnew DataBuilder().tag(EMSTag.REQ_BAT_SOC).build()\n).build()\nconnection.send(request)\n.then(response => {\nconsole.log(new StringFrameConverter().convert(response))\n})\n
The output should look something like this:
Timestamp: 2023-09-19T19:08:44.664348Z\nWithChecksum: true\n Tag: EMS.POWER_PV - 01800001\n Type: INT32 - 06\n Value: 0 - 00000000\n Tag: EMS.POWER_BAT - 01800002\n Type: INT32 - 06\n Value: -639 - 81fdffff\n Tag: EMS.POWER_GRID - 01800004\n Type: INT32 - 06\n Value: 23 - 17000000\n Tag: EMS.POWER_HOME - 01800003\n Type: INT32 - 06\n Value: 662 - 96020000\n Tag: EMS.BAT_SOC - 01800008\n Type: UCHAR8 - 03\n Value: 86 - 56\n
"},{"location":"lowlevel/crypt/","title":"Encryption","text":"The crypt
package contains the AES implementations. Each request and response frame must be AES encrypted. Here E3DC has made some specifications which are mapped in the class BouncyCastleAESCipher
.
E3DC requires some peculiarities that make the AES algrithm used here insecure.
None of this can be changed, the home power plant requires exactly this algorithm. Therefore use this class only here and otherwise please not!
"},{"location":"lowlevel/crypt/#usage","title":"Usage","text":"TypeScriptIf you want to intervene in the encryption, you can implement your own AesCipherFactory and make this known to the DefaultHomePowerPlantConnectionFactory in the structure.
The classes are not used directly, but created automatically by the ConnectionBuilder
. Only if you want to exchange the supplied implementation, you can set your factory at the builder:
val connectionBuilder = ConnectionBuilder()\n.withAddress(host)\n.withPortalUser(portalUser)\n.withPortalPassword(portalPassword)\n.withRSCPPassword(rscpPassword)\n.withCipherFactory(myCustomCipherFactory)\n
ConnectionBuilder connectionBuilder = new ConnectionBuilder()\n.withAddress(host)\n.withPortalUser(portalUser)\n.withPortalPassword(portalPassword)\n.withRSCPPassword(rscpPassword)\n.withCipherFactory(myCustomCipherFactory);\n
const factory = new DefaultHomePowerPlantConnectionFactory(\nconnectionData,\nmyCustomAESImplementation);\n
"},{"location":"lowlevel/frame/","title":"Frames and Data","text":"Frames are used to send requests to the home power plant. The home power plant also responds in frames. Each frame consists of a series (at least one) of data blocks. The type of a block is determined by so-called tags.
"},{"location":"lowlevel/frame/#tags","title":"Tags","text":"Tags describe the type of a data block. They tell whether the contained value is a number or a text, and whether it is, for example, the current PV prodction or a load limit.
The tags I know are located as enum classes in the package de.jnkconsulting.e3dc.easyrscp.api.frame.tags
. For each namespace defined by E3DC there is a separate enum class.
What exactly the namespaces mean is described in the RSCP section.
If a tag is needed, either as a request or as a response, which are not defined here in enum classes, an instance of UnknownTag
can be used. The original byte value remains here and can be used accordingly.
The easiest way to create frames is to use the FrameBuilder
and the DataBuilder
classes.
You must know what you are doing. Neither the FrameBuilder
nor the DataBuilder
check if the combination makes any sense!
Here is an example of how to put together a query frame that asks for some live data:
KotlinJavaTypeScriptimport de.jnkconsulting.e3dc.easyrscp.api.frame.tags.EMSTag\nimport de.jnkconsulting.e3dc.easyrscp.frame.DataBuilder\nimport de.jnkconsulting.e3dc.easyrscp.frame.FrameBuilder\nimport de.jnkconsulting.e3dc.easyrscp.service.converter.StringFrameConverter\nfun main() {\nval requestFrame = FrameBuilder()\n.addData(\nDataBuilder().tag(EMSTag.REQ_POWER_PV).build(),\nDataBuilder().tag(EMSTag.REQ_POWER_BAT).build(),\nDataBuilder().tag(EMSTag.REQ_POWER_GRID).build(),\nDataBuilder().tag(EMSTag.REQ_POWER_HOME).build(),\nDataBuilder().tag(EMSTag.REQ_BAT_SOC).build(),\n).build()\nprintln(StringFrameConverter().invoke(requestFrame))\n}\n
import de.jnkconsulting.e3dc.easyrscp.api.frame.Frame;\nimport de.jnkconsulting.e3dc.easyrscp.api.frame.tags.EMSTag;\nimport de.jnkconsulting.e3dc.easyrscp.frame.DataBuilder;\nimport de.jnkconsulting.e3dc.easyrscp.frame.FrameBuilder;\nimport de.jnkconsulting.e3dc.easyrscp.service.converter.StringFrameConverter;\npublic class FrameExamples {\npublic static void main(String[] args) {\nFrame requestFrame = new FrameBuilder()\n.addData(\nnew DataBuilder().tag(EMSTag.REQ_POWER_PV).build(),\nnew DataBuilder().tag(EMSTag.REQ_POWER_BAT).build(),\nnew DataBuilder().tag(EMSTag.REQ_POWER_GRID).build(),\nnew DataBuilder().tag(EMSTag.REQ_POWER_HOME).build(),\nnew DataBuilder().tag(EMSTag.REQ_BAT_SOC).build()\n).build();\nSystem.out.println(new StringFrameConverter().invoke(requestFrame));\n}\n}\n
import {FrameBuilder, DataBuilder, StringFrameConverter, EMSTag} from 'easy-rscp';\nconst request = new FrameBuilder()\n.addData(\nnew DataBuilder().tag(EMSTag.REQ_POWER_PV).build(),\nnew DataBuilder().tag(EMSTag.REQ_POWER_BAT).build(),\nnew DataBuilder().tag(EMSTag.REQ_POWER_GRID).build(),\nnew DataBuilder().tag(EMSTag.REQ_POWER_HOME).build(),\nnew DataBuilder().tag(EMSTag.REQ_BAT_SOC).build()\n).build();\nconsole.log(new StringFrameConverter().convert(requestFrame));\n
The output should look something like this:
Timestamp: 2023-09-19T20:32:43.305569Z\nWithChecksum: true\n Tag: EMS.REQ_POWER_PV - 01000001\n Type: NONE - 00\n Value: [NONE] - \n Tag: EMS.REQ_POWER_BAT - 01000002\n Type: NONE - 00\n Value: [NONE] - \n Tag: EMS.REQ_POWER_GRID - 01000004\n Type: NONE - 00\n Value: [NONE] - \n Tag: EMS.REQ_POWER_HOME - 01000003\n Type: NONE - 00\n Value: [NONE] - \n Tag: EMS.REQ_BAT_SOC - 01000008\n Type: NONE - 00\n Value: [NONE] - \n
"},{"location":"lowlevel/frame/#reading-frames","title":"Reading frames","text":"Reading data from a frame can be complex. Theoretically, a frame can contain data blocks of type container
. So, blocks that contain other blocks. To make it as comfortable as possible to access the individual values, the frame class provides various functions to access the contents in any depth. For this purpose different xxxByTag
functions are available. The functions search for a specific data block with the given tag. There is an additional vararg parameter, which maps the path through possibly existing containers.
Here is an example to read the house consumption of a certain day. For this, a request was made to the home power plant database. In response, a frame with a nested structure is provided. The searched value is in the following structure:
DBTag.HISTORY_DATA_DAY -> DBTag.SUM_CONTAINER -> DBTag.CONSUMPTION\n
DBTag.HISTORY_DATA_DAY
and DBTag.SUM_CONTAINER
are data blocks of type container
. DBTag.CONSUMPTION
contains a float value indicating the total house consumption of the day. This value can be accessed as follows:
KotlinJavaTypeScriptval answerFrame: Frame = ...\nval consumption = answerFrame.floatByTag(\nDBTag.CONSUMPTION, DBTag.HISTORY_DATA_DAY, DBTag.SUM_CONTAINER)\n
Frame answerFrame = ...;\nfloat consumption = amswerFrame.floatByTag(\nDBTag.CONSUMPTION, DBTag.HISTORY_DATA_DAY, DBTag.SUM_CONTAINER);\n
import {Frame, DBTag} from 'easy-rscp';\nconst answerFrame: Frame = ...\nconst consumption = answerFrame.floatByTag(\nDBTag.CONSUMPTION, DBTag.HISTORY_DATA_DAY, DBTag.SUM_CONTAINER)\n
The various xxxByTag
usually always return a value. If the corresponding tag is not found, a default value is returned (For numeric values 0, for string \"\" etc.).
The xxxByTag
methods have their limit when a container contains several data blocks of the same type. This happens quite rarely (in database queries for example). But if it does, then you have to access manually according to the content.
If you want to work with the lowlevel API, you must be familiar with the RSCP protocol, at least in its basic features. I therefore recommend to also read the RSCP section.
The API of easy-rscp consists of three areas, which are separated by the packages connection
, crypt
and frame
.
Here are all the classes that are needed to establish a connection to the home power plant.
To create and correctly connect the different objects, a ConnectionBuilder
is available. More info in the Connection setup section.
Here are the implementations of AESCipher
and AESCipherFactory
. easy-rscp uses the Java Crypto Framework BouncyCastle for the implementations.
If you want to work without BouncyCastle dependency, you can implement the two interfaces yourself and set them accordingly in the ConnectionBuilder
.
Everything that has to do with frames and data blocks, parsing etc. is located here.
"},{"location":"rscp/authentication/","title":"Authentication","text":"After a TCP/IP connection to the home power plant has been established, an authentication frame must first be sent.
This consists of a data block RSCP.REQ_AUTHENTICATION
of type container
. This contains two data blocks. Once RSCP.AUTHENTICATION_USER
and RSCP.AUTHENTICATION_PASSWORD
. Each of the type string. The access data to the E3DC portal are set as values.
In the example, the username TEST
and the password 123
were used:
Timestamp: 2023-09-20T14:22:25.542694Z\nWithChecksum: true\n Tag: RSCP.REQ_AUTHENTICATION - 00000001\n Type: CONTAINER - 0e\n Tag: RSCP.AUTHENTICATION_USER - 00000002\n Type: STRING - 0d\n Value: TEST - 54455354\n\n Tag: RSCP.AUTHENTICATION_PASSWORD - 00000003\n Type: STRING - 0d\n Value: 123 - 313233\n
e3dc001121000b650000000070da58201c00010000000e1500020000000d040054455354030000000d030031323378babe16\n
"},{"location":"rscp/authentication/#response-frame-on-successful-authentication","title":"Response frame on successful authentication","text":"Human readableAs hex code Timestamp: 2023-09-20T14:26:14.787390Z\nWithChecksum: true\n Tag: RSCP.AUTHENTICATION - 00800001\n Type: UCHAR8 - 03\n Value: 10 - 0a\n
e3dc001106010b6500000000309eee2e0800010080000301000ad4e6f5cf\n
The response frame contains the value 10
. This specifies some kind of authorization. The following values are known (Even though we usually always have to make do with the USER level):
Timestamp: 2023-09-20T14:27:42.534655Z\nWithChecksum: true\n Tag: RSCP.AUTHENTICATION - 00800001\n Type: INT32 - 06\n Value: 0 - 00000000\n
e3dc00115e010b65000000001830de1f0b00010080000604000000000003120cf6\n
"},{"location":"rscp/basic-rscp/","title":"Basic stuff","text":"E3DC's home power plants offer a TCP/IP interface for read and write access. The protocol used for the content is proprietary and is called RSCP.
The RSCP structure is well documented by E3DC, but in terms of content there is almost no documentation at all. In addition, the documentation is incomplete, partly incorrect and not publicly available. The E3DC service portal, to which every customer has access, contains the RSCP documentation.
This section aims to explain the subtleties of the protocol in as much detail as possible. For example, if you want to create your own framework in another language, you can use the documentation as a basis.
Help is welcomeMuch of the knowledge about RSCP that has been written down here has been determined by trial and error. There are still large gaps in our knowledge and certainly there will be errors. If you can contribute something to the documentation, you are welcome. Just report in a ticket.
"},{"location":"rscp/basic-rscp/#base-information","title":"Base information","text":"The home power plant listens on a specific port (usually 5033) and waits for RSCP requests. An RSCP request is a byte block called a frame. A frame contains different metadata and 1-n data blocks. On this page I only want to give a short overview, the whole frame structure is described byte by byte in the section Frames in Detail.
"},{"location":"rscp/basic-rscp/#communication-principle","title":"Communication principle","text":"RSCP works according to the send/response principle. This means that you send a request frame to the home power plant and you get the appropriate response frame back in reply. A special authentication frame must be sent beforehand. All further requests must be handled in the same connection. Otherwise another authentication is necessary. How the authentication works is described in the section Authentication.
The home power plant does not provide transport encryption. But each frame must be AES256 encrypted. Details about the algorithm can be found in the Encryption section.
Sending and receiving a frame:
sequenceDiagram\n Client->>Client: buildAuthenticationFrame()\n Client->>Client: encryptAuthenticationFrame(authenticationFrame)\n Client->>home power plant: send(encryptedAuthenticationFrame)\n home power plant-->>Client: respond(encryptedAuthenticationAnswerFrame)\n Client->>Client: buildRequestFrame()\n Client->>Client: encryptRequestFrame(requestFrame)\n Client->>home power plant: send(encryptedRequestFrame)\n home power plant-->Client: respond(encryptedAnswerFrame)\n Client->>Client: decrypt(encryptedAnswerFrame)\n Client->>Client: useAndBeHappy(answerFrame)
"},{"location":"rscp/encryption/","title":"Encryption","text":"This section describes how to encrypt (or decrypt) a frame. It is assumed that some terms are already known. AES, IV, etc. are not described in detail because they are part of the standard AES algorithm.
"},{"location":"rscp/encryption/#the-basics","title":"The basics","text":"Each frame must be AES-256 encrypted. The response frames must be decrypted accordingly. The RSCP password configured on the home power plant is used as the key (see Setup).
Note the handling of the IV. Initially, an IV completely filled with the values 0xFF is used. Afterwards the last encrypted block is always used as IV for the next one (AES standard). This also applies across frames. So when you send the authentication frame, you have to remember the last block, because it is needed as IV for the next frame.
On the decryption side it is the same. The initial IV is filled with 0xFF. When decrypting you have to remember the last block again, because it is needed for the next response frame.
Do not use this algorithm anywhere else!The algorithm given by E3DC is not completely secure. Therefore, do not use the algorithm or the encryption implementation in any other project. Three problems make the algorithm insecure:
password derivation function
: AES is byte based. Here AES-256 -> A 32Byte long key consisting of binary data is used. Now it is quite inconvenient for humans to remember binary data, so E3DC decided to use a text as key. This can be done, but then you should use a password derivation function
(PBKDF2, Argon2, etc). Otherwise the key space is limited to the printable characters, and thus the security is reduced considerably. E3DC does not do this!In addition to pointing out that please do not use the algorithm anywhere else, it is also worth mentioning that the data transmitted in the frames cannot be considered completely safe either. Be aware of this. Among other things, your E3DC Portal username and E3DC Portal password will be transmitted!
Usually the transmission is done in your private network, but I don't want to leave it unmentioned. When you log in to the E3DC portal with your data, these are SSL encrypted and therefore not readable in your private network. When communicating with the home power plant you have to rely on the poorly implemented AES algorithm.
Good AES vs Bad AESJust to clarify: AES itself is not considered insecure as of today (end of 2023) and is a very good symmetric algorithm. However, if you choose the parameters incorrectly, the security is unfortunately gone.
"},{"location":"rscp/encryption/#aes-parameter","title":"AES Parameter","text":"To use your AES algorithm correctly, you need to consider the following parameters:
To visualize the frame structure I used Gantt charts here. They are actually meant for something else, but they serve their purpose and can be easily generated with mermaid.js. Unfortunately the diagrams don't offer a zoom function, so use the browser zoom!
The following diagrams show the structure of a frame. And this byte by byte. Please note that the bytes are transmitted in little endian order.
"},{"location":"rscp/frame-details/#frame","title":"Frame","text":"gantt\n title Structure of a frame\n dateFormat YYYY-MM-DD\n axisFormat %d\n tickInterval 1day\n section Frame\n Whole frame : frame, 2014-01-01, 30d \n section Header \n Magic bytes : mb, 2014-01-01, 2d\n CTRL : ctrl, after mb, 2d\n Timestamp : ts, after ctrl, 12d\n length : dlength, after ts, 2d\n section Data \n Data : crit, data, after dlength, 8d\n section Checksum\n CRC32 : active, crc, after data, 4d
In the diagram above the structure of a frame is shown. Please note that the length of the data block is variable and is only shown here as an example of 8 bytes. In reality a data block is significantly longer.
"},{"location":"rscp/frame-details/#magic-bytes","title":"Magic Bytes","text":"Each frame starts with a 2 byte MagicByte block. This always contains the values E3DC (Hexcode ...)
gantt\n title Magic Bytes\n dateFormat YYYY-MM-DD\n axisFormat %d\n tickInterval 1day\n section Magic Bytes\n 0xE3 : e3, 2014-01-01, 1d\n 0xDC : dc, after e3, 1d
"},{"location":"rscp/frame-details/#control-bytes","title":"Control Bytes","text":"The control bytes currently contain 2 pieces of information.
Both information are placed in the first byte. The second byte is intended for future use is currently always 0.
gantt\n title Control Bytes\n dateFormat YYYY-MM-DD\n axisFormat %d\n tickInterval 1day\n section CRC true\n 0x00 : b1, 2014-01-01, 1d\n 0x11 : b2, after b1, 1d\n section CRC false\n 0x00 : b1, 2014-01-01, 1d\n 0x01 : b2, after b1, 1d
"},{"location":"rscp/frame-details/#timestamp","title":"Timestamp","text":"The timestamp area indicates the creation time of the frame. In seconds and nanoseconds since 01/01/1970 00:00. As I understand it in UTC.
The first 8 bytes indicate the seconds value and the next 4 bytes the nanoseconds.
The following timestamp block, represents the timestamp 2023-09-20T07:57:05.362489Z.
Since the 01.01.1970 00:00 o'clock up to this Timestamp 1,695,196,625 seconds and 362,489,000 nanoseconds have passed.
gantt\n title Timestamp\n dateFormat YYYY-MM-DD\n axisFormat %d\n tickInterval 1day\n section Timestamp\n 0xd1a50a6500000000 : seconds, 2014-01-01, 8d\n 0xa8249b15 : nanos, after seconds, 4d
"},{"location":"rscp/frame-details/#length","title":"Length","text":"In the length field the length of the following data block is indicated in bytes. Since a frame can consist of several data blocks, the sum of all data block lengths is specified here.
In the example 28bytes (0x1c00 in hexcode Little Endian)
gantt\n title Length\n dateFormat YYYY-MM-DD\n axisFormat %d\n tickInterval 1day\n section Timestamp\n 0x1c00 : length, 2014-01-01, 2d
"},{"location":"rscp/frame-details/#data-block","title":"Data block","text":"One or more data blocks follow. The length is determined by the previous length field. The structure is described in detail in the data block area.
"},{"location":"rscp/frame-details/#checksum","title":"Checksum","text":"If the frame uses a checksum, it is specified here, with a length of 4 bytes. The standard algorithm CRC32 is used.
gantt\n title CRC32\n dateFormat YYYY-MM-DD\n axisFormat %d\n tickInterval 1day\n section Timestamp\n 0xe2a60c5d : crc, 2014-01-01, 4d
"},{"location":"rscp/frame-details/#datenblock","title":"Datenblock","text":"A data block represents a concrete value in a frame. Exception: A data block is of the type 'container'. Then it contains further data blocks
gantt\n title Structure of a Data Block\n dateFormat YYYY-MM-DD\n axisFormat %d\n tickInterval 1day\n section Data\n Whole Block : block, 2014-01-01, 15d \n section Header \n Tag : tag, 2014-01-01, 4d\n Type : type, after tag, 1d\n Length : length, after type, 2d \n section Value \n Value : crit, value, after length, 8d
"},{"location":"rscp/frame-details/#tag","title":"Tag","text":"The tag gives a meaning to the content. It describes which area of the home power plant it is (namespace), whether it is a request or response block, and which field from the respective namespace is meant.
For example the tag MAC_ADDRESS
from the namespace INFO
. This has the value 0a80000a
.
gantt\n title The Tag INFO.MAC_ADDRESS\n dateFormat YYYY-MM-DD\n axisFormat %d\n tickInterval 1day\n section Namespace\n 0x0a : ns, 2014-01-01, 1d \n section Tag \n 0x00800a : tag, after ns, 3d
Info Whether it is a request or response block can be seen by the first bit(!) of the tag area. 0=request, 1=response
"},{"location":"rscp/frame-details/#type","title":"Type","text":"In the next byte the data type is specified. That is, how the value area in the block is to be interpreted (number, string, etc.).
gantt\n title Datatype String\n dateFormat YYYY-MM-DD\n axisFormat %d\n tickInterval 1day\n section String\n 0x0d : type, 2014-01-01, 1d
"},{"location":"rscp/frame-details/#length_1","title":"Length","text":"This area is 2 bytes long and specifies the length in bytes of the following value area.
gantt\n title Lengthj\n dateFormat YYYY-MM-DD\n axisFormat %d\n tickInterval 1day\n section String\n 0x1100 : length, 2014-01-01, 2d
"},{"location":"rscp/frame-details/#value","title":"Value","text":"Here hides the actual value, which can be interpreted depending on length and type. Here is an example for the software release number H20_2023_024
(tag INFO.SW_RELEASE
)
gantt\n title Example String\n dateFormat YYYY-MM-DD\n axisFormat %d\n tickInterval 1day\n section String\n 0x4832305f323032335f303234 : value, 2014-01-01, 2d
Blocks as values If the data type is container
, the value area contains a complete further data block(s)!
To make things a little clearer, let's take a look at a request frame and its corresponding response frame.
We want to query the live data of the current PV production and the battery. For this purpose, we first assemble the query frame.
In human readable form:
Timestamp: 2023-09-20T08:42:32.818989Z\nWithChecksum: true\n Tag: EMS.REQ_POWER_PV - 01000001\n Type: NONE - 00\n Value: [NONE] - \n Tag: EMS.REQ_POWER_BAT - 01000002\n Type: NONE - 00\n Value: [NONE] - \n
In home power plant readable form:
e3dc001178b00a6500000000c8c7d0300e0001000001000000020000010000004c769f09\n
And once disassembled:
Bytes Value Description 1 - 2 e3dc Magic Bytes 3 - 4 0011 Controlbytes -> protocol version 1 and checksum is enabled 5 - 12 78b00a6500000000 Timestamp, seconds since 01.01.1970 00:00 UTC -> 1,695,199,352 13 - 16 c8c7d030 Timestamp, Nanoseconds -> 818,989,000 17 - 18 0e00 Length of the data block area in bytes -> 14 bytes 18 - 21 01000001 Namespace + Tag -> EMS.REQ_POWER_PV 22 00 Data type -> None 23 - 24 0000 Length of the data block -> Data type isnone
, therefore 0 25 - 28 02000001 Namespace + Tag -> EMS.REQ_POWER_BAT 29 00 Data type -> None 31-32 0000 Length of the data block -> data type is none
, therefore 0 33-36 4c769f09 CRC32 checksum As a response we get the following frame:
In human readable form:
Timestamp: 2023-09-20T08:42:35.019685Z\nWithChecksum: true\n Tag: EMS.POWER_PV - 01800001\n Type: INT32 - 06\n Value: 4687 - 4f120000\n Tag: EMS.POWER_BAT - 01800002\n Type: INT32 - 06\n Value: 2139 - 5b080000\n
In home power plant readable form:
e3dc00117bb00a6500000000885e2c011600010080010604004f120000020080010604005b08000058156e18\n
And once disassembled:
Bytes Value Description 1 - 2 e3dc Magic Bytes 3 - 4 0011 Kontrolbytes -> Protocol version 1 and checksum is activated 5 - 12 7bb00a6500000000 Timestamp, seconds since 01.01.1970 00:00 UTC -> 1,695,199,355 13 - 16 885e2c01 Timestamp, Nanoseconds -> 19,685,000 17 - 18 1600 Length of the data block area in bytes -> 22 bytes 18 - 21 01008001 Namespace + Tag -> EMS.POWER_PV 22 06 Data type -> INT32 23 - 24 0400 Length of the data block -> data type isINT32
, therefore 4 bytes 25 - 28 4f120000 Actual value -> data type is INT32
, therefore 4,687 29 - 32 02008001 Namespace + Tag -> EMS.POWER_BAT 33 06 Data type -> INT32 34 - 35 0400 Length of the data block -> data type is INT32
, therefore 4 bytes 36 - 39 5b080000 Actual value -> data type is INT32
, therefore 2,139 40 - 43 58156e18 CRC32 checksume"},{"location":"rscp/frame-details/#namespaces","title":"Namespaces","text":"E3DC has split all tags into different namespaces to create some overview. All namespaces known to me are documented in kdoc in the enum class de.jnkconsulting.e3dc.easyrscp.api.frame.Namespace
.
The multitude of tags is also documented in kdoc. Each namespace has its own enum class to ensure at least some overview.
If I know anything about the structure and meaning of the tag, it is stored in kdoc. Partly E3DC has documented a little bit. If available, I have included the original E3DC documentation in kdoc and marked it with Original E3DC Documentation:
. Once translated in English and once in German, as supplied by E3DC.
Here you can find the tags TAG API.
"},{"location":"rscp/frame-details/#data-types","title":"Data types","text":"The protocol defines a set of data types. These are documented in the enum class de.jnkconsulting.e3dc.easyrscp.api.frame.DataType
.
There are currently three ways to interfere with the operation of the service or lowlevel API.
Listener \u2192 Are called at specific points in the request/response lifecycle
FrameConverter \u2192 Read data from a response frame from the home power plant and convert the data into an object
FrameCreator \u2192 Generates request frames to the home power plant
"},{"location":"service/extension-points/#listener","title":"Listener","text":"For instances of type ConnectionBuilder
any number of RSCPRequestResponseListener
can be registered. These listeners are called at different points in the request-response cycle.
In TypeScript you can pass the listeners to the ConnectionFactory
KotlinJavaTypeScriptval connectionBuilder = ConnectionBuilder()\n.withAddress(host)\n.withPortalUser(portalUser)\n.withPortalPassword(portalPassword)\n.withRSCPPassword(rscpPassword)\n.addRequestResponseListener(listener1, listener2)\n
ConnectionBuilder connectionBuilder = new ConnectionBuilder()\n.withAddress(host)\n.withPortalUser(portalUser)\n.withPortalPassword(portalPassword)\n.withRSCPPassword(rscpPassword)\n.addRequestResponseListener(listener1, listener2);\n
const factory = new DefaultHomePowerPlantConnectionFactory(\nconnectionData,\naesFactory,\nsocketFactory,\nframeParser,\nlistenerArray\n)\n
Please note The listeners are only used if NO own instance of type HomePowerPlantConnectionFactory
or ConnectionPool
is configured at the Builder!
sequenceDiagram\n autonumber\n DefaultHomePowerPlantConnection->>Listener: onBeforeRequestFrameEncryption(event)\n DefaultHomePowerPlantConnection->>AESCipher: encrypt(frameBytes)\n DefaultHomePowerPlantConnection->>Listener: onBeforeRequestSend(event)\n DefaultHomePowerPlantConnection->>HomePowerPlant: send(encryptedFrameBytes)\n DefaultHomePowerPlantConnection->>Listener: onAfterRequestSend(event)\n HomePowerPlant->>DefaultHomePowerPlantConnection: sendAnswer(encryptedAnswerFrameBytes)\n DefaultHomePowerPlantConnection->>Listener: onAnswerReceived(event)\n DefaultHomePowerPlantConnection->>AESCipher: decrypt(encryptedAnswerFrameBytes)\n DefaultHomePowerPlantConnection->>Listener: onAnswerDecrypted(event)\n DefaultHomePowerPlantConnection->>FrameParser: parseRSCPFrame(decryptedAnswerBytes)\n DefaultHomePowerPlantConnection->>Listener: onAnswerParsed(event)
"},{"location":"service/extension-points/#frameconverter","title":"FrameConverter","text":"Each service offers the possibility to set its own FrameConverter
instances. The builders offer corresponding functions here.
A FrameConverter
reads a response frame from the home power plant and converts it into a corresponding logical object. The service API already provides corresponding converters. Normally you don't need to do anything here. But if you want to influence the conversion process, you can create your own converter and set it as constructor parameter or via one of the builders.
Here is an example how to set your own converter at the InfoService
:
import de.jnkconsulting.e3dc.easyrscp.api.frame.Frame\nimport de.jnkconsulting.e3dc.easyrscp.api.frame.tags.InfoTag\nimport de.jnkconsulting.e3dc.easyrscp.api.service.model.ProductionDate\nimport de.jnkconsulting.e3dc.easyrscp.api.service.model.SystemInfo\nimport de.jnkconsulting.e3dc.easyrscp.service.builder.ConnectionBuilder\nimport de.jnkconsulting.e3dc.easyrscp.service.builder.InfoServiceBuilder\nimport de.jnkconsulting.e3dc.easyrscp.service.converter.FrameConverter\nclass CustomConverter: FrameConverter<SystemInfo> {\noverride fun invoke(frame: Frame) =\nSystemInfo(\nserialNumber = frame.stringByTag(InfoTag.SERIAL_NUMBER),\nsoftwareVersion = frame.stringByTag(InfoTag.SW_RELEASE),\nproductionDate = ProductionDate(frame.stringByTag(InfoTag.PRODUCTION_DATE)),\n)\n}\nfun main() {\nval connectionBuilder = ConnectionBuilder()\n.withAddress(host)\n.withPortalUser(portalUser)\n.withPortalPassword(portalPassword)\n.withRSCPPassword(rscpPassword)\nval builder = InfoServiceBuilder()\n.withConnectionBuilder(connectionBuilder)\n.withSystemInfoFrameConverter(CustomConverter())\n}\n
import de.jnkconsulting.e3dc.easyrscp.api.frame.Frame;\nimport de.jnkconsulting.e3dc.easyrscp.api.frame.tags.InfoTag;\nimport de.jnkconsulting.e3dc.easyrscp.api.service.InfoService;\nimport de.jnkconsulting.e3dc.easyrscp.api.service.model.ProductionDate;\nimport de.jnkconsulting.e3dc.easyrscp.api.service.model.SystemInfo;\nimport de.jnkconsulting.e3dc.easyrscp.service.builder.ConnectionBuilder;\nimport de.jnkconsulting.e3dc.easyrscp.service.builder.InfoServiceBuilder;\nimport de.jnkconsulting.e3dc.easyrscp.service.converter.FrameConverter;\npublic class CustomConverter implements FrameConverter<SystemInfo> {\n@Override\npublic SystemInfo invoke(Frame frame) {\nString serialNumber = frame.stringByTag(InfoTag.SERIAL_NUMBER);\nString softwareVersion = frame.stringByTag(InfoTag.SW_RELEASE);\nProductionDate productionDate = new ProductionDate(frame.stringByTag(InfoTag.PRODUCTION_DATE));\nreturn new SystemInfo(serialNumber, softwareVersion, productionDate);\n}\nprivate static String host = ...;\nprivate static String portalUser = ...;\nprivate static String portalPassword = ...;\nprivate static String rscpPassword = ...;\npublic static void main(String[] args) {\nConnectionBuilder connectionBuilder = new ConnectionBuilder()\n.withAddress(host)\n.withPortalUser(portalUser)\n.withPortalPassword(portalPassword)\n.withRSCPPassword(rscpPassword);\nInfoServiceBuilder infoServiceBuilder = new InfoServiceBuilder()\n.withConnectionBuilder(connectionBuilder)\n.withSystemInfoFrameConverter(new CustomConverter()); }\n}
import {FrameConverter, Frame} from 'easy-rscp';\nclass CustomConverter implements FrameConverter<SystemInfo> {\nconvert(frame: Frame): SystemInfo {\nconst serialNumber = frame.stringByTag(InfoTag.SERIAL_NUMBER);\nconst softwareVersion = frame.stringByTag(InfoTag.SW_RELEASE);\nconst productionDate = ... // parse production date\nreturn {\nserialNumber: serialNumber,\nsoftwareVersion: softwareVersion,\nproductionDate: productionDate,\n}\n}\n} const service = new DefaultInfoService(\nconnection,\nnew RequestSystemInfosCreator(),\nnew CustomConverter()\n)\n
"},{"location":"service/extension-points/#framecreator","title":"FrameCreator","text":"Each service offers the possibility to set its own FrameCreator
instances. The builders offer corresponding functions here.
A FrameCreator
creates request frames that are sent to the home power plant. The service API already provides corresponding creators. Normally you don't need to do anything here. But if you want to influence the creation process, you can create your own creator and set it as constructor parameter or via one of the builders.
Here is an example of how to set your own creator on the InfoService
:
import de.jnkconsulting.e3dc.easyrscp.api.frame.tags.InfoTag\nimport de.jnkconsulting.e3dc.easyrscp.frame.DataBuilder\nimport de.jnkconsulting.e3dc.easyrscp.frame.FrameBuilder\nimport de.jnkconsulting.e3dc.easyrscp.service.builder.ConnectionBuilder\nimport de.jnkconsulting.e3dc.easyrscp.service.builder.InfoServiceBuilder\nimport de.jnkconsulting.e3dc.easyrscp.service.creator.FrameCreator\nclass CustomCreator: FrameCreator<Nothing?> {\noverride fun invoke(param: Nothing?) =\nFrameBuilder()\n.addData(\nDataBuilder().tag(InfoTag.REQ_MAC_ADDRESS).build(),\nDataBuilder().tag(InfoTag.REQ_PRODUCTION_DATE).build(),\nDataBuilder().tag(InfoTag.REQ_SERIAL_NUMBER).build(),\nDataBuilder().tag(InfoTag.REQ_SW_RELEASE).build()\n)\n.build()\n}\nfun main() {\nval connectionBuilder = ConnectionBuilder()\n.withAddress(host)\n.withPortalUser(portalUser)\n.withPortalPassword(portalPassword)\n.withRSCPPassword(rscpPassword)\nval builder = InfoServiceBuilder()\n.withConnectionBuilder(connectionBuilder)\n.withRequestReadSystemInfoCreator(CustomCreator())\n}\n
import de.jnkconsulting.e3dc.easyrscp.api.frame.Frame;\nimport de.jnkconsulting.e3dc.easyrscp.api.frame.tags.InfoTag;\nimport de.jnkconsulting.e3dc.easyrscp.api.service.InfoService;\nimport de.jnkconsulting.e3dc.easyrscp.api.service.model.SystemInfo;\nimport de.jnkconsulting.e3dc.easyrscp.frame.DataBuilder;\nimport de.jnkconsulting.e3dc.easyrscp.frame.FrameBuilder;\nimport de.jnkconsulting.e3dc.easyrscp.service.builder.ConnectionBuilder;\nimport de.jnkconsulting.e3dc.easyrscp.service.builder.InfoServiceBuilder;\nimport de.jnkconsulting.e3dc.easyrscp.service.creator.FrameCreator;\npublic class CustomCreator implements FrameCreator<Void> {\n@Override\npublic Frame invoke(Void unused) {\nreturn new FrameBuilder()\n.addData(\nnew DataBuilder().tag(InfoTag.REQ_MAC_ADDRESS).build(),\nnew DataBuilder().tag(InfoTag.REQ_PRODUCTION_DATE).build(),\nnew DataBuilder().tag(InfoTag.REQ_SERIAL_NUMBER).build(),\nnew DataBuilder().tag(InfoTag.REQ_SW_RELEASE).build()\n)\n.build();\n}\nprivate static String host = ...;\nprivate static String portalUser = ...;\nprivate static String portalPassword = ...;\nprivate static String rscpPassword = ...;\npublic static void main(String[] args) {\nConnectionBuilder connectionBuilder = new ConnectionBuilder()\n.withAddress(host)\n.withPortalUser(portalUser)\n.withPortalPassword(portalPassword)\n.withRSCPPassword(rscpPassword);\nInfoServiceBuilder infoServiceBuilder = new InfoServiceBuilder()\n.withConnectionBuilder(connectionBuilder)\n.withRequestReadSystemInfoCreator(new CustomCreator());\n}\n}
import {FrameCreator, Frame, FrameBuilder, DataBuilder} from 'easy-rscp';\nclass CustomCreator implements FrameCreator<undefined> {\ncreate(input: undefined): Frame {\nreturn new FrameBuilder()\n.addData(\nnew DataBuilder().tag(InfoTag.REQ_MAC_ADDRESS).build(),\nnew DataBuilder().tag(InfoTag.REQ_PRODUCTION_DATE).build(),\nnew DataBuilder().tag(InfoTag.REQ_SERIAL_NUMBER).build(),\nnew DataBuilder().tag(InfoTag.REQ_SW_RELEASE).build()\n)\n.build();\n}\n} const service = new DefaultInfoService(\nconnection,\nnew CustomCreator()\n)\n
"},{"location":"service/general-concepts/","title":"General concept","text":"The service API is based on the lowlevel API. Its goal is to hide all details of the RSCP interface behind service facades and to make working with the home power plant more logical.
You want more?It should be noted that the RSCP interface is incredibly complex and only a very small part is currently used here. The expansion of the service API is planned and will be developed further. If you want to contribute here, have developed your own useful services, etc.: just create a PR or create a ticket with the title 'Maintainer'!
Until then, the low-level API can be used. Here all RSCP possibilities are open to you.
"},{"location":"service/general-concepts/#service-creation","title":"Service creation","text":"Generally, each service can be created simply with the constructor. Each service has exactly one mandatory parameter: The ConnectionPool. All other parameters are optional and have default values. Especially for the Java developers among you, the default parameters will not work because they are a Kotlin construct. Therefore, the use of the Builder API is recommended.
For each service there is an own builder, which can create the service and if necessary allows to set the optional extension points. Each builder has a mandatory parameter. Namely an instance of the type ConnectionBuilder
is needed. This creates the instances for encryption, connection setup etc. Don't worry, with the ConnectionBuilder it is sufficient to specify only the connection data. Everything else is optional. Here is an example:
The TypeScript versiond does not have the ServiceBuilder concept. You can create connections directly using the ConnectionFactory
KotlinJavaTypeScriptval connectionBuilder = ConnectionBuilder()\n.withAddress(host)\n.withPortalUser(portalUser)\n.withPortalPassword(portalPassword)\n.withRSCPPassword(rscpPassword)\n
ConnectionBuilder connectionBuilder = new ConnectionBuilder()\n.withAddress(host)\n.withPortalUser(portalUser)\n.withPortalPassword(portalPassword)\n.withRSCPPassword(rscpPassword);\n
const connectionData: E3dcConnectionData = {\naddress: host,\nport: 5033,\nportalUser: portalUser,\nportalPassword: portalPassword,\nrscpPassword: rscpPassword\n}\nconst factory = new DefaultHomePowerPlantConnectionFactory(connectionData)\nconst connection = await factory.openConnection()\n
Parameter Description address IP address or DNS name of the home power plant portalUser Username. Corresponds to the username from the E3DC portal portalPassword Password. Corresponds to the password on the E3DC portal rscpPassword Encryption password. This value is configured directly at the home power plant and must be identical here With this instance you can configure all other ServiceBuilders. This is the only mandatory information that is required.
KotlinJavaTypeScriptval serviceBuilder = InfoServiceBuilder()\n.withConnectionBuilder(connectionBuilder)\nval service = serviceBuilder.buildService()\nval systemInfos = service.readSystemInfo()\nprintln(systemInfos)\n
InfoServiceBuilder infoServiceBuilder = new InfoServiceBuilder()\n.withConnectionBuilder(connectionBuilder);\nInfoService service = infoServiceBuilder.buildService();\nSystemInfo infos = service.readSystemInfo();\nSystem.out.println(infos);\n
const service = new DefaultInfoService(connection)\nservice.readSystemInfo()\n.then(systemInfos => console.log(systemInfos)\n
"},{"location":"service/general-concepts/#extension-points","title":"Extension points","text":"The service API provides several points where you can intervene in the way it works. The details are described in the section Extension points.
"},{"location":"service/general-concepts/#logging-kotlinjava-only","title":"Logging (Kotlin/Java only)","text":"easy-rscp uses internally SLF4J as logging framework. You may have already noticed that you get the following warning when running the examples:
SLF4J: No SLF4J providers were found.\nSLF4J: Defaulting to no-operation (NOP) logger implementation\nSLF4J: See https://www.slf4j.org/codes.html#noProviders for further details.\n
This is because easy-rscp does not provide a logging provider. Depending on which logging framework you use (Log4j, java logging, or whatever), you have to declare the corresponding provider implementation as a dependency. SLF4J provides instructions on what to do at the link https://www.slf4j.org/codes.html#noProviders.
Why another dependency?Normally you will not need the log messages from easy-rscp. But it can be helpful for debugging. easy-rscp logs only on debug, trace or error level to avoid too much noise. We don't want to impose a logging framework on you, so we use SLF4J to abstract the framework. You have the choice!
"}]} \ No newline at end of file +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"easy-rscp","text":"easy-rscp is an INOFFICIAL library written in Kotlin (fully Java compatible) for accessing E3DC home power plants.
E3DC is a brand of HagerEnergy Gmbh (website). I have nothing to do with the company, except that I own a home power plant from E3DC and wanted to include it in my SmartHome. This \"desire\" gave birth to easy-rscp.
"},{"location":"#what-is-easy-rscp","title":"What is easy-rscp?","text":"E3DC's home power plants offer a TCP/IP interface for read and write access. The protocol used for the content is proprietary and is called RSCP. The RSCP structure is well documented by E3DC, but in terms of content there is almost no documentation at all. In addition, the documentation is incomplete, partly incorrect and not publicly available. The E3DC service portal, to which every customer has access, contains the RSCP documentation. easy-rscp and this documentation have the goal to make RSCP more easily accessible for the open source world.
"},{"location":"#what-do-i-get","title":"What do I get?","text":""},{"location":"#as-a-kotlin-developer","title":"As a Kotlin developer","text":"A library written entirely in Kotlin to access home power plants from E3DC. Easy to use, well documented -> Have fun.
"},{"location":"#as-a-java-developer","title":"As a Java developer","text":"Basically the same as a Kotlin developer. The bytecode is Java compatible and in the examples you can choose between Kotlin and Java.
Small disadvantage for you (besides the Java syntax, but you should be used to that), as a dependency smaller Kotlin libraries will be necessary.
"},{"location":"#as-ja-typescript-or-javascript-developer","title":"As ja TypeScript or JavaScript developer","text":"You will find what you are looking for in the sister project easy-rscp-js. The core functionality is identical. The documentation of both projects is summarized here.
"},{"location":"#as-xyz-developer","title":"As XYZ Developer","text":"Ok, you can't use the library directly. But in the course of the project this documentation was created. Especially the chapter RSCP should be interesting for you. Here you find a description of the RSCP protocol and all tags and meanings known to me. Maybe this will help you.
"},{"location":"#where-do-i-start","title":"Where do I start?","text":"Best in the Getting started section.
"},{"location":"#can-i-participate","title":"Can I participate","text":"Absolutely!
"},{"location":"#i-have-found-a-bug","title":"I have found a bug","text":"Too bad, but that can happen. Create a new Bug and we'll take a look.
You already know the solution? Very cool! Post a PR and we'll bring it to the main branch.
"},{"location":"#i-have-cool-extensions-improvements-fixes","title":"I have cool extensions, improvements fixes","text":"The best thing to do is to create a PR.
You can also participate in the project as a maintainer. Create a ticket here and we will see how you can support best.
"},{"location":"#i-have-further-knowledge-about-the-rscp-protocol","title":"I have further knowledge about the RSCP protocol","text":"Every drop helps to decode this protocol completely. You can create a PR for the doc, or create a ticket.
If you want to describe a specific RSCP tag, it is best to go to the API description and the corresponding tag class. At each entry you will find a link to create a ticket for exactly this tag.
"},{"location":"#special-thanks","title":"Special thanks","text":"The RSCP protocol is virtually undocumented and there is no help available from E3DC. This library would not have been possible without numerous other projects. I would like to highlight two projects and two people in particular.
First of all, a big \"thank you\" to Stefan. The developer of the incredible iOS my4E. Although he himself had no benefit from answering my questions about the RSCP protocol, he did it anyway and was a great help! Not a matter of course.
A lot of inspiration and also help in understanding the RSCP protocol came from Uli. The developer of the E3DC plugin for ioBroker. Many thanks for the support.
"},{"location":"#license","title":"License","text":"The library is licensed under the MIT License. License
"},{"location":"about/license/","title":"License","text":""},{"location":"about/license/#_1","title":"License","text":"MIT License\n\nCopyright (c) 2023 Jochen Zink\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n
"},{"location":"about/license/#included-projects","title":"Included projects","text":""},{"location":"about/license/#this-documentation","title":"This documentation","text":"This documentation was created using the great MkDocs tool and the wonderful Material for MkDocs theme:
Without all the work of many, many OpenSource developers, this library would not have been possible. Many thanks to the authors of these fantastic projects:
Lists of 30 third-party dependencies.\n (The Apache Software License, Version 2.0) kotlin-logging (io.github.microutils:kotlin-logging-jvm:3.0.5 - https://github.com/oshai/kotlin-logging)\n (Apache License, Version 2.0) MockK Agent API (io.mockk:mockk-agent-api-jvm:1.13.9 - https://mockk.io)\n (Apache License, Version 2.0) MockK (io.mockk:mockk-agent-jvm:1.13.9 - https://mockk.io)\n (Apache License, Version 2.0) MockK Core (io.mockk:mockk-core-jvm:1.13.9 - https://mockk.io)\n (Apache License, Version 2.0) MockK DSL (io.mockk:mockk-dsl-jvm:1.13.9 - https://mockk.io)\n (Apache License, Version 2.0) MockK (io.mockk:mockk-jvm:1.13.9 - https://mockk.io)\n (Eclipse Public License 1.0) JUnit (junit:junit:4.13.2 - http://junit.org)\n (Apache License, Version 2.0) Byte Buddy (without dependencies) (net.bytebuddy:byte-buddy:1.14.6 - https://bytebuddy.net/byte-buddy)\n (Apache License, Version 2.0) Byte Buddy agent (net.bytebuddy:byte-buddy-agent:1.14.6 - https://bytebuddy.net/byte-buddy-agent)\n (The Apache License, Version 2.0) org.apiguardian:apiguardian-api (org.apiguardian:apiguardian-api:1.1.2 - https://github.com/apiguardian-team/apiguardian)\n (Bouncy Castle Licence) Bouncy Castle Provider (org.bouncycastle:bcprov-jdk18on:1.77 - https://www.bouncycastle.org/java.html)\n (New BSD License) Hamcrest Core (org.hamcrest:hamcrest-core:1.3 - https://github.com/hamcrest/JavaHamcrest/hamcrest-core)\n (The Apache Software License, Version 2.0) IntelliJ IDEA Annotations (org.jetbrains:annotations:13.0 - http://www.jetbrains.org)\n (The Apache License, Version 2.0) Kotlin Reflect (org.jetbrains.kotlin:kotlin-reflect:1.9.10 - https://kotlinlang.org/)\n (The Apache License, Version 2.0) Kotlin Stdlib (org.jetbrains.kotlin:kotlin-stdlib:1.9.10 - https://kotlinlang.org/)\n (The Apache License, Version 2.0) Kotlin Stdlib Common (org.jetbrains.kotlin:kotlin-stdlib-common:1.9.10 - https://kotlinlang.org/)\n (The Apache License, Version 2.0) Kotlin Stdlib Jdk7 (org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.10 - https://kotlinlang.org/)\n (The Apache License, Version 2.0) Kotlin Stdlib Jdk8 (org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.10 - https://kotlinlang.org/)\n (The Apache Software License, Version 2.0) kotlinx-coroutines-core (org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4 - https://github.com/Kotlin/kotlinx.coroutines)\n (The Apache Software License, Version 2.0) kotlinx-coroutines-core (org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.6.4 - https://github.com/Kotlin/kotlinx.coroutines)\n (Eclipse Public License v2.0) JUnit Jupiter (Aggregator) (org.junit.jupiter:junit-jupiter:5.10.1 - https://junit.org/junit5/)\n (Eclipse Public License v2.0) JUnit Jupiter API (org.junit.jupiter:junit-jupiter-api:5.10.1 - https://junit.org/junit5/)\n (Eclipse Public License v2.0) JUnit Jupiter Engine (org.junit.jupiter:junit-jupiter-engine:5.10.1 - https://junit.org/junit5/)\n (Eclipse Public License v2.0) JUnit Jupiter Params (org.junit.jupiter:junit-jupiter-params:5.10.1 - https://junit.org/junit5/)\n (Eclipse Public License v2.0) JUnit Platform Commons (org.junit.platform:junit-platform-commons:1.10.1 - https://junit.org/junit5/)\n (Eclipse Public License v2.0) JUnit Platform Engine API (org.junit.platform:junit-platform-engine:1.10.1 - https://junit.org/junit5/)\n (Apache License, Version 2.0) Objenesis (org.objenesis:objenesis:3.3 - http://objenesis.org/objenesis)\n (The Apache License, Version 2.0) org.opentest4j:opentest4j (org.opentest4j:opentest4j:1.3.0 - https://github.com/ota4j-team/opentest4j)\n (MIT License) SLF4J API Module (org.slf4j:slf4j-api:2.0.11 - http://www.slf4j.org)\n (MIT License) SLF4J Simple Provider (org.slf4j:slf4j-simple:2.0.11 - http://www.slf4j.org)\n
"},{"location":"about/release-notes/","title":"Release Notes","text":""},{"location":"about/release-notes/#version-230-2024-02-18","title":"Version 2.3.0 (2024-02-18)","text":"NONE
, the function none()
must no longer be called explicitly. This behavior is now default.In this example we will establish a connection to the home power plant and read out some system information.
"},{"location":"getting-started/hello-world/#provide-connection-data","title":"Provide connection data","text":"For connection building it is easiest to use the ConnectionBuilder()
class. For TypeScript you can use the factory classes. Here is an example with the minimum necessary configuration:
val connectionBuilder = ConnectionBuilder()\n.withAddress(host)\n.withPortalUser(portalUser)\n.withPortalPassword(portalPassword)\n.withRSCPPassword(rscpPassword)\n
ConnectionBuilder connectionBuilder = new ConnectionBuilder()\n.withAddress(host)\n.withPortalUser(portalUser)\n.withPortalPassword(portalPassword)\n.withRSCPPassword(rscpPassword);\n
const connectionData: E3dcConnectionData = {\naddress: host,\nport: 5033,\nportalUser: portalUser,\nportalPassword: portalPassword,\nrscpPassword: rscpPassword\n}\nconst factory = new DefaultHomePowerPlantConnectionFactory(connectionData)\nconst connection = await factory.openConnection()\n
Parameter Description address IP address or DNS name of the home power plant portalUser Username. Corresponds to the username from the E3DC portal portalPassword Password. Corresponds to the password on the E3DC portal rscpPassword Encryption password. This value is configured directly at the home power plant and must be identical here"},{"location":"getting-started/hello-world/#create-service-builder","title":"Create Service Builder","text":"The access itself is done via one of the services. Here in the example we use the InfoService
. The easiest way to create it is to use the InfoServiceBuilder()
class. In TypeScript you can easily create a service if you have created an RSCP connection:
val serviceBuilder = InfoServiceBuilder()\n.withConnectionBuilder(connectionBuilder)\n
InfoServiceBuilder infoServiceBuilder = new InfoServiceBuilder()\n.withConnectionBuilder(connectionBuilder);\n
const service = new DefaultInfoService(connection)\n
"},{"location":"getting-started/hello-world/#create-and-use-service","title":"Create and use service","text":"And now ... have fun:
KotlinJavaTypeScriptval service = serviceBuilder.buildService()\nval systemInfos = service.readSystemInfo()\nprintln(systemInfos)\n
InfoService service = infoServiceBuilder.buildService();\nSystemInfo infos = service.readSystemInfo();\nSystem.out.println(infos);\n
const service = new DefaultInfoService(connection)\nservice.readSystemInfo()\n.then(systemInfos => console.log(systemInfos)\n
"},{"location":"getting-started/hello-world/#all-together","title":"All together","text":"KotlinJavaTypeScript import de.jnkconsulting.e3dc.easyrscp.connection.ConnectionBuilder\nimport de.jnkconsulting.e3dc.easyrscp.service.builder.InfoServiceBuilder\nval host = \"192.168.1.2\" // IP address of your home power plant\nval portalUser = \"my_e3dc_username\" // The username you use to log in to the E3DC portal\nval portalPassword = \"my_e3dc_password\" // The password to your E3DC portal access\nval rscpPassword = \"my_rscp_password\" // The password you set on your home power plant \nfun main() {\nval connectionBuilder = ConnectionBuilder()\n.withAddress(host)\n.withPortalUser(portalUser)\n.withPortalPassword(portalPassword)\n.withRSCPPassword(rscpPassword)\nval serviceBuilder = InfoServiceBuilder()\n.withConnectionBuilder(connectionBuilder)\nval service = serviceBuilder.buildService()\nval systemInfos = service.readSystemInfo()\nprintln(systemInfos)\n}\n
import de.jnkconsulting.e3dc.easyrscp.api.service.InfoService;\nimport de.jnkconsulting.e3dc.easyrscp.api.service.model.SystemInfo;\nimport de.jnkconsulting.e3dc.easyrscp.connection.ConnectionBuilder;\nimport de.jnkconsulting.e3dc.easyrscp.service.builder.InfoServiceBuilder;\npublic class ReadSystemInfos {\nprivate static String host = \"192.168.1.2\"; // IP address of your home power plant\nprivate static String portalUser = \"my_e3dc_username\"; // The username you use to log in to the E3DC portal\nprivate static String portalPassword = \"my_e3dc_password\"; // The password to your E3DC portal access\nprivate static String rscpPassword = \"my_rscp_password\"; // The password you set on your home power plant\npublic static void main(String[] args) {\nConnectionBuilder connectionBuilder = new ConnectionBuilder()\n.withAddress(host)\n.withPortalUser(portalUser)\n.withPortalPassword(portalPassword)\n.withRSCPPassword(rscpPassword);\nInfoServiceBuilder infoServiceBuilder = new InfoServiceBuilder()\n.withConnectionBuilder(connectionBuilder);\nInfoService service = infoServiceBuilder.buildService();\nSystemInfo infos = service.readSystemInfo();\nSystem.out.println(infos);\n}\n}\n
import {E3dcConnectionData, DefaultHomePowerPlantConnectionFactory, DefaultInfoService} from 'easy-rscp';\nconst connectionData: E3dcConnectionData = {\naddress: host,\nport: 5033,\nportalUser: portalUser,\nportalPassword: portalPassword,\nrscpPassword: rscpPassword\n}\nconst factory = new DefaultHomePowerPlantConnectionFactory(connectionData)\nconst connection = await factory.openConnection()\nconst service = new DefaultInfoService(connection)\nservice.readSystemInfo()\n.then(systemInfos => console.log(systemInfos)\n
If everything worked, you should see something like the following result (The serialNumber is hidden here):
SystemInfo(serialNumber=***, softwareVersion=H20_2023_024, productionDate=ProductionDate(rscpValue='KW27 2022', calendarWeek=27, year=2022, localDate=2022-07-04))\n
That's it. Your first RSCP request to the home power plant is done!
"},{"location":"getting-started/hello-world/#whats-next","title":"What's next?","text":"Either you read the documentation of the Service API or, if you want to code more, you look at the integration tests in the service module. These are normal unit tests that are only executed when certain environment variables are set. There is one test for each service function. You can find the tests in the service module under src/test/kotlin/de/jnkconsulting/e3dc/easyrscp/service
. The variables are visible in the code and correspond to the connection data to the home power plant.
By default, the RSCP interface is disabled on the home power plant. Therefore you have to go to the home power plant once and activate the RSCP interface. Details can be found in the manual of your home power plant.
As an example I show the setup on my S10X (On other models it should be similar). The pictures are in german (sorry for that), but it should be easy to adapt.
"},{"location":"getting-started/setup/#setup-on-the-s10x-home-power-plant","title":"Setup on the S10X home power plant","text":"Switch from the main page to the main menu Select \"Personalize\" in the main menu Select \"Profile\" here Go to the next page Set password and confirm -> The small lamp must now light up green"},{"location":"getting-started/setup/#using-the-library","title":"Using the library","text":"Warning of damage to your home power plantThis library is provided absolutely without any warranty. You can use it for write access to E3DC home power plants. The library does not validate in any way whether the requests make sense. The usage is completely in the responsibility of the user!
The library is divided into three sections
artifact-id Description jnk-easy-rscp-api Mainly contains interfaces and descriptions of the library. The other modules implement the interfaces. jnk-easy-rscp-lowlevel RSCP interface at the bottom. Here you can compile an RSCP request down to the byte, send it and interpret the response. jnk-easy-rscp-service Higher level that hides RSCP subtleties behind services. Most users should use this module. The scope will be extended in future version. Maven centralThe artifacts are available in the maven central repository and for easy-rscp-js on npm
MavenGradle (groovy)Gradle (kotlin)npm<dependency>\n<groupId>de.jnk-consulting.e3dc.easyrscp</groupId>\n<artifactId>jnk-easy-rscp-service</artifactId>\n<version>2.0.0</version>\n</dependency>\n
implementation 'de.jnk-consulting.e3dc.easyrscp:jnk-easy-rscp-service:2.0.0'\n
implementation(\"de.jnk-consulting.e3dc.easyrscp:jnk-easy-rscp-service:2.0.0\")\n
npm i easy-rscp\n
"},{"location":"lowlevel/connection/","title":"Connection setup","text":"The connection
package contains all classes that are needed to establish a connection to the home power plant. The class DefaultHomePowerPlantConnection
serves as connection point. Here frames are sent to the home power plant and the answers are parsed.
To create a connection the class DefaultHomePowerPlantConectionFactory
is available.
As ConnectionPool
the implementation SingleInstanceConnectionPool
is available, which keeps Thread Save open a connection to the home power plant.
To simplify the creation and dependencies, there is the ConnectionBuilder
which can create the instances with little configuration effort.
In the TypeScript version there is no ConnectionPool and no ConnectionBuilder. The connection ensures that all requests are sent serialized
"},{"location":"lowlevel/connection/#connectionbuilder-usage","title":"ConnectionBuilder usage","text":"In the following example we create a ConnectionBuilder instance, get a ConnectionPool and request the current live production data. To be able to read the answer we use the StringFrameConverter
to output the answer frame:
import de.jnkconsulting.e3dc.easyrscp.api.frame.tags.EMSTag\nimport de.jnkconsulting.e3dc.easyrscp.frame.DataBuilder\nimport de.jnkconsulting.e3dc.easyrscp.frame.FrameBuilder\nimport de.jnkconsulting.e3dc.easyrscp.connection.ConnectionBuilder\nimport de.jnkconsulting.e3dc.easyrscp.service.converter.StringFrameConverter\nval host = \"192.168.1.2\" // IP address of your home power plant\nval portalUser = \"my_e3dc_username\" // The username you use to log in to the E3DC portal\nval portalPassword = \"my_e3dc_password\" // The password to your E3DC portal access\nval rscpPassword = \"my_rscp_password\" // The password you set on your home power plant\nfun main() {\nval connectionBuilder = ConnectionBuilder()\n.withAddress(host)\n.withPortalUser(portalUser)\n.withPortalPassword(portalPassword)\n.withRSCPPassword(rscpPassword)\nval connectionPool = connectionBuilder.buildConnectionPool()\nval answer = connectionPool.executeAndRelease {\nit.send(FrameBuilder()\n.addData(\nDataBuilder().tag(EMSTag.REQ_POWER_PV).build(),\nDataBuilder().tag(EMSTag.REQ_POWER_BAT).build(),\nDataBuilder().tag(EMSTag.REQ_POWER_GRID).build(),\nDataBuilder().tag(EMSTag.REQ_POWER_HOME).build(),\nDataBuilder().tag(EMSTag.REQ_BAT_SOC).build(),\n).build()\n)\n}\nprintln(StringFrameConverter().invoke(answer))\n}\n
import de.jnkconsulting.e3dc.easyrscp.api.connection.ConnectionPool;\nimport de.jnkconsulting.e3dc.easyrscp.api.frame.Frame;\nimport de.jnkconsulting.e3dc.easyrscp.api.frame.tags.EMSTag;\nimport de.jnkconsulting.e3dc.easyrscp.connection.ConnectionBuilder;\nimport de.jnkconsulting.e3dc.easyrscp.frame.DataBuilder;\nimport de.jnkconsulting.e3dc.easyrscp.frame.FrameBuilder;\nimport de.jnkconsulting.e3dc.easyrscp.service.converter.StringFrameConverter;\npublic class ConnectionBuilderExample {\nprivate static String host = \"192.168.1.2\"; // IP address of your home power plant\nprivate static String portalUser = \"my_e3dc_username\"; // The username you use to log in to the E3DC portal\nprivate static String portalPassword = \"my_e3dc_password\"; // The password to your E3DC portal access\nprivate static String rscpPassword = \"my_rscp_password\"; // The password you set on your home power plant\npublic static void main(String[] args) {\nConnectionBuilder connectionBuilder = new ConnectionBuilder()\n.withAddress(host)\n.withPortalUser(portalUser)\n.withPortalPassword(portalPassword)\n.withRSCPPassword(rscpPassword);\nConnectionPool connectionPool = connectionBuilder.buildConnectionPool();\nFrame answer = connectionPool.executeAndRelease(connection -> {\nFrame requestFrame = new FrameBuilder()\n.addData(\nnew DataBuilder().tag(EMSTag.REQ_POWER_PV).build(),\nnew DataBuilder().tag(EMSTag.REQ_POWER_BAT).build(),\nnew DataBuilder().tag(EMSTag.REQ_POWER_GRID).build(),\nnew DataBuilder().tag(EMSTag.REQ_POWER_HOME).build(),\nnew DataBuilder().tag(EMSTag.REQ_BAT_SOC).build()\n).build();\nreturn connection.send(requestFrame);\n});\nSystem.out.println(new StringFrameConverter().invoke(answer));\n}\n}\n
import {\nE3dcConnectionData, DefaultHomePowerPlantConnectionFactory, FrameBuilder,\nDataBuilder,\nStringFrameConverter} from 'easy-rscp';\nconst connectionData: E3dcConnectionData = {\naddress: host,\nport: 5033,\nportalUser: portalUser,\nportalPassword: portalPassword,\nrscpPassword: rscpPassword\n}\nconst factory = new DefaultHomePowerPlantConnectionFactory(connectionData)\nconst connection = await factory.openConnection()\nconst request = new FrameBuilder()\n.addData(\nnew DataBuilder().tag(EMSTag.REQ_POWER_PV).build(),\nnew DataBuilder().tag(EMSTag.REQ_POWER_BAT).build(),\nnew DataBuilder().tag(EMSTag.REQ_POWER_GRID).build(),\nnew DataBuilder().tag(EMSTag.REQ_POWER_HOME).build(),\nnew DataBuilder().tag(EMSTag.REQ_BAT_SOC).build()\n).build()\nconnection.send(request)\n.then(response => {\nconsole.log(new StringFrameConverter().convert(response))\n})\n
The output should look something like this:
Timestamp: 2023-09-19T19:08:44.664348Z\nWithChecksum: true\n Tag: EMS.POWER_PV - 01800001\n Type: INT32 - 06\n Value: 0 - 00000000\n Tag: EMS.POWER_BAT - 01800002\n Type: INT32 - 06\n Value: -639 - 81fdffff\n Tag: EMS.POWER_GRID - 01800004\n Type: INT32 - 06\n Value: 23 - 17000000\n Tag: EMS.POWER_HOME - 01800003\n Type: INT32 - 06\n Value: 662 - 96020000\n Tag: EMS.BAT_SOC - 01800008\n Type: UCHAR8 - 03\n Value: 86 - 56\n
"},{"location":"lowlevel/crypt/","title":"Encryption","text":"The crypt
package contains the AES implementations. Each request and response frame must be AES encrypted. Here E3DC has made some specifications which are mapped in the class BouncyCastleAESCipher
.
E3DC requires some peculiarities that make the AES algrithm used here insecure.
None of this can be changed, the home power plant requires exactly this algorithm. Therefore use this class only here and otherwise please not!
"},{"location":"lowlevel/crypt/#usage","title":"Usage","text":"TypeScriptIf you want to intervene in the encryption, you can implement your own AesCipherFactory and make this known to the DefaultHomePowerPlantConnectionFactory in the structure.
The classes are not used directly, but created automatically by the ConnectionBuilder
. Only if you want to exchange the supplied implementation, you can set your factory at the builder:
val connectionBuilder = ConnectionBuilder()\n.withAddress(host)\n.withPortalUser(portalUser)\n.withPortalPassword(portalPassword)\n.withRSCPPassword(rscpPassword)\n.withCipherFactory(myCustomCipherFactory)\n
ConnectionBuilder connectionBuilder = new ConnectionBuilder()\n.withAddress(host)\n.withPortalUser(portalUser)\n.withPortalPassword(portalPassword)\n.withRSCPPassword(rscpPassword)\n.withCipherFactory(myCustomCipherFactory);\n
const factory = new DefaultHomePowerPlantConnectionFactory(\nconnectionData,\nmyCustomAESImplementation);\n
"},{"location":"lowlevel/frame/","title":"Frames and Data","text":"Frames are used to send requests to the home power plant. The home power plant also responds in frames. Each frame consists of a series (at least one) of data blocks. The type of a block is determined by so-called tags.
"},{"location":"lowlevel/frame/#tags","title":"Tags","text":"Tags describe the type of a data block. They tell whether the contained value is a number or a text, and whether it is, for example, the current PV prodction or a load limit.
The tags I know are located as enum classes in the package de.jnkconsulting.e3dc.easyrscp.api.frame.tags
. For each namespace defined by E3DC there is a separate enum class.
What exactly the namespaces mean is described in the RSCP section.
If a tag is needed, either as a request or as a response, which are not defined here in enum classes, an instance of UnknownTag
can be used. The original byte value remains here and can be used accordingly.
The easiest way to create frames is to use the FrameBuilder
and the DataBuilder
classes.
You must know what you are doing. Neither the FrameBuilder
nor the DataBuilder
check if the combination makes any sense!
Here is an example of how to put together a query frame that asks for some live data:
KotlinJavaTypeScriptimport de.jnkconsulting.e3dc.easyrscp.api.frame.tags.EMSTag\nimport de.jnkconsulting.e3dc.easyrscp.frame.DataBuilder\nimport de.jnkconsulting.e3dc.easyrscp.frame.FrameBuilder\nimport de.jnkconsulting.e3dc.easyrscp.service.converter.StringFrameConverter\nfun main() {\nval requestFrame = FrameBuilder()\n.addData(\nDataBuilder().tag(EMSTag.REQ_POWER_PV).build(),\nDataBuilder().tag(EMSTag.REQ_POWER_BAT).build(),\nDataBuilder().tag(EMSTag.REQ_POWER_GRID).build(),\nDataBuilder().tag(EMSTag.REQ_POWER_HOME).build(),\nDataBuilder().tag(EMSTag.REQ_BAT_SOC).build(),\n).build()\nprintln(StringFrameConverter().invoke(requestFrame))\n}\n
import de.jnkconsulting.e3dc.easyrscp.api.frame.Frame;\nimport de.jnkconsulting.e3dc.easyrscp.api.frame.tags.EMSTag;\nimport de.jnkconsulting.e3dc.easyrscp.frame.DataBuilder;\nimport de.jnkconsulting.e3dc.easyrscp.frame.FrameBuilder;\nimport de.jnkconsulting.e3dc.easyrscp.service.converter.StringFrameConverter;\npublic class FrameExamples {\npublic static void main(String[] args) {\nFrame requestFrame = new FrameBuilder()\n.addData(\nnew DataBuilder().tag(EMSTag.REQ_POWER_PV).build(),\nnew DataBuilder().tag(EMSTag.REQ_POWER_BAT).build(),\nnew DataBuilder().tag(EMSTag.REQ_POWER_GRID).build(),\nnew DataBuilder().tag(EMSTag.REQ_POWER_HOME).build(),\nnew DataBuilder().tag(EMSTag.REQ_BAT_SOC).build()\n).build();\nSystem.out.println(new StringFrameConverter().invoke(requestFrame));\n}\n}\n
import {FrameBuilder, DataBuilder, StringFrameConverter, EMSTag} from 'easy-rscp';\nconst request = new FrameBuilder()\n.addData(\nnew DataBuilder().tag(EMSTag.REQ_POWER_PV).build(),\nnew DataBuilder().tag(EMSTag.REQ_POWER_BAT).build(),\nnew DataBuilder().tag(EMSTag.REQ_POWER_GRID).build(),\nnew DataBuilder().tag(EMSTag.REQ_POWER_HOME).build(),\nnew DataBuilder().tag(EMSTag.REQ_BAT_SOC).build()\n).build();\nconsole.log(new StringFrameConverter().convert(requestFrame));\n
The output should look something like this:
Timestamp: 2023-09-19T20:32:43.305569Z\nWithChecksum: true\n Tag: EMS.REQ_POWER_PV - 01000001\n Type: NONE - 00\n Value: [NONE] - \n Tag: EMS.REQ_POWER_BAT - 01000002\n Type: NONE - 00\n Value: [NONE] - \n Tag: EMS.REQ_POWER_GRID - 01000004\n Type: NONE - 00\n Value: [NONE] - \n Tag: EMS.REQ_POWER_HOME - 01000003\n Type: NONE - 00\n Value: [NONE] - \n Tag: EMS.REQ_BAT_SOC - 01000008\n Type: NONE - 00\n Value: [NONE] - \n
"},{"location":"lowlevel/frame/#reading-frames","title":"Reading frames","text":"Reading data from a frame can be complex. Theoretically, a frame can contain data blocks of type container
. So, blocks that contain other blocks. To make it as comfortable as possible to access the individual values, the frame class provides various functions to access the contents in any depth. For this purpose different xxxByTag
functions are available. The functions search for a specific data block with the given tag. There is an additional vararg parameter, which maps the path through possibly existing containers.
Here is an example to read the house consumption of a certain day. For this, a request was made to the home power plant database. In response, a frame with a nested structure is provided. The searched value is in the following structure:
DBTag.HISTORY_DATA_DAY -> DBTag.SUM_CONTAINER -> DBTag.CONSUMPTION\n
DBTag.HISTORY_DATA_DAY
and DBTag.SUM_CONTAINER
are data blocks of type container
. DBTag.CONSUMPTION
contains a float value indicating the total house consumption of the day. This value can be accessed as follows:
KotlinJavaTypeScriptval answerFrame: Frame = ...\nval consumption = answerFrame.floatByTag(\nDBTag.CONSUMPTION, DBTag.HISTORY_DATA_DAY, DBTag.SUM_CONTAINER)\n
Frame answerFrame = ...;\nfloat consumption = amswerFrame.floatByTag(\nDBTag.CONSUMPTION, DBTag.HISTORY_DATA_DAY, DBTag.SUM_CONTAINER);\n
import {Frame, DBTag} from 'easy-rscp';\nconst answerFrame: Frame = ...\nconst consumption = answerFrame.floatByTag(\nDBTag.CONSUMPTION, DBTag.HISTORY_DATA_DAY, DBTag.SUM_CONTAINER)\n
The various xxxByTag
usually always return a value. If the corresponding tag is not found, a default value is returned (For numeric values 0, for string \"\" etc.).
The xxxByTag
methods have their limit when a container contains several data blocks of the same type. This happens quite rarely (in database queries for example). But if it does, then you have to access manually according to the content.
If you want to work with the lowlevel API, you must be familiar with the RSCP protocol, at least in its basic features. I therefore recommend to also read the RSCP section.
The API of easy-rscp consists of three areas, which are separated by the packages connection
, crypt
and frame
.
Here are all the classes that are needed to establish a connection to the home power plant.
To create and correctly connect the different objects, a ConnectionBuilder
is available. More info in the Connection setup section.
Here are the implementations of AESCipher
and AESCipherFactory
. easy-rscp uses the Java Crypto Framework BouncyCastle for the implementations.
If you want to work without BouncyCastle dependency, you can implement the two interfaces yourself and set them accordingly in the ConnectionBuilder
.
Everything that has to do with frames and data blocks, parsing etc. is located here.
"},{"location":"rscp/authentication/","title":"Authentication","text":"After a TCP/IP connection to the home power plant has been established, an authentication frame must first be sent.
This consists of a data block RSCP.REQ_AUTHENTICATION
of type container
. This contains two data blocks. Once RSCP.AUTHENTICATION_USER
and RSCP.AUTHENTICATION_PASSWORD
. Each of the type string. The access data to the E3DC portal are set as values.
In the example, the username TEST
and the password 123
were used:
Timestamp: 2023-09-20T14:22:25.542694Z\nWithChecksum: true\n Tag: RSCP.REQ_AUTHENTICATION - 00000001\n Type: CONTAINER - 0e\n Tag: RSCP.AUTHENTICATION_USER - 00000002\n Type: STRING - 0d\n Value: TEST - 54455354\n\n Tag: RSCP.AUTHENTICATION_PASSWORD - 00000003\n Type: STRING - 0d\n Value: 123 - 313233\n
e3dc001121000b650000000070da58201c00010000000e1500020000000d040054455354030000000d030031323378babe16\n
"},{"location":"rscp/authentication/#response-frame-on-successful-authentication","title":"Response frame on successful authentication","text":"Human readableAs hex code Timestamp: 2023-09-20T14:26:14.787390Z\nWithChecksum: true\n Tag: RSCP.AUTHENTICATION - 00800001\n Type: UCHAR8 - 03\n Value: 10 - 0a\n
e3dc001106010b6500000000309eee2e0800010080000301000ad4e6f5cf\n
The response frame contains the value 10
. This specifies some kind of authorization. The following values are known (Even though we usually always have to make do with the USER level):
Timestamp: 2023-09-20T14:27:42.534655Z\nWithChecksum: true\n Tag: RSCP.AUTHENTICATION - 00800001\n Type: INT32 - 06\n Value: 0 - 00000000\n
e3dc00115e010b65000000001830de1f0b00010080000604000000000003120cf6\n
"},{"location":"rscp/basic-rscp/","title":"Basic stuff","text":"E3DC's home power plants offer a TCP/IP interface for read and write access. The protocol used for the content is proprietary and is called RSCP.
The RSCP structure is well documented by E3DC, but in terms of content there is almost no documentation at all. In addition, the documentation is incomplete, partly incorrect and not publicly available. The E3DC service portal, to which every customer has access, contains the RSCP documentation.
This section aims to explain the subtleties of the protocol in as much detail as possible. For example, if you want to create your own framework in another language, you can use the documentation as a basis.
Help is welcomeMuch of the knowledge about RSCP that has been written down here has been determined by trial and error. There are still large gaps in our knowledge and certainly there will be errors. If you can contribute something to the documentation, you are welcome. Just report in a ticket.
"},{"location":"rscp/basic-rscp/#base-information","title":"Base information","text":"The home power plant listens on a specific port (usually 5033) and waits for RSCP requests. An RSCP request is a byte block called a frame. A frame contains different metadata and 1-n data blocks. On this page I only want to give a short overview, the whole frame structure is described byte by byte in the section Frames in Detail.
"},{"location":"rscp/basic-rscp/#communication-principle","title":"Communication principle","text":"RSCP works according to the send/response principle. This means that you send a request frame to the home power plant and you get the appropriate response frame back in reply. A special authentication frame must be sent beforehand. All further requests must be handled in the same connection. Otherwise another authentication is necessary. How the authentication works is described in the section Authentication.
The home power plant does not provide transport encryption. But each frame must be AES256 encrypted. Details about the algorithm can be found in the Encryption section.
Sending and receiving a frame:
sequenceDiagram\n Client->>Client: buildAuthenticationFrame()\n Client->>Client: encryptAuthenticationFrame(authenticationFrame)\n Client->>home power plant: send(encryptedAuthenticationFrame)\n home power plant-->>Client: respond(encryptedAuthenticationAnswerFrame)\n Client->>Client: buildRequestFrame()\n Client->>Client: encryptRequestFrame(requestFrame)\n Client->>home power plant: send(encryptedRequestFrame)\n home power plant-->Client: respond(encryptedAnswerFrame)\n Client->>Client: decrypt(encryptedAnswerFrame)\n Client->>Client: useAndBeHappy(answerFrame)
"},{"location":"rscp/encryption/","title":"Encryption","text":"This section describes how to encrypt (or decrypt) a frame. It is assumed that some terms are already known. AES, IV, etc. are not described in detail because they are part of the standard AES algorithm.
"},{"location":"rscp/encryption/#the-basics","title":"The basics","text":"Each frame must be AES-256 encrypted. The response frames must be decrypted accordingly. The RSCP password configured on the home power plant is used as the key (see Setup).
Note the handling of the IV. Initially, an IV completely filled with the values 0xFF is used. Afterwards the last encrypted block is always used as IV for the next one (AES standard). This also applies across frames. So when you send the authentication frame, you have to remember the last block, because it is needed as IV for the next frame.
On the decryption side it is the same. The initial IV is filled with 0xFF. When decrypting you have to remember the last block again, because it is needed for the next response frame.
Do not use this algorithm anywhere else!The algorithm given by E3DC is not completely secure. Therefore, do not use the algorithm or the encryption implementation in any other project. Three problems make the algorithm insecure:
password derivation function
: AES is byte based. Here AES-256 -> A 32Byte long key consisting of binary data is used. Now it is quite inconvenient for humans to remember binary data, so E3DC decided to use a text as key. This can be done, but then you should use a password derivation function
(PBKDF2, Argon2, etc). Otherwise the key space is limited to the printable characters, and thus the security is reduced considerably. E3DC does not do this!In addition to pointing out that please do not use the algorithm anywhere else, it is also worth mentioning that the data transmitted in the frames cannot be considered completely safe either. Be aware of this. Among other things, your E3DC Portal username and E3DC Portal password will be transmitted!
Usually the transmission is done in your private network, but I don't want to leave it unmentioned. When you log in to the E3DC portal with your data, these are SSL encrypted and therefore not readable in your private network. When communicating with the home power plant you have to rely on the poorly implemented AES algorithm.
Good AES vs Bad AESJust to clarify: AES itself is not considered insecure as of today (end of 2023) and is a very good symmetric algorithm. However, if you choose the parameters incorrectly, the security is unfortunately gone.
"},{"location":"rscp/encryption/#aes-parameter","title":"AES Parameter","text":"To use your AES algorithm correctly, you need to consider the following parameters:
To visualize the frame structure I used Gantt charts here. They are actually meant for something else, but they serve their purpose and can be easily generated with mermaid.js. Unfortunately the diagrams don't offer a zoom function, so use the browser zoom!
The following diagrams show the structure of a frame. And this byte by byte. Please note that the bytes are transmitted in little endian order.
"},{"location":"rscp/frame-details/#frame","title":"Frame","text":"gantt\n title Structure of a frame\n dateFormat YYYY-MM-DD\n axisFormat %d\n tickInterval 1day\n section Frame\n Whole frame : frame, 2014-01-01, 30d \n section Header \n Magic bytes : mb, 2014-01-01, 2d\n CTRL : ctrl, after mb, 2d\n Timestamp : ts, after ctrl, 12d\n length : dlength, after ts, 2d\n section Data \n Data : crit, data, after dlength, 8d\n section Checksum\n CRC32 : active, crc, after data, 4d
In the diagram above the structure of a frame is shown. Please note that the length of the data block is variable and is only shown here as an example of 8 bytes. In reality a data block is significantly longer.
"},{"location":"rscp/frame-details/#magic-bytes","title":"Magic Bytes","text":"Each frame starts with a 2 byte MagicByte block. This always contains the values E3DC (Hexcode ...)
gantt\n title Magic Bytes\n dateFormat YYYY-MM-DD\n axisFormat %d\n tickInterval 1day\n section Magic Bytes\n 0xE3 : e3, 2014-01-01, 1d\n 0xDC : dc, after e3, 1d
"},{"location":"rscp/frame-details/#control-bytes","title":"Control Bytes","text":"The control bytes currently contain 2 pieces of information.
Both information are placed in the first byte. The second byte is intended for future use is currently always 0.
gantt\n title Control Bytes\n dateFormat YYYY-MM-DD\n axisFormat %d\n tickInterval 1day\n section CRC true\n 0x00 : b1, 2014-01-01, 1d\n 0x11 : b2, after b1, 1d\n section CRC false\n 0x00 : b1, 2014-01-01, 1d\n 0x01 : b2, after b1, 1d
"},{"location":"rscp/frame-details/#timestamp","title":"Timestamp","text":"The timestamp area indicates the creation time of the frame. In seconds and nanoseconds since 01/01/1970 00:00. As I understand it in UTC.
The first 8 bytes indicate the seconds value and the next 4 bytes the nanoseconds.
The following timestamp block, represents the timestamp 2023-09-20T07:57:05.362489Z.
Since the 01.01.1970 00:00 o'clock up to this Timestamp 1,695,196,625 seconds and 362,489,000 nanoseconds have passed.
gantt\n title Timestamp\n dateFormat YYYY-MM-DD\n axisFormat %d\n tickInterval 1day\n section Timestamp\n 0xd1a50a6500000000 : seconds, 2014-01-01, 8d\n 0xa8249b15 : nanos, after seconds, 4d
"},{"location":"rscp/frame-details/#length","title":"Length","text":"In the length field the length of the following data block is indicated in bytes. Since a frame can consist of several data blocks, the sum of all data block lengths is specified here.
In the example 28bytes (0x1c00 in hexcode Little Endian)
gantt\n title Length\n dateFormat YYYY-MM-DD\n axisFormat %d\n tickInterval 1day\n section Timestamp\n 0x1c00 : length, 2014-01-01, 2d
"},{"location":"rscp/frame-details/#data-block","title":"Data block","text":"One or more data blocks follow. The length is determined by the previous length field. The structure is described in detail in the data block area.
"},{"location":"rscp/frame-details/#checksum","title":"Checksum","text":"If the frame uses a checksum, it is specified here, with a length of 4 bytes. The standard algorithm CRC32 is used.
gantt\n title CRC32\n dateFormat YYYY-MM-DD\n axisFormat %d\n tickInterval 1day\n section Timestamp\n 0xe2a60c5d : crc, 2014-01-01, 4d
"},{"location":"rscp/frame-details/#datenblock","title":"Datenblock","text":"A data block represents a concrete value in a frame. Exception: A data block is of the type 'container'. Then it contains further data blocks
gantt\n title Structure of a Data Block\n dateFormat YYYY-MM-DD\n axisFormat %d\n tickInterval 1day\n section Data\n Whole Block : block, 2014-01-01, 15d \n section Header \n Tag : tag, 2014-01-01, 4d\n Type : type, after tag, 1d\n Length : length, after type, 2d \n section Value \n Value : crit, value, after length, 8d
"},{"location":"rscp/frame-details/#tag","title":"Tag","text":"The tag gives a meaning to the content. It describes which area of the home power plant it is (namespace), whether it is a request or response block, and which field from the respective namespace is meant.
For example the tag MAC_ADDRESS
from the namespace INFO
. This has the value 0a80000a
.
gantt\n title The Tag INFO.MAC_ADDRESS\n dateFormat YYYY-MM-DD\n axisFormat %d\n tickInterval 1day\n section Namespace\n 0x0a : ns, 2014-01-01, 1d \n section Tag \n 0x00800a : tag, after ns, 3d
Info Whether it is a request or response block can be seen by the first bit(!) of the tag area. 0=request, 1=response
"},{"location":"rscp/frame-details/#type","title":"Type","text":"In the next byte the data type is specified. That is, how the value area in the block is to be interpreted (number, string, etc.).
gantt\n title Datatype String\n dateFormat YYYY-MM-DD\n axisFormat %d\n tickInterval 1day\n section String\n 0x0d : type, 2014-01-01, 1d
"},{"location":"rscp/frame-details/#length_1","title":"Length","text":"This area is 2 bytes long and specifies the length in bytes of the following value area.
gantt\n title Lengthj\n dateFormat YYYY-MM-DD\n axisFormat %d\n tickInterval 1day\n section String\n 0x1100 : length, 2014-01-01, 2d
"},{"location":"rscp/frame-details/#value","title":"Value","text":"Here hides the actual value, which can be interpreted depending on length and type. Here is an example for the software release number H20_2023_024
(tag INFO.SW_RELEASE
)
gantt\n title Example String\n dateFormat YYYY-MM-DD\n axisFormat %d\n tickInterval 1day\n section String\n 0x4832305f323032335f303234 : value, 2014-01-01, 2d
Blocks as values If the data type is container
, the value area contains a complete further data block(s)!
To make things a little clearer, let's take a look at a request frame and its corresponding response frame.
We want to query the live data of the current PV production and the battery. For this purpose, we first assemble the query frame.
In human readable form:
Timestamp: 2023-09-20T08:42:32.818989Z\nWithChecksum: true\n Tag: EMS.REQ_POWER_PV - 01000001\n Type: NONE - 00\n Value: [NONE] - \n Tag: EMS.REQ_POWER_BAT - 01000002\n Type: NONE - 00\n Value: [NONE] - \n
In home power plant readable form:
e3dc001178b00a6500000000c8c7d0300e0001000001000000020000010000004c769f09\n
And once disassembled:
Bytes Value Description 1 - 2 e3dc Magic Bytes 3 - 4 0011 Controlbytes -> protocol version 1 and checksum is enabled 5 - 12 78b00a6500000000 Timestamp, seconds since 01.01.1970 00:00 UTC -> 1,695,199,352 13 - 16 c8c7d030 Timestamp, Nanoseconds -> 818,989,000 17 - 18 0e00 Length of the data block area in bytes -> 14 bytes 18 - 21 01000001 Namespace + Tag -> EMS.REQ_POWER_PV 22 00 Data type -> None 23 - 24 0000 Length of the data block -> Data type isnone
, therefore 0 25 - 28 02000001 Namespace + Tag -> EMS.REQ_POWER_BAT 29 00 Data type -> None 31-32 0000 Length of the data block -> data type is none
, therefore 0 33-36 4c769f09 CRC32 checksum As a response we get the following frame:
In human readable form:
Timestamp: 2023-09-20T08:42:35.019685Z\nWithChecksum: true\n Tag: EMS.POWER_PV - 01800001\n Type: INT32 - 06\n Value: 4687 - 4f120000\n Tag: EMS.POWER_BAT - 01800002\n Type: INT32 - 06\n Value: 2139 - 5b080000\n
In home power plant readable form:
e3dc00117bb00a6500000000885e2c011600010080010604004f120000020080010604005b08000058156e18\n
And once disassembled:
Bytes Value Description 1 - 2 e3dc Magic Bytes 3 - 4 0011 Kontrolbytes -> Protocol version 1 and checksum is activated 5 - 12 7bb00a6500000000 Timestamp, seconds since 01.01.1970 00:00 UTC -> 1,695,199,355 13 - 16 885e2c01 Timestamp, Nanoseconds -> 19,685,000 17 - 18 1600 Length of the data block area in bytes -> 22 bytes 18 - 21 01008001 Namespace + Tag -> EMS.POWER_PV 22 06 Data type -> INT32 23 - 24 0400 Length of the data block -> data type isINT32
, therefore 4 bytes 25 - 28 4f120000 Actual value -> data type is INT32
, therefore 4,687 29 - 32 02008001 Namespace + Tag -> EMS.POWER_BAT 33 06 Data type -> INT32 34 - 35 0400 Length of the data block -> data type is INT32
, therefore 4 bytes 36 - 39 5b080000 Actual value -> data type is INT32
, therefore 2,139 40 - 43 58156e18 CRC32 checksume"},{"location":"rscp/frame-details/#namespaces","title":"Namespaces","text":"E3DC has split all tags into different namespaces to create some overview. All namespaces known to me are documented in kdoc in the enum class de.jnkconsulting.e3dc.easyrscp.api.frame.Namespace
.
The multitude of tags is also documented in kdoc. Each namespace has its own enum class to ensure at least some overview.
If I know anything about the structure and meaning of the tag, it is stored in kdoc. Partly E3DC has documented a little bit. If available, I have included the original E3DC documentation in kdoc and marked it with Original E3DC Documentation:
. Once translated in English and once in German, as supplied by E3DC.
Here you can find the tags TAG API.
"},{"location":"rscp/frame-details/#data-types","title":"Data types","text":"The protocol defines a set of data types. These are documented in the enum class de.jnkconsulting.e3dc.easyrscp.api.frame.DataType
.
There are currently three ways to interfere with the operation of the service or lowlevel API.
Listener \u2192 Are called at specific points in the request/response lifecycle
FrameConverter \u2192 Read data from a response frame from the home power plant and convert the data into an object
FrameCreator \u2192 Generates request frames to the home power plant
"},{"location":"service/extension-points/#listener","title":"Listener","text":"For instances of type ConnectionBuilder
any number of RSCPRequestResponseListener
can be registered. These listeners are called at different points in the request-response cycle.
In TypeScript you can pass the listeners to the ConnectionFactory
KotlinJavaTypeScriptval connectionBuilder = ConnectionBuilder()\n.withAddress(host)\n.withPortalUser(portalUser)\n.withPortalPassword(portalPassword)\n.withRSCPPassword(rscpPassword)\n.addRequestResponseListener(listener1, listener2)\n
ConnectionBuilder connectionBuilder = new ConnectionBuilder()\n.withAddress(host)\n.withPortalUser(portalUser)\n.withPortalPassword(portalPassword)\n.withRSCPPassword(rscpPassword)\n.addRequestResponseListener(listener1, listener2);\n
const factory = new DefaultHomePowerPlantConnectionFactory(\nconnectionData,\naesFactory,\nsocketFactory,\nframeParser,\nlistenerArray\n)\n
Please note The listeners are only used if NO own instance of type HomePowerPlantConnectionFactory
or ConnectionPool
is configured at the Builder!
sequenceDiagram\n autonumber\n DefaultHomePowerPlantConnection->>Listener: onBeforeRequestFrameEncryption(event)\n DefaultHomePowerPlantConnection->>AESCipher: encrypt(frameBytes)\n DefaultHomePowerPlantConnection->>Listener: onBeforeRequestSend(event)\n DefaultHomePowerPlantConnection->>HomePowerPlant: send(encryptedFrameBytes)\n DefaultHomePowerPlantConnection->>Listener: onAfterRequestSend(event)\n HomePowerPlant->>DefaultHomePowerPlantConnection: sendAnswer(encryptedAnswerFrameBytes)\n DefaultHomePowerPlantConnection->>Listener: onAnswerReceived(event)\n DefaultHomePowerPlantConnection->>AESCipher: decrypt(encryptedAnswerFrameBytes)\n DefaultHomePowerPlantConnection->>Listener: onAnswerDecrypted(event)\n DefaultHomePowerPlantConnection->>FrameParser: parseRSCPFrame(decryptedAnswerBytes)\n DefaultHomePowerPlantConnection->>Listener: onAnswerParsed(event)
"},{"location":"service/extension-points/#frameconverter","title":"FrameConverter","text":"Each service offers the possibility to set its own FrameConverter
instances. The builders offer corresponding functions here.
A FrameConverter
reads a response frame from the home power plant and converts it into a corresponding logical object. The service API already provides corresponding converters. Normally you don't need to do anything here. But if you want to influence the conversion process, you can create your own converter and set it as constructor parameter or via one of the builders.
Here is an example how to set your own converter at the InfoService
:
import de.jnkconsulting.e3dc.easyrscp.api.frame.Frame\nimport de.jnkconsulting.e3dc.easyrscp.api.frame.tags.InfoTag\nimport de.jnkconsulting.e3dc.easyrscp.api.service.model.ProductionDate\nimport de.jnkconsulting.e3dc.easyrscp.api.service.model.SystemInfo\nimport de.jnkconsulting.e3dc.easyrscp.service.builder.ConnectionBuilder\nimport de.jnkconsulting.e3dc.easyrscp.service.builder.InfoServiceBuilder\nimport de.jnkconsulting.e3dc.easyrscp.service.converter.FrameConverter\nclass CustomConverter: FrameConverter<SystemInfo> {\noverride fun invoke(frame: Frame) =\nSystemInfo(\nserialNumber = frame.stringByTag(InfoTag.SERIAL_NUMBER),\nsoftwareVersion = frame.stringByTag(InfoTag.SW_RELEASE),\nproductionDate = ProductionDate(frame.stringByTag(InfoTag.PRODUCTION_DATE)),\n)\n}\nfun main() {\nval connectionBuilder = ConnectionBuilder()\n.withAddress(host)\n.withPortalUser(portalUser)\n.withPortalPassword(portalPassword)\n.withRSCPPassword(rscpPassword)\nval builder = InfoServiceBuilder()\n.withConnectionBuilder(connectionBuilder)\n.withSystemInfoFrameConverter(CustomConverter())\n}\n
import de.jnkconsulting.e3dc.easyrscp.api.frame.Frame;\nimport de.jnkconsulting.e3dc.easyrscp.api.frame.tags.InfoTag;\nimport de.jnkconsulting.e3dc.easyrscp.api.service.InfoService;\nimport de.jnkconsulting.e3dc.easyrscp.api.service.model.ProductionDate;\nimport de.jnkconsulting.e3dc.easyrscp.api.service.model.SystemInfo;\nimport de.jnkconsulting.e3dc.easyrscp.service.builder.ConnectionBuilder;\nimport de.jnkconsulting.e3dc.easyrscp.service.builder.InfoServiceBuilder;\nimport de.jnkconsulting.e3dc.easyrscp.service.converter.FrameConverter;\npublic class CustomConverter implements FrameConverter<SystemInfo> {\n@Override\npublic SystemInfo invoke(Frame frame) {\nString serialNumber = frame.stringByTag(InfoTag.SERIAL_NUMBER);\nString softwareVersion = frame.stringByTag(InfoTag.SW_RELEASE);\nProductionDate productionDate = new ProductionDate(frame.stringByTag(InfoTag.PRODUCTION_DATE));\nreturn new SystemInfo(serialNumber, softwareVersion, productionDate);\n}\nprivate static String host = ...;\nprivate static String portalUser = ...;\nprivate static String portalPassword = ...;\nprivate static String rscpPassword = ...;\npublic static void main(String[] args) {\nConnectionBuilder connectionBuilder = new ConnectionBuilder()\n.withAddress(host)\n.withPortalUser(portalUser)\n.withPortalPassword(portalPassword)\n.withRSCPPassword(rscpPassword);\nInfoServiceBuilder infoServiceBuilder = new InfoServiceBuilder()\n.withConnectionBuilder(connectionBuilder)\n.withSystemInfoFrameConverter(new CustomConverter()); }\n}
import {FrameConverter, Frame} from 'easy-rscp';\nclass CustomConverter implements FrameConverter<SystemInfo> {\nconvert(frame: Frame): SystemInfo {\nconst serialNumber = frame.stringByTag(InfoTag.SERIAL_NUMBER);\nconst softwareVersion = frame.stringByTag(InfoTag.SW_RELEASE);\nconst productionDate = ... // parse production date\nreturn {\nserialNumber: serialNumber,\nsoftwareVersion: softwareVersion,\nproductionDate: productionDate,\n}\n}\n} const service = new DefaultInfoService(\nconnection,\nnew RequestSystemInfosCreator(),\nnew CustomConverter()\n)\n
"},{"location":"service/extension-points/#framecreator","title":"FrameCreator","text":"Each service offers the possibility to set its own FrameCreator
instances. The builders offer corresponding functions here.
A FrameCreator
creates request frames that are sent to the home power plant. The service API already provides corresponding creators. Normally you don't need to do anything here. But if you want to influence the creation process, you can create your own creator and set it as constructor parameter or via one of the builders.
Here is an example of how to set your own creator on the InfoService
:
import de.jnkconsulting.e3dc.easyrscp.api.frame.tags.InfoTag\nimport de.jnkconsulting.e3dc.easyrscp.frame.DataBuilder\nimport de.jnkconsulting.e3dc.easyrscp.frame.FrameBuilder\nimport de.jnkconsulting.e3dc.easyrscp.service.builder.ConnectionBuilder\nimport de.jnkconsulting.e3dc.easyrscp.service.builder.InfoServiceBuilder\nimport de.jnkconsulting.e3dc.easyrscp.service.creator.FrameCreator\nclass CustomCreator: FrameCreator<Nothing?> {\noverride fun invoke(param: Nothing?) =\nFrameBuilder()\n.addData(\nDataBuilder().tag(InfoTag.REQ_MAC_ADDRESS).build(),\nDataBuilder().tag(InfoTag.REQ_PRODUCTION_DATE).build(),\nDataBuilder().tag(InfoTag.REQ_SERIAL_NUMBER).build(),\nDataBuilder().tag(InfoTag.REQ_SW_RELEASE).build()\n)\n.build()\n}\nfun main() {\nval connectionBuilder = ConnectionBuilder()\n.withAddress(host)\n.withPortalUser(portalUser)\n.withPortalPassword(portalPassword)\n.withRSCPPassword(rscpPassword)\nval builder = InfoServiceBuilder()\n.withConnectionBuilder(connectionBuilder)\n.withRequestReadSystemInfoCreator(CustomCreator())\n}\n
import de.jnkconsulting.e3dc.easyrscp.api.frame.Frame;\nimport de.jnkconsulting.e3dc.easyrscp.api.frame.tags.InfoTag;\nimport de.jnkconsulting.e3dc.easyrscp.api.service.InfoService;\nimport de.jnkconsulting.e3dc.easyrscp.api.service.model.SystemInfo;\nimport de.jnkconsulting.e3dc.easyrscp.frame.DataBuilder;\nimport de.jnkconsulting.e3dc.easyrscp.frame.FrameBuilder;\nimport de.jnkconsulting.e3dc.easyrscp.service.builder.ConnectionBuilder;\nimport de.jnkconsulting.e3dc.easyrscp.service.builder.InfoServiceBuilder;\nimport de.jnkconsulting.e3dc.easyrscp.service.creator.FrameCreator;\npublic class CustomCreator implements FrameCreator<Void> {\n@Override\npublic Frame invoke(Void unused) {\nreturn new FrameBuilder()\n.addData(\nnew DataBuilder().tag(InfoTag.REQ_MAC_ADDRESS).build(),\nnew DataBuilder().tag(InfoTag.REQ_PRODUCTION_DATE).build(),\nnew DataBuilder().tag(InfoTag.REQ_SERIAL_NUMBER).build(),\nnew DataBuilder().tag(InfoTag.REQ_SW_RELEASE).build()\n)\n.build();\n}\nprivate static String host = ...;\nprivate static String portalUser = ...;\nprivate static String portalPassword = ...;\nprivate static String rscpPassword = ...;\npublic static void main(String[] args) {\nConnectionBuilder connectionBuilder = new ConnectionBuilder()\n.withAddress(host)\n.withPortalUser(portalUser)\n.withPortalPassword(portalPassword)\n.withRSCPPassword(rscpPassword);\nInfoServiceBuilder infoServiceBuilder = new InfoServiceBuilder()\n.withConnectionBuilder(connectionBuilder)\n.withRequestReadSystemInfoCreator(new CustomCreator());\n}\n}
import {FrameCreator, Frame, FrameBuilder, DataBuilder} from 'easy-rscp';\nclass CustomCreator implements FrameCreator<undefined> {\ncreate(input: undefined): Frame {\nreturn new FrameBuilder()\n.addData(\nnew DataBuilder().tag(InfoTag.REQ_MAC_ADDRESS).build(),\nnew DataBuilder().tag(InfoTag.REQ_PRODUCTION_DATE).build(),\nnew DataBuilder().tag(InfoTag.REQ_SERIAL_NUMBER).build(),\nnew DataBuilder().tag(InfoTag.REQ_SW_RELEASE).build()\n)\n.build();\n}\n} const service = new DefaultInfoService(\nconnection,\nnew CustomCreator()\n)\n
"},{"location":"service/general-concepts/","title":"General concept","text":"The service API is based on the lowlevel API. Its goal is to hide all details of the RSCP interface behind service facades and to make working with the home power plant more logical.
You want more?It should be noted that the RSCP interface is incredibly complex and only a very small part is currently used here. The expansion of the service API is planned and will be developed further. If you want to contribute here, have developed your own useful services, etc.: just create a PR or create a ticket with the title 'Maintainer'!
Until then, the low-level API can be used. Here all RSCP possibilities are open to you.
"},{"location":"service/general-concepts/#service-creation","title":"Service creation","text":"Generally, each service can be created simply with the constructor. Each service has exactly one mandatory parameter: The ConnectionPool. All other parameters are optional and have default values. Especially for the Java developers among you, the default parameters will not work because they are a Kotlin construct. Therefore, the use of the Builder API is recommended.
For each service there is an own builder, which can create the service and if necessary allows to set the optional extension points. Each builder has a mandatory parameter. Namely an instance of the type ConnectionBuilder
is needed. This creates the instances for encryption, connection setup etc. Don't worry, with the ConnectionBuilder it is sufficient to specify only the connection data. Everything else is optional. Here is an example:
The TypeScript versiond does not have the ServiceBuilder concept. You can create connections directly using the ConnectionFactory
KotlinJavaTypeScriptval connectionBuilder = ConnectionBuilder()\n.withAddress(host)\n.withPortalUser(portalUser)\n.withPortalPassword(portalPassword)\n.withRSCPPassword(rscpPassword)\n
ConnectionBuilder connectionBuilder = new ConnectionBuilder()\n.withAddress(host)\n.withPortalUser(portalUser)\n.withPortalPassword(portalPassword)\n.withRSCPPassword(rscpPassword);\n
const connectionData: E3dcConnectionData = {\naddress: host,\nport: 5033,\nportalUser: portalUser,\nportalPassword: portalPassword,\nrscpPassword: rscpPassword\n}\nconst factory = new DefaultHomePowerPlantConnectionFactory(connectionData)\nconst connection = await factory.openConnection()\n
Parameter Description address IP address or DNS name of the home power plant portalUser Username. Corresponds to the username from the E3DC portal portalPassword Password. Corresponds to the password on the E3DC portal rscpPassword Encryption password. This value is configured directly at the home power plant and must be identical here With this instance you can configure all other ServiceBuilders. This is the only mandatory information that is required.
KotlinJavaTypeScriptval serviceBuilder = InfoServiceBuilder()\n.withConnectionBuilder(connectionBuilder)\nval service = serviceBuilder.buildService()\nval systemInfos = service.readSystemInfo()\nprintln(systemInfos)\n
InfoServiceBuilder infoServiceBuilder = new InfoServiceBuilder()\n.withConnectionBuilder(connectionBuilder);\nInfoService service = infoServiceBuilder.buildService();\nSystemInfo infos = service.readSystemInfo();\nSystem.out.println(infos);\n
const service = new DefaultInfoService(connection)\nservice.readSystemInfo()\n.then(systemInfos => console.log(systemInfos)\n
"},{"location":"service/general-concepts/#extension-points","title":"Extension points","text":"The service API provides several points where you can intervene in the way it works. The details are described in the section Extension points.
"},{"location":"service/general-concepts/#logging-kotlinjava-only","title":"Logging (Kotlin/Java only)","text":"easy-rscp uses internally SLF4J as logging framework. You may have already noticed that you get the following warning when running the examples:
SLF4J: No SLF4J providers were found.\nSLF4J: Defaulting to no-operation (NOP) logger implementation\nSLF4J: See https://www.slf4j.org/codes.html#noProviders for further details.\n
This is because easy-rscp does not provide a logging provider. Depending on which logging framework you use (Log4j, java logging, or whatever), you have to declare the corresponding provider implementation as a dependency. SLF4J provides instructions on what to do at the link https://www.slf4j.org/codes.html#noProviders.
Why another dependency?Normally you will not need the log messages from easy-rscp. But it can be helpful for debugging. easy-rscp logs only on debug, trace or error level to avoid too much noise. We don't want to impose a logging framework on you, so we use SLF4J to abstract the framework. You have the choice!
"}]} \ No newline at end of file diff --git a/sitemap.xml.gz b/sitemap.xml.gz index 5bfd01af..a273ca45 100644 Binary files a/sitemap.xml.gz and b/sitemap.xml.gz differ