From 249c97ce8e6f20c35bef47ab68180d4c96f322dd Mon Sep 17 00:00:00 2001 From: Dan Hamik Date: Tue, 26 Nov 2024 10:01:05 -0600 Subject: [PATCH] initial commit --- .gitea/workflows/check-build.yml | 29 ++++ .gitea/workflows/push-build.yml | 42 +++++ Dockerfile | 11 ++ docker-compose.yml | 9 ++ enphase.py | 261 +++++++++++++++++++++++++++++++ init.sh | 2 + requirements.txt | 5 + 7 files changed, 359 insertions(+) create mode 100644 .gitea/workflows/check-build.yml create mode 100644 .gitea/workflows/push-build.yml create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 enphase.py create mode 100644 init.sh create mode 100644 requirements.txt diff --git a/.gitea/workflows/check-build.yml b/.gitea/workflows/check-build.yml new file mode 100644 index 0000000..35ac4f3 --- /dev/null +++ b/.gitea/workflows/check-build.yml @@ -0,0 +1,29 @@ +Name: Commit Checks +run-name: ${{ gitea.actor }} +on: + push: + branches: + - "**" + - "!main" + +jobs: + Plan: + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./ + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 +# + - name: List Contents + id: ls + run: ls -al + + + - name: Deploy + run: | + echo "${{ secrets.GITEASECRET_TOKEN }}" | docker login git.hamik.net -u paradizelost --password-stdin + docker build -t git.hamik.net/paradizelost/enphase:dev . + docker push git.hamik.net/paradizelost/enphse:dev \ No newline at end of file diff --git a/.gitea/workflows/push-build.yml b/.gitea/workflows/push-build.yml new file mode 100644 index 0000000..3590299 --- /dev/null +++ b/.gitea/workflows/push-build.yml @@ -0,0 +1,42 @@ +Name: Container Build Push +run-name: ${{ gitea.actor }} +on: + pull_request: + types: [closed] + +jobs: + Plan: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: List Contents + id: ls + run: ls -al + + - name: Deploy + run: | + echo "${{ secrets.GITEASECRET_TOKEN }}" | docker login git.hamik.net -u paradizelost --password-stdin + docker build -t git.hamik.net/paradizelost/librarynotices:latest . + docker push git.hamik.net/paradizelost/librarynotices + Deploy: + needs: [Plan] # + runs-on: librarynoticehost + defaults: + run: + working-directory: /docker/librarynotices + steps: + - name: Destroy Container + id: down + run: docker compose down + + - name: Pull Updates + id: pull + run: docker compose pull + + - name: Start Containers + id: start + run: docker compose up -d \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6911d02 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM debian:bullseye-slim +# +RUN apt update && apt install python3 python3-pip -y +COPY enphase.py /usr/local/bin/ +COPY requirements.txt / +COPY init.sh / +RUN chmod +x /init.sh +RUN chmod +x /usr/local/bin/enphase.py +RUN pip3 install -r /requirements.txt +RUN apt clean +CMD ["/init.sh"] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..98b2516 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,9 @@ +services: + libnotices: + image: git.hamik.net/paradizelost/enphase:latest + container_name: libnotices + restart: always + ports: + - "80:80" + volumes: + - "./credentials:/var/www/.aws/credentials" \ No newline at end of file diff --git a/enphase.py b/enphase.py new file mode 100644 index 0000000..fce888f --- /dev/null +++ b/enphase.py @@ -0,0 +1,261 @@ +#!/usr/bin/python3 +import requests +#from tabulate import tabulate +import json +import time +import os +from datetime import datetime +from json import JSONEncoder +from urllib3.exceptions import InsecureRequestWarning + +class ElectricalData: + def __init__(self, data): + self.production = [ProductionData(prod_data) for prod_data in data.get("production", [])] + self.consumption = [ConsumptionData(cons_data) for cons_data in data.get("consumption", [])] + self.storage = [StorageData(storage_data) for storage_data in data.get("storage", [])] + def __str__(self): + return f"Type: {self.type}, Active Count: {self.active_count}, Reading Time: {self.reading_time}, W Now: {self.w_now}, WH Lifetime: {self.wh_lifetime}" + def to_json(self): + return { + "production": [prod.to_json() for prod in self.production], + "consumption": [cons.to_json() for cons in self.consumption], + "storage": [storage.to_json() for storage in self.storage] + } +class ProductionData: + def __init__(self, prod_data): + self.type = prod_data.get("type", "") + self.active_count = prod_data.get("activeCount", 0) + self.reading_time = prod_data.get("readingTime", 0) + self.w_now = prod_data.get("wNow", 0) + self.wh_lifetime = prod_data.get("whLifetime", 0) + self.wh_today = prod_data.get("whToday",0) + + def __str__(self): + production_str = "\n".join(str(prod) for prod in self.production) + consumption_str = "\n".join(str(cons) for cons in self.consumption) + storage_str = "\n".join(str(storage) for storage in self.storage) + return f"Production:\n{production_str}\n\nConsumption:\n{consumption_str}\n\nStorage:\n{storage_str}" + def to_json(self): + return { + "type": self.type, + "active_count": self.active_count, + "reading_time": self.reading_time, + "w_now": self.w_now, + "wh_lifetime": float(self.wh_lifetime), # Convert to float explicitly + "wh_today": float(self.wh_today) + } +class ConsumptionData: + def __init__(self, cons_data): + self.type = cons_data.get("type", "") + self.active_count = cons_data.get("activeCount", 0) + self.measurement_type = cons_data.get("measurementType", "") + self.reading_time = cons_data.get("readingTime", 0) + self.w_now = cons_data.get("wNow", 0) + self.wh_lifetime = cons_data.get("whLifetime", 0) + self.varh_lead_lifetime = cons_data.get("varhLeadLifetime", 0) + self.varh_lag_lifetime = cons_data.get("varhLagLifetime", 0) + self.vah_lifetime = cons_data.get("vahLifetime", 0) + self.rms_current = cons_data.get("rmsCurrent", 0) + self.rms_voltage = cons_data.get("rmsVoltage", 0) + self.react_pwr = cons_data.get("reactPwr", 0) + self.apprnt_pwr = cons_data.get("apprntPwr", 0) + self.pwr_factor = cons_data.get("pwrFactor", 0) + self.wh_today = cons_data.get("whToday", 0) + self.wh_last_seven_days = cons_data.get("whLastSevenDays", 0) + self.vah_today = cons_data.get("vahToday", 0) + self.varh_lead_today = cons_data.get("varhLeadToday", 0) + self.varh_lag_today = cons_data.get("varhLagToday", 0) + self.lines = [ElectricalLine(line_data) for line_data in cons_data.get("lines", [])] + def __str__(self): + lines_str = "\n".join(str(line) for line in self.lines) + return f"Type: {self.type}, Active Count: {self.active_count}, Measurement Type: {self.measurement_type}, Reading Time: {self.reading_time}, W Now: {self.w_now}, WH Lifetime: {self.wh_lifetime}, Lines: {lines_str}" + def to_json(self): + return { + "type": self.type, + "active_count": self.active_count, + "measurement_type": self.measurement_type, + "reading_time": self.reading_time, + "w_now": self.w_now, + "wh_lifetime": self.wh_lifetime, + "varh_lead_lifetime": self.varh_lead_lifetime, + "varh_lag_lifetime": self.varh_lag_lifetime, + "vah_lifetime": self.vah_lifetime, + "rms_current": self.rms_current, + "rms_voltage": self.rms_voltage, + "react_pwr": self.react_pwr, + "apprnt_pwr": self.apprnt_pwr, + "pwr_factor": self.pwr_factor, + "wh_today": self.wh_today, + "wh_last_seven_days": self.wh_last_seven_days, + "vah_today": self.vah_today, + "varh_lead_today": self.varh_lead_today, + "varh_lag_today": self.varh_lag_today, + "lines": [line.to_json() for line in self.lines] + } +class StorageData: + def __init__(self, storage_data): + self.type = storage_data.get("type", "") + self.active_count = storage_data.get("activeCount", 0) + self.reading_time = storage_data.get("readingTime", 0) + self.w_now = storage_data.get("wNow", 0) + self.wh_now = storage_data.get("whNow", 0) + self.state = storage_data.get("state", "") + def __str__(self): + return f"Type: {self.type}, Active Count: {self.active_count}, Reading Time: {self.reading_time}, W Now: {self.w_now}, WH Now: {self.wh_now}, State: {self.state}" + def to_json(self): + return { + "type": self.type, + "active_count": self.active_count, + "reading_time": self.reading_time, + "w_now": self.w_now, + "wh_now": self.wh_now, + "state": self.state + } +class ElectricalLine: + def __init__(self, line_data): + self.w_now = line_data.get("wNow", 0) + self.wh_lifetime = line_data.get("whLifetime", 0) + self.varh_lead_lifetime = line_data.get("varhLeadLifetime", 0) + self.varh_lag_lifetime = line_data.get("varhLagLifetime", 0) + self.vah_lifetime = line_data.get("vahLifetime", 0) + self.rms_current = line_data.get("rmsCurrent", 0) + self.rms_voltage = line_data.get("rmsVoltage", 0) + self.react_pwr = line_data.get("reactPwr", 0) + self.apprnt_pwr = line_data.get("apprntPwr", 0) + self.pwr_factor = line_data.get("pwrFactor", 0) + self.wh_today = line_data.get("whToday", 0) + self.wh_last_seven_days = line_data.get("whLastSevenDays", 0) + self.vah_today = line_data.get("vahToday", 0) + self.varh_lead_today = line_data.get("varhLeadToday", 0) + self.varh_lag_today = line_data.get("varhLagToday", 0) + def __str__(self): + return f"W Now: {self.w_now}, WH Lifetime: {self.wh_lifetime}, Varh Lead Lifetime: {self.varh_lead_lifetime}, Varh Lag Lifetime: {self.varh_lag_lifetime}, VAh Lifetime: {self.vah_lifetime}, RMS Current: {self.rms_current}, RMS Voltage: {self.rms_voltage}, React Power: {self.react_pwr}, Apparent Power: {self.apprnt_pwr}, Power Factor: {self.pwr_factor}, WH Today: {self.wh_today}, WH Last Seven Days: {self.wh_last_seven_days}, VAh Today: {self.vah_today}, Varh Lead Today: {self.varh_lead_today}, Varh Lag Today: {self.varh_lag_today}" + def to_json(self): + return { + "w_now": self.w_now, + "wh_lifetime": self.wh_lifetime, + "varh_lead_lifetime": self.varh_lead_lifetime, + "varh_lag_lifetime": self.varh_lag_lifetime, + "vah_lifetime": self.vah_lifetime, + "rms_current": self.rms_current, + "rms_voltage": self.rms_voltage, + "react_pwr": self.react_pwr, + "apprnt_pwr": self.apprnt_pwr, + "pwr_factor": self.pwr_factor, + "wh_today": self.wh_today, + "wh_last_seven_days": self.wh_last_seven_days, + "vah_today": self.vah_today, + "varh_lead_today": self.varh_lead_today, + "varh_lag_today": self.varh_lag_today + } +class inverter(): + def __init__(self,serialnumber,lastreportdate,devtype,lastreportwatts,maxreportwatts): + self.serialnumber=str(serialnumber) + self.lastreportdate=int(lastreportdate) + self.devtype=int(devtype) + self.lastreportwatts=int(lastreportwatts) + self.maxreportwatts=int(maxreportwatts) + def print(self): + print(self.serialnumber, self.lastreportdate, self.devtype, self.lastreportwatts, self.maxreportwatts) +enphasetoken = os.environ["ENPHASE_TOKEN"] +enphaseurl = os.environ["ENPHASE_URL"] +infdbuser = os.environ["INFLUX_USER"] +infdbpass = os.environ["INFLUX_PASSWORD"] +influxuri = os.environ["INFLUX_URI"] +infdbuser = os.environ["INFLUX_USER"] + + +while True: + inverterlist=[] + session = requests.Session() + session.verify = False + session.headers.update({"Authorization":"Bearer "+enphasetoken}) + details = enphaseurl + "/production.json?details=1" + inverteruri= enphaseurl + "/api/v1/production/inverters" + mainresponse = session.get(details,verify=False).json() + inverterresponse = session.get(inverteruri,verify=False).json() + #print(json.dumps(mainresponse,indent=1)) + for inverterdata in inverterresponse: + myinverter = inverter(inverterdata['serialNumber'],inverterdata['lastReportDate'],inverterdata['devType'],inverterdata['lastReportWatts'],inverterdata['maxReportWatts']) + inverterlist.append(myinverter) + #print(json.dumps(response.json(),indent=1)) + #for myinverter in inverterlist: + #pass + # myinverter.print() + print(json.dumps(mainresponse,indent=1)) + maindata = ElectricalData(mainresponse) + influxsession = requests.Session() + influxsession.verify = False + influxsession.auth = (infdbuser, infdbpass) + #influxsession.post( url=influxuri, data="NetConsumption value=10") + measurements = "" + totalproduction=0 + for myinverter in inverterlist: + totalproduction+=myinverter.lastreportwatts + measurements += f"Production,Panel={myinverter.serialnumber} value={myinverter.lastreportwatts}\n" + measurements += f"MaxProduction,Panel={myinverter.serialnumber} value={myinverter.maxreportwatts}\n" + measurements += f"TotalProduction value={totalproduction}\n" + measurements += f"TodayProduction value={maindata.production[1].wh_today}\n" + consumptionTypes=[ + "Total", + "Net" + ] + currentconsumption=0 + for constype in consumptionTypes: + if constype=='Total': + consindex=0 + elif constype=='Net': + consindex=1 + linenum=0 + consdata=maindata.consumption[consindex] + for line in consdata.lines: + measurements += f"{constype}_Leg_w_now,Leg={str(linenum)} value={line.w_now}\n" + measurements += f"{constype}_Leg_wh_lifetime,Leg={str(linenum)} value={line.wh_lifetime}\n" + measurements += f"{constype}_Leg_varh_lead_lifetime,Leg={str(linenum)} value={line.varh_lead_lifetime}\n" + measurements += f"{constype}_Leg_varh_lag_lifetime,Leg={str(linenum)} value={line.varh_lag_lifetime}\n" + measurements += f"{constype}_Leg_vah_lifetime,Leg={str(linenum)} value={line.vah_lifetime}\n" + measurements += f"{constype}_Leg_rms_current,Leg={str(linenum)} value={line.rms_current}\n" + measurements += f"{constype}_Leg_rms_voltage,Leg={str(linenum)} value={line.rms_voltage}\n" + measurements += f"{constype}_Leg_react_pwr,Leg={str(linenum)} value={line.react_pwr}\n" + measurements += f"{constype}_Leg_apprnt_pwr,Leg={str(linenum)} value={line.apprnt_pwr}\n" + measurements += f"{constype}_Leg_pwr_factor,Leg={str(linenum)} value={line.pwr_factor}\n" + measurements += f"{constype}_Leg_wh_today,Leg={str(linenum)} value={line.wh_today}\n" + measurements += f"{constype}_Leg_wh_last_seven_days,Leg={str(linenum)} value={line.wh_last_seven_days}\n" + measurements += f"{constype}_Leg_vah_today,Leg={str(linenum)} value={line.vah_today}\n" + measurements += f"{constype}_Leg_varh_lead_today,Leg={str(linenum)} value={line.varh_lead_today}\n" + measurements += f"{constype}_Leg_varh_lag_today,Leg={str(linenum)} value={line.varh_lag_today}\n" + linenum +=1 + measurements += f"{constype}_w_now value={consdata.w_now}\n" + measurements += f"{constype}_wh_lifetime value={consdata.wh_lifetime}\n" + measurements += f"{constype}_varh_lead_lifetime value={consdata.varh_lead_lifetime}\n" + measurements += f"{constype}_varh_lag_lifetime value={consdata.varh_lag_lifetime}\n" + measurements += f"{constype}_vah_lifetime value={consdata.vah_lifetime}\n" + measurements += f"{constype}_rms_current value={consdata.rms_current}\n" + measurements += f"{constype}_rms_voltage value={consdata.rms_voltage}\n" + measurements += f"{constype}_react_pwr value={consdata.react_pwr}\n" + measurements += f"{constype}_apprnt_pwr value={consdata.apprnt_pwr}\n" + measurements += f"{constype}_pwr_factor value={consdata.pwr_factor}\n" + measurements += f"{constype}_vah_lifetime value={consdata.vah_lifetime}\n" + measurements += f"{constype}_wh_today value={consdata.wh_today}\n" + measurements += f"{constype}_wh_last_seven_days value={consdata.wh_last_seven_days}\n" + measurements += f"{constype}_vah_today value={consdata.vah_today}\n" + measurements += f"{constype}_varh_lead_today value={consdata.varh_lead_today}\n" + measurements += f"{constype}_varh_lag_today value={consdata.varh_lag_today}\n" + if consindex==1: + currentconsumption=consdata.w_now + if currentconsumption > 0: + totalexport = 0 + NetConsumption=currentconsumption + elif currentconsumption < 0: + totalexport = - int(currentconsumption) + NetConsumption=0 + else: + totalexport=0 + NetConsumption=0 + productionconsumed = maindata.production[1].w_now - totalexport + measurements+= f"TotalExport value={totalexport}\n" + measurements+= f"NetConsumption value={NetConsumption}\n" + measurements+= f"ProductionConsumed value={productionconsumed}\n" + #print(str(productionconsumed)) + influxsession.post( url=influxuri, data=measurements) + time.sleep(5) diff --git a/init.sh b/init.sh new file mode 100644 index 0000000..a507f5b --- /dev/null +++ b/init.sh @@ -0,0 +1,2 @@ +#!/bin/bash +/usr/local/bin/enphase.py \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e6593da --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +boto==2.49.0 +boto3==1.26.27 +botocore==1.29.27 +requests==2.28.1 +tabulate==0.8.9 \ No newline at end of file