Community
OpenEnergyMonitor

Community

PowerWall Data Integration

@bwduncan

Thx for yr suggestions in this thread …

To recap - I’m trying to adapt the published EmonHubTeslaPowerWallInterfacer.py so I can grab more data from another Tesla API – eg: battery instant power.

I have no Python expertise but mindful that an infinite number of chimps given an infinite amount of time could type The Bible, like a chimp I set to work. But after days spent on this, I’m now heartily sick of bananas. For example, it took me 2 days to discover that in Python to comment out a line it was necessary to use #space – who’d have thought that?

I finally settled on what I thought was a methodical approach. Below is a version of the original EmonHubTeslaPowerWallInterfacer.py with all the alternatives I can think of as comment lines – I could swap lines in/out easily using nano.

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'], timeout=10, verify=False).json()  # per @bwduncan
                    # aggregates = requests.get(self._settings['url'], timeout=10, verify=False)  # per @bwduncan but no .json()
					# reply = requests.get(self._settings['url'], timeout=10, verify=False).json()  # per @bwduncan & reply
                    # reply = requests.get(self._settings['url'], timeout=10, verify=False)  # per @bwduncan but no .json() & reply
					
					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


					
                # Create cargo object
                c = Cargo.new_cargo()
                c.nodeid = self._settings['name']
                c.names = ["soc"]
                # c.realdata = [data['percentage']]
				c.realdata = [data['battery']['instant_power']]
				# c = Cargo.new_cargo(names=['battery_instant_power'], realdata=[aggregates['battery']['instant_power'])   # per @bwduncan
				# c = Cargo.new_cargo(names=['battery_instant_power'], realdata=[data['battery']['instant_power'])   # per @bwduncan but data
				# c = Cargo.new_cargo(names=['battery_instant_power'], realdata=[aggregates['battery']['instant_power']])  # added ]
                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)

But whatever alternative I’ve tried, I always got a popup error message and emonhub stopped (but could be restarted with … sudo systemctl start emonhub.service)


EmonCMS Error

-------------

Message: SyntaxError: unexpected token: identifier

Route: Lib/jquery-1.11.3.min.js

Line: 1

Column: 4

The emonhub log is not informative.

Then, quite by chance, I ran EmonHubTeslaPowerWallInterfacer.py as is but with a url containing /api/meters/aggregates

The log revealed the wealth of data that is available …

2020-07-31 23:12:48,043 DEBUG    PowerWall  PowerWall Request response: {"site":{"last_communication_time":"2020-07-31T23:12:47.889536541+01:00","instant_power":4.8070068359375,"instant_reactive_power":169.26604461669922,"instant_apparent_power":169.33428824341266,"frequency":49.95012283325195,"energy_exported":1101936.5047250446,"energy_imported":876557.3925028223,"instant_average_voltage":124.1665776570638,"instant_total_current":0,"i_a_current":0,"i_b_current":0,"i_c_current":0,"timeout":1500000000},"battery":{"last_communication_time":"2020-07-31T23:12:47.890245881+01:00","instant_power":2130,"instant_reactive_power":-20,"instant_apparent_power":2130.0938946440833,"frequency":49.969,"energy_exported":319410,"energy_imported":393570,"instant_average_voltage":248.33333333333334,"instant_total_current":-50.3,"i_a_current":0,"i_b_current":0,"i_c_current":0,"timeout":1500000000},"load":{"last_communication_time":"2020-07-31T23:12:47.889536541+01:00","instant_power":2147.7866236629907,"instant_reactive_power":151.9973041128399,"instant_apparent_power":2153.158275938683,"frequency":49.95012283325195,"energy_exported":0,"energy_imported":-299539.11222222226,"instant_average_voltage":124.1665776570638,"instant_total_current":17.297622791819002,"i_a_current":0,"i_b_current":0,"i_c_current":0,"timeout":1500000000},"solar":{"last_communication_time":"2020-07-31T23:12:47.890574865+01:00","instant_power":0,"instant_reactive_power":0,"instant_apparent_power":0,"frequency":0,"energy_exported":0,"energy_imported":0,"instant_average_voltage":0,"instant_total_current":0,"i_a_current":0,"i_b_current":0,"i_c_current":0,"timeout":1500000000}}
2020-07-31 23:12:48,044 WARNING  PowerWall  PowerWall Percentage key not found

And the warning – PowerWall Percentage key not found – suggests the code operated OK to that point. And emonhub continued to operate.

I’d more than welcome any further suggestions.

And I’m happy to be a testbed of any ideas suggested by those not having a PowerWall.

TIA

From a browser, what URL do you use, and what do you get back?

Basic steps are

  1. Create return object (cargo)
  2. Send URL
  3. Load cargo with data returned from URL.
  4. return cargo

If you want to use 2 URLs, do steps 2 & 3 again so

  1. Create return object (cargo)
  2. Send URL 1
  3. Load cargo with data returned from URL 1.
  4. Send URL 2
  5. Load cargo with data returned from URL 2.
  6. return cargo