Fetch Tesla Power Wall State of charge using EmonHub

@bwduncan

Hoping I properly understood yr suggestion, I put this in emonhub.conf …

[[PowerWall]]
    Type = EmonHubTeslaPowerWallInterfacer
    [[[init_settings]]]
    [[[runtimesettings]]]
        pubchannels = ToEmonCMS,
        name = powerwall
        url = http://192.168.1.100/aggregates = requests.get(self._settings['url'] + '/api/meters/aggregates', timeout=10, verify=False).json()  
c = Cargo.new_cargo(names=['battery_instant_power'], realdata=[aggregates['battery']['instant_power'])
        ###url = http://192.168.1.100/api/system_status/soe
        readinterval = 10

… and got the following …

2020-07-09 02:05:16,503 WARNING  MainThread PowerWall thread is dead.
2020-07-09 02:05:16,504 WARNING  MainThread Attempting to restart thread PowerWall (thread has been restarted 106 times...
2020-07-09 02:05:16,505 INFO     MainThread Creating EmonHubTeslaPowerWallInterfacer 'PowerWall' 
2020-07-09 02:05:16,507 INFO     MainThread Setting PowerWall name: powerwall
2020-07-09 02:05:16,507 INFO     MainThread Setting PowerWall url: ["http://192.168.1.100/aggregates = requests.get(self._settings['url']   '/api/meters/aggregates'", 'timeout=10', 'verify=False).json()']
2020-07-09 02:05:16,508 INFO     MainThread Setting PowerWall readinterval: 10
2020-07-09 02:05:16,509 DEBUG    MainThread Setting PowerWall pubchannels: ['ToEmonCMS']
2020-07-09 02:05:16,514 WARNING  PowerWall  PowerWall couldn't send to server: No connection adapters were found for '["http://192.168.1.100/aggregates = requests.get(self._settings['url']   '/api/meters/aggregates'", 'timeout=10', 'verify=False).json()']'
2020-07-09 02:05:16,515 WARNING  PowerWall  Exception caught in PowerWall thread. Traceback (most recent call last):
  File "/opt/openenergymonitor/emonhub/src/emonhub_interfacer.py", line 32, in wrapper
    return f(*args)
  File "/opt/openenergymonitor/emonhub/src/emonhub_interfacer.py", line 99, in run
    rxc = self.read()
  File "/opt/openenergymonitor/emonhub/src/interfacers/EmonHubTeslaPowerWallInterfacer.py", line 43, in read
    jsonstr = reply.text.rstrip()
UnboundLocalError: local variable 'reply' referenced before assignment

Clearly I’ve misunderstood?

@johnbanks, the question is what do you want to do with the information (rhetorical)?

I’ve said before that emoncms doesn’t really want to go down the road of Home Automation; there are other, better systems to do that.

In your case, there is a ready made solution in form of the Home Assistant integration Tesla Powerwall - Home Assistant. You can then send the data to emoncms if you want to record it (back to the first question). If you want to control stuff, HA is better suited to that.

Unfortunately yes, Bruce was saying to reduce the 'url =` entry in the conf to just the IP address and also edit the source code, ie edit the Tesla Interfacer code in emonhub to try if it achieves what you want.

@bwduncan - in previous interfacers (hive, tplink, wunderground, darksky and some modbus devices etc) I have tackled this by having a list of “emonhub supported metrics” for the interfacer at hand that can be defined in a conf setting. E.G. if only the battery instant power was wanted, payload = battery_instant_power or if more metrics wanted payload = battery_instant_power, metricB, metricC etc. This approach gives access to all available metrics (if supported in emonhub) but doesn’t swamp the user with data, it also allows for rearranging the payload eg payload = metricC, battery_instant_power, metricB to allow more elaborate processing to be used in emoncms, which is input order dependent. It requires a bit more work by the coder but makes a much more “one size fits all” interfacer. There can be (and in my case always will be), of course a default payload hard coded for when the payload is not defined, that could be the most useful “standard” metrics for the average user and also allow for a suitable emoncms device module template to follow.

1 Like

@pb66 - Many thx for yr added insight.

In the Interfacer stanza in emonhub.conf, I’ve now just got the URL and have tried with/without quotes, with/without trailing slash and plain http:

I’ve also modified EmonHubTeslaPowerWallInterfacer.py as follows …

import time, json, Cargo, requests
from emonhub_interfacer import EmonHubInterfacer

"""class EmonHubTeslaPowerWallInterfacer

Fetch Tesla Power Wall state of charge

"""

class EmonHubTeslaPowerWallInterfacer(EmonHubInterfacer):

    def __init__(self, name):
        super().__init__(name)

        self._settings.update(self._defaults)

        # Interfacer specific settings
        self._template_settings = {'name': 'powerwall',
                                   'url': False,
                                   'readinterval': 10.0}

        # FIXME is there a good reason to reduce this from the default of 1000? If so, document it here.
        # set an absolute upper limit for number of items to process per post
        self._item_limit = 250

        # Fetch first reading at one interval lengths time
        self._last_time = 0

    def read(self):
        # Request Power Wall data at user specified interval
        if time.time() - self._last_time >= self._settings['readinterval']:
            self._last_time = time.time()

            # If URL is set, fetch the SOC
            if self._settings['url']:
                # HTTP Request
                try:
                    ##reply = requests.get(self._settings['url'], timeout=int(self._settings['readinterval']), verify=False)
					aggregates = requests.get(self._settings['url'] + '/api/meters/aggregates', timeout=10, verify=False).json()
                    reply.raise_for_status()  # Raise an exception if status code isn't 200
                except requests.exceptions.RequestException as ex:
                    self._log.warning("%s couldn't send to server: %s", self.name, ex)

                jsonstr = reply.text.rstrip()
                self._log.debug("%s Request response: %s", self.name, jsonstr)

                # Decode JSON
                try:
                    data = json.loads(jsonstr)
                except Exception:  # FIXME Too general exception
                    self._log.warning("%s Invalid JSON", self.name)
                    return

                # Check if battery percentage key is in data object
                ##if not 'percentage' in data:
                    ##self._log.warning("%s Percentage key not found", self.name)
                    ##return

                # Create cargo object
                ##c = Cargo.new_cargo()
                ##c.nodeid = self._settings['name']
                ##c.names = ["soc"]
                ##c.realdata = [data['percentage']]
				c = Cargo.new_cargo(names=['battery_instant_power'], realdata=[aggregates['battery']['instant_power'])
                return c

        # return empty if not time
        return

    def set(self, **kwargs):
        for key, setting in self._template_settings.items():
            # Decide which setting value to use
            if key in kwargs.keys():
                setting = kwargs[key]
            else:
                setting = self._template_settings[key]
            if key in self._settings and self._settings[key] == setting:
                continue
            elif key == 'readinterval':
                self._log.info("Setting %s %s: %s", self.name, key, setting)
                self._settings[key] = float(setting)
                continue
            elif key == 'name':
                self._log.info("Setting %s %s: %s", self.name, key, setting)
                self._settings[key] = setting
                continue
            elif key == 'url':
                self._log.info("Setting %s %s: %s", self.name, key, setting)
                self._settings[key] = setting
                continue
            else:
                self._log.warning("'%s' is not valid for %s: %s", setting, self.name, key)

        # include kwargs from parent
        super().set(**kwargs)

The double hashes are my commenting out.
And I’ve added the 2 lines suggested by @bwduncan at lines 39 and 64.
I’m a total beginner at json - and those lines just looked like the best fit.

However - it did not work - emonhub failed to restart.

I tried to restart emonhb.service via SSH with the following result …

pi@emonpi-node-15:~ $ sudo systemctl start emonhub.service
pi@emonpi-node-15:~ $ sudo systemctl status emonhub.service
● emonhub.service - emonHub service description
   Loaded: loaded (/opt/openenergymonitor/emonhub/service/emonhub.service; enabled; vendor preset: enabled)
   Active: failed (Result: exit-code) since Thu 2020-07-09 15:06:24 BST; 2s ago
  Process: 7452 ExecStartPre=/bin/mkdir -p /var/log/emonhub/ (code=exited, status=0/SUCCESS)
  Process: 7453 ExecStartPre=/bin/chgrp -R emonhub /var/log/emonhub/ (code=exited, status=0/SUCCESS)
  Process: 7454 ExecStartPre=/bin/chmod 775 /var/log/emonhub/ (code=exited, status=0/SUCCESS)
  Process: 7455 ExecStart=/usr/local/bin/emonhub/emonhub.py --config-file=/etc/emonhub/emonhub.conf --logfile=/var/log/emonhub/emonhub.log (code=exited, status=1/FAILURE)
 Main PID: 7455 (code=exited, status=1/FAILURE)

Jul 09 15:06:24 emonpi-node-15 systemd[1]: emonhub.service: Service RestartSec=100ms expired, scheduling restart.
Jul 09 15:06:24 emonpi-node-15 systemd[1]: emonhub.service: Scheduled restart job, restart counter is at 5.
Jul 09 15:06:24 emonpi-node-15 systemd[1]: Stopped emonHub service description.
Jul 09 15:06:24 emonpi-node-15 systemd[1]: emonhub.service: Start request repeated too quickly.
Jul 09 15:06:24 emonpi-node-15 systemd[1]: emonhub.service: Failed with result 'exit-code'.
Jul 09 15:06:24 emonpi-node-15 systemd[1]: Failed to start emonHub service description.
pi@emonpi-node-15:~ $

That makes no sense to me and when I revert to the published PowerWall Interfacer, emonhub service starts per normal.
I’m running the latest ver 10.2.3 - even did an extra Update to be sure.

Any pointers would be most welcome - thx

PS: I did look at the EmonHubTemplateInterfacer.py as a possible alternative but that looked more complex