Skip to content

Commit

Permalink
Merge pull request #14 from videojedi/main
Browse files Browse the repository at this point in the history
libbi support
  • Loading branch information
CJNE authored Sep 21, 2023
2 parents 004c7f0 + e87819b commit c4b899e
Show file tree
Hide file tree
Showing 12 changed files with 196 additions and 25 deletions.
21 changes: 17 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,23 @@ loop = asyncio.get_event_loop()
loop.run_until_complete(get_data())
```

## libbi support
very early
reads a few values like State of Charge
gets the current status
## Libbi support
Very early and basic support of Libbi.

- Reads a few values such as State of Charge, DCPV CT
- Battery in and out energy
- Gets and sets the current status
- Change priority of Libbi

cli examples:
```
myenergi libbi show
myenergi libbi mode normal
myenergi libbi mode stop
myenergi libbi priority 1
myenergi libbi energy
```



## Credits
Expand Down
2 changes: 1 addition & 1 deletion pymyenergi/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.0.28
0.0.29
12 changes: 12 additions & 0 deletions pymyenergi/base_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,11 @@ async def fetch_history_data(
"ct4": 0,
"ct5": 0,
"ct6": 0,
"ive1": 0,
"ivi1": 0,
"bdp1": 0,
"bcp1": 0,
"pvp1": 0
}
if resolution == MINUTE:
url = f"/cgi-jday-{self.prefix}{self._serialno}-{date_from.year}-{date_from.month}-{date_from.day}-{date_from.hour}-0-{how_long}"
Expand Down Expand Up @@ -162,10 +167,17 @@ async def fetch_history_data(
"generated": round(energy_wh["gep"] / 1000, 2),
"grid_import": round(energy_wh["imp"] / 1000, 2),
"grid_export": round(energy_wh["exp"] / 1000, 2),
"battery_charge": round(energy_wh["bcp1"] / 1000, 2),
"battery_discharge": round(energy_wh["bdp1"] / 1000, 2),
"inverter_export": round(energy_wh["ive1"] /1000, 2),
"inverter_import": round(energy_wh["ivi1"] /1000, 2),
"device_boosted": device_boosted,
"device_green": device_green,
"device_total": device_boosted + device_green
}
if resolution == MINUTE:
return_data["pv_total"] = round(energy_wh["pvp1"] / 1000, 2)

for i in range(6):
key = f"ct{i+1}"
if hasattr(self, key):
Expand Down
10 changes: 8 additions & 2 deletions pymyenergi/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,18 @@ async def main(args):
sys.exit(f"A mode must be specifed, one of {modes}")
await device.set_charge_mode(args.arg[0])
print(f"Charging was set to {args.arg[0].capitalize()}")
elif args.action == "mode" and args.command in [EDDI, LIBBI]:
elif args.action == "mode" and args.command == EDDI:
if len(args.arg) < 1 or args.arg[0].capitalize() not in EDDI_MODES:
modes = ", ".join(EDDI_MODES)
sys.exit(f"A mode must be specifed, one of {modes}")
await device.set_operating_mode(args.arg[0])
print(f"Operating mode was set to {args.arg[0].capitalize()}")
elif args.action == "mode" and args.command == LIBBI:
if len(args.arg) < 1 or args.arg[0].capitalize() not in LIBBI_MODES:
modes = ", ".join(LIBBI_MODES)
sys.exit(f"A mode must be specifed, one of {modes}")
await device.set_operating_mode(args.arg[0])
print(f"Operating mode was set to {args.arg[0].capitalize()}")
elif args.action == "mingreen" and args.command == ZAPPI:
if len(args.arg) < 1:
sys.exit("A minimum green level must be provided")
Expand Down Expand Up @@ -204,7 +210,7 @@ def cli():
LIBBI, help="use libbi --help for available commands"
)
subparser_libbi.add_argument("-s", "--serial", dest="serial", default=None)
subparser_libbi.add_argument("action", choices=["show","mode","priority"])
subparser_libbi.add_argument("action", choices=["show","mode","priority","energy"])
subparser_libbi.add_argument("arg", nargs="*")

args = parser.parse_args()
Expand Down
1 change: 1 addition & 0 deletions pymyenergi/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ def power_charging(self):
def power_battery(self):
"""Battery total power"""
return self._totals.get(CT_BATTERY, 0)


def find_device_name(self, key, default_value):
"""Find device or site name"""
Expand Down
50 changes: 37 additions & 13 deletions pymyenergi/libbi.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@
5:'Charging',
6:'Discharging',
7:'Duration Charging',
101:'Idle?',
102:'102',
104:'104' }
234:'Calibration Charge' }

LIBBI_MODES = ["Stopped","Normal"]
LIBBI_MODE_NAMES = ["STOP", "BALANCE"]

class Libbi(BaseDevice):
"""Libbi Client for myenergi API."""
Expand All @@ -32,8 +34,6 @@ def __init__(self, connection: Connection, serialno, data={}) -> None:
@property
def kind(self):
return LIBBI



@property
def status(self):
Expand All @@ -43,6 +43,11 @@ def status(self):
return STATES[n]
else:
return n

@property
def local_mode(self):
"""Get current known status"""
return self._data.get("lmo", 1)

@property
def prefix(self):
Expand All @@ -52,7 +57,7 @@ def prefix(self):
def ct_keys(self):
"""Return CT key names that are not none"""
keys = {}
for i in range(3):
for i in range(6):
ct = getattr(self, f"ct{i+1}")
if ct.name_as_key == "ct_none":
continue
Expand Down Expand Up @@ -133,8 +138,31 @@ def battery_size(self):
def inverter_size(self):
"""Inverter size in kwh"""
return self._data.get("mic", 0) /1000


@property
def grid_import(self):
"""Grid import from history data"""
return self.history_data.get("grid_import", 0)

@property
def grid_export(self):
"""Grid export from history data"""
return self.history_data.get("grid_export", 0)

@property
def battery_charge(self):
"""Battery charge from history data"""
return self.history_data.get("battery_charge", 0)

@property
def battery_discharge(self):
"""Battery discharge from history data"""
return self.history_data.get("battery_discharge", 0)

@property
def generated(self):
"""Solar generation from history data"""
return self.history_data.get("generated", 0)

@property
def prefix(self):
Expand All @@ -143,15 +171,12 @@ def prefix(self):

async def set_operating_mode(self, mode: str):
"""Stopped or normal mode"""
print(f"set mode")
print("current mode", self._data["lmo"])
mode_int = LIBBI_MODES.index(mode.capitalize())
await self._connection.get(
f"/cgi-libbi-mode-{self.prefix}{self._serialno}-{mode_int}"
)
if mode_int == 0:
self._data["sta"] = 0
else:
self._data["sta"] = 1
self._data["lmo"] = LIBBI_MODE_NAMES[mode_int]
return True

async def set_priority(self, priority):
Expand All @@ -177,9 +202,10 @@ def show(self, short_format=False):
ret = ret + f"Battery size: {self.battery_size}kWh\n"
ret = ret + f"Inverter size: {self.inverter_size}kWh\n"
ret = ret + f"State of Charge: {self.state_of_charge}%\n"
ret = ret + f"Generated: {self.power_generated}W\n"
ret = ret + f"Generating: {self.power_generated}W\n"
ret = ret + f"Grid: {self.power_grid}W\n"
ret = ret + f"Status : {self.status}\n"
ret = ret + f"Local Mode : {self.local_mode}\n"
ret = ret + f"CT 1 {self.ct1.name} {self.ct1.power}W phase {self.ct1.phase}\n"
ret = ret + f"CT 2 {self.ct2.name} {self.ct2.power}W phase {self.ct2.phase}\n"
ret = ret + f"CT 3 {self.ct3.name} {self.ct3.power}W phase {self.ct3.phase}\n"
Expand All @@ -188,6 +214,4 @@ def show(self, short_format=False):
ret = ret + f"CT 6 {self.ct6.name} {self.ct6.power}W phase {self.ct6.phase}\n"
for key in self.ct_keys:
ret = ret + f"Energy {key} {self.history_data.get(key, 0)}Wh\n"


return ret
8 changes: 4 additions & 4 deletions requirements_test.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
-r requirements_dev.txt
pytest-sugar==0.9.4
pytest-timeout==1.4.2
pytest-sugar==0.9.7
pytest-timeout==2.1.0
pytest-httpx
pytest-xdist==2.2.1
pytest==6.2.4
pytest-xdist==3.3.1
pytest==7.4.2
pytest-asyncio
9 changes: 9 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,15 @@ def harvi_fetch_data_fixture():
yield


@pytest.fixture(name="libbi_fetch_data_fixture")
def libbi_fetch_data_fixture():
"""Mock data from client.fetch_data()"""
with patch(
"pymyenergi.libbi.Libbi.fetch_data", return_value=load_fixture_json("libbi")
):
yield


# @pytest.fixture
# def eddi_connection_mock():
# with patch("pymyenergi.eddi.Eddi._connection"):
Expand Down
46 changes: 46 additions & 0 deletions tests/fixtures/client.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
{ "key": "Z17005900", "val": "Test Zappi 2" },
{ "key": "H10645200", "val": "Test Harvi 1" },
{ "key": "H10644500", "val": "Test Harvi 2" },
{ "key": "L24047164", "val": "Test Libbi 1" },
{ "key": "siteName", "val": "Test Site" }
]
},
Expand Down Expand Up @@ -154,6 +155,51 @@
}
]
},
{
"libbi": [
{
"sno": 24047164,
"dat": "20-09-2023",
"tim": "14: 53: 03",
"ectp1": -457,
"ectp2": -72,
"ectp3": 0,
"ectt1": "Internal Load",
"ectt2": "Grid",
"ectt3": "None",
"ectp4": 0,
"ectp5": 320,
"ectt4": "None",
"ectt5": "DCPV",
"ectt6": "None",
"dst": 1,
"tz": 0,
"lmo": "BALANCE",
"sta": 6,
"frq": 49.92,
"pri": 1,
"soc": 45,
"isp": "True",
"pha": 1,
"vol": 2350,
"mbc": 20400,
"mic": 5000,
"gen": 320,
"grd": -38,
"div": -457,
"ect1p": 1,
"ect2p": 1,
"ect3p": 1,
"batteryDischargingBoost": "False",
"pvDirectlyConnected": "True",
"g100LockoutState": "NONE",
"cmt": 254,
"fwv": "3702S5.041",
"newAppAvailable": "False",
"newBootloaderAvailable": "False"
}
]
},
{
"asn": "s8.myenergi.net",
"fwv": "3401S3077"
Expand Down
41 changes: 41 additions & 0 deletions tests/fixtures/libbi.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"sno": 24047164,
"dat": "20-09-2023",
"tim": "14: 53: 03",
"ectp1": -457,
"ectp2": -72,
"ectp3": 0,
"ectt1": "Internal Load",
"ectt2": "Grid",
"ectt3": "None",
"ectp4": 0,
"ectp5": 320,
"ectt4": "None",
"ectt5": "DCPV",
"ectt6": "None",
"dst": 1,
"tz": 0,
"lmo": "BALANCE",
"sta": 6,
"frq": 49.92,
"pri": 1,
"soc": 45,
"isp": "True",
"pha": 1,
"vol": 2350,
"mbc": 20400,
"mic": 5000,
"gen": 320,
"grd": -38,
"div": -457,
"ect1p": 1,
"ect2p": 1,
"ect3p": 1,
"batteryDischargingBoost": "False",
"pvDirectlyConnected": "True",
"g100LockoutState": "NONE",
"cmt": 254,
"fwv": "3702S5.041",
"newAppAvailable": "False",
"newBootloaderAvailable": "False"
}
10 changes: 9 additions & 1 deletion tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from pymyenergi.eddi import Eddi
from pymyenergi.harvi import Harvi
from pymyenergi.zappi import Zappi
from pymyenergi.libbi import Libbi

# All test coroutines will be treated as marked.
pytestmark = pytest.mark.asyncio
Expand All @@ -25,7 +26,7 @@ async def test_init_error(error_on_client_fetch_data):
async def test_get_all_devices(client_fetch_data_fixture):
client = MyenergiClient(conn)
devices = await client.get_devices()
assert len(devices) == 5
assert len(devices) == 6


async def test_get_eddi_devices(client_fetch_data_fixture):
Expand Down Expand Up @@ -62,3 +63,10 @@ async def test_1p_harvi_eddi_solar_battery(client_1p_zappi_harvi_solar_battery_f
assert client.power_battery == 3000
assert client.power_charging == 2000
assert client.consumption_home == 16000


async def test_get_libbi_devices(client_fetch_data_fixture):
client = MyenergiClient(conn)
devices = await client.get_devices("libbi")
assert len(devices) == 1
assert isinstance(devices[0], Libbi)
11 changes: 11 additions & 0 deletions tests/test_libbi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import pytest
from pymyenergi.libbi import Libbi

pytestmark = pytest.mark.asyncio


async def test_refresh(libbi_fetch_data_fixture):
"""Test Libbi data"""
libbi = Libbi({}, 24047164)
await libbi.refresh()
assert libbi.serial_number == 24047164

0 comments on commit c4b899e

Please sign in to comment.