From 3742080094d48c0ecd91a8c6744ea6d5c34d70ea Mon Sep 17 00:00:00 2001 From: Klaas Schoute Date: Wed, 1 Sep 2021 01:08:31 +0200 Subject: [PATCH] Add new data entities to dataclass model (#16) * Update data in models * Add exception for wrong data source * Add extra tests for pytest --- omnikinverter/__init__.py | 3 +- omnikinverter/exceptions.py | 4 ++ omnikinverter/models.py | 35 +++++++++++------ test_output.py | 4 +- tests/fixtures/status.json | 3 +- tests/fixtures/wrong_status.js | 1 + tests/{test_input.py => test_omnik.py} | 54 +++++++++++++++++++++++--- 7 files changed, 84 insertions(+), 20 deletions(-) create mode 100644 tests/fixtures/wrong_status.js rename tests/{test_input.py => test_omnik.py} (52%) diff --git a/omnikinverter/__init__.py b/omnikinverter/__init__.py index 53782af..8ed1b72 100644 --- a/omnikinverter/__init__.py +++ b/omnikinverter/__init__.py @@ -1,6 +1,6 @@ """Asynchronous Python client for the Omnik Inverter.""" -from .models import Inverter +from .models import Inverter, OmnikInverterWrongSourceError from .omnikinverter import ( OmnikInverter, OmnikInverterConnectionError, @@ -12,4 +12,5 @@ "OmnikInverter", "OmnikInverterError", "OmnikInverterConnectionError", + "OmnikInverterWrongSourceError", ] diff --git a/omnikinverter/exceptions.py b/omnikinverter/exceptions.py index 0d507c5..0caf9ce 100644 --- a/omnikinverter/exceptions.py +++ b/omnikinverter/exceptions.py @@ -7,3 +7,7 @@ class OmnikInverterError(Exception): class OmnikInverterConnectionError(OmnikInverterError): """Omnik Inverter connection exception.""" + + +class OmnikInverterWrongSourceError(OmnikInverterError): + """Omnik Inverter wrong data source url exception.""" diff --git a/omnikinverter/models.py b/omnikinverter/models.py index 9811233..f3fc213 100644 --- a/omnikinverter/models.py +++ b/omnikinverter/models.py @@ -6,6 +6,8 @@ from dataclasses import dataclass from typing import Any +from .exceptions import OmnikInverterWrongSourceError + @dataclass class Inverter: @@ -13,7 +15,9 @@ class Inverter: serial_number: str | None model: str | None - firmware: str | None + firmware_main: str | None + firmware_slave: str | None + solar_rated_power: int | None solar_current_power: int | None solar_energy_today: float | None solar_energy_total: float | None @@ -31,10 +35,12 @@ def from_json(data: dict[str, Any]) -> Inverter: data = json.loads(data) return Inverter( - serial_number=data["g_sn"], + serial_number=data["i_sn"], model=data["i_modle"], - firmware=data["i_ver_m"], - solar_current_power=data["i_pow_n"], + firmware_main=data["i_ver_m"], + firmware_slave=data["i_ver_s"], + solar_rated_power=int(data["i_pow"]), + solar_current_power=int(data["i_pow_n"]), solar_energy_today=float(data["i_eday"]), solar_energy_total=float(data["i_eall"]), ) @@ -56,17 +62,24 @@ def get_values(position): else: matches = re.search(r'(?<=myDeviceArray\[0\]=").*?(?=";)', data) - data_list = matches.group(0).split(",") - if position in [5, 6, 7]: - if position == 5: - return int(data_list[position]) - return float(data_list[position]) / 100 - return data_list[position] + try: + data_list = matches.group(0).split(",") + if position in [4, 5, 6, 7]: + if position in [4, 5]: + return int(data_list[position]) + return float(data_list[position]) / 100 + return data_list[position] + except AttributeError as exception: + raise OmnikInverterWrongSourceError( + "Your inverter has no data source from a javascript file." + ) from exception return Inverter( serial_number=get_values(0), model=get_values(3), - firmware=get_values(2), + firmware_main=get_values(1), + firmware_slave=get_values(2), + solar_rated_power=get_values(4), solar_current_power=get_values(5), solar_energy_today=get_values(6), solar_energy_total=get_values(7), diff --git a/test_output.py b/test_output.py index 48c7212..484e377 100644 --- a/test_output.py +++ b/test_output.py @@ -17,7 +17,9 @@ async def main(): print() print(f"Serial Number: {inverter.serial_number}") print(f"Model: {inverter.model}") - print(f"Firmware: {inverter.firmware}") + print(f"Firmware Main: {inverter.firmware_main}") + print(f"Firmware Slave: {inverter.firmware_slave}") + print(f"Rated Power: {inverter.solar_rated_power}") print(f"Current Power: {inverter.solar_current_power}") print(f"Energy Production Today: {inverter.solar_energy_today}") print(f"Energy Production Total: {inverter.solar_energy_total}") diff --git a/tests/fixtures/status.json b/tests/fixtures/status.json index 5d4f9ed..f7d1b12 100644 --- a/tests/fixtures/status.json +++ b/tests/fixtures/status.json @@ -1,6 +1,5 @@ { - "g_sn":"12345678910", - "g_ver":"VER:ME-111001-V1.0.6(2015-10-16)", + "i_sn":"12345678910", "i_ver_m":"V1.25Build23261", "i_ver_s":"V1.40Build52927", "i_modle":"omnik2000tl2", diff --git a/tests/fixtures/wrong_status.js b/tests/fixtures/wrong_status.js new file mode 100644 index 0000000..2ff82b5 --- /dev/null +++ b/tests/fixtures/wrong_status.js @@ -0,0 +1 @@ +var txt;var opt;function initPage(){txt=window.parent.reList().page.status;opt=window.parent.reList().page.option;for(var A=1;A<13;A++){inner("t"+A,txt["t"+A]);inner("p1_"+A,txt.pg1[A]);inner("p2_"+A,txt.pg2[A]);inner("p3_"+A,txt.pg3[A]);inner("p4_"+A,txt.pg4[A])}inner("p3_1_1",txt.pg1[6]);inner("p3_2_1",txt.pg1[6]);inner("p3_1_2",txt.pg3[6]);inner("p3_2_2",txt.pg3[6]);inner("p3_1_3",txt.pg3[4]);inner("p3_2_3",txt.pg3[4]);inner("p3_2_4",txt.pg3[5]);inner("p3_2_5",txt.pg3[7]);changeFont();child_getH()}function initData(){child_getH();ajax("status.json?CMD=inv_query&rand="+Math.random())}function ajaxReturn(B){if(B.time!=null){inner("gtype",txt.pg1["s3"]);inner("gsn",B.g_sn);inner("ver",B.g_ver);inner("time",new Date(B.time*1000).toString());inner("worktime",B.g_time+" "+txt.pg1["s4"]);inner("error",B.g_err);inner("mac",B.mac);inner("auto_ip",enb_txt(B.auto_ip));inner("ip",B.ip);inner("sub",B.sub);inner("gate",B.gate);inner("auto_dns",enb_txt(B.auto_dns));inner("dns",B.dns);inner("dns_bak",B.dns_bak);inner("type",reInvBrand(B.i_type));if(B.i_num==null||B.i_num==0){}else{show("inv_data",true);inner("list",B.i_sn);var A=[B.i_sn,B.i_ver_m,B.i_ver_s,B.i_modle,B.i_pow,B.i_pow_n,B.i_eday,B.i_eall,B.i_alarm,B.i_last_t];inv_data_show(A)}inner("ip_a",B.l_a_ip);inner("port_a",B.l_a_port);inner("agreement_a",agr_txt(9,B.l_a_agr));if(B.ip_b!=null){show("server_b",true);inner("ip_b",B.ip_b);inner("port_b",B.port_b);inner("domain_b",B.ipdm_b);inner("agreement_b",agr_txt(9,B.agr_b))}if(B.i_status!=null){if(B.i_status==0){show("inv_data",false)}else{show("inv_data",true)}}}if(B.conn_a!=null){initStatus("status_0",B.conn_a)}if(B.conn_b!=null){initStatus("status_1",B.conn_b)}}var sels=1;function upfold(A){var B=getCon("pg"+A);if(B.style.display=="none"){B.style.display="";getCon("p"+A).src="image/up.png"}else{B.style.display="none";getCon("p"+A).src="image/down.png"}child_getH();if(A==3&&B.style.display!="none"){ajax("s_ping.json?CMD=inv_query&server=110&rand="+Math.random())}}function inv_data_show(A){var D=new Array("list","mver","sver","inv_tp","rate_p","now_p","day_e","all_e","alarm","uptime");if(A!=null){for(var B=1;B<10;B++){if(A[B]==""){if(isNaN(parseInt(A[B]))){inner(D[B],"---");continue}}var C=A[B];switch(B){case 4:C+=" W";break;case 5:if(C*1==4294967295){C="---"}else{C+=" W"}break;case 6:if(C*1==4294967295){C="---"}else{C+=" kWh"}break;case 7:if(C*1==4294967295){C="---"}else{C+=" kWh"}break;case 9:if(getCon("list").innerHTML=="---"){C="---"}else{C+=txt.pg2["s2"]}break}inner(D[B],C)}}}function initStatus(C,B){var A=txt.pg3["s1"];var D=txt.pg3["s2"];if(B==1){getCon(C).innerHTML=A}else{getCon(C).innerHTML=D}}function enb_txt(A){var B=txt.pg2["s4"];if(A==1){B=txt.pg2["s3"]}return B}function agr_txt(C,A){var B="";if(C==1){B=opt[1];if(A==1){B=opt[2]}}if(C==3){B=opt[3];if(A==1){B=opt[4]}}if(C==5){B=opt[5];if(A==1){B=opt[6]}}if(C==7){B=opt[7];if(A==1){B=opt[8]}}if(C==9){if(A==2){B=opt[11]}else{if(A==1){B=opt[10]}else{if(A==3){B=opt[12]}else{B=opt[9]}}}}return B}; diff --git a/tests/test_input.py b/tests/test_omnik.py similarity index 52% rename from tests/test_input.py rename to tests/test_omnik.py index 6ff1811..665f14f 100644 --- a/tests/test_input.py +++ b/tests/test_omnik.py @@ -1,8 +1,15 @@ """Test for retrieving information from the Omnik Inverter device.""" +import asyncio + import aiohttp import pytest -from omnikinverter import Inverter, OmnikInverter +from omnikinverter import ( + Inverter, + OmnikInverter, + OmnikInverterConnectionError, + OmnikInverterWrongSourceError, +) from . import load_fixtures @@ -24,9 +31,10 @@ async def test_js_input(aresponses): omnik = OmnikInverter(host="example.com", use_json=False, session=session) inverter: Inverter = await omnik.inverter() assert inverter - assert inverter.firmware == "V5.3-00157" - assert inverter.model == "omnik2000tl2" assert inverter.serial_number == "12345678910" + assert inverter.firmware_main == "NL2-V9.8-5931" + assert inverter.firmware_slave == "V5.3-00157" + assert inverter.model == "omnik2000tl2" assert inverter.solar_current_power == 1010 assert inverter.solar_energy_today == 4.88 assert inverter.solar_energy_total == 1053.19 @@ -49,9 +57,45 @@ async def test_json_input(aresponses): omnik = OmnikInverter(host="example.com", use_json=True, session=session) inverter: Inverter = await omnik.inverter() assert inverter - assert inverter.firmware == "V1.25Build23261" - assert inverter.model == "omnik2000tl2" assert inverter.serial_number == "12345678910" + assert inverter.firmware_slave == "V1.40Build52927" + assert inverter.firmware_main == "V1.25Build23261" + assert inverter.model == "omnik2000tl2" assert inverter.solar_current_power == 1225 assert inverter.solar_energy_today == 10.90 assert inverter.solar_energy_total == 8674.0 + + +@pytest.mark.asyncio +async def test_wrong_source(aresponses): + """Test on wrong data source error raise.""" + aresponses.add( + "example.com", + "/js/status.js", + "GET", + aresponses.Response( + status=200, + headers={"Content-Type": "application/x-javascript"}, + text=load_fixtures("wrong_status.js"), + ), + ) + async with aiohttp.ClientSession() as session: + omnik = OmnikInverter(host="example.com", use_json=False, session=session) + with pytest.raises(OmnikInverterWrongSourceError): + assert await omnik.inverter() + + +@pytest.mark.asyncio +async def test_timeout(aresponses): + """Test request timeout from Omnik Inverter.""" + # Faking a timeout by sleeping + async def response_handler(_): + await asyncio.sleep(0.2) + return aresponses.Response(body="Goodmorning!", text=load_fixtures("status.js")) + + aresponses.add("example.com", "/js/status.js", "GET", response_handler) + + async with aiohttp.ClientSession() as session: + omnik = OmnikInverter(host="example.com", use_json=False, session=session) + with pytest.raises(OmnikInverterConnectionError): + assert await omnik.inverter()