inverter page
This commit is contained in:
parent
de56aa3ae9
commit
8a89dd77be
108
bin/web_kbn.py
108
bin/web_kbn.py
@ -3,16 +3,17 @@ import asyncio
|
||||
import jinja2
|
||||
import aiohttp_jinja2
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import inverterd
|
||||
import __py_include
|
||||
|
||||
from io import StringIO
|
||||
from aiohttp.web import HTTPFound
|
||||
from typing import Optional, Union
|
||||
from homekit.config import config, AppConfigUnit
|
||||
from homekit.util import homekit_path, filesize_fmt, seconds_to_human_readable_string
|
||||
from homekit.modem import E3372, ModemsConfig, MacroNetWorkType
|
||||
from aiohttp import web
|
||||
from homekit.inverter.config import InverterdConfig
|
||||
from homekit import http
|
||||
|
||||
|
||||
@ -90,6 +91,69 @@ def get_modem_data(modem_cfg: dict, get_raw=False) -> Union[dict, tuple]:
|
||||
}
|
||||
|
||||
|
||||
def get_inverter_client() -> inverterd.Client:
|
||||
cl = inverterd.Client(host=InverterdConfig()['remote_addr'].host)
|
||||
cl.connect()
|
||||
cl.format(inverterd.Format.JSON)
|
||||
return cl
|
||||
|
||||
|
||||
def get_inverter_data() -> tuple:
|
||||
cl = get_inverter_client()
|
||||
|
||||
status = json.loads(cl.exec('get-status'))['data']
|
||||
rated = json.loads(cl.exec('get-rated'))['data']
|
||||
|
||||
power_direction = status['battery_power_direction'].lower()
|
||||
power_direction = re.sub('ge$', 'ging', power_direction)
|
||||
|
||||
charging_rate = ''
|
||||
if power_direction == 'charging':
|
||||
charging_rate = ' @ %s %s' % (
|
||||
status['battery_charge_current']['value'],
|
||||
status['battery_charge_current']['unit'])
|
||||
elif power_direction == 'discharging':
|
||||
charging_rate = ' @ %s %s' % (
|
||||
status['battery_discharge_current']['value'],
|
||||
status['battery_discharge_current']['unit'])
|
||||
|
||||
html = '<b>Battery:</b> %s %s' % (
|
||||
status['battery_voltage']['value'],
|
||||
status['battery_voltage']['unit'])
|
||||
html += ' (%s%s, ' % (
|
||||
status['battery_capacity']['value'],
|
||||
status['battery_capacity']['unit'])
|
||||
html += '%s%s)' % (power_direction, charging_rate)
|
||||
|
||||
html += "\n"
|
||||
html += '<b>Load:</b> %s %s' % (
|
||||
status['ac_output_active_power']['value'],
|
||||
status['ac_output_active_power']['unit'])
|
||||
html += ' (%s%%)' % (status['output_load_percent']['value'],)
|
||||
|
||||
if status['pv1_input_power']['value'] > 0:
|
||||
html += "\n"
|
||||
html += '<b>Input power:</b> %s %s' % (
|
||||
status['pv1_input_power']['value'],
|
||||
status['pv1_input_power']['unit'])
|
||||
|
||||
if status['grid_voltage']['value'] > 0 or status['grid_freq']['value'] > 0:
|
||||
html += "\n"
|
||||
html += '<b>AC input:</b> %s %s' % (
|
||||
status['grid_voltage']['value'],
|
||||
status['grid_voltage']['unit'])
|
||||
html += ', %s %s' % (
|
||||
status['grid_freq']['value'],
|
||||
status['grid_freq']['unit'])
|
||||
|
||||
html += "\n"
|
||||
html += '<b>Priority:</b> %s' % (rated['output_source_priority'],)
|
||||
|
||||
html = html.replace("\n", '<br>')
|
||||
|
||||
return status, rated, html
|
||||
|
||||
|
||||
class WebSite(http.HTTPServer):
|
||||
_modems_config: ModemsConfig
|
||||
|
||||
@ -108,10 +172,14 @@ class WebSite(http.HTTPServer):
|
||||
|
||||
self.app.router.add_static('/assets/', path=homekit_path('web', 'kbn_assets'))
|
||||
|
||||
self.get('/main.cgi', self.get_index)
|
||||
self.get('/modems.cgi', self.get_modems)
|
||||
self.get('/modems/info.ajx', self.get_modems_ajax)
|
||||
self.get('/modems/verbose.cgi', self.get_modems_verbose)
|
||||
self.get('/main.cgi', self.index)
|
||||
|
||||
self.get('/modems.cgi', self.modems)
|
||||
self.get('/modems/info.ajx', self.modems_ajx)
|
||||
self.get('/modems/verbose.cgi', self.modems_verbose)
|
||||
|
||||
self.get('/inverter.cgi', self.inverter)
|
||||
self.get('/inverter.ajx', self.inverter_ajx)
|
||||
|
||||
async def render_page(self,
|
||||
req: http.Request,
|
||||
@ -129,16 +197,16 @@ class WebSite(http.HTTPServer):
|
||||
response = aiohttp_jinja2.render_template(template_name+'.j2', req, context=context)
|
||||
return response
|
||||
|
||||
async def get_index(self, req: http.Request):
|
||||
async def index(self, req: http.Request):
|
||||
return await self.render_page(req, 'index',
|
||||
title="Home web site")
|
||||
|
||||
async def get_modems(self, req: http.Request):
|
||||
async def modems(self, req: http.Request):
|
||||
return await self.render_page(req, 'modems',
|
||||
title='Состояние модемов',
|
||||
context=dict(modems=self._modems_config))
|
||||
|
||||
async def get_modems_ajax(self, req: http.Request):
|
||||
async def modems_ajx(self, req: http.Request):
|
||||
modem = req.query.get('id', None)
|
||||
if modem not in self._modems_config.getkeys():
|
||||
raise ValueError('invalid modem id')
|
||||
@ -154,7 +222,7 @@ class WebSite(http.HTTPServer):
|
||||
|
||||
return self.ok({'html': html})
|
||||
|
||||
async def get_modems_verbose(self, req: http.Request):
|
||||
async def modems_verbose(self, req: http.Request):
|
||||
modem = req.query.get('id', None)
|
||||
if modem not in self._modems_config.getkeys():
|
||||
raise ValueError('invalid modem id')
|
||||
@ -175,6 +243,26 @@ class WebSite(http.HTTPServer):
|
||||
title=f'Подробная информация о модеме "{modem_name}"',
|
||||
context=dict(data=data, modem_name=modem_name))
|
||||
|
||||
async def inverter(self, req: http.Request):
|
||||
action = req.query.get('do', None)
|
||||
if action == 'set-osp':
|
||||
val = req.query.get('value')
|
||||
if val not in ('sub', 'sbu'):
|
||||
raise ValueError('invalid osp value')
|
||||
cl = get_inverter_client()
|
||||
cl.exec('set-output-source-priority',
|
||||
arguments=(val.upper(),))
|
||||
raise HTTPFound('/inverter.cgi')
|
||||
|
||||
status, rated, html = await asyncio.get_event_loop().run_in_executor(None, get_inverter_data)
|
||||
return await self.render_page(req, 'inverter',
|
||||
title='Инвертор',
|
||||
context=dict(status=status, rated=rated, html=html))
|
||||
|
||||
async def inverter_ajx(self, req: http.Request):
|
||||
status, rated, html = await asyncio.get_event_loop().run_in_executor(None, get_inverter_data)
|
||||
return self.ok({'html': html})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
config.load_app(WebKbnConfig)
|
||||
|
@ -3,7 +3,7 @@ import asyncio
|
||||
|
||||
from enum import Enum
|
||||
from aiohttp import web
|
||||
from aiohttp.web import Response
|
||||
from aiohttp.web import Response, HTTPFound
|
||||
from aiohttp.web_exceptions import HTTPNotFound
|
||||
|
||||
from ..util import stringify, format_tb, Addr
|
||||
@ -21,6 +21,9 @@ async def errors_handler_middleware(request, handler):
|
||||
except HTTPNotFound:
|
||||
return web.json_response({'error': 'not found'}, status=404)
|
||||
|
||||
except HTTPFound as exc:
|
||||
raise exc
|
||||
|
||||
except Exception as exc:
|
||||
_logger.exception(exc)
|
||||
data = {
|
||||
|
@ -8,6 +8,6 @@ class InverterdConfig(ConfigUnit):
|
||||
@classmethod
|
||||
def schema(cls) -> Optional[dict]:
|
||||
return {
|
||||
'remote_addr': {'type': 'string'},
|
||||
'local_addr': {'type': 'string'},
|
||||
'remote_addr': cls._addr_schema(required=True),
|
||||
'local_addr': cls._addr_schema(required=True),
|
||||
}
|
@ -1,104 +0,0 @@
|
||||
<?php
|
||||
|
||||
class InverterHandler extends RequestHandler
|
||||
{
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
$this->tpl->add_static('inverter.js');
|
||||
}
|
||||
|
||||
public function GET_status_page() {
|
||||
$inv = $this->getClient();
|
||||
|
||||
$status = jsonDecode($inv->exec('get-status'))['data'];
|
||||
$rated = jsonDecode($inv->exec('get-rated'))['data'];
|
||||
|
||||
$this->tpl->set([
|
||||
'status' => $status,
|
||||
'rated' => $rated,
|
||||
'html' => $this->renderStatusHtml($status, $rated)
|
||||
]);
|
||||
$this->tpl->set_title('Инвертор');
|
||||
$this->tpl->render_page('inverter_page.twig');
|
||||
}
|
||||
|
||||
public function GET_set_osp() {
|
||||
list($osp) = $this->input('e:value(=sub|sbu)');
|
||||
$inv = $this->getClient();
|
||||
try {
|
||||
$inv->exec('set-output-source-priority', [strtoupper($osp)]);
|
||||
} catch (Exception $e) {
|
||||
die('Ошибка: '.jsonDecode($e->getMessage())['message']);
|
||||
}
|
||||
redirect('/inverter/');
|
||||
}
|
||||
|
||||
public function GET_status_ajax() {
|
||||
$inv = $this->getClient();
|
||||
$status = jsonDecode($inv->exec('get-status'))['data'];
|
||||
$rated = jsonDecode($inv->exec('get-rated'))['data'];
|
||||
ajax_ok(['html' => $this->renderStatusHtml($status, $rated)]);
|
||||
}
|
||||
|
||||
protected function renderStatusHtml(array $status, array $rated) {
|
||||
$power_direction = strtolower($status['battery_power_direction']);
|
||||
$power_direction = preg_replace('/ge$/', 'ging', $power_direction);
|
||||
|
||||
$charging_rate = '';
|
||||
if ($power_direction == 'charging')
|
||||
$charging_rate = sprintf(' @ %s %s',
|
||||
$status['battery_charge_current']['value'],
|
||||
$status['battery_charge_current']['unit']);
|
||||
else if ($power_direction == 'discharging')
|
||||
$charging_rate = sprintf(' @ %s %s',
|
||||
$status['battery_discharge_current']['value'],
|
||||
$status['battery_discharge_current']['unit']);
|
||||
|
||||
$html = sprintf('<b>Battery:</b> %s %s',
|
||||
$status['battery_voltage']['value'],
|
||||
$status['battery_voltage']['unit']);
|
||||
$html .= sprintf(' (%s%s, ',
|
||||
$status['battery_capacity']['value'],
|
||||
$status['battery_capacity']['unit']);
|
||||
$html .= sprintf('%s%s)',
|
||||
$power_direction,
|
||||
$charging_rate);
|
||||
|
||||
$html .= "\n".sprintf('<b>Load:</b> %s %s',
|
||||
$status['ac_output_active_power']['value'],
|
||||
$status['ac_output_active_power']['unit']);
|
||||
$html .= sprintf(' (%s%%)',
|
||||
$status['output_load_percent']['value']);
|
||||
|
||||
if ($status['pv1_input_power']['value'] > 0)
|
||||
$html .= "\n".sprintf('<b>Input power:</b> %s %s',
|
||||
$status['pv1_input_power']['value'],
|
||||
$status['pv1_input_power']['unit']);
|
||||
|
||||
if ($status['grid_voltage']['value'] > 0 or $status['grid_freq']['value'] > 0) {
|
||||
$html .= "\n".sprintf('<b>AC input:</b> %s %s',
|
||||
$status['grid_voltage']['value'],
|
||||
$status['grid_voltage']['unit']);
|
||||
$html .= sprintf(', %s %s',
|
||||
$status['grid_freq']['value'],
|
||||
$status['grid_freq']['unit']);
|
||||
}
|
||||
|
||||
$html .= "\n".sprintf('<b>Priority:</b> %s',
|
||||
$rated['output_source_priority']);
|
||||
|
||||
return nl2br($html);
|
||||
}
|
||||
|
||||
protected function getClient(): InverterdClient {
|
||||
global $config;
|
||||
if (isset($_GET['alt']) && $_GET['alt'] == 1)
|
||||
$config['inverterd_host'] = '192.168.5.223';
|
||||
$inv = new InverterdClient($config['inverterd_host'], $config['inverterd_port']);
|
||||
$inv->setFormat('json');
|
||||
return $inv;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -4,11 +4,6 @@ require_once __DIR__.'/../init.php';
|
||||
|
||||
$router = new router;
|
||||
|
||||
// modem
|
||||
$router->add('modem/', 'Modem status_page');
|
||||
$router->add('modem/verbose/', 'Modem verbose_page');
|
||||
$router->add('modem/get.ajax', 'Modem status_get_ajax');
|
||||
|
||||
$router->add('routing/', 'Modem routing_smallhome_page');
|
||||
$router->add('routing/switch-small-home/', 'Modem routing_smallhome_switch');
|
||||
$router->add('routing/{ipsets,dhcp}/', 'Modem routing_${1}_page');
|
||||
@ -18,9 +13,7 @@ $router->add('sms/', 'Modem sms');
|
||||
// $router->add('modem/set.ajax', 'Modem ctl_set_ajax');
|
||||
|
||||
// inverter
|
||||
$router->add('inverter/', 'Inverter status_page');
|
||||
$router->add('inverter/set-osp/', 'Inverter set_osp');
|
||||
$router->add('inverter/status.ajax', 'Inverter status_ajax');
|
||||
|
||||
// misc
|
||||
$router->add('/', 'Misc main');
|
||||
|
@ -1,20 +0,0 @@
|
||||
{% include 'bc.twig' with {
|
||||
history: [
|
||||
{text: "Инвертор" }
|
||||
]
|
||||
} %}
|
||||
|
||||
<h6 class="text-primary">Статус</h6>
|
||||
<div id="inverter_status">
|
||||
{{ html|raw }}
|
||||
</div>
|
||||
|
||||
<div class="pt-3">
|
||||
<a href="/inverter/set-osp/?value={{ rated.output_source_priority == 'Solar-Battery-Utility' ? 'sub' : 'sbu' }}">
|
||||
<button type="button" class="btn btn-primary">Переключить на <b>{{ rated.output_source_priority == 'Solar-Battery-Utility' ? 'Solar-Utility-Battery' : 'Solar-Battery-Utility' }}</b></button>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% js %}
|
||||
Inverter.poll();
|
||||
{% endjs %}
|
@ -349,3 +349,20 @@ var ModemStatus = {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var Inverter = {
|
||||
poll: function () {
|
||||
setInterval(this._tick, 1000);
|
||||
},
|
||||
|
||||
_tick: function() {
|
||||
ajax.get('/inverter.ajx')
|
||||
.then(({response}) => {
|
||||
if (response) {
|
||||
var el = document.getElementById('inverter_status');
|
||||
el.innerHTML = response.html;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
@ -1,15 +0,0 @@
|
||||
var Inverter = {
|
||||
poll: function () {
|
||||
setInterval(this._tick, 1000);
|
||||
},
|
||||
|
||||
_tick: function() {
|
||||
ajax.get('/inverter/status.ajax')
|
||||
.then(({response}) => {
|
||||
if (response) {
|
||||
var el = document.getElementById('inverter_status');
|
||||
el.innerHTML = response.html;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
20
web/kbn_templates/inverter.j2
Normal file
20
web/kbn_templates/inverter.j2
Normal file
@ -0,0 +1,20 @@
|
||||
{% extends "base.j2" %}
|
||||
|
||||
{% block content %}
|
||||
{{ breadcrumbs([{'text': 'Инвертор'}]) }}
|
||||
|
||||
<h6 class="text-primary">Статус</h6>
|
||||
<div id="inverter_status">
|
||||
{{ html|safe }}
|
||||
</div>
|
||||
|
||||
<div class="pt-3">
|
||||
<a href="/inverter.cgi?do=set-osp&value={{ 'sub' if rated.output_source_priority == 'Solar-Battery-Utility' else 'sbu' }}">
|
||||
<button type="button" class="btn btn-primary">Переключить на <b>{{ 'Solar-Utility-Battery' if rated.output_source_priority == 'Solar-Battery-Utility' else 'Solar-Battery-Utility' }}</b></button>
|
||||
</a>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
Inverter.poll();
|
||||
{% endblock %}
|
Loading…
x
Reference in New Issue
Block a user