Solar Inverter: Kostal Plenticore Plus integration

I would like to integrate my Kostal Plenticore Plus solar inverter with HA.
Basically I would just like to read consumption stats.

I found this openHab binding:

Have you seen something similar for HA?

Bo

Hi Bo,
Im trying to program a component at the moment. But im struggling with the authentication.
The API is very strange (and not documented)

You can access the API like this:
http : / /-IP-/api/v1/

1 Like

200 lines of code just for the authentication, jesus :confused:

Could be the complicatest Auth for a web API i’ve seen so far… Maybe polling the values via Modbus is easier.

I think the way to go is really just getting the data via Modbus. Problem here: I’m struggling with reading the first value at all…

modbus:
  - type: tcp
    host: 192.168.1.11
    port: 502
    name: energy_meter

  - type: tcp
    host: 192.168.1.22
    port: 1502
    name: inverter
sensor:
- platform: modbus
  scan_interval: 1
  registers:
    - name: Aktueller_Verbrauch
      hub: energy_meter
      unit_of_measurement: W
      slave: 1
      register: 0
      count: 2
      scale: 0.1
      
- platform: modbus
  scan_interval: 1
  registers:
    - name: Total_DC_power
      hub: inverter
      unit_of_measurement: W
      slave: 71
      register: 100
      count: 2
      #reverse_order: true
      #scale: 0.1
      data_type: float

The Energy Meter works. The inverter doesn’t. It shows some value but that is garbage. I tried to reverse or only read one, doesn’t help.

I found a python script in some PV forum which works very good in reading the values via Modbus though:

Documentation from Kostal regarding the registers:

I found another working implementation - ioBroker this time. I use that one now to publish to MQTT and read it from Home Assistant. If anyone is able to develop are Plugin for HA, here is the sourcecode:

This time “only” 100 lines for authentication:

function login() {
	let nonce = KOSTAL.getNonce();
	let payload = {
		username: 'user',
		nonce: nonce
	};
	apiCall('POST', 'auth/start', payload, function(body, code, headers) {
		if(code !== 200) {
			adapter.log.warn('Login failed with code ' + code + ': ' + body);
			return;
		}

		var json = JSON.parse(body);
		if(!json.nonce) {
			adapter.log.warn('No nonce in json reply to start: ' + body);
			return;
		}

		var mainTransactionId = json.transactionId;
		var serverNonce = json.nonce;
		var salt = json.salt;
		var hashRounds = parseInt(json.rounds);

		var r = KOSTAL.pbkdf2(devicePassword, KOSTAL.base64.toBits(salt), hashRounds);
		var sKey = new KOSTAL.hash.hmac(r, KOSTAL.hash.sha256).mac('Client Key');
		var cKey = new KOSTAL.hash.hmac(r, KOSTAL.hash.sha256).mac('Server Key');
		var sHash = KOSTAL.hash.sha256.hash(sKey);
		var hashString = 'n=user,r=' + nonce + ',r=' + serverNonce + ',s=' + salt + ',i=' + hashRounds + ',c=biws,r=' + serverNonce;
		var sHmac = new KOSTAL.hash.hmac(sHash, KOSTAL.hash.sha256).mac(hashString);
		var cHmac = new KOSTAL.hash.hmac(cKey, KOSTAL.hash.sha256).mac(hashString);
		var proof = sKey.map(function(l, n) {
			return l ^ sHmac[n];
		});
		
		var payload = {
			transactionId: mainTransactionId,
			proof: KOSTAL.base64.fromBits(proof)
		};

		apiCall('POST', 'auth/finish', payload, function(body, code, headers) {
			if(code !== 200) {
				adapter.log.warn('auth/finish failed with code ' + code + ': ' + body);
				return;
			}

			var json = JSON.parse(body);
			if(!json.token) {
				adapter.log.warn('No nonce in json reply to finish: ' + body);
				return;
			}

			var bitSignature = KOSTAL.base64.toBits(json.signature);

			if(!KOSTAL.bitArray.equal(bitSignature, cHmac)) {
				adapter.log.warn('Signature verification failed!');
				return;
			}

			var hashHmac = new KOSTAL.hash.hmac(sHash, KOSTAL.hash.sha256);
			hashHmac.update('Session Key');
			hashHmac.update(hashString);
			hashHmac.update(sKey);
			var digest = hashHmac.digest();
			json.protocol_key = digest;
			json.transactionId = mainTransactionId;

			var pkey = json.protocol_key,
				tok = json.token,
				transId = json.transactionId,
				encToken = KOSTAL.encrypt(pkey, tok);
			var iv = encToken.iv,
				tag = encToken.tag,
				ciph = encToken.ciphertext,
				payload = {
					transactionId: transId,
					iv: KOSTAL.base64.fromBits(iv),
					tag: KOSTAL.base64.fromBits(tag),
					payload: KOSTAL.base64.fromBits(ciph)
				};
			
			apiCall('POST', 'auth/create_session', payload, function(body, code, headers) {
				if(code !== 200) {
					adapter.log.warn('auth/create_session failed with code ' + code + ': ' + body);
					return;
				}

				var json = JSON.parse(body);
				if(!json.sessionId) {
					adapter.log.warn('No session id in json reply to create session: ' + body);
					return;
				}

				loginSessionId = json.sessionId;
				adapter.log.debug('Session id is ' + loginSessionId);

				loginSuccess();
			});
		});


	});
}


That should do the job :slight_smile:
Now we have to create a component.

Hello Lukas,
I saw that you started a git repo. I am interested as well. If you need some support in testing or others things that do not require the full dev environment please let me know. My Kostal plenticore should be installed end of March.

Great work here at hassio!

Greetings,
Sebastian

Hi Sebastian,

yes I created a simple component. But its not finished at all.
I can upload it to my github this evening

Greetings,
Lukas

Hi Lukas,
great. I will have a look at it. Are there beginners/Routine parts I could support in?

Greetings,
Sebastian


It is online.

The following things are missing in my opinion:

  • Error handling
  • Dynamic entities so that the user can select which entities should be requested and shown in ha. It is static at the moment (see line 32,33 in sensor.py)

Optional:

  • Ability to add the sensor on the web gui

I would be happy if someone would help me with it.

Is there something we can reuse from the Fronius or SMA implementation?
I am still a bloody beginner in hassio. What I recognized is that both go with async. I am currently getting myself aquainted with the classes and methods hoping that I can contribute.

yes, those are good examples. As far as I know is not necessary to do it with async (I have no experience with it). I have read some part of the dev docs now and I think the easiest way to get it as dynamic and functional as possible is to just add a for loop to the setup_platform function and loop through the parameters witch we can read from the configuration.yml.

Looks good :slight_smile:


Bildschirmfoto vom 2020-03-15 13-12-37

2 Likes

Wow that looks good - well done :slight_smile:

1 Like

Great job. You should add a “send me a beer” Button in GitHub :wink:
Any further steps to make it a git loadable package? I will definitely try when the kostal system is installed.

1 Like

I want to add it to home assistant as an official component. I even created a pull request and noticed afterwards it does not work as perfect as is thought :smiley: An closed it again :stuck_out_tongue:

Things I noticed:

  • If the cookie is not valid anymore after a restart of the inverter for example or a firmware update, HA can not reconnect and spams the log full with error messages until you restart home assistant in order to re authenticate.
  • Naming is not perfect. I had to guess which value in the API means what. And I maybe made mistakes. I will check that.
  • https://developers.home-assistant.io/docs/integration_quality_scale_index/ at least silver or better gold rating would be nice
  • A sensor for energy that will me sold is missing

Maybe we can add it to HACs until its fixed

https://github.com/ITTV-tools/homeassistant-kostalplenticore -> Integration
It is now possible to add it to HACs

1 Like

I added two more sensor in the new Version:


“Kostal Battery Power” shows with how much power the battery is charging and discharging
“Kostal power grid” shows much power you are buying and selling

The values are negative if the energy is going out of the inverter and positive if it goes inside.