PVOuput Uploader

Has anyone already created python code to upload Electricity Consumption and Solar Generation data from HA up into PVOutput ( http://pvoutput.org/ )

(I know there’s a plugin to download data, but I’m talking about uploading here)

found this: https://github.com/MartijnBraam/pvoutput

Can you please provide a guide on how you got it to work? I’m looking to do the same thing but don’t really know how to get it working. Any help/config examples would be very much appreciated!

I pretty much just used the cost from MartiynBraam I linked to above…he gives an example on his front page which then calls the code he has in the init.py file…

OK, so I have just got around to playing with this. I have put the script in my python_scripts folder and am using an automation to call it but not getting anywhere with it. My automation is:

- alias: UploadPvoutput
hide_entity: False
trigger:
  platform: time
  minutes: '/5'
action:
  - service: python_script.pvoutput
    data:
      api_key:"<apikey>"
      system_id:"<id>"
      power_generation:200
      temperature:39.2
      voltage:800

Right now I’m just using static numbers to test it but understand I need a template to feed actual numbers.

I’ve tried a few variations of what the ‘data’ looks like to no avail - all I keep getting is:

“Invalid config for [automation]: expected dict for dictionary value @ data[‘action’][0][‘data’]. Got None.”

Can you please help?

I haven’t touched HA for some months but I can show you what I have if that helps…probably bad coding practice throughout :slight_smile:

(1) picking up current cost values and publishing to MQTT queue so it’s available:

#!/usr/bin/python
# -*- coding: utf-8 -*-
# CurrentCostReader.py

import serial
import xml.etree.ElementTree as ET
import paho.mqtt.client as mqtt
import json

MQTT_Host = "localhost"
MQTT_Port = "1883"
MQTT_User = "homeassistant"
MQTT_Password = "XXXXX"
SERIAL_PORT = '/dev/ttyUSB0'

client = mqtt.Client("current-cost-publisher") 
client.username_pw_set(MQTT_User, MQTT_Password)
client.connect(MQTT_Host)


def get_data(port=SERIAL_PORT):  
        # Read In Data from Serial
        ser = serial.Serial(port, 57600)
        xmldata = ser.readline().strip().decode("utf-8")
        ser.close()

        # Parse Values from XML
        root = ET.fromstring(xmldata)
        sensor = root.find('sensor').text
        power = float(root.find('ch1')[0].text)

        return (sensor, power)

while(True):
        try:
                # Get Data
                (sensor, power) = get_data()
                # state_topic
                stateTopic = "current-cost/%s" % (sensor)
                # payload
                payload = {}
                payload["power"] = power
                payloadJson = json.dumps(payload)
                # publish
                print("PUBLISH: %s : %s" % (stateTopic, payloadJson))
                client.publish(stateTopic, payloadJson)

        except:
                pass

Then using App Daemon as the means to run a ongoing app to monitor the updates the following:

import appdaemon.plugins.hass.hassapi as hass
from pvoutput import PvOutputApi

class PvOutputApi_AppDaemon(hass.Hass):

  def initialize(self):
     self.log("Test AppDaemon App - Initializing")
     self.listen_state(self.motion)
     self.run_in(self.log_pvoutput, 5)
     self.power = None
     self.solar = 0
     self.api = PvOutputApi(api_key="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", system_id="xxxxx")

  def motion(self, entity, attribute, old, new, kwargs):
    if entity == "sensor.power_consumption":
        self.power = float(new)
    if entity == "sensor.solar_generation":
        self.solar = float(new)

  def log_pvoutput(self, kwargs):
    if (self.power is not None): 
        # Schedule next run early in case of failure calling PVOuput
        self.run_in(self.log_pvoutput, 60*5)

        # Now call PVOuput
        self.log("PVOUTPUT: Power:%s, Solar:%s" % (self.power, self.solar) )
        responseStr = self.api.update_status(self.log, power_generation=self.solar, power_consumption=self.power)

    else:
        self.log("AWAITING BOTH TO BE NOT NULL. Power=%s, Solar=%s" % (self.power, self.solar) )
        self.run_in(self.log_pvoutput, 5)

And the library the above calls:

import requests
import datetime


class PvOutputApi(object):
    def __init__(self, api_key, system_id, cumulative=False):
        self.api_key = api_key
        self.system_id = system_id
        self.cumulative = cumulative
        self.api_url = 'http://pvoutput.org/service/r2/addstatus.jsp'

    def ADLog(self, text):
        with open("/home/homeassistant/Devel/appdaemon/appdaemon.log", 'a') as log:
            log.write(text + "\n")

    def update_status(self, logFn, energy_generation=None, power_generation=None, energy_consumption=None,
                      power_consumption=None, temperature=None,
                      voltage=None, time=None):

        if not energy_generation and not power_generation and not energy_consumption and not power_consumption:
            raise ValueError("You need at least one of: enegergy_generation, power_generation,"
                             "energy_consumption, power_consumption")

        # if not time:
        time = datetime.datetime.now()

        parameters = {
            'c': "1" if self.cumulative else "0",
            'd': '{:%Y%m%d}'.format(time),
            't': '{:%H:%M}'.format(time)
        }

        if energy_generation:
            parameters['v1'] = str(energy_generation)

        if power_generation:
            parameters['v2'] = str(power_generation)

        if energy_consumption:
            parameters['v3'] = str(energy_consumption)

        if power_consumption:
            parameters['v4'] = str(power_consumption)

        if temperature:
            parameters['v5'] = str(temperature)

        if voltage:
            parameters['v6'] = str(voltage)

        headers = {
            'X-Pvoutput-Apikey': self.api_key,
            'X-Pvoutput-SystemId': self.system_id
        }

        res = requests.post(self.api_url, parameters, headers=headers)

        summaryStr = "PVOUTPUT POST\nURL:%s\nParameters:%s\nHeaders:%s\nResponse Code:%s\nResponse:%s\n" % (self.api_url, parameters, headers, res.status_code, res)
        return summaryStr

Thanks - I appreciate the reply but that is way beyond me.

I got a little further and am able to call that script but get a:

Traceback (most recent call last):
  File "/usr/lib/python3.6/site-packages/homeassistant/components/python_script.py", line 166, in execute
    exec(compiled.code, restricted_globals, local)
  File "pvoutput.py", line 5, in <module>
NameError: name 'object' is not defined

I think I will just go the CURL method and see where that gets me.

OK, for anybody interested, I have the solution!! It has taken a while since I’ve been busy, but it’s quite simple:

shell_command:
   pvoutputcurl: 'curl -d "d={{now().strftime("%Y%m%d")}}" -d "t={{now().strftime("%H:%M")}}" -d "v4={{states.sensor.studioforpvoutput_mean.state|round(0)}}" -H "X-Pvoutput-Apikey: apikeygoeshere" -H "X-Pvoutput-SystemId: idgoeshere" https://pvoutput.org/service/r2/addstatus.jsp'   

(the sensor mean has a max age of 5 minutes of realtime readings)

And then run an automation:

  - alias: UploadPvoutput
    hide_entity: False
    trigger:
      platform: time
      minutes: '/5'
      seconds: 00
    action:
      service: shell_command.pvoutputcurl

In the above example I am uploading “v4”. but you can do whatever you want as per the documentation:

https://pvoutput.org/help.html#api-addstatus

8 Likes

Hello

I have copied your shell command and automation, but when it runs i get the error below. Any suggestions?

2019-01-11 11:40:05 INFO (MainThread) [homeassistant.components.automation] Executing pvoupload
2019-01-11 11:40:05 INFO (MainThread) [homeassistant.helpers.script] Script pvoupload: Running script
2019-01-11 11:40:05 INFO (MainThread) [homeassistant.helpers.script] Script pvoupload: Executing step call service

2019-01-11 11:40:05 ERROR (MainThread) [homeassistant.components.shell_command] Error rendering command template: UndefinedError: ‘None’ has no attribute ‘state’
Traceback (most recent call last):
File “/usr/src/app/homeassistant/helpers/template.py”, line 138, in async_render
return self._compiled.render(kwargs).strip()
File “/usr/local/lib/python3.6/site-packages/jinja2/asyncsupport.py”, line 76, in render
return original_render(self, *args, **kwargs)
File “/usr/local/lib/python3.6/site-packages/jinja2/environment.py”, line 1008, in render
return self.environment.handle_exception(exc_info, True)
File “/usr/local/lib/python3.6/site-packages/jinja2/environment.py”, line 780, in handle_exception
reraise(exc_type, exc_value, tb)
File “/usr/local/lib/python3.6/site-packages/jinja2/_compat.py”, line 37, in reraise
raise value.with_traceback(tb)
File “<template>”, line 1, in top-level template code
File “/usr/src/app/homeassistant/helpers/template.py”, line 446, in forgiving_round
value = round(float(value), precision)
jinja2.exceptions.UndefinedError: ‘None’ has no attribute ‘state’

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File “/usr/src/app/homeassistant/components/shell_command.py”, line 54, in async_service_handler
rendered_args = args_compiled.async_render(service.data)
File “/usr/src/app/homeassistant/helpers/template.py”, line 140, in async_render
raise TemplateError(err)
homeassistant.exceptions.TemplateError: UndefinedError: ‘None’ has no attribute ‘state’

I’m no HA/python expert, but that sounds like you don’t have a value associated with what you’re trying to put in the curl command. Have you made sure the value works in the templating area in HA?

Hi Greenhouse

thankyou for the quick response. No, i have not setup a value nor tested anything in the templating area. This is my first foray into curl/automation. Back to the doco i go :slight_smile:

In the pvoutput curl statement I changed

states.sensor.studioforpvoutput_mean.state

to

states.sensor.currentcost_power.state

and now the error is gone and it is uploading power usage data :slight_smile:

As an update to this, I made a couple of modifications to my automation as to only upload when my internet is ‘online’ (heaps of internet drop outs in our 3rd world internet here in Australia).

In addition to that, sometimes I get an error “homeassistant.exceptions.TemplateError: UndefinedError: ‘None’ has no attribute ‘state’”, even when there is a state. My solution is to wait 10 seconds and then try again, with an almost 100% success rate (before the double-up I would get 1-3 missed uploads a day).

  - alias: UploadPvoutput
    initial_state: 'on'    
    hide_entity: False
    trigger:
      platform: time_pattern
      minutes: '/5'
      seconds: 00  
    action:
      - wait_template: "{{ is_state('binary_sensor.online', 'on') }}"
        timeout: '00:00:30'
        continue_on_timeout: 'false'
      - service: shell_command.pvshellgh        
      - delay: 0:00:10
      - service: shell_command.pvshellgh 
1 Like

Thank you Adam
the simple curl solution saved me so much time and frustration to try to upload to pvoutput.org

I was looking for this too and I am happy I found this solution. I don;t like my API key in the YAML files though as I use Github so I did this to template the API key:

sensor:
  - platform: template
    sensors:
      pvoutput_api_key:
        value_template: !secret pvoutput_api_key
        
      pvoutput_system_id:
        value_template: !secret pvoutput_system_id

and in the curl command:

shell_command:
   pvoutput_generation: 'curl -d "d={{now().strftime("%Y%m%d")}}" -d "t={{now().strftime("%H:%M")}}" -d "v2={{states.sensor.cs31011195_output_power.state|round(0)}}" -H "X-Pvoutput-Apikey: {{states.sensor.pvoutput_api_key.state}}" -H "X-Pvoutput-SystemId: {{states.sensor.pvoutput_system_id.state}}" https://pvoutput.org/service/r2/addstatus.jsp'

   pvoutput_consumption: 'curl -d "d={{now().strftime("%Y%m%d")}}" -d "t={{now().strftime("%H:%M")}}" -d "c1=1" -d "v3={{(states.sensor.energy_consumption_tariff_1.state|int)+(states.sensor.energy_consumption_tariff_2.state|int)}}" -H "X-Pvoutput-Apikey: {{states.sensor.pvoutput_api_key.state}}" -H "X-Pvoutput-SystemId: {{states.sensor.pvoutput_system_id.state}}" https://pvoutput.org/service/r2/addstatus.jsp'

I am using two commands as I have a cumulative energy usage value from my smart meter and a power generation value from the solar power invertor. (note: the c1=1 means cumulative)

3 Likes

Hi Didier,
I am testing Home assistant for the first time and i am struggling trying to use the description you posted.
I input the portion sensor and shell_command into configuration.yaml . The API and system id into the file secret.yalm
Checking the file configuration.yaml system says it is OK …no errors… but how to run every 5 minutes the command to upload data to PVOUTPUT?
Would be very appreaciated a support from you.

many thanks in advance
Cheers

Finally I did it !! :grinning:
automation file was wrongly formatted.

Hi @ciottomate glad you figured it out already :slight_smile: I was on holiday and could not respond any sooner.

For those who come looking for the answer, create an automation rule that runs every 5 mins and runs the two shell scripts:

  alias: PVOutput Uploader
  description: Uploads values to PVOutput.
  trigger:
  - platform: time_pattern
    minutes: /5
  condition: []
  action:
  - service: shell_command.pvoutput_consumption
    data: {}
  - service: shell_command.pvoutput_generation
    data: {}
  mode: single
1 Like

Hi,

So I got the API working fine thanks to your posts above, however I’m a bit confused how I create a 5min average sensor to send to PVoutput.
I have a template sensor that calculates my instantaneous house consumption (removing solar generation):

      # Template sensor for values of power consumption (active_power < 0)
      energy_consumption:
        friendly_name: "Power Consumption"
        unit_of_measurement: 'W'
        value_template: >-
          {% if (states('sensor.house_consumption')|float - 685 - states('sensor.pv_power')|float - states('sensor.pv_power_2')|float) < 0 %}
            {{ (states('sensor.house_consumption')|float - 685 - states('sensor.pv_power')|float - states('sensor.pv_power_2')|float) * -1 }}
          {% else %}
            {{ (states('sensor.house_consumption')|float - 685 + states('sensor.pv_power')|float + states('sensor.pv_power_2')|float) }}
          {% endif %}

How do I turn that into a 5 min average to send up to Pvoutput.
You mentioned you had something with a timestamp or similar?

There doesn’t seem to be a average, only min-max median, confused.

Thanks for any bone you can throw me.

For anyone looking in the future, I think I figured it out using statistics:

sensor:
  # Sensor for 5 min average consumption for Pvoutput
  - platform: statistics
    name: "Energy Consumption 5min"
    entity_id: sensor.energy_consumption
    state_characteristic: mean
    max_age:
      minutes: 5
    sampling_size: 300
1 Like