Python Script with HASS.IO

Hardware: Rpi3
OS: HASS.IO
Version: 0.114.0

Objective:
Upload Solar Energy Generation from APSystem API to PVOUTPUT service localy (the same Hassio Rpi)
I use that known Script for that:

#!/usr/bin/python

import requests
import json
from datetime import date
from datetime import datetime
import os.path

#id's and keys
ECU_ID = '##########'
PV_OUTPUT_SYSTEMID = '999999'
PV_OUTPUT_APIKEY = '############'

#enter a path and filename below, a file wil be create to save the last update datetime
LAST_UPDATE_FILE = "/config/python_scripts/lastupdate"" #example "text.txt" or "/home/pi/aps/lastupdate"

#usually all below this point should not be modified
MAX_NUMBER_HISTORY = 20
APSYSTEMS_URL = 'http://api.apsystemsema.com:8073/apsema/v1/ecu/getPowerInfo'
PVOUTPUT_URL = 'http://pvoutput.org/service/r2/addstatus.jsp'

def readLastUpdate():
    f = open(LAST_UPDATE_FILE,"r")
    datestring = f.read()
    f.close()
    return datetime.strptime(datestring, "%Y%m%d %H:%M")

def writeLastUpdate(timestringminutes):
    f = open(LAST_UPDATE_FILE,"w+")
    f.write(getDateStringOfToday()+ ' ' +timestringminutes)
    f.close()

def getDateStringOfToday():
    return date.today().strftime("%Y%m%d");

def getDataFromAPS():
    headers = {
        'Content-Type': 'application/x-www-form-urlencoded',
    }

    data = {
      'ecuId': ECU_ID,
      'filter': 'power',
      'date': getDateStringOfToday()
    }

    response = requests.post(APSYSTEMS_URL, headers=headers, data=data)
    return response.json();

def sendUpdateToPVOutput(timestringminutes, powerstring):
    pvoutputdata = {
      'd': getDateStringOfToday(),
      't': timestringminutes,
      'v2': powerstring
    }

    headerspv = {
        'X-Pvoutput-SystemId': PV_OUTPUT_SYSTEMID,
        'X-Pvoutput-Apikey': PV_OUTPUT_APIKEY
    }

    responsepv = requests.post(PVOUTPUT_URL, headers=headerspv, data=pvoutputdata)

    print ("Response: " + responsepv.text + " updated: " + timestringminutes + " power: " + powerstring)

if not os.path.isfile(LAST_UPDATE_FILE):
    writeLastUpdate('00:00') #create file for the first time

rootdict = getDataFromAPS()
timesstring = rootdict.get("data").get("time")
powersstring = rootdict.get("data").get("power")

timelist = json.loads(timesstring)
powerlist = json.loads(powersstring)
latestUpdate = readLastUpdate()
print("Found latest update: ")
print(latestUpdate)

i = len(timelist) - 1
count = 0;
while i >= 0 and count < MAX_NUMBER_HISTORY:
    timestringminutes = timelist[i][:-3] #get time and strip the seconds
    powerstring = powerlist[i] #get power

    currentUpdate = datetime.strptime(getDateStringOfToday()+ ' ' +timestringminutes, "%Y%m%d %H:%M")

    if currentUpdate > latestUpdate:
        sendUpdateToPVOutput(timestringminutes, powerstring)
    else:
        print("No update needed for: " + timestringminutes)

    if count == 0:
        writeLastUpdate(timestringminutes)
        
    i -= 1
    count += 1

Every time I run the script from Developers Tools I get two error messages:

Logger: homeassistant.components.python_script
Source: components/python_script/__init__.py:153
Integration: Python Scripts ([documentation](https://www.home-assistant.io/integrations/python_script), [issues](https://github.com/home-assistant/home-assistant/issues?q=is%3Aissue+is%3Aopen+label%3A%22integration%3A+python_script%22))
First occurred: 14:37:20 (2 occurrences)
Last logged: 14:47:48

Warning loading script apstopvoutput.py: Line 50: Prints, but never reads 'printed' variable., Line None: Prints, but never reads 'printed' variable.
Logger: homeassistant.components.python_script.apstopvoutput.py
Source: components/python_script/__init__.py:205
Integration: Python Scripts (documentation, issues)
First occurred: 14:37:20 (2 occurrences)
Last logged: 14:47:48

Error executing script: __import__ not found
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/components/python_script/__init__.py", line 205, in execute
    exec(compiled.code, restricted_globals)
  File "apstopvoutput.py", line 3, in <module>
ImportError: __import__ not found

Am I doing something impossible on running Python Scripts under HASS.IO?
In time, the script works perfect on my Mac’s terminal and the HASS sensor as well.
I just need to run that script each 10 minutes without needing another machine!

You can’t import in python_script. Use either AppDaemon or pyscript if you need to import.

1 Like

The python_script integration runs in a very limited sandbox, which means that you basically can’t import anything. It only works for simpler things more or less. You can use shell_command or move over too AppDaemon for instance.

Hi guys, thanks for the help.

I am trying the AppDaemon way!
Almost there!!!

I am reading the long manual from appdaemon but, unfortunately I have almost no prerequisites on programming, so I think I will need your help again!

I got the script running once when starting AppDaemon Add-on, with an error:
There is no Class declared on the script, I think that is the problem!!!

I want it runs every 10 minutes!
If I just restart the Add-on using HA Automation it would work, but… totaly wrong way I know
Do i need to declare a class named APStoPVOUTPUT to call the app from HA, or do I need to schedule it on the apps.yaml? And How do I do that?

So, the script (apstopvoutput.py):

#!/usr/bin/python

import requests
import json
from datetime import date
from datetime import datetime
import os.path

#id's and keys
ECU_ID = '999999999'
PV_OUTPUT_SYSTEMID = '99999'
PV_OUTPUT_APIKEY = '#############'

#enter a path and filename below, a file wil be create to save the last update datetime
LAST_UPDATE_FILE = "/config/appdaemon/apps/lastupdate" #example "text.txt" or "/home/pi/aps/lastupdate"

#usually all below this point should not be modified
MAX_NUMBER_HISTORY = 20
APSYSTEMS_URL = 'http://api.apsystemsema.com:8073/apsema/v1/ecu/getPowerInfo'
PVOUTPUT_URL = 'http://pvoutput.org/service/r2/addstatus.jsp'

def readLastUpdate():
    f = open(LAST_UPDATE_FILE,"r")
    datestring = f.read()
    f.close()
    return datetime.strptime(datestring, "%Y%m%d %H:%M")

def writeLastUpdate(timestringminutes):
    f = open(LAST_UPDATE_FILE,"w+")
    f.write(getDateStringOfToday()+ ' ' +timestringminutes)
    f.close()

def getDateStringOfToday():
    return date.today().strftime("%Y%m%d");

def getDataFromAPS():
    headers = {
        'Content-Type': 'application/x-www-form-urlencoded',
    }

    data = {
      'ecuId': ECU_ID,
      'filter': 'power',
      'date': getDateStringOfToday()
    }

    response = requests.post(APSYSTEMS_URL, headers=headers, data=data)
    return response.json();

def sendUpdateToPVOutput(timestringminutes, powerstring):
    pvoutputdata = {
      'd': getDateStringOfToday(),
      't': timestringminutes,
      'v2': powerstring
    }

    headerspv = {
        'X-Pvoutput-SystemId': PV_OUTPUT_SYSTEMID,
        'X-Pvoutput-Apikey': PV_OUTPUT_APIKEY
    }

    responsepv = requests.post(PVOUTPUT_URL, headers=headerspv, data=pvoutputdata)

    print ("Response: " + responsepv.text + " updated: " + timestringminutes + " power: " + powerstring)

if not os.path.isfile(LAST_UPDATE_FILE):
    writeLastUpdate('00:00') #create file for the first time

rootdict = getDataFromAPS()
timesstring = rootdict.get("data").get("time")
powersstring = rootdict.get("data").get("power")

timelist = json.loads(timesstring)
powerlist = json.loads(powersstring)
latestUpdate = readLastUpdate()
print("Found latest update: ")
print(latestUpdate)

i = len(timelist) - 1
count = 0;
while i >= 0 and count < MAX_NUMBER_HISTORY:
    timestringminutes = timelist[i][:-3] #get time and strip the seconds
    powerstring = powerlist[i] #get power

    currentUpdate = datetime.strptime(getDateStringOfToday()+ ' ' +timestringminutes, "%Y%m%d %H:%M")

    if currentUpdate > latestUpdate:
        sendUpdateToPVOutput(timestringminutes, powerstring)
    else:
        print("No update needed for: " + timestringminutes)

    if count == 0:
        writeLastUpdate(timestringminutes)
        
    i -= 1
    count += 1

Here’s my apps.yaml:
(I just copied the way HelloWorld way written, but, as I said, there is no class declared on the script)

---
apstopvoutput:
  module: apstopvoutput
  class: APStoPVOUTPUT

and the error I get from log:

2020-08-22 14:31:42.221630 INFO AppDaemon: Initializing app apstopvoutput using class APStoPVOUTPUT from module apstopvoutput
2020-08-22 14:31:42.226333 WARNING AppDaemon: Unable to find class APStoPVOUTPUT in module apstopvoutput - 'apstopvoutput' is not initialized
2020-08-22 14:31:42.229926 WARNING AppDaemon: Unable to find module apstopvoutput - initialize() skipped
2020-08-22 14:31:42.233532 INFO AppDaemon: App initialization complete ```

Yes, you need a class. I suggest reading the inteoduction on how to create your first app here.
You can use the run_every method to run a callback every x seconds

Ok, I see!!!
Now the challenge is convert a Python Script into an AppDaemon App.
There should be a simple tutorial for that, shouldn’t?

So, I read that @Burningstone, but for me, it isn’t clear enough, because they don’t explain the basic, for exemple, where do I need to put my script? directly after the initialize section or call another def from self.run_every just like I did below.

I created a class called APStoPVOUTPUT, used the run_every inside initialize to make it run every 10 minutes starting at 6:30 am.

I didn’t try yet because apsystem api is off now.

import hassapi as hass
import requests
import json
from datetime import date
from datetime import datetime
import os.path

class APStoPVOUTPUT(hass.Hass):
  #initialize() function which will be called at startup and reload
  def initialize(self):
    # Create a time object for 6:30
    time = datetime.time(6, 30, 0)
    # Schedule to run every 10 minutes at 6:30
    self.run_every(self.apstopv, time, 600)

   # Our callback function will be called by the scheduler
  def apstopv(self, kwargs):
    ECU_ID = '999999999'
    PV_OUTPUT_SYSTEMID = '99999'
    PV_OUTPUT_APIKEY = '###################'

    LAST_UPDATE_FILE = "/config/appdaemon/apps/lastupdate" #example "text.txt" or "/home/pi/aps/lastupdate"

    MAX_NUMBER_HISTORY = 20
    APSYSTEMS_URL = 'http://api.apsystemsema.com:8073/apsema/v1/ecu/getPowerInfo'
    PVOUTPUT_URL = 'http://pvoutput.org/service/r2/addstatus.jsp'
    
    def readLastUpdate():
        f = open(LAST_UPDATE_FILE,"r")
        datestring = f.read()
        f.close()
        return datetime.strptime(datestring, "%Y%m%d %H:%M")

    def writeLastUpdate(timestringminutes):
        f = open(LAST_UPDATE_FILE,"w+")
        f.write(getDateStringOfToday()+ ' ' +timestringminutes)
        f.close()

    def getDateStringOfToday():
        return date.today().strftime("%Y%m%d");

    def getDataFromAPS():
        headers = {
            'Content-Type': 'application/x-www-form-urlencoded',
        }

        data = {
          'ecuId': ECU_ID,
          'filter': 'power',
          'date': getDateStringOfToday()
        }

        response = requests.post(APSYSTEMS_URL, headers=headers, data=data)
        return response.json();

    def sendUpdateToPVOutput(timestringminutes, powerstring):
        pvoutputdata = {
          'd': getDateStringOfToday(),
          't': timestringminutes,
          'v2': powerstring
        }

        headerspv = {
            'X-Pvoutput-SystemId': PV_OUTPUT_SYSTEMID,
            'X-Pvoutput-Apikey': PV_OUTPUT_APIKEY
        }

        responsepv = requests.post(PVOUTPUT_URL, headers=headerspv, data=pvoutputdata)

        print ("Response: " + responsepv.text + " updated: " + timestringminutes + " power: " + powerstring)

    if not os.path.isfile(LAST_UPDATE_FILE):
        writeLastUpdate('00:00') #create file for the first time

    rootdict = getDataFromAPS()
    timesstring = rootdict.get("data").get("time")
    powersstring = rootdict.get("data").get("power")

    timelist = json.loads(timesstring)
    powerlist = json.loads(powersstring)
    latestUpdate = readLastUpdate()
    print("Found latest update: ")
    print(latestUpdate)

    i = len(timelist) - 1
    count = 0;
    while i >= 0 and count < MAX_NUMBER_HISTORY:
        timestringminutes = timelist[i][:-3] #get time and strip the seconds
        powerstring = powerlist[i] #get power

        currentUpdate = datetime.strptime(getDateStringOfToday()+ ' ' +timestringminutes, "%Y%m%d %H:%M")

        if currentUpdate > latestUpdate:
            sendUpdateToPVOutput(timestringminutes, powerstring)
        else:
            print("No update needed for: " + timestringminutes)

        if count == 0:
            writeLastUpdate(timestringminutes)
        
        i -= 1
        count += 1
    

Tested and got these errors:

2020-08-23 19:39:32.676089 INFO AppDaemon: Loading App Module: /config/appdaemon/apps/apstopvoutput.py
2020-08-23 19:39:32.732570 INFO AppDaemon: Initializing app apstopvoutput using class APStoPVOUTPUT from module apstopvoutput
2020-08-23 19:39:32.748771 WARNING apstopvoutput: ------------------------------------------------------------
2020-08-23 19:39:32.751564 WARNING apstopvoutput: Unexpected error in worker for App apstopvoutput:
2020-08-23 19:39:32.753691 WARNING apstopvoutput: Worker Ags: {}
2020-08-23 19:39:32.755617 WARNING apstopvoutput: ------------------------------------------------------------
2020-08-23 19:39:32.771868 WARNING apstopvoutput: Traceback (most recent call last):
  File "/usr/lib/python3.8/site-packages/appdaemon/app_management.py", line 150, in initialize_app
    await utils.run_in_executor(self, init)
  File "/usr/lib/python3.8/site-packages/appdaemon/utils.py", line 290, in run_in_executor
    response = future.result()
  File "/usr/lib/python3.8/concurrent/futures/thread.py", line 57, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/config/appdaemon/apps/apstopvoutput.py", line 12, in initialize
    time = datetime.time(6, 30, 0)
TypeError: descriptor 'time' for 'datetime.datetime' objects doesn't apply to a 'int' object
2020-08-23 19:39:32.773865 WARNING apstopvoutput: ------------------------------------------------------------
2020-08-23 19:39:32.777263 INFO AppDaemon: App initialization complete

You need to do from datetime import time at the beginning and remove datetime from datetime.time in line 12.

You can also import datetime and get rid of all the from datetime lines. If you do this, you need to rework your other uses of datetime and date.

The confusing part is that datetime is part of the datetime package, i.e. there is a datetime.datetime.

Thanks @MatthewFlamm I got it!!!

In fact, i gave up using the time schedule inside the app, because I need an end time as well…

So I think I will use both:
constrain_start_time: “07:00:00”
constrain_end_time: “20:00:00”

as soon I discovery how to use it!!! :neutral_face:

so I left the app this way, and It worked…
Now I just need to set up for running just when sun is above horizon!

import hassapi as hass
import requests
import json
from datetime import date
from datetime import datetime
import os.path

class APStoPVOUTPUT(hass.Hass):
  #initialize() function which will be called at startup and reload
  def initialize(self):
    # Schedule to run every 10 minutes
    self.run_every(self.apstopv, "now", 600)

   # Our callback function will be called by the scheduler
  def apstopv(self, kwargs):
    ECU_ID = '99999999999'
    PV_OUTPUT_SYSTEMID = '99999'
    PV_OUTPUT_APIKEY = '#################'

    LAST_UPDATE_FILE = "/config/appdaemon/apps/lastupdate" #example "text.txt" or "/home/pi/aps/lastupdate"

    MAX_NUMBER_HISTORY = 20
    APSYSTEMS_URL = 'http://api.apsystemsema.com:8073/apsema/v1/ecu/getPowerInfo'
    PVOUTPUT_URL = 'http://pvoutput.org/service/r2/addstatus.jsp'
    
    def readLastUpdate():
        f = open(LAST_UPDATE_FILE,"r")
        datestring = f.read()
        f.close()
        return datetime.strptime(datestring, "%Y%m%d %H:%M")

    def writeLastUpdate(timestringminutes):
        f = open(LAST_UPDATE_FILE,"w+")
        f.write(getDateStringOfToday()+ ' ' +timestringminutes)
        f.close()

    def getDateStringOfToday():
        return date.today().strftime("%Y%m%d");

    def getDataFromAPS():
        headers = {
            'Content-Type': 'application/x-www-form-urlencoded',
        }

        data = {
          'ecuId': ECU_ID,
          'filter': 'power',
          'date': getDateStringOfToday()
        }

        response = requests.post(APSYSTEMS_URL, headers=headers, data=data)
        return response.json();

    def sendUpdateToPVOutput(timestringminutes, powerstring):
        pvoutputdata = {
          'd': getDateStringOfToday(),
          't': timestringminutes,
          'v2': powerstring
        }

        headerspv = {
            'X-Pvoutput-SystemId': PV_OUTPUT_SYSTEMID,
            'X-Pvoutput-Apikey': PV_OUTPUT_APIKEY
        }

        responsepv = requests.post(PVOUTPUT_URL, headers=headerspv, data=pvoutputdata)

        print ("Response: " + responsepv.text + " updated: " + timestringminutes + " power: " + powerstring)

    if not os.path.isfile(LAST_UPDATE_FILE):
        writeLastUpdate('00:00') #create file for the first time

    rootdict = getDataFromAPS()
    timesstring = rootdict.get("data").get("time")
    powersstring = rootdict.get("data").get("power")

    timelist = json.loads(timesstring)
    powerlist = json.loads(powersstring)
    latestUpdate = readLastUpdate()
    print("Found latest update: ")
    print(latestUpdate)

    i = len(timelist) - 1
    count = 0;
    while i >= 0 and count < MAX_NUMBER_HISTORY:
        timestringminutes = timelist[i][:-3] #get time and strip the seconds
        powerstring = powerlist[i] #get power

        currentUpdate = datetime.strptime(getDateStringOfToday()+ ' ' +timestringminutes, "%Y%m%d %H:%M")

        if currentUpdate > latestUpdate:
            sendUpdateToPVOutput(timestringminutes, powerstring)
        else:
            print("No update needed for: " + timestringminutes)

        if count == 0:
            writeLastUpdate(timestringminutes)
        
        i -= 1
        count += 1
    

Will it work?

My apps.yaml

---
apstopvoutput:
  module: apstopvoutput
  class: APStoPVOUTPUT
  constrain_start_time: sunrise
  constrain_end_time: sunset + 00:45:00

I didn’t take a detailed look at your code, but looks correct in general, also the two constraints are correct. Does it work? Do you get some errors?

Yesterday it was working fine. When there was nothing to update from APSystem API
Today, when there is something to update, it shows these errors:
Tryed removing constraints (the only thing I changed last night) but still errors:

2020-08-24 08:38:54.889217 INFO AppDaemon: Initializing app apstopvoutput using class APStoPVOUTPUT from module apstopvoutput
2020-08-24 08:38:56.084958 WARNING apstopvoutput: ------------------------------------------------------------
2020-08-24 08:38:56.087405 WARNING apstopvoutput: Unexpected error in worker for App apstopvoutput:
2020-08-24 08:38:56.090083 WARNING apstopvoutput: Worker Ags: {'id': '7a73efd3d51a485ab4ea74039a9867a5', 'name': 'apstopvoutput', 'objectid': '9075be7ff06d4e51bb7eef318aff95d4', 'type': 'scheduler', 'function': <bound method APStoPVOUTPUT.apstopv of <apstopvoutput.APStoPVOUTPUT object at 0x757ab370>>, 'pin_app': True, 'pin_thread': 0, 'kwargs': {'interval': 600, '__thread_id': 'thread-0'}}
2020-08-24 08:38:56.092584 WARNING apstopvoutput: ------------------------------------------------------------
2020-08-24 08:38:56.096950 WARNING apstopvoutput: Traceback (most recent call last):
  File "/usr/lib/python3.8/site-packages/appdaemon/threading.py", line 887, in worker
    funcref(self.AD.sched.sanitize_timer_kwargs(app, args["kwargs"]))
  File "/config/appdaemon/apps/apstopvoutput.py", line 74, in apstopv
    timesstring = rootdict.get("data").get("time")
AttributeError: 'NoneType' object has no attribute 'get'
2020-08-24 08:38:56.099205 WARNING apstopvoutput: ------------------------------------------------------------
1 Like

btw, the script just stoped working from my mac terminal as well…
That is realy strange!!! Maybe something updated from python, apsystem api, etc on this night?

IT IS WORKING!!!

Great news… those error was about my EMA not conected to internet.
I just fixed and the app started working fine!