Skip to content

Commit

Permalink
#3: Fix connection loss to Sun2000 (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
olivergregorius committed Mar 11, 2023
1 parent 32aba36 commit 5613cb3
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 18 deletions.
7 changes: 2 additions & 5 deletions application/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,8 @@ def post_register_values() -> Response:
register_names = request.get_json()['registers']
registers = utilities.validate_registers(equipment, register_names)

register_data = []
for register in registers:
register_data.append(utilities.get_register_data(register))

response = {'equipment': equipment.value, 'registers': register_data}
registers_data = utilities.get_registers_data(registers)
response = {'equipment': equipment.value, 'registers': registers_data}

return jsonify(response)

Expand Down
23 changes: 20 additions & 3 deletions application/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,16 @@ def __init__(self, app: Flask):
self.logger.info(f'Log level set to {self.config[ENV_LOG_LEVEL]}')

self.sun2000 = inverter.Sun2000(self.config[ENV_INVERTER_HOST], self.config[ENV_INVERTER_PORT])
self.sun2000.connect()
if not self.sun2000.connected:
exit('Connection to inverter could not be established')
self.check_inverter_connection()

self.logger.info('Ready to accept requests')

def check_inverter_connection(self) -> None:
self.sun2000.connect()
if not self.sun2000.isConnected():
exit('Connection to inverter could not be established')
self.sun2000.disconnect()

def validate_auth_header(self) -> None:
api_key = request.headers.get('x-api-key')
if api_key is None or api_key not in self.config[ENV_ACCEPTED_API_KEYS]:
Expand Down Expand Up @@ -74,6 +78,19 @@ def validate_registers(self, equipment: Equipment, register_names: List[str]) ->

return registers

def get_registers_data(self, registers: List[Union[InverterEquipmentRegister, BatteryEquipmentRegister, MeterEquipmentRegister]]) -> List[dict]:
self.sun2000.connect()
if not self.sun2000.isConnected():
abort(502, 'Connection to inverter could not be established')

registers_data = []
for register in registers:
registers_data.append(self.get_register_data(register))

self.sun2000.disconnect()

return registers_data

def get_register_data(self, register: Union[InverterEquipmentRegister, BatteryEquipmentRegister, MeterEquipmentRegister]) -> dict:
self.logger.debug(f'Reading data for register {register.name}')
register_definition = register.value
Expand Down
25 changes: 25 additions & 0 deletions docs/api-specification.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ paths:
examples:
No Equipment Error:
$ref: '#/components/examples/NoEquipmentError'
Invalid Equipment Error:
$ref: '#/components/examples/InvalidEquipmentError'
401:
description: Unauthorized
content:
Expand Down Expand Up @@ -62,6 +64,12 @@ paths:
schema:
$ref: '#/components/schemas/Error'
examples:
No Equipment Error:
$ref: '#/components/examples/NoEquipmentError'
Invalid Equipment Error:
$ref: '#/components/examples/InvalidEquipmentError'
No Register Error:
$ref: '#/components/examples/NoRegistersError'
Invalid Register Error:
$ref: '#/components/examples/InvalidRegisterError'
401:
Expand All @@ -73,6 +81,15 @@ paths:
examples:
Unauthorized:
$ref: '#/components/examples/UnauthorizedError'
502:
description: Bad Gateway
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
examples:
Inverter Connection Error:
$ref: '#/components/examples/InverterConnectionError'
components:
securitySchemes:
ApiKeyAuth:
Expand Down Expand Up @@ -316,6 +333,10 @@ components:
description: Invalid value for equipment given
value:
message: Invalid value for equipment
NoRegistersError:
description: No registers given
value:
message: No value for registers
InvalidRegisterError:
description: Invalid register name given
value:
Expand All @@ -324,3 +345,7 @@ components:
description: Unauthorized access
value:
message: No or invalid API-key provided
InverterConnectionError:
description: Error connecting to inverter
value:
message: Connection to inverter could not be established
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
sun2000_modbus==1.3.0
sun2000_modbus==2.0.0
Flask==2.2.2
4 changes: 2 additions & 2 deletions tests/sun2000mock.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@


def connect_success(self):
self.connected = True
return True


def connect_fail(self):
self.connected = False
return False


MockedRawResponses = {
Expand Down
49 changes: 42 additions & 7 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@ class MainTest(unittest.TestCase):
@patch(
'sun2000_modbus.inverter.Sun2000.connect', sun2000mock.connect_success
)
@patch(
'sun2000_modbus.inverter.Sun2000.isConnected', sun2000mock.connect_success
)
def setUp(self) -> None:
test_config = {
'INVERTER_HOST': '192.168.200.1',
'INVERTER_PORT': 6607,
'INVERTER_HOST': '1.2.3.4',
'INVERTER_PORT': 502,
'ACCEPTED_API_KEYS': '12345,98765',
'LOG_LEVEL': 'DEBUG'
}
Expand All @@ -28,7 +31,7 @@ def test_unauthorized_access_to_GET_registers_returns_401(self) -> None:
self.assertEqual(401, response.status_code)
self.assertEqual({'message': 'No or invalid API-key provided'}, response.get_json())

def test_any_api_key_is_valid(self) -> None:
def test_any_defined_api_key_is_valid(self) -> None:
# Testing first API Key 12345
response = self.client.get('/registers', query_string={'equipment': 'inverter'}, headers={'x-api-key': '12345'})

Expand Down Expand Up @@ -152,6 +155,12 @@ def test_providing_no_or_invalid_registers_calling_POST_registervalues_returns_4
self.assertEqual(400, response.status_code)
self.assertEqual({'message': 'At least one invalid register passed'}, response.get_json())

@patch(
'sun2000_modbus.inverter.Sun2000.connect', sun2000mock.connect_success
)
@patch(
'sun2000_modbus.inverter.Sun2000.isConnected', sun2000mock.connect_success
)
@patch(
'sun2000_modbus.inverter.Sun2000.read_raw_value', sun2000mock.mock_read_raw_value
)
Expand All @@ -171,9 +180,9 @@ def test_calling_POST_registervalues_for_inverter_equipment_returns_requested_re
{
'name': 'RatedPower',
'type': 'number',
'unit': 'kW',
'unit': 'W',
'value': '10000',
'gain': 1000
'gain': 1
},
{
'name': 'State1',
Expand All @@ -198,6 +207,12 @@ def test_calling_POST_registervalues_for_inverter_equipment_returns_requested_re
self.assertEqual(200, response.status_code)
self.assertEqual(expected_response_json, response.get_json())

@patch(
'sun2000_modbus.inverter.Sun2000.connect', sun2000mock.connect_success
)
@patch(
'sun2000_modbus.inverter.Sun2000.isConnected', sun2000mock.connect_success
)
@patch(
'sun2000_modbus.inverter.Sun2000.read_raw_value', sun2000mock.mock_read_raw_value
)
Expand Down Expand Up @@ -229,6 +244,12 @@ def test_calling_POST_registervalues_for_battery_equipment_returns_requested_reg
self.assertEqual(200, response.status_code)
self.assertEqual(expected_response_json, response.get_json())

@patch(
'sun2000_modbus.inverter.Sun2000.connect', sun2000mock.connect_success
)
@patch(
'sun2000_modbus.inverter.Sun2000.isConnected', sun2000mock.connect_success
)
@patch(
'sun2000_modbus.inverter.Sun2000.read_raw_value', sun2000mock.mock_read_raw_value
)
Expand Down Expand Up @@ -261,6 +282,20 @@ def test_calling_POST_registervalues_for_meter_equipment_returns_requested_regis
self.assertEqual(200, response.status_code)
self.assertEqual(expected_response_json, response.get_json())

@patch(
'sun2000_modbus.inverter.Sun2000.connect', sun2000mock.connect_fail
)
@patch(
'sun2000_modbus.inverter.Sun2000.isConnected', sun2000mock.connect_fail
)
def test_calling_POST_registervalues_when_inverter_connection_fails_returns_502(self) -> None:
registers = ['Model']
response = self.client.post('/register-values', data=json.dumps({'equipment': 'inverter', 'registers': registers}), content_type='application/json',
headers={'x-api-key': '12345'})

self.assertEqual(502, response.status_code)
self.assertEqual({'message': 'Connection to inverter could not be established'}, response.get_json())

def test_calling_GET_health_returns_204(self) -> None:
response = self.client.get('/health')

Expand All @@ -274,8 +309,8 @@ class ConnectionFailTest(unittest.TestCase):
)
def test_connection_to_inverter_fails_application_exits(self) -> None:
test_config = {
'INVERTER_HOST': '192.168.200.1',
'INVERTER_PORT': 6607,
'INVERTER_HOST': '1.2.3.4',
'INVERTER_PORT': 502,
'ACCEPTED_API_KEYS': '12345,98765',
'LOG_LEVEL': 'DEBUG'
}
Expand Down

0 comments on commit 5613cb3

Please sign in to comment.