tahyonline / octopus-energy

Download smart meter data from Octopus Energy (UK)
MIT License
2 stars 1 forks source link

Cater for multiple MPANs #2

Closed vjirasek closed 2 years ago

vjirasek commented 2 years ago

Hi, I am Octopus Energy customer. I have both the gas and the electricity meters. As such, in the Developer section under my account I see two different MPANs.

Thinking how of the best way to update the code to accommodate this:

  1. Change JSON to be a list of accounts - then the CSV file naming code needs drastic change
  2. Run the code with parameter to use a different config file What do you think?
tahyonline commented 2 years ago

Hello @vjirasek

Can you try what you get with the gas MPAN?

Wondering if the code as is would work with the gas readings at all.

Originally I intended to get a list of MPANs and serials etc. and save into files like <MPAN>-<SERIAL>.csv, but as it turns out, there are no API endpoints to query these for an account.

So if you see any meaningful data coming out for the gas readings, then we can add multiple MPANs/serials in the config:

{
    "url": "https://api.octopus.energy",
    "apikey": "",
    "meters": [
        {"mpan": "",
        "serial": ""}
    ]
}
vjirasek commented 2 years ago

Hi, yes I get the meaningful data. The structure is same. Of course, the URL is slightly different:

line 254 would be: "/v1/gas-meter-points/%s/meters/%s/consumption" %

I also wonder if this structure of config.json would make more sense - it simplifies the code as you don't have to construct URL in the code, and also you can specify the file name and path as you want.

{ "accounts": [ { "name": "electricity", "url": "https://api.octopus.energy/v1/electricity-meter-points/xxxMPAN/meters/xxSERIAL/consumption/", "apikey": "API key", "file": "electricity-house.csv" }, { "name": "gas", "url": "https://api.octopus.energy/v1/gas-meter-points/gasMPAN/meters/gasSERIAL/consumption/", "apikey": "API key", "file": "gas-house.csv" } ] }

Then the main code could change to this below, and remove loading of config from the OctoRead object. The input into the OctoObject main routine is the dictionary for one account. This makes the config.json universal as I could have 10s of account there.

if name == "main": print("Download Octopus Energy Meter Readings\n")

read JSON config and assign it to dictionary object cfg

# read `config.json`
CONFIG_FILE = 'config.json'
with open(CONFIG_FILE, "r") as F:
    cfg = json.load(F)
for account in cfg["accounts"]:
    octoreader = OctoReader()
    octoreader.main(account)
tahyonline commented 2 years ago

Hello @vjirasek

I have pushed an update with the ability to add multiple meters. The configuration is similar to your suggestion above, but can still use the simple single-meter one, too.

I am working on a GUI, so that will change things anyway, but at least the command line version is also able to do multiple accounts or meters.

Please let me know if this works on your end -- I have only one meter and so both configurations work on that one, but your testing on the two meters would be much appreciated.

Kind regards, Thomas

vjirasek commented 2 years ago

Hi Thomas, the code works (with minor update) and downloads electricity and gas. I had to update the init function and completely removed the logic between simple and advanced. Even when you have just one account use the same config, just one account. Makes the code simple.

def init(self): """ Sets up the new object

    Reads the configuration file,
    checks that required settings are in the configuration,
    initialises attributes.
    """
    try:
        # read `config.json`
        with open("config.json", "r") as F:
            cfg = json.load(F)

        self.accounts = []

        for acfg in cfg["accounts"]:
            missing = []
            for required_config in ["url", "apikey", "csv", "name"]:
                if required_config not in acfg:
                    missing.append(required_config)
            if len(missing):
                # abort if any missing
                missing = ", ".join(missing)
                _quit("Missing required configuration %s" % missing, 1)

            self.accounts.append({
                "name": acfg["name"],
                "apiurl": acfg["url"],
                "apikey": acfg["apikey"],
                "csv": acfg["csv"],
            })

        # set 'now' -- note that the API uses timezones
        self.now = datetime.now(LOCAL_TIMEZONE).replace(microsecond=0)
        # the time delta is the number of days to retrieve in one API request
        self.delta = timedelta(TIME_DELTA)
        if self.delta > timedelta(365):
            _quit("Source code constant error: time delta must be less than or equal to 365 days", 1)
        # each hour has two records, so each day has 48
        self.page_size = TIME_DELTA * 48

        self.cfg = cfg

    except Exception as ex:
        _quit("Error loading configuration file (config.json): " + str(ex), 1)

and you can use this as example for the config with two meters:

{ "accounts": [ { "name": "Electricity", "url": "https://api.octopus.energy/v1/electricity-meter-points/YYYYY/meters/XXXXXX/consumption/", "apikey": "sk_live_KHDUWLDJS", "csv": "../electricity.csv" }, { "name": "Gas", "url": "https://api.octopus.energy/v1/gas-meter-points/ZZZZZ/meters/RRRRR/consumption/", "apikey": "sk_live_KHDUWLDJS", "csv": "../gas.csv" } ] }

tahyonline commented 2 years ago

Ah, I see -- thanks @vjirasek

Well, I actually like the original config file (which I am using) so I am keeping it for now.

Will see how to move forward with the web UI and analytics, which way to go. I tend to want to make it so that no URL would need to be written by the user.

In any case, your config file should now work with the fixed code. Would appreciate if you could check...

Cheers, Thomas

vjirasek commented 2 years ago

Hi. All works great. Thanks for the update. Well done.

tahyonline commented 2 years ago

Cheers!