Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
05ff426b28 | ||
|
|
45af7a30d8 | ||
|
|
8aab541afa | ||
|
|
7672c4b927 | ||
|
|
b0a9460257 | ||
|
|
3c69f1295e | ||
|
|
282a02ecdb | ||
|
|
8503ef966f | ||
|
|
a4ee4c5224 | ||
|
|
4478e8f204 | ||
|
|
a50c13fd8a | ||
|
|
ee1e7f92b2 | ||
|
|
5704075682 | ||
|
|
52e4933923 | ||
|
|
00a82ca3e5 | ||
|
|
7658aeaa8c | ||
|
|
790ff5a011 | ||
|
|
935cf27139 | ||
|
|
503068f6e7 | ||
|
|
5c868d589d | ||
|
|
1cca8ffd5d | ||
|
|
f291eb33ac | ||
|
|
f0c505c332 | ||
|
|
7eafe4a90b | ||
|
|
d23527a48b | ||
|
|
c341f86e5c | ||
|
|
939ed6cdab | ||
|
|
2da41707a9 | ||
|
|
460cb01146 | ||
|
|
1b2bc8e200 | ||
|
|
d5acb44648 | ||
|
|
c64cf41757 | ||
|
|
9250bb26f2 | ||
|
|
b2c6eca2d5 | ||
|
|
5cec043015 | ||
|
|
7c9a483677 | ||
|
|
1bfe7a688a | ||
|
|
05104aa8eb | ||
|
|
ca3c318d8f | ||
|
|
2648918dda | ||
|
|
8b50fdec21 | ||
|
|
5b6e23251a | ||
|
|
769daa0857 | ||
|
|
76979531b8 | ||
|
|
7779076498 | ||
|
|
45e2e0334e |
11
.github/dependabot.yaml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
22
.github/workflows/pio-dependabot.yaml
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
name: PlatformIO Dependabot
|
||||
on:
|
||||
workflow_dispatch: # option to manually trigger the workflow
|
||||
schedule:
|
||||
# Runs every day at 00:00
|
||||
- cron: "0 0 * * *"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
dependabot:
|
||||
runs-on: ubuntu-latest
|
||||
name: run PlatformIO Dependabot
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: run PlatformIO Dependabot
|
||||
uses: peterus/platformio_dependabot@v1.2.0
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
4
.gitignore
vendored
@@ -1,6 +1,8 @@
|
||||
.pio
|
||||
.vscode
|
||||
build/*.bin
|
||||
data/**/*.gz
|
||||
data/*
|
||||
secrets.ini
|
||||
node_modules
|
||||
package-lock.json
|
||||
!.gitkeep
|
||||
@@ -42,7 +42,7 @@ All available information and instructions can be found in the wiki:
|
||||
* [Quick Start](https://github.com/Laxilef/OTGateway/wiki#quick-start)
|
||||
* [Build firmware](https://github.com/Laxilef/OTGateway/wiki#build-firmware)
|
||||
* [Flash firmware via ESP Flash Download Tool](https://github.com/Laxilef/OTGateway/wiki#flash-firmware-via-esp-flash-download-tool)
|
||||
* [HomeAsssistant settings](https://github.com/Laxilef/OTGateway/wiki#homeasssistant-settings)
|
||||
* [Settings](https://github.com/Laxilef/OTGateway/wiki#settings)
|
||||
* [External temperature sensors](https://github.com/Laxilef/OTGateway/wiki#external-temperature-sensors)
|
||||
* [Reporting indoor/outdoor temperature from any Home Assistant sensor](https://github.com/Laxilef/OTGateway/wiki#reporting-indooroutdoor-temperature-from-any-home-assistant-sensor)
|
||||
* [Reporting outdoor temperature from Home Assistant weather integration](https://github.com/Laxilef/OTGateway/wiki#reporting-outdoor-temperature-from-home-assistant-weather-integration)
|
||||
|
||||
BIN
assets/2D_PCB_bottom.png
Normal file
|
After Width: | Height: | Size: 261 KiB |
BIN
assets/2D_PCB_top.png
Normal file
|
After Width: | Height: | Size: 326 KiB |
BIN
assets/3D_PCB.png
Normal file
|
After Width: | Height: | Size: 684 KiB |
BIN
assets/BOM.xlsx
BIN
assets/CPL.csv
15692
assets/Schematic.pdf
12
assets/blueprint_import.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="40" aria-label="Import blueprint to My Home Assistant" style="border-radius:24px;width:auto" viewBox="0 0 592 96">
|
||||
<rect width="592" height="96" fill="#18BCF2" rx="48"/>
|
||||
<path fill="#fff" d="M42.55 60.2h4.59V36.75h-4.59Zm10.35 0h4.59V43.99l7.23 10.58 7.24-10.62V60.2h4.56V36.75h-4.56v.03l-.4-.03-6.84 10.12-6.83-10.12-.4.03v-.03H52.9Zm29.38 0h4.59v-7.77h4.49c4.72 0 8.07-3.29 8.07-7.87 0-4.59-3.48-7.88-8.44-7.84l-4.66.03h-4.05Zm8.61-19.26c2.27-.04 3.88 1.47 3.88 3.62 0 2.14-1.47 3.65-3.51 3.65h-4.39v-7.27Zm22.98 19.66c6.97 0 11.92-5.02 11.92-12.09 0-7.1-4.95-12.13-12.02-12.13-7.04 0-12 4.99-12 12.13 0 7.07 5 12.09 12.1 12.09m0-4.19c-4.36 0-7.41-3.28-7.41-7.9 0-4.66 3.02-7.94 7.31-7.94 4.32 0 7.33 3.28 7.33 7.94 0 4.62-2.98 7.9-7.23 7.9m16.31 3.79h4.59v-8.31h3.52l4.79 8.31h5.19l-5.42-9.11c2.71-1.21 4.48-3.69 4.48-6.77 0-4.45-3.48-7.64-8.44-7.6l-4.65.03h-4.06Zm8.51-19.26c2.28 0 3.89 1.37 3.89 3.38 0 1.98-1.61 3.38-3.65 3.38h-4.16v-6.76Zm18.33 19.26h4.59V40.94h7v-4.19h-18.63v4.19h7.04Zm23.91 0h9.02c4.69 0 7.77-2.41 7.77-6.7 0-2.65-1.24-4.42-3.42-5.53 1.64-1.17 2.61-2.78 2.61-4.55 0-4.43-3.21-6.7-8.04-6.7l-3.45.03h-4.49Zm7.68-19.8c2.21-.03 3.61 1.07 3.61 2.95s-1.27 2.95-3.25 2.95h-3.55v-5.9Zm.53 9.65c2.41-.03 3.89 1.17 3.89 3.08 0 1.81-1.34 3.02-3.52 3.02h-4.09v-6.1h.24Zm12.97 10.15h14.9v-4.19H206.7V36.75h-4.59Zm18.32-8.74c0 5.62 3.69 9.21 9.51 9.21 5.9 0 9.65-3.59 9.65-9.21V36.75H235v14.71c0 3.01-1.97 4.95-4.99 4.95-3.01 0-4.99-1.94-4.99-4.95V36.75h-4.59Zm24.59 8.74h14.97v-4.19h-10.38v-5.66h8.84v-4.09h-8.84v-5.32h10.25v-4.19h-14.84Zm20.4 0h4.59v-7.77h4.49c4.72 0 8.07-3.29 8.07-7.87 0-4.59-3.48-7.88-8.44-7.84l-4.66.03h-4.05Zm8.61-19.26c2.28-.04 3.89 1.47 3.89 3.62 0 2.14-1.48 3.65-3.52 3.65h-4.39v-7.27Zm12.26 19.26h4.59v-8.31h3.52l4.79 8.31h5.19l-5.43-9.11c2.72-1.21 4.49-3.69 4.49-6.77 0-4.45-3.48-7.64-8.44-7.6l-4.65.03h-4.06Zm8.51-19.26c2.28 0 3.89 1.37 3.89 3.38 0 1.98-1.61 3.38-3.65 3.38h-4.16v-6.76Zm13.77 19.26h4.59V36.75h-4.59Zm10.35 0 4.59.03V44.12l11.66 16.08h4.55V36.75h-4.55v15.81l-11.46-15.81h-4.79Zm31.52 0h4.59V40.94h7.01v-4.19h-18.63v4.19h7.03Zm28.88 0h4.59V40.94h7v-4.19h-18.62v4.19h7.03Zm26 .4c6.97 0 11.92-5.02 11.92-12.09 0-7.1-4.95-12.13-12.02-12.13-7.04 0-12 4.99-12 12.13 0 7.07 5 12.09 12.1 12.09m0-4.19c-4.36 0-7.41-3.28-7.41-7.9 0-4.66 3.02-7.94 7.31-7.94 4.32 0 7.33 3.28 7.33 7.94 0 4.62-2.98 7.9-7.23 7.9"/>
|
||||
<g style="transform:translate(95px,0)">
|
||||
<rect width="137" height="64" x="344" y="16" fill="#F2F4F9" rx="32"/>
|
||||
<path fill="#18BCF2" d="M394.419 37.047V60.5h-4.297V46.797L384.716 60.5h-4.157l-5.343-13.594V60.5h-4.188V37.047h4.188l7.422 18.36 7.484-18.36zm9.365 0 5.344 9.89 5.344-9.89h4.766l-7.969 14.14V60.5h-4.391v-9.312l-8.031-14.141zM457 60c0 1.65-1.35 3-3 3h-24c-1.65 0-3-1.35-3-3v-9c0-1.65.95-3.95 2.12-5.12l10.76-10.76a3 3 0 0 1 4.24 0l10.76 10.76c1.17 1.17 2.12 3.47 2.12 5.12z"/>
|
||||
<path fill="#F2F4F9" stroke="#F2F4F9" d="M442 45.5a2 2 0 1 0 0-4 2 2 0 0 0 0 4Z"/>
|
||||
<path fill="#F2F4F9" stroke="#F2F4F9" stroke-miterlimit="10" d="M449.5 53.5a2 2 0 1 0 0-4 2 2 0 0 0 0 4ZM434.5 57.5a2 2 0 1 0 0-4 2 2 0 0 0 0 4Z"/>
|
||||
<path fill="none" stroke="#F2F4F9" stroke-miterlimit="10" stroke-width="2.25" d="M442 43.48V63l-7.5-7.5M449.5 51.46l-7.41 7.41"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
@@ -1,3 +1,6 @@
|
||||
# Package for Home Assistant Packages
|
||||
# More info: https://www.home-assistant.io/docs/configuration/packages/
|
||||
|
||||
dhw_meter:
|
||||
sensor:
|
||||
- platform: integration
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
# Script for reporting outdoor temperature to the controller from home assistant weather integration
|
||||
# Updated: 07.12.2023
|
||||
|
||||
alias: Report outdoor temp to controller from weather
|
||||
description: ""
|
||||
variables:
|
||||
# The source weather from which we take the temperature
|
||||
source_entity: "weather.home"
|
||||
|
||||
# Target entity number where we set the temperature
|
||||
# If the prefix has not changed, then you do not need to change it
|
||||
target_entity: "number.opentherm_outdoor_temp"
|
||||
trigger:
|
||||
- platform: time_pattern
|
||||
seconds: /30
|
||||
condition:
|
||||
- condition: template
|
||||
value_template: "{{ states(source_entity) != 'unavailable' and states(target_entity) != 'unavailable' }}"
|
||||
action:
|
||||
- if:
|
||||
- condition: template
|
||||
value_template: "{{ (state_attr(source_entity, 'temperature')|float(0) - states(target_entity)|float(0)) | abs | round(2) >= 0.1 }}"
|
||||
then:
|
||||
- service: number.set_value
|
||||
data:
|
||||
value: "{{ state_attr(source_entity, 'temperature')|float(0)|round(2) }}"
|
||||
target:
|
||||
entity_id: "{{ target_entity }}"
|
||||
mode: single
|
||||
@@ -1,30 +0,0 @@
|
||||
# Script for reporting indoor/outdoor temperature to the controller from any home assistant sensor
|
||||
# Updated: 07.12.2023
|
||||
|
||||
alias: Report temp to controller
|
||||
description: ""
|
||||
variables:
|
||||
# The source sensor from which we take the temperature
|
||||
source_entity: "sensor.livingroom_temperature"
|
||||
|
||||
# Target entity number where we set the temperature
|
||||
# To report indoor temperature: number.opentherm_indoor_temp
|
||||
# To report outdoor temperature: number.opentherm_outdoor_temp
|
||||
target_entity: "number.opentherm_indoor_temp"
|
||||
trigger:
|
||||
- platform: time_pattern
|
||||
seconds: /30
|
||||
condition:
|
||||
- condition: template
|
||||
value_template: "{{ states(source_entity) != 'unavailable' and states(target_entity) != 'unavailable' }}"
|
||||
action:
|
||||
- if:
|
||||
- condition: template
|
||||
value_template: "{{ (states(source_entity)|float(0) - states(target_entity)|float(0)) | abs | round(2) >= 0.01 }}"
|
||||
then:
|
||||
- service: number.set_value
|
||||
data:
|
||||
value: "{{ states(source_entity)|float(0)|round(2) }}"
|
||||
target:
|
||||
entity_id: "{{ target_entity }}"
|
||||
mode: single
|
||||
48
assets/ha/report_temp_to_otgateway.yaml
Normal file
@@ -0,0 +1,48 @@
|
||||
# Blueprint for reporting indoor/outdoor temperature to OpenTherm Gateway from any home assistant sensor
|
||||
# Updated: 03.09.2024
|
||||
|
||||
blueprint:
|
||||
name: Report temp to OpenTherm Gateway
|
||||
domain: automation
|
||||
author: "Laxilef"
|
||||
|
||||
input:
|
||||
source_entity:
|
||||
name: Source entity
|
||||
description: "Temperature data source"
|
||||
selector:
|
||||
entity:
|
||||
multiple: false
|
||||
filter:
|
||||
- domain: sensor
|
||||
device_class: temperature
|
||||
target_entity:
|
||||
name: Target entity
|
||||
description: "Usually ``number.opentherm_indoor_temp`` or ``number.opentherm_outdoor_temp``"
|
||||
default: "number.opentherm_indoor_temp"
|
||||
selector:
|
||||
entity:
|
||||
multiple: false
|
||||
filter:
|
||||
- domain: number
|
||||
|
||||
mode: single
|
||||
variables:
|
||||
source_entity: !input source_entity
|
||||
target_entity: !input target_entity
|
||||
trigger:
|
||||
- platform: time_pattern
|
||||
seconds: /30
|
||||
condition:
|
||||
- condition: template
|
||||
value_template: "{{ states(source_entity) != 'unavailable' and states(target_entity) != 'unavailable' }}"
|
||||
action:
|
||||
- if:
|
||||
- condition: template
|
||||
value_template: "{{ (states(source_entity)|float(0) - states(target_entity)|float(0)) | abs | round(2) >= 0.01 }}"
|
||||
then:
|
||||
- service: number.set_value
|
||||
data:
|
||||
value: "{{ states(source_entity)|float(0)|round(2) }}"
|
||||
target:
|
||||
entity_id: "{{ target_entity }}"
|
||||
47
assets/ha/report_temp_to_otgateway_from_weather.yaml
Normal file
@@ -0,0 +1,47 @@
|
||||
# Blueprint for reporting temperature to OpenTherm Gateway from home assistant weather integration
|
||||
# Updated: 03.09.2024
|
||||
|
||||
blueprint:
|
||||
name: Report temp to OpenTherm Gateway from Weather
|
||||
domain: automation
|
||||
author: "Laxilef"
|
||||
|
||||
input:
|
||||
source_entity:
|
||||
name: Source entity
|
||||
description: "Temperature data source"
|
||||
selector:
|
||||
entity:
|
||||
multiple: false
|
||||
filter:
|
||||
- domain: weather
|
||||
target_entity:
|
||||
name: Target entity
|
||||
description: "Usually ``number.opentherm_outdoor_temp``"
|
||||
default: "number.opentherm_outdoor_temp"
|
||||
selector:
|
||||
entity:
|
||||
multiple: false
|
||||
filter:
|
||||
- domain: number
|
||||
|
||||
mode: single
|
||||
variables:
|
||||
source_entity: !input source_entity
|
||||
target_entity: !input target_entity
|
||||
trigger:
|
||||
- platform: time_pattern
|
||||
seconds: /30
|
||||
condition:
|
||||
- condition: template
|
||||
value_template: "{{ states(source_entity) != 'unavailable' and states(target_entity) != 'unavailable' }}"
|
||||
action:
|
||||
- if:
|
||||
- condition: template
|
||||
value_template: "{{ (state_attr(source_entity, 'temperature')|float(0) - states(target_entity)|float(0)) | abs | round(2) >= 0.1 }}"
|
||||
then:
|
||||
- service: number.set_value
|
||||
data:
|
||||
value: "{{ state_attr(source_entity, 'temperature')|float(0)|round(2) }}"
|
||||
target:
|
||||
entity_id: "{{ target_entity }}"
|
||||
|
Before Width: | Height: | Size: 675 KiB |
|
Before Width: | Height: | Size: 154 KiB |
142
gulpfile.js
Normal file
@@ -0,0 +1,142 @@
|
||||
const { src, dest, series, parallel } = require('gulp');
|
||||
const concat = require('gulp-concat');
|
||||
const gzip = require('gulp-gzip');
|
||||
const postcss = require('gulp-postcss');
|
||||
const cssnano = require('cssnano');
|
||||
const terser = require('gulp-terser');
|
||||
const jsonminify = require('gulp-jsonminify');
|
||||
const htmlmin = require('gulp-html-minifier-terser');
|
||||
|
||||
// Paths for tasks
|
||||
let paths = {
|
||||
styles: {
|
||||
dest: 'data/static/',
|
||||
bundles: {
|
||||
'app.css': [
|
||||
'src_data/styles/pico.min.css',
|
||||
'src_data/styles/iconly.css',
|
||||
'src_data/styles/app.css'
|
||||
]
|
||||
}
|
||||
},
|
||||
scripts: {
|
||||
dest: 'data/static/',
|
||||
bundles: {
|
||||
'app.js': [
|
||||
'src_data/scripts/i18n.min.js',
|
||||
'src_data/scripts/lang.js',
|
||||
'src_data/scripts/utils.js'
|
||||
]
|
||||
}
|
||||
},
|
||||
json: [
|
||||
{
|
||||
src: 'src_data/locales/*.json',
|
||||
dest: 'data/static/locales/'
|
||||
}
|
||||
],
|
||||
static: [
|
||||
{
|
||||
src: 'src_data/fonts/*.*',
|
||||
dest: 'data/static/fonts/'
|
||||
},
|
||||
{
|
||||
src: 'src_data/images/*.*',
|
||||
dest: 'data/static/images/'
|
||||
}
|
||||
],
|
||||
pages: {
|
||||
src: 'src_data/pages/*.html',
|
||||
dest: 'data/pages/'
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
// Tasks
|
||||
const styles = (cb) => {
|
||||
for (let name in paths.styles.bundles) {
|
||||
const items = paths.styles.bundles[name];
|
||||
|
||||
src(items)
|
||||
.pipe(postcss([
|
||||
cssnano({ preset: 'advanced' })
|
||||
]))
|
||||
.pipe(concat(name))
|
||||
.pipe(gzip({
|
||||
append: true
|
||||
}))
|
||||
.pipe(dest(paths.styles.dest));
|
||||
}
|
||||
|
||||
cb();
|
||||
}
|
||||
|
||||
const scripts = (cb) => {
|
||||
for (let name in paths.scripts.bundles) {
|
||||
const items = paths.scripts.bundles[name];
|
||||
|
||||
src(items)
|
||||
.pipe(terser().on('error', console.error))
|
||||
.pipe(concat(name))
|
||||
.pipe(gzip({
|
||||
append: true
|
||||
}))
|
||||
.pipe(dest(paths.scripts.dest));
|
||||
}
|
||||
|
||||
cb();
|
||||
}
|
||||
|
||||
const jsonFiles = (cb) => {
|
||||
for (let i in paths.json) {
|
||||
const item = paths.json[i];
|
||||
|
||||
src(item.src)
|
||||
.pipe(jsonminify())
|
||||
.pipe(gzip({
|
||||
append: true
|
||||
}))
|
||||
.pipe(dest(item.dest));
|
||||
}
|
||||
|
||||
cb();
|
||||
}
|
||||
|
||||
const staticFiles = (cb) => {
|
||||
for (let i in paths.static) {
|
||||
const item = paths.static[i];
|
||||
|
||||
src(item.src, { encoding: false })
|
||||
.pipe(gzip({
|
||||
append: true
|
||||
}))
|
||||
.pipe(dest(item.dest));
|
||||
}
|
||||
|
||||
cb();
|
||||
}
|
||||
|
||||
const pages = () => {
|
||||
return src(paths.pages.src)
|
||||
.pipe(htmlmin({
|
||||
html5: true,
|
||||
caseSensitive: true,
|
||||
collapseWhitespace: true,
|
||||
collapseInlineTagWhitespace: true,
|
||||
conservativeCollapse: true,
|
||||
removeComments: true,
|
||||
minifyJS: true
|
||||
}))
|
||||
.pipe(gzip({
|
||||
append: true
|
||||
}))
|
||||
.pipe(dest(paths.pages.dest));
|
||||
}
|
||||
|
||||
exports.build_styles = styles;
|
||||
exports.build_scripts = scripts;
|
||||
exports.build_json = jsonFiles;
|
||||
exports.build_static = staticFiles;
|
||||
exports.build_pages = pages;
|
||||
exports.build_all = parallel(styles, scripts, jsonFiles, staticFiles, pages);
|
||||
@@ -79,7 +79,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
unsigned long setBoilerStatus(bool enableCentralHeating, bool enableHotWater, bool enableCooling, bool enableOutsideTemperatureCompensation, bool enableCentralHeating2, bool summerWinterMode, bool dhwBlocking) {
|
||||
unsigned long setBoilerStatus(bool enableCentralHeating, bool enableHotWater, bool enableCooling, bool enableOutsideTemperatureCompensation, bool enableCentralHeating2, bool summerWinterMode, bool dhwBlocking, uint8_t lb = 0) {
|
||||
unsigned int data = enableCentralHeating
|
||||
| (enableHotWater << 1)
|
||||
| (enableCooling << 2)
|
||||
@@ -87,7 +87,9 @@ public:
|
||||
| (enableCentralHeating2 << 4)
|
||||
| (summerWinterMode << 5)
|
||||
| (dhwBlocking << 6);
|
||||
|
||||
data <<= 8;
|
||||
data |= lb;
|
||||
|
||||
return this->sendRequest(buildRequest(
|
||||
OpenThermMessageType::READ_DATA,
|
||||
|
||||
@@ -13,6 +13,7 @@ void NetworkConnection::setup(bool useDhcp) {
|
||||
|
||||
void NetworkConnection::reset() {
|
||||
status = Status::NONE;
|
||||
rawDisconnectReason = 0;
|
||||
disconnectReason = DisconnectReason::NONE;
|
||||
}
|
||||
|
||||
@@ -33,22 +34,25 @@ void NetworkConnection::onEvent(System_Event_t *event) {
|
||||
switch (event->event) {
|
||||
case EVENT_STAMODE_CONNECTED:
|
||||
status = useDhcp ? Status::CONNECTING : Status::CONNECTED;
|
||||
rawDisconnectReason = 0;
|
||||
disconnectReason = DisconnectReason::NONE;
|
||||
|
||||
break;
|
||||
|
||||
case EVENT_STAMODE_GOT_IP:
|
||||
status = Status::CONNECTED;
|
||||
rawDisconnectReason = 0;
|
||||
disconnectReason = DisconnectReason::NONE;
|
||||
break;
|
||||
|
||||
case EVENT_STAMODE_DHCP_TIMEOUT:
|
||||
status = Status::DISCONNECTED;
|
||||
rawDisconnectReason = 0;
|
||||
disconnectReason = DisconnectReason::DHCP_TIMEOUT;
|
||||
break;
|
||||
|
||||
case EVENT_STAMODE_DISCONNECTED:
|
||||
status = Status::DISCONNECTED;
|
||||
rawDisconnectReason = event->event_info.disconnected.reason;
|
||||
disconnectReason = convertDisconnectReason(event->event_info.disconnected.reason);
|
||||
|
||||
// https://github.com/esp8266/Arduino/blob/d5eb265f78bff9deb7063d10030a02d021c8c66c/libraries/ESP8266WiFi/src/ESP8266WiFiGeneric.cpp#L231
|
||||
@@ -63,6 +67,7 @@ void NetworkConnection::onEvent(System_Event_t *event) {
|
||||
auto& src = event->event_info.auth_change;
|
||||
if ((src.old_mode != AUTH_OPEN) && (src.new_mode == AUTH_OPEN)) {
|
||||
status = Status::DISCONNECTED;
|
||||
rawDisconnectReason = 0;
|
||||
disconnectReason = DisconnectReason::OTHER;
|
||||
|
||||
wifi_station_disconnect();
|
||||
@@ -79,25 +84,27 @@ void NetworkConnection::onEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
|
||||
switch (event) {
|
||||
case ARDUINO_EVENT_WIFI_STA_CONNECTED:
|
||||
status = useDhcp ? Status::CONNECTING : Status::CONNECTED;
|
||||
rawDisconnectReason = 0;
|
||||
disconnectReason = DisconnectReason::NONE;
|
||||
|
||||
break;
|
||||
|
||||
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
|
||||
case ARDUINO_EVENT_WIFI_STA_GOT_IP6:
|
||||
status = Status::CONNECTED;
|
||||
rawDisconnectReason = 0;
|
||||
disconnectReason = DisconnectReason::NONE;
|
||||
break;
|
||||
|
||||
case ARDUINO_EVENT_WIFI_STA_LOST_IP:
|
||||
status = Status::DISCONNECTED;
|
||||
rawDisconnectReason = 0;
|
||||
disconnectReason = DisconnectReason::DHCP_TIMEOUT;
|
||||
break;
|
||||
|
||||
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
|
||||
status = Status::DISCONNECTED;
|
||||
rawDisconnectReason = info.wifi_sta_disconnected.reason;
|
||||
disconnectReason = convertDisconnectReason(info.wifi_sta_disconnected.reason);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -147,4 +154,5 @@ NetworkConnection::DisconnectReason NetworkConnection::convertDisconnectReason(u
|
||||
|
||||
bool NetworkConnection::useDhcp = false;
|
||||
NetworkConnection::Status NetworkConnection::status = Status::NONE;
|
||||
NetworkConnection::DisconnectReason NetworkConnection::disconnectReason = DisconnectReason::NONE;
|
||||
NetworkConnection::DisconnectReason NetworkConnection::disconnectReason = DisconnectReason::NONE;
|
||||
uint8_t NetworkConnection::rawDisconnectReason = 0;
|
||||
@@ -27,6 +27,7 @@ namespace NetworkUtils {
|
||||
|
||||
static Status status;
|
||||
static DisconnectReason disconnectReason;
|
||||
static uint8_t rawDisconnectReason;
|
||||
|
||||
static void setup(bool useDhcp);
|
||||
static void setUseDhcp(bool value);
|
||||
|
||||
@@ -212,7 +212,7 @@ namespace NetworkUtils {
|
||||
}
|
||||
#endif*/
|
||||
|
||||
WiFi.disconnect(false, true);
|
||||
this->disconnect();
|
||||
}
|
||||
|
||||
if (!this->hasStaCredentials()) {
|
||||
@@ -266,13 +266,17 @@ namespace NetworkUtils {
|
||||
return false;
|
||||
}
|
||||
|
||||
void disconnect() {
|
||||
WiFi.disconnect(false, true);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (this->reconnectFlag) {
|
||||
this->delayCallback(5000);
|
||||
|
||||
Log.sinfoln(FPSTR(L_NETWORK), F("Reconnecting..."));
|
||||
this->reconnectFlag = false;
|
||||
this->resetWifi();
|
||||
this->disconnect();
|
||||
NetworkConnection::reset();
|
||||
this->delayCallback(1000);
|
||||
|
||||
@@ -297,7 +301,7 @@ namespace NetworkUtils {
|
||||
}
|
||||
|
||||
if (this->isApEnabled() && millis() - this->connectedTime > this->reconnectInterval && !this->hasApClients()) {
|
||||
Log.sinfoln(FPSTR(L_NETWORK), F("Stop AP because connected, start only STA"));
|
||||
Log.sinfoln(FPSTR(L_NETWORK), F("Stop AP because STA connected"));
|
||||
|
||||
WiFi.mode(WIFI_STA);
|
||||
return;
|
||||
@@ -348,7 +352,7 @@ namespace NetworkUtils {
|
||||
|
||||
NetworkConnection::reset();
|
||||
if (!this->connect(true, this->connectionTimeout)) {
|
||||
Log.straceln(FPSTR(L_NETWORK), F("Connection failed. Status: %d, reason: %d"), NetworkConnection::getStatus(), NetworkConnection::getDisconnectReason());
|
||||
Log.straceln(FPSTR(L_NETWORK), F("Connection failed. Status: %d, reason: %d, raw reason: %d"), NetworkConnection::getStatus(), NetworkConnection::getDisconnectReason(), NetworkConnection::rawDisconnectReason);
|
||||
}
|
||||
|
||||
this->prevReconnectingTime = millis();
|
||||
|
||||
@@ -33,18 +33,16 @@ public:
|
||||
}
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
bool canHandle(HTTPMethod method, const String uri) override {
|
||||
#else
|
||||
bool canHandle(HTTPMethod method, const String& uri) override {
|
||||
bool canHandle(WebServer &server, HTTPMethod method, const String &uri) override {
|
||||
return this->canHandle(method, uri);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool canHandle(HTTPMethod method, const String& uri) override {
|
||||
return uri.equals(this->uri) && (!this->canHandleCallback || this->canHandleCallback(method, uri));
|
||||
}
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
bool handle(WebServer& server, HTTPMethod method, const String uri) override {
|
||||
#else
|
||||
bool handle(WebServer& server, HTTPMethod method, const String& uri) override {
|
||||
#endif
|
||||
if (!this->canHandle(method, uri)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -28,18 +28,16 @@ public:
|
||||
}
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
bool canHandle(HTTPMethod method, const String uri) override {
|
||||
#else
|
||||
bool canHandle(HTTPMethod method, const String& uri) override {
|
||||
bool canHandle(WebServer &server, HTTPMethod method, const String &uri) override {
|
||||
return this->canHandle(method, uri);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool canHandle(HTTPMethod method, const String& uri) override {
|
||||
return method == HTTP_GET && uri.equals(this->uri) && (!this->canHandleCallback || this->canHandleCallback(method, uri));
|
||||
}
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
bool handle(WebServer& server, HTTPMethod method, const String uri) override {
|
||||
#else
|
||||
bool handle(WebServer& server, HTTPMethod method, const String& uri) override {
|
||||
#endif
|
||||
if (!this->canHandle(method, uri)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -58,26 +58,26 @@ public:
|
||||
}
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
bool canHandle(HTTPMethod method, const String uri) override {
|
||||
#else
|
||||
bool canHandle(HTTPMethod method, const String& uri) override {
|
||||
bool canHandle(WebServer &server, HTTPMethod method, const String &uri) override {
|
||||
return this->canHandle(method, uri);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool canHandle(HTTPMethod method, const String& uri) override {
|
||||
return method == HTTP_POST && uri.equals(this->uri) && (!this->canHandleCallback || this->canHandleCallback(method, uri));
|
||||
}
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
bool canUpload(const String uri) override {
|
||||
#else
|
||||
bool canUpload(const String& uri) override {
|
||||
bool canUpload(WebServer &server, const String &uri) override {
|
||||
return this->canUpload(uri);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool canUpload(const String& uri) override {
|
||||
return uri.equals(this->uri) && (!this->canUploadCallback || this->canUploadCallback(uri));
|
||||
}
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
bool handle(WebServer& server, HTTPMethod method, const String uri) override {
|
||||
#else
|
||||
bool handle(WebServer& server, HTTPMethod method, const String& uri) override {
|
||||
#endif
|
||||
if (this->afterUpgradeCallback) {
|
||||
this->afterUpgradeCallback(this->firmwareResult, this->filesystemResult);
|
||||
}
|
||||
@@ -91,11 +91,7 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
void upload(WebServer& server, const String uri, HTTPUpload& upload) override {
|
||||
#else
|
||||
void upload(WebServer& server, const String& uri, HTTPUpload& upload) override {
|
||||
#endif
|
||||
UpgradeResult* result;
|
||||
if (upload.name.equals("firmware")) {
|
||||
result = &this->firmwareResult;
|
||||
|
||||
17
package.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "otgateway",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cssnano": "^7.0.2",
|
||||
"cssnano-preset-advanced": "^7.0.2",
|
||||
"gulp": "^5.0.0",
|
||||
"gulp-concat": "^2.6.1",
|
||||
"gulp-gzip": "^1.4.2",
|
||||
"gulp-html-minifier-terser": "^7.1.0",
|
||||
"gulp-jsonminify": "^1.1.0",
|
||||
"gulp-postcss": "^10.0.0",
|
||||
"gulp-terser": "^2.1.0"
|
||||
}
|
||||
}
|
||||
@@ -13,19 +13,19 @@
|
||||
extra_configs = secrets.default.ini
|
||||
|
||||
[env]
|
||||
version = 1.4.5
|
||||
framework = arduino
|
||||
lib_deps =
|
||||
bblanchon/ArduinoJson@^7.0.4
|
||||
bblanchon/ArduinoJson@^7.1.0
|
||||
;ihormelnyk/OpenTherm Library@^1.1.5
|
||||
https://github.com/ihormelnyk/opentherm_library#master
|
||||
arduino-libraries/ArduinoMqttClient@^0.1.8
|
||||
lennarthennigs/ESP Telnet@^2.2
|
||||
gyverlibs/FileData@^1.0.2
|
||||
gyverlibs/GyverPID@^3.3.2
|
||||
gyverlibs/GyverBlinker@^1.0
|
||||
https://github.com/PaulStoffregen/OneWire#master
|
||||
milesburton/DallasTemperature@^3.11.0
|
||||
laxilef/TinyLogger@^1.1.0
|
||||
gyverlibs/GyverBlinker@^1.1.1
|
||||
https://github.com/pstolarz/Arduino-Temperature-Control-Library.git#OneWireNg
|
||||
laxilef/TinyLogger@^1.1.1
|
||||
build_flags =
|
||||
-D PIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY
|
||||
;-D PIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH
|
||||
@@ -33,6 +33,8 @@ build_flags =
|
||||
-mtext-section-literals
|
||||
-D MQTT_CLIENT_STD_FUNCTION_CALLBACK=1
|
||||
;-D DEBUG_ESP_CORE -D DEBUG_ESP_WIFI -D DEBUG_ESP_HTTP_SERVER -D DEBUG_ESP_PORT=Serial
|
||||
-D BUILD_VERSION='"${this.version}"'
|
||||
-D BUILD_ENV='"$PIOENV"'
|
||||
-D USE_SERIAL=${secrets.use_serial}
|
||||
-D USE_TELNET=${secrets.use_telnet}
|
||||
-D DEBUG_BY_DEFAULT=${secrets.debug}
|
||||
@@ -53,7 +55,6 @@ monitor_speed = 115200
|
||||
monitor_filters = direct
|
||||
board_build.flash_mode = dio
|
||||
board_build.filesystem = littlefs
|
||||
version = 1.4.1
|
||||
|
||||
; Defaults
|
||||
[esp8266_defaults]
|
||||
@@ -69,15 +70,17 @@ board_build.ldscript = eagle.flash.4m1m.ld
|
||||
|
||||
[esp32_defaults]
|
||||
;platform = espressif32@^6.7
|
||||
platform = https://github.com/platformio/platform-espressif32.git
|
||||
;platform = https://github.com/platformio/platform-espressif32.git
|
||||
;platform_packages =
|
||||
; framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.5
|
||||
; framework-arduinoespressif32-libs @ https://github.com/espressif/esp32-arduino-lib-builder/releases/download/idf-release_v5.1/esp32-arduino-libs-idf-release_v5.1-33fbade6.zip
|
||||
platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
|
||||
platform_packages =
|
||||
;platformio/framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32/archive/refs/tags/2.0.17.zip
|
||||
framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.1
|
||||
framework-arduinoespressif32-libs @ https://github.com/espressif/arduino-esp32/releases/download/3.0.1/esp32-arduino-libs-3.0.1.zip
|
||||
board_build.partitions = esp32_partitions.csv
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
laxilef/ESP32Scheduler@^1.0.1
|
||||
nimble_lib = h2zero/NimBLE-Arduino@^1.4.2
|
||||
lib_ignore =
|
||||
extra_scripts =
|
||||
post:tools/esp32.py
|
||||
@@ -136,6 +139,22 @@ build_flags =
|
||||
-D DEFAULT_STATUS_LED_GPIO=13
|
||||
-D DEFAULT_OT_RX_LED_GPIO=15
|
||||
|
||||
[env:nodemcu_8266]
|
||||
platform = ${esp8266_defaults.platform}
|
||||
board = nodemcuv2
|
||||
lib_deps = ${esp8266_defaults.lib_deps}
|
||||
lib_ignore = ${esp8266_defaults.lib_ignore}
|
||||
extra_scripts = ${esp8266_defaults.extra_scripts}
|
||||
board_build.ldscript = ${esp8266_defaults.board_build.ldscript}
|
||||
build_flags =
|
||||
${esp8266_defaults.build_flags}
|
||||
-D DEFAULT_OT_IN_GPIO=13
|
||||
-D DEFAULT_OT_OUT_GPIO=15
|
||||
-D DEFAULT_SENSOR_OUTDOOR_GPIO=12
|
||||
-D DEFAULT_SENSOR_INDOOR_GPIO=4
|
||||
-D DEFAULT_STATUS_LED_GPIO=2
|
||||
-D DEFAULT_OT_RX_LED_GPIO=16
|
||||
|
||||
[env:s2_mini]
|
||||
platform = ${esp32_defaults.platform}
|
||||
platform_packages = ${esp32_defaults.platform_packages}
|
||||
@@ -164,7 +183,7 @@ board = lolin_s3_mini
|
||||
board_build.partitions = ${esp32_defaults.board_build.partitions}
|
||||
lib_deps =
|
||||
${esp32_defaults.lib_deps}
|
||||
h2zero/NimBLE-Arduino@^1.4.1
|
||||
${esp32_defaults.nimble_lib}
|
||||
lib_ignore = ${esp32_defaults.lib_ignore}
|
||||
extra_scripts = ${esp32_defaults.extra_scripts}
|
||||
build_unflags =
|
||||
@@ -188,7 +207,7 @@ board = lolin_c3_mini
|
||||
board_build.partitions = ${esp32_defaults.board_build.partitions}
|
||||
lib_deps =
|
||||
${esp32_defaults.lib_deps}
|
||||
h2zero/NimBLE-Arduino@^1.4.1
|
||||
${esp32_defaults.nimble_lib}
|
||||
lib_ignore = ${esp32_defaults.lib_ignore}
|
||||
extra_scripts = ${esp32_defaults.extra_scripts}
|
||||
build_unflags =
|
||||
@@ -203,26 +222,25 @@ build_flags =
|
||||
-D DEFAULT_STATUS_LED_GPIO=4
|
||||
-D DEFAULT_OT_RX_LED_GPIO=5
|
||||
|
||||
[env:nodemcu_32s]
|
||||
[env:nodemcu_32]
|
||||
platform = ${esp32_defaults.platform}
|
||||
platform_packages = ${esp32_defaults.platform_packages}
|
||||
board = nodemcu-32s
|
||||
board_build.partitions = ${esp32_defaults.board_build.partitions}
|
||||
lib_deps =
|
||||
${esp32_defaults.lib_deps}
|
||||
h2zero/NimBLE-Arduino@^1.4.1
|
||||
${esp32_defaults.nimble_lib}
|
||||
lib_ignore = ${esp32_defaults.lib_ignore}
|
||||
extra_scripts = ${esp32_defaults.extra_scripts}
|
||||
build_flags =
|
||||
${esp32_defaults.build_flags}
|
||||
-D USE_BLE=1
|
||||
-D DEFAULT_OT_IN_GPIO=21
|
||||
-D DEFAULT_OT_OUT_GPIO=22
|
||||
-D DEFAULT_SENSOR_OUTDOOR_GPIO=12
|
||||
-D DEFAULT_SENSOR_INDOOR_GPIO=13
|
||||
-D DEFAULT_STATUS_LED_GPIO=2 ; 18
|
||||
-D DEFAULT_OT_IN_GPIO=16
|
||||
-D DEFAULT_OT_OUT_GPIO=4
|
||||
-D DEFAULT_SENSOR_OUTDOOR_GPIO=15
|
||||
-D DEFAULT_SENSOR_INDOOR_GPIO=26
|
||||
-D DEFAULT_STATUS_LED_GPIO=2
|
||||
-D DEFAULT_OT_RX_LED_GPIO=19
|
||||
;-D WOKWI=1
|
||||
|
||||
[env:d1_mini32]
|
||||
platform = ${esp32_defaults.platform}
|
||||
@@ -231,7 +249,7 @@ board = wemos_d1_mini32
|
||||
board_build.partitions = ${esp32_defaults.board_build.partitions}
|
||||
lib_deps =
|
||||
${esp32_defaults.lib_deps}
|
||||
h2zero/NimBLE-Arduino@^1.4.1
|
||||
${esp32_defaults.nimble_lib}
|
||||
lib_ignore = ${esp32_defaults.lib_ignore}
|
||||
extra_scripts = ${esp32_defaults.extra_scripts}
|
||||
build_flags =
|
||||
@@ -243,3 +261,20 @@ build_flags =
|
||||
-D DEFAULT_SENSOR_INDOOR_GPIO=18
|
||||
-D DEFAULT_STATUS_LED_GPIO=2
|
||||
-D DEFAULT_OT_RX_LED_GPIO=19
|
||||
|
||||
[env:esp32_c6]
|
||||
platform = ${esp32_defaults.platform}
|
||||
platform_packages = ${esp32_defaults.platform_packages}
|
||||
board = esp32-c6-devkitm-1
|
||||
board_build.partitions = ${esp32_defaults.board_build.partitions}
|
||||
lib_deps =
|
||||
${esp32_defaults.lib_deps}
|
||||
;${esp32_defaults.nimble_lib}
|
||||
lib_ignore = ${esp32_defaults.lib_ignore}
|
||||
extra_scripts = ${esp32_defaults.extra_scripts}
|
||||
build_unflags =
|
||||
-mtext-section-literals
|
||||
build_flags =
|
||||
${esp32_defaults.build_flags}
|
||||
; Currently the NimBLE library is incompatible with ESP32 C6
|
||||
;-D USE_BLE=1
|
||||
@@ -885,13 +885,33 @@ public:
|
||||
doc[FPSTR(HA_NAME)] = F("Fault code");
|
||||
doc[FPSTR(HA_ICON)] = F("mdi:chat-alert-outline");
|
||||
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state"));
|
||||
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ \"E%02d\"|format(value_json.sensors.faultCode) }}");
|
||||
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ \"%02d (0x%02X)\"|format(value_json.sensors.faultCode, value_json.sensors.faultCode) }}");
|
||||
doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
|
||||
doc.shrinkToFit();
|
||||
|
||||
return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("fault_code")).c_str(), doc);
|
||||
}
|
||||
|
||||
bool publishSensorDiagnosticCode(bool enabledByDefault = true) {
|
||||
JsonDocument doc;
|
||||
doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status"));
|
||||
doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("state"));
|
||||
doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.states.otStatus and value_json.states.fault or value_json.states.diagnostic, 'online', 'offline') }}");
|
||||
doc[FPSTR(HA_AVAILABILITY_MODE)] = F("all");
|
||||
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
|
||||
doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("diagnostic_code"));
|
||||
doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("diagnostic_code"));
|
||||
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic");
|
||||
doc[FPSTR(HA_NAME)] = F("Diagnostic code");
|
||||
doc[FPSTR(HA_ICON)] = F("mdi:chat-alert-outline");
|
||||
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state"));
|
||||
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ \"%02d (0x%02X)\"|format(value_json.sensors.diagnosticCode, value_json.sensors.diagnosticCode) }}");
|
||||
doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
|
||||
doc.shrinkToFit();
|
||||
|
||||
return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("diagnostic_code")).c_str(), doc);
|
||||
}
|
||||
|
||||
bool publishSensorRssi(bool enabledByDefault = true) {
|
||||
JsonDocument doc;
|
||||
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status"));
|
||||
|
||||
101
src/MainTask.h
@@ -29,7 +29,6 @@ protected:
|
||||
enum class PumpStartReason {NONE, HEATING, ANTISTUCK};
|
||||
|
||||
Blinker* blinker = nullptr;
|
||||
unsigned long firstFailConnect = 0;
|
||||
unsigned long lastHeapInfo = 0;
|
||||
unsigned int minFreeHeap = 0;
|
||||
unsigned int minMaxFreeBlockHeap = 0;
|
||||
@@ -39,6 +38,8 @@ protected:
|
||||
PumpStartReason extPumpStartReason = PumpStartReason::NONE;
|
||||
unsigned long externalPumpStartTime = 0;
|
||||
bool telnetStarted = false;
|
||||
bool emergencyDetected = false;
|
||||
unsigned long emergencyFlipTime = 0;
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
const char* getTaskName() override {
|
||||
@@ -85,10 +86,6 @@ protected:
|
||||
vars.states.mqtt = tMqtt->isConnected();
|
||||
vars.sensors.rssi = network->isConnected() ? WiFi.RSSI() : 0;
|
||||
|
||||
if (vars.states.emergency && !settings.emergency.enable) {
|
||||
vars.states.emergency = false;
|
||||
}
|
||||
|
||||
if (network->isConnected()) {
|
||||
if (!this->telnetStarted && telnetStream != nullptr) {
|
||||
telnetStream->begin(23, false);
|
||||
@@ -102,17 +99,6 @@ protected:
|
||||
tMqtt->disable();
|
||||
}
|
||||
|
||||
if (!vars.states.emergency && settings.emergency.enable && settings.emergency.onMqttFault && !tMqtt->isEnabled()) {
|
||||
vars.states.emergency = true;
|
||||
|
||||
} else if (vars.states.emergency && !settings.emergency.onMqttFault) {
|
||||
vars.states.emergency = false;
|
||||
}
|
||||
|
||||
if (this->firstFailConnect != 0) {
|
||||
this->firstFailConnect = 0;
|
||||
}
|
||||
|
||||
if ( Log.getLevel() != TinyLogger::Level::INFO && !settings.system.debug ) {
|
||||
Log.setLevel(TinyLogger::Level::INFO);
|
||||
|
||||
@@ -129,21 +115,10 @@ protected:
|
||||
if (tMqtt->isEnabled()) {
|
||||
tMqtt->disable();
|
||||
}
|
||||
|
||||
if (!vars.states.emergency && settings.emergency.enable && settings.emergency.onNetworkFault) {
|
||||
if (this->firstFailConnect == 0) {
|
||||
this->firstFailConnect = millis();
|
||||
}
|
||||
|
||||
if (millis() - this->firstFailConnect > (settings.emergency.tresholdTime * 1000)) {
|
||||
vars.states.emergency = true;
|
||||
Log.sinfoln(FPSTR(L_MAIN), F("Emergency mode enabled"));
|
||||
}
|
||||
}
|
||||
}
|
||||
this->yield();
|
||||
|
||||
|
||||
this->emergency();
|
||||
this->ledStatus();
|
||||
this->externalPump();
|
||||
this->yield();
|
||||
@@ -213,6 +188,76 @@ protected:
|
||||
}
|
||||
}
|
||||
|
||||
void emergency() {
|
||||
if (!settings.emergency.enable && vars.states.emergency) {
|
||||
this->emergencyDetected = false;
|
||||
vars.states.emergency = false;
|
||||
|
||||
Log.sinfoln(FPSTR(L_MAIN), F("Emergency mode disabled"));
|
||||
}
|
||||
|
||||
if (!settings.emergency.enable) {
|
||||
return;
|
||||
}
|
||||
|
||||
// flags
|
||||
uint8_t emergencyFlags = 0b00000000;
|
||||
|
||||
// set network flag
|
||||
if (settings.emergency.onNetworkFault && !network->isConnected()) {
|
||||
emergencyFlags |= 0b00000001;
|
||||
}
|
||||
|
||||
// set mqtt flag
|
||||
if (settings.emergency.onMqttFault && (!tMqtt->isEnabled() || !tMqtt->isConnected())) {
|
||||
emergencyFlags |= 0b00000010;
|
||||
}
|
||||
|
||||
// set outdoor sensor flag
|
||||
if (settings.sensors.outdoor.type == SensorType::DS18B20 || settings.sensors.outdoor.type == SensorType::BLUETOOTH) {
|
||||
if (settings.emergency.onOutdoorSensorDisconnect && !vars.sensors.outdoor.connected) {
|
||||
emergencyFlags |= 0b00000100;
|
||||
}
|
||||
}
|
||||
|
||||
// set indoor sensor flag
|
||||
if (settings.sensors.indoor.type == SensorType::DS18B20 || settings.sensors.indoor.type == SensorType::BLUETOOTH) {
|
||||
if (settings.emergency.onIndoorSensorDisconnect && !vars.sensors.indoor.connected) {
|
||||
emergencyFlags |= 0b00001000;
|
||||
}
|
||||
}
|
||||
|
||||
// if any flags is true
|
||||
if ((emergencyFlags & 0b00001111) != 0) {
|
||||
if (!this->emergencyDetected) {
|
||||
// flip flag
|
||||
this->emergencyDetected = true;
|
||||
this->emergencyFlipTime = millis();
|
||||
|
||||
} else if (this->emergencyDetected && !vars.states.emergency) {
|
||||
// enable emergency
|
||||
if (millis() - this->emergencyFlipTime > (settings.emergency.tresholdTime * 1000)) {
|
||||
vars.states.emergency = true;
|
||||
Log.sinfoln(FPSTR(L_MAIN), F("Emergency mode enabled (%hhu)"), emergencyFlags);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
if (this->emergencyDetected) {
|
||||
// flip flag
|
||||
this->emergencyDetected = false;
|
||||
this->emergencyFlipTime = millis();
|
||||
|
||||
} else if (!this->emergencyDetected && vars.states.emergency) {
|
||||
// disable emergency
|
||||
if (millis() - this->emergencyFlipTime > 30000) {
|
||||
vars.states.emergency = false;
|
||||
Log.sinfoln(FPSTR(L_MAIN), F("Emergency mode disabled"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ledStatus() {
|
||||
uint8_t errors[4];
|
||||
uint8_t errCount = 0;
|
||||
|
||||
@@ -163,7 +163,7 @@ protected:
|
||||
|
||||
// ha helper settings
|
||||
this->haHelper->setDevicePrefix(settings.mqtt.prefix);
|
||||
this->haHelper->setDeviceVersion(PROJECT_VERSION);
|
||||
this->haHelper->setDeviceVersion(BUILD_VERSION);
|
||||
this->haHelper->setDeviceModel(PROJECT_NAME);
|
||||
this->haHelper->setDeviceName(PROJECT_NAME);
|
||||
this->haHelper->setWriter(this->writer);
|
||||
@@ -198,17 +198,6 @@ protected:
|
||||
this->onConnect();
|
||||
}
|
||||
|
||||
if (settings.emergency.enable && settings.emergency.onMqttFault) {
|
||||
if (!this->connected && !vars.states.emergency && millis() - this->disconnectedTime > (settings.emergency.tresholdTime * 1000)) {
|
||||
vars.states.emergency = true;
|
||||
Log.sinfoln(FPSTR(L_MQTT), F("Emergency mode enabled"));
|
||||
|
||||
} else if (this->connected && vars.states.emergency && millis() - this->connectedTime > 10000) {
|
||||
vars.states.emergency = false;
|
||||
Log.sinfoln(FPSTR(L_MQTT), F("Emergency mode disabled"));
|
||||
}
|
||||
}
|
||||
|
||||
if (!this->connected) {
|
||||
return;
|
||||
}
|
||||
@@ -369,6 +358,7 @@ protected:
|
||||
this->haHelper->publishSensorModulation(false);
|
||||
this->haHelper->publishSensorPressure(settings.system.unitSystem, false);
|
||||
this->haHelper->publishSensorFaultCode();
|
||||
this->haHelper->publishSensorDiagnosticCode();
|
||||
this->haHelper->publishSensorRssi(false);
|
||||
this->haHelper->publishSensorUptime(false);
|
||||
|
||||
|
||||
@@ -22,8 +22,6 @@ protected:
|
||||
bool isInitialized = false;
|
||||
unsigned long initializedTime = 0;
|
||||
unsigned int initializedMemberIdCode = 0;
|
||||
byte dhwFlowRateMultiplier = 1;
|
||||
byte pressureMultiplier = 1;
|
||||
bool pump = true;
|
||||
unsigned long lastSuccessResponse = 0;
|
||||
unsigned long prevUpdateNonEssentialVars = 0;
|
||||
@@ -165,6 +163,16 @@ protected:
|
||||
heatingCh2Enabled = settings.opentherm.dhwPresent && settings.dhw.enable;
|
||||
}
|
||||
|
||||
// Set boiler status LB
|
||||
// Some boilers require this, although this is against protocol
|
||||
uint8_t statusLb = 0;
|
||||
|
||||
// Immergas fix
|
||||
// https://arduino.ru/forum/programmirovanie/termostat-opentherm-na-esp8266?page=15#comment-649392
|
||||
if (settings.opentherm.immergasFix) {
|
||||
statusLb = 0xCA;
|
||||
}
|
||||
|
||||
unsigned long response = this->instance->setBoilerStatus(
|
||||
heatingEnabled,
|
||||
settings.opentherm.dhwPresent && settings.dhw.enable,
|
||||
@@ -172,7 +180,8 @@ protected:
|
||||
settings.opentherm.nativeHeatingControl,
|
||||
heatingCh2Enabled,
|
||||
settings.opentherm.summerWinterMode,
|
||||
settings.opentherm.dhwBlocking
|
||||
settings.opentherm.dhwBlocking,
|
||||
statusLb
|
||||
);
|
||||
|
||||
if (!CustomOpenTherm::isValidResponse(response)) {
|
||||
@@ -217,8 +226,6 @@ protected:
|
||||
this->isInitialized = true;
|
||||
this->initializedTime = millis();
|
||||
this->initializedMemberIdCode = settings.opentherm.memberIdCode;
|
||||
this->dhwFlowRateMultiplier = 1;
|
||||
this->pressureMultiplier = 1;
|
||||
this->initialize();
|
||||
}
|
||||
|
||||
@@ -247,7 +254,7 @@ protected:
|
||||
// These parameters will be updated every minute
|
||||
if (millis() - this->prevUpdateNonEssentialVars > 60000) {
|
||||
if (!heatingEnabled && settings.opentherm.modulationSyncWithHeating) {
|
||||
if (setMaxModulationLevel(0)) {
|
||||
if (this->setMaxModulationLevel(0)) {
|
||||
Log.snoticeln(FPSTR(L_OT_HEATING), F("Set max modulation 0% (off)"));
|
||||
|
||||
} else {
|
||||
@@ -255,7 +262,7 @@ protected:
|
||||
}
|
||||
|
||||
} else {
|
||||
if (setMaxModulationLevel(settings.heating.maxModulation)) {
|
||||
if (this->setMaxModulationLevel(settings.heating.maxModulation)) {
|
||||
Log.snoticeln(FPSTR(L_OT_HEATING), F("Set max modulation %hhu%%"), settings.heating.maxModulation);
|
||||
|
||||
} else {
|
||||
@@ -266,7 +273,7 @@ protected:
|
||||
|
||||
// Get DHW min/max temp (if necessary)
|
||||
if (settings.opentherm.dhwPresent && settings.opentherm.getMinMaxTemp) {
|
||||
if (updateMinMaxDhwTemp()) {
|
||||
if (this->updateMinMaxDhwTemp()) {
|
||||
if (settings.dhw.minTemp < vars.parameters.dhwMinTemp) {
|
||||
settings.dhw.minTemp = vars.parameters.dhwMinTemp;
|
||||
fsSettings.update();
|
||||
@@ -296,7 +303,7 @@ protected:
|
||||
|
||||
// Get heating min/max temp
|
||||
if (settings.opentherm.getMinMaxTemp) {
|
||||
if (updateMinMaxHeatingTemp()) {
|
||||
if (this->updateMinMaxHeatingTemp()) {
|
||||
if (settings.heating.minTemp < vars.parameters.heatingMinTemp) {
|
||||
settings.heating.minTemp = vars.parameters.heatingMinTemp;
|
||||
fsSettings.update();
|
||||
@@ -323,20 +330,33 @@ protected:
|
||||
fsSettings.update();
|
||||
}
|
||||
|
||||
// Get outdoor temp (if necessary)
|
||||
if (settings.sensors.outdoor.type == SensorType::BOILER) {
|
||||
updateOutsideTemp();
|
||||
}
|
||||
|
||||
// Get fault code (if necessary)
|
||||
if (vars.states.fault) {
|
||||
updateFaultCode();
|
||||
this->updateFaultCode();
|
||||
|
||||
} else if (vars.sensors.faultCode != 0) {
|
||||
vars.sensors.faultCode = 0;
|
||||
}
|
||||
|
||||
updatePressure();
|
||||
// Get diagnostic code (if necessary)
|
||||
if (vars.states.fault || vars.states.diagnostic) {
|
||||
this->updateDiagCode();
|
||||
|
||||
} else if (vars.sensors.diagnosticCode != 0) {
|
||||
vars.sensors.diagnosticCode = 0;
|
||||
}
|
||||
|
||||
// If filtering is disabled, then it is enough to
|
||||
// update these parameters once a minute
|
||||
if (!settings.opentherm.filterNumValues.enable) {
|
||||
// Get outdoor temp (if necessary)
|
||||
if (settings.sensors.outdoor.type == SensorType::BOILER) {
|
||||
this->updateOutdoorTemp();
|
||||
}
|
||||
|
||||
// Get pressure
|
||||
this->updatePressure();
|
||||
}
|
||||
|
||||
this->prevUpdateNonEssentialVars = millis();
|
||||
}
|
||||
@@ -344,7 +364,7 @@ protected:
|
||||
|
||||
// Get current modulation level (if necessary)
|
||||
if (vars.states.flame) {
|
||||
updateModulationLevel();
|
||||
this->updateModulationLevel();
|
||||
|
||||
} else {
|
||||
vars.sensors.modulation = 0;
|
||||
@@ -352,8 +372,8 @@ protected:
|
||||
|
||||
// Update DHW sensors (if necessary)
|
||||
if (settings.opentherm.dhwPresent) {
|
||||
updateDhwTemp();
|
||||
updateDhwFlowRate();
|
||||
this->updateDhwTemp();
|
||||
this->updateDhwFlowRate();
|
||||
|
||||
} else {
|
||||
vars.temperatures.dhw = 0.0f;
|
||||
@@ -361,13 +381,25 @@ protected:
|
||||
}
|
||||
|
||||
// Get current heating temp
|
||||
updateHeatingTemp();
|
||||
this->updateHeatingTemp();
|
||||
|
||||
// Get heating return temp
|
||||
updateHeatingReturnTemp();
|
||||
this->updateHeatingReturnTemp();
|
||||
|
||||
// Get exhaust temp
|
||||
updateExhaustTemp();
|
||||
this->updateExhaustTemp();
|
||||
|
||||
// If filtering is enabled, these parameters
|
||||
// must be updated every time.
|
||||
if (settings.opentherm.filterNumValues.enable) {
|
||||
// Get outdoor temp (if necessary)
|
||||
if (settings.sensors.outdoor.type == SensorType::BOILER) {
|
||||
this->updateOutdoorTemp();
|
||||
}
|
||||
|
||||
// Get pressure
|
||||
this->updatePressure();
|
||||
}
|
||||
|
||||
|
||||
// Fault reset action
|
||||
@@ -538,6 +570,13 @@ protected:
|
||||
Log.swarningln(FPSTR(L_OT), F("Get slave OT version failed"));
|
||||
}
|
||||
|
||||
if (this->setMasterOtVersion(2.2f)) {
|
||||
Log.straceln(FPSTR(L_OT), F("Master OT version: %f"), vars.parameters.masterOtVersion);
|
||||
|
||||
} else {
|
||||
Log.swarningln(FPSTR(L_OT), F("Set master OT version failed"));
|
||||
}
|
||||
|
||||
if (this->updateSlaveConfig()) {
|
||||
Log.straceln(FPSTR(L_OT), F("Slave member id: %u, flags: %u"), vars.parameters.slaveMemberId, vars.parameters.slaveFlags);
|
||||
|
||||
@@ -773,7 +812,7 @@ protected:
|
||||
return CustomOpenTherm::isValidResponse(response);
|
||||
}
|
||||
|
||||
bool updateOutsideTemp() {
|
||||
bool updateOutdoorTemp() {
|
||||
unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
|
||||
OpenThermRequestType::READ_DATA,
|
||||
OpenThermMessageID::Toutside,
|
||||
@@ -784,12 +823,19 @@ protected:
|
||||
return false;
|
||||
}
|
||||
|
||||
vars.temperatures.outdoor = settings.sensors.outdoor.offset + convertTemp(
|
||||
float value = settings.sensors.outdoor.offset + convertTemp(
|
||||
CustomOpenTherm::getFloat(response),
|
||||
settings.opentherm.unitSystem,
|
||||
settings.system.unitSystem
|
||||
);
|
||||
|
||||
if (settings.opentherm.filterNumValues.enable && fabs(vars.temperatures.outdoor) >= 0.1f) {
|
||||
vars.temperatures.outdoor += (value - vars.temperatures.outdoor) * settings.opentherm.filterNumValues.factor;
|
||||
|
||||
} else {
|
||||
vars.temperatures.outdoor = value;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -809,12 +855,19 @@ protected:
|
||||
return false;
|
||||
}
|
||||
|
||||
vars.temperatures.exhaust = convertTemp(
|
||||
value = convertTemp(
|
||||
value,
|
||||
settings.opentherm.unitSystem,
|
||||
settings.system.unitSystem
|
||||
);
|
||||
|
||||
if (settings.opentherm.filterNumValues.enable && fabs(vars.temperatures.exhaust) >= 0.1f) {
|
||||
vars.temperatures.exhaust += (value - vars.temperatures.exhaust) * settings.opentherm.filterNumValues.factor;
|
||||
|
||||
} else {
|
||||
vars.temperatures.exhaust = value;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -834,12 +887,19 @@ protected:
|
||||
return false;
|
||||
}
|
||||
|
||||
vars.temperatures.heating = convertTemp(
|
||||
value = convertTemp(
|
||||
value,
|
||||
settings.opentherm.unitSystem,
|
||||
settings.system.unitSystem
|
||||
);
|
||||
|
||||
if (settings.opentherm.filterNumValues.enable && fabs(vars.temperatures.heating) >= 0.1f) {
|
||||
vars.temperatures.heating += (value - vars.temperatures.heating) * settings.opentherm.filterNumValues.factor;
|
||||
|
||||
} else {
|
||||
vars.temperatures.heating = value;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -854,12 +914,20 @@ protected:
|
||||
return false;
|
||||
}
|
||||
|
||||
vars.temperatures.heatingReturn = convertTemp(
|
||||
float value = convertTemp(
|
||||
CustomOpenTherm::getFloat(response),
|
||||
settings.opentherm.unitSystem,
|
||||
settings.system.unitSystem
|
||||
);
|
||||
|
||||
if (settings.opentherm.filterNumValues.enable && fabs(vars.temperatures.heatingReturn) >= 0.1f) {
|
||||
vars.temperatures.heatingReturn += (value - vars.temperatures.heatingReturn) * settings.opentherm.filterNumValues.factor;
|
||||
|
||||
} else {
|
||||
vars.temperatures.heatingReturn = value;
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -880,12 +948,19 @@ protected:
|
||||
return false;
|
||||
}
|
||||
|
||||
vars.temperatures.dhw = convertTemp(
|
||||
value = convertTemp(
|
||||
value,
|
||||
settings.opentherm.unitSystem,
|
||||
settings.system.unitSystem
|
||||
);
|
||||
|
||||
if (settings.opentherm.filterNumValues.enable && fabs(vars.temperatures.dhw) >= 0.1f) {
|
||||
vars.temperatures.dhw += (value - vars.temperatures.dhw) * settings.opentherm.filterNumValues.factor;
|
||||
|
||||
} else {
|
||||
vars.temperatures.dhw = value;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -901,15 +976,17 @@ protected:
|
||||
}
|
||||
|
||||
float value = CustomOpenTherm::getFloat(response);
|
||||
if (this->dhwFlowRateMultiplier != 10 && value > convertVolume(16, UnitSystem::METRIC, settings.opentherm.unitSystem)) {
|
||||
this->dhwFlowRateMultiplier = 10;
|
||||
if (value < 0 || value > convertVolume(16, UnitSystem::METRIC, settings.opentherm.unitSystem)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
vars.sensors.dhwFlowRate = convertVolume(
|
||||
value / this->dhwFlowRateMultiplier,
|
||||
value = convertVolume(
|
||||
value * settings.opentherm.dhwFlowRateFactor,
|
||||
settings.opentherm.unitSystem,
|
||||
settings.system.unitSystem
|
||||
);
|
||||
|
||||
vars.sensors.dhwFlowRate = value > 0.09f ? value : 0.0f;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -922,6 +999,7 @@ protected:
|
||||
));
|
||||
|
||||
if (!CustomOpenTherm::isValidResponse(response)) {
|
||||
vars.sensors.faultCode = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -929,6 +1007,22 @@ protected:
|
||||
return true;
|
||||
}
|
||||
|
||||
bool updateDiagCode() {
|
||||
unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
|
||||
OpenThermRequestType::READ_DATA,
|
||||
OpenThermMessageID::OEMDiagnosticCode,
|
||||
0
|
||||
));
|
||||
|
||||
if (!CustomOpenTherm::isValidResponse(response)) {
|
||||
vars.sensors.diagnosticCode = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
vars.sensors.diagnosticCode = CustomOpenTherm::getUInt(response);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool updateModulationLevel() {
|
||||
unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
|
||||
OpenThermRequestType::READ_DATA,
|
||||
@@ -940,7 +1034,13 @@ protected:
|
||||
return false;
|
||||
}
|
||||
|
||||
vars.sensors.modulation = CustomOpenTherm::getFloat(response);
|
||||
float value = CustomOpenTherm::getFloat(response);
|
||||
if (settings.opentherm.filterNumValues.enable && fabs(vars.sensors.modulation) >= 0.1f) {
|
||||
vars.sensors.modulation += (value - vars.sensors.modulation) * settings.opentherm.filterNumValues.factor;
|
||||
|
||||
} else {
|
||||
vars.sensors.modulation = value;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -957,16 +1057,23 @@ protected:
|
||||
}
|
||||
|
||||
float value = CustomOpenTherm::getFloat(response);
|
||||
if (this->pressureMultiplier != 10 && value > convertPressure(5, UnitSystem::METRIC, settings.opentherm.unitSystem)) {
|
||||
this->pressureMultiplier = 10;
|
||||
if (value < 0 || value > convertPressure(5, UnitSystem::METRIC, settings.opentherm.unitSystem)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
vars.sensors.pressure = convertPressure(
|
||||
value / this->pressureMultiplier,
|
||||
value = convertPressure(
|
||||
value * settings.opentherm.pressureFactor,
|
||||
settings.opentherm.unitSystem,
|
||||
settings.system.unitSystem
|
||||
);
|
||||
|
||||
if (settings.opentherm.filterNumValues.enable && fabs(vars.sensors.pressure) >= 0.1f) {
|
||||
vars.sensors.pressure += (value - vars.sensors.pressure) * settings.opentherm.filterNumValues.factor;
|
||||
|
||||
} else {
|
||||
vars.sensors.pressure = value;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -77,21 +77,21 @@ protected:
|
||||
#endif
|
||||
|
||||
// index page
|
||||
/*auto indexPage = (new DynamicPage("/", &LittleFS, "/index.html"))
|
||||
/*auto indexPage = (new DynamicPage("/", &LittleFS, "/pages/index.html"))
|
||||
->setTemplateCallback([](const char* var) -> String {
|
||||
String result;
|
||||
|
||||
if (strcmp(var, "ver") == 0) {
|
||||
result = PROJECT_VERSION;
|
||||
result = BUILD_VERSION;
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
this->webServer->addHandler(indexPage);*/
|
||||
this->webServer->addHandler(new StaticPage("/", &LittleFS, "/index.html", PORTAL_CACHE));
|
||||
this->webServer->addHandler(new StaticPage("/", &LittleFS, "/pages/index.html", PORTAL_CACHE));
|
||||
|
||||
// dashboard page
|
||||
auto dashboardPage = (new StaticPage("/dashboard.html", &LittleFS, "/dashboard.html", PORTAL_CACHE))
|
||||
auto dashboardPage = (new StaticPage("/dashboard.html", &LittleFS, "/pages/dashboard.html", PORTAL_CACHE))
|
||||
->setBeforeSendCallback([this]() {
|
||||
if (this->isAuthRequired() && !this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
|
||||
this->webServer->requestAuthentication(DIGEST_AUTH);
|
||||
@@ -117,7 +117,7 @@ protected:
|
||||
});
|
||||
|
||||
// network settings page
|
||||
auto networkPage = (new StaticPage("/network.html", &LittleFS, "/network.html", PORTAL_CACHE))
|
||||
auto networkPage = (new StaticPage("/network.html", &LittleFS, "/pages/network.html", PORTAL_CACHE))
|
||||
->setBeforeSendCallback([this]() {
|
||||
if (this->isAuthRequired() && !this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
|
||||
this->webServer->requestAuthentication(DIGEST_AUTH);
|
||||
@@ -129,7 +129,7 @@ protected:
|
||||
this->webServer->addHandler(networkPage);
|
||||
|
||||
// settings page
|
||||
auto settingsPage = (new StaticPage("/settings.html", &LittleFS, "/settings.html", PORTAL_CACHE))
|
||||
auto settingsPage = (new StaticPage("/settings.html", &LittleFS, "/pages/settings.html", PORTAL_CACHE))
|
||||
->setBeforeSendCallback([this]() {
|
||||
if (this->isAuthRequired() && !this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
|
||||
this->webServer->requestAuthentication(DIGEST_AUTH);
|
||||
@@ -141,7 +141,7 @@ protected:
|
||||
this->webServer->addHandler(settingsPage);
|
||||
|
||||
// upgrade page
|
||||
auto upgradePage = (new StaticPage("/upgrade.html", &LittleFS, "/upgrade.html", PORTAL_CACHE))
|
||||
auto upgradePage = (new StaticPage("/upgrade.html", &LittleFS, "/pages/upgrade.html", PORTAL_CACHE))
|
||||
->setBeforeSendCallback([this]() {
|
||||
if (this->isAuthRequired() && !this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
|
||||
this->webServer->requestAuthentication(DIGEST_AUTH);
|
||||
@@ -250,6 +250,7 @@ protected:
|
||||
fsNetworkSettings.update();
|
||||
network->setHostname(networkSettings.hostname)
|
||||
->setStaCredentials(networkSettings.sta.ssid, networkSettings.sta.password, networkSettings.sta.channel)
|
||||
->setApCredentials(networkSettings.ap.ssid, networkSettings.ap.password, networkSettings.ap.channel)
|
||||
->setUseDhcp(networkSettings.useDhcp)
|
||||
->setStaticConfig(
|
||||
networkSettings.staticConfig.ip,
|
||||
@@ -326,6 +327,7 @@ protected:
|
||||
fsNetworkSettings.update();
|
||||
network->setHostname(networkSettings.hostname)
|
||||
->setStaCredentials(networkSettings.sta.ssid, networkSettings.sta.password, networkSettings.sta.channel)
|
||||
->setApCredentials(networkSettings.ap.ssid, networkSettings.ap.password, networkSettings.ap.channel)
|
||||
->setUseDhcp(networkSettings.useDhcp)
|
||||
->setStaticConfig(
|
||||
networkSettings.staticConfig.ip,
|
||||
@@ -513,8 +515,9 @@ protected:
|
||||
doc["network"]["gateway"] = isConnected ? network->getStaGateway().toString() : "";
|
||||
doc["network"]["dns"] = isConnected ? network->getStaDns().toString() : "";
|
||||
|
||||
doc["system"]["version"] = PROJECT_VERSION;
|
||||
doc["system"]["buildVersion"] = BUILD_VERSION;
|
||||
doc["system"]["buildDate"] = __DATE__ " " __TIME__;
|
||||
doc["system"]["buildEnv"] = BUILD_ENV;
|
||||
doc["system"]["uptime"] = millis() / 1000ul;
|
||||
doc["system"]["totalHeap"] = getTotalHeap();
|
||||
doc["system"]["freeHeap"] = getFreeHeap();
|
||||
@@ -572,13 +575,17 @@ protected:
|
||||
}
|
||||
});
|
||||
|
||||
this->webServer->serveStatic("/favicon.ico", LittleFS, "/static/favicon.ico", PORTAL_CACHE);
|
||||
this->webServer->serveStatic("/favicon.ico", LittleFS, "/static/images/favicon.ico", PORTAL_CACHE);
|
||||
this->webServer->serveStatic("/static", LittleFS, "/static", PORTAL_CACHE);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// web server
|
||||
if (!this->stateWebServer() && (network->isApEnabled() || network->isConnected()) && millis() - this->webServerChangeState >= this->changeStateInterval) {
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
this->delay(250);
|
||||
#endif
|
||||
|
||||
this->startWebServer();
|
||||
Log.straceln(FPSTR(L_PORTAL_WEBSERVER), F("Started: AP up or STA connected"));
|
||||
|
||||
@@ -623,6 +630,10 @@ protected:
|
||||
if (this->stateWebServer()) {
|
||||
this->webServer->handleClient();
|
||||
}
|
||||
|
||||
if (!this->stateDnsServer() && !this->stateWebServer()) {
|
||||
this->delay(250);
|
||||
}
|
||||
}
|
||||
|
||||
bool isAuthRequired() {
|
||||
|
||||
@@ -139,7 +139,8 @@ protected:
|
||||
|
||||
// if use pid
|
||||
if (settings.pid.enable) {
|
||||
if (vars.parameters.heatingEnabled) {
|
||||
//if (vars.parameters.heatingEnabled) {
|
||||
if (settings.heating.enable) {
|
||||
float pidResult = getPidTemp(
|
||||
settings.equitherm.enable ? (settings.pid.maxTemp * -1) : settings.pid.minTemp,
|
||||
settings.pid.maxTemp
|
||||
@@ -160,8 +161,8 @@ protected:
|
||||
}
|
||||
|
||||
} else if (fabs(pidRegulator.integral) > 0.0001f) {
|
||||
pidRegulator.integral = 0;
|
||||
Log.sinfoln(FPSTR(L_REGULATOR_PID), F("Integral sum has been reset"));
|
||||
pidRegulator.integral = 0;
|
||||
Log.sinfoln(FPSTR(L_REGULATOR_PID), F("Integral sum has been reset"));
|
||||
}
|
||||
|
||||
// default temp, manual mode
|
||||
@@ -198,6 +199,10 @@ protected:
|
||||
etRegulator.Kt = 0;
|
||||
etRegulator.indoorTemp = 0;
|
||||
|
||||
} else if ((settings.sensors.indoor.type == SensorType::DS18B20 || settings.sensors.indoor.type == SensorType::BLUETOOTH) && !vars.sensors.indoor.connected) {
|
||||
etRegulator.Kt = 0;
|
||||
etRegulator.indoorTemp = 0;
|
||||
|
||||
} else {
|
||||
etRegulator.Kt = settings.equitherm.t_factor;
|
||||
etRegulator.indoorTemp = indoorTemp;
|
||||
@@ -255,7 +260,7 @@ protected:
|
||||
pidRegulator.setLimits(minTemp, maxTemp);
|
||||
pidRegulator.setDt(settings.pid.dt * 1000u);
|
||||
pidRegulator.input = vars.temperatures.indoor;
|
||||
pidRegulator.setpoint = settings.heating.target;
|
||||
pidRegulator.setpoint = vars.states.emergency ? settings.emergency.target : settings.heating.target;
|
||||
|
||||
return pidRegulator.getResultTimer();
|
||||
}
|
||||
|
||||
@@ -35,19 +35,18 @@ protected:
|
||||
unsigned long initOutdoorSensorTime = 0;
|
||||
unsigned long startOutdoorConversionTime = 0;
|
||||
float filteredOutdoorTemp = 0;
|
||||
bool emptyOutdoorTemp = true;
|
||||
float prevFilteredOutdoorTemp = 0;
|
||||
|
||||
bool initIndoorSensor = false;
|
||||
unsigned long initIndoorSensorTime = 0;
|
||||
unsigned long startIndoorConversionTime = 0;
|
||||
float filteredIndoorTemp = 0;
|
||||
bool emptyIndoorTemp = true;
|
||||
float prevFilteredIndoorTemp = 0;
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
#if USE_BLE
|
||||
BLEClient* pBleClient = nullptr;
|
||||
bool initBleSensor = false;
|
||||
bool initBleNotify = false;
|
||||
unsigned long outdoorConnectedTime = 0;
|
||||
unsigned long indoorConnectedTime = 0;
|
||||
#endif
|
||||
|
||||
const char* getTaskName() override {
|
||||
@@ -69,26 +68,62 @@ protected:
|
||||
#endif
|
||||
|
||||
void loop() {
|
||||
bool indoorTempUpdated = false;
|
||||
bool outdoorTempUpdated = false;
|
||||
|
||||
if (settings.sensors.outdoor.type == SensorType::DS18B20 && GPIO_IS_VALID(settings.sensors.outdoor.gpio)) {
|
||||
outdoorTemperatureSensor();
|
||||
outdoorTempUpdated = true;
|
||||
}
|
||||
|
||||
if (settings.sensors.indoor.type == SensorType::DS18B20 && GPIO_IS_VALID(settings.sensors.indoor.gpio)) {
|
||||
indoorTemperatureSensor();
|
||||
indoorTempUpdated = true;
|
||||
}
|
||||
#if USE_BLE
|
||||
else if (settings.sensors.indoor.type == SensorType::BLUETOOTH) {
|
||||
indoorTemperatureBluetoothSensor();
|
||||
indoorTempUpdated = true;
|
||||
if (!NimBLEDevice::getInitialized() && millis() > 5000) {
|
||||
Log.sinfoln(FPSTR(L_SENSORS_BLE), F("Init BLE"));
|
||||
BLEDevice::init("");
|
||||
NimBLEDevice::setPower(ESP_PWR_LVL_P9);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (outdoorTempUpdated) {
|
||||
if (settings.sensors.outdoor.type == SensorType::DS18B20 && GPIO_IS_VALID(settings.sensors.outdoor.gpio)) {
|
||||
outdoorDallasSensor();
|
||||
}
|
||||
#if USE_BLE
|
||||
else if (settings.sensors.outdoor.type == SensorType::BLUETOOTH) {
|
||||
bool connected = this->bluetoothSensor(
|
||||
BLEAddress(settings.sensors.outdoor.bleAddress),
|
||||
&vars.sensors.outdoor.rssi,
|
||||
&this->filteredOutdoorTemp,
|
||||
&vars.sensors.outdoor.humidity,
|
||||
&vars.sensors.outdoor.battery
|
||||
);
|
||||
|
||||
if (connected) {
|
||||
this->outdoorConnectedTime = millis();
|
||||
vars.sensors.outdoor.connected = true;
|
||||
|
||||
} else if (millis() - this->outdoorConnectedTime > 60000) {
|
||||
vars.sensors.outdoor.connected = false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (settings.sensors.indoor.type == SensorType::DS18B20 && GPIO_IS_VALID(settings.sensors.indoor.gpio)) {
|
||||
indoorDallasSensor();
|
||||
}
|
||||
#if USE_BLE
|
||||
else if (settings.sensors.indoor.type == SensorType::BLUETOOTH) {
|
||||
bool connected = this->bluetoothSensor(
|
||||
BLEAddress(settings.sensors.indoor.bleAddress),
|
||||
&vars.sensors.indoor.rssi,
|
||||
&this->filteredIndoorTemp,
|
||||
&vars.sensors.indoor.humidity,
|
||||
&vars.sensors.indoor.battery
|
||||
);
|
||||
|
||||
if (connected) {
|
||||
this->indoorConnectedTime = millis();
|
||||
vars.sensors.indoor.connected = true;
|
||||
|
||||
} else if (millis() - this->indoorConnectedTime > 60000) {
|
||||
vars.sensors.indoor.connected = false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// convert
|
||||
if (fabs(this->prevFilteredOutdoorTemp - this->filteredOutdoorTemp) >= 0.1f) {
|
||||
float newTemp = settings.sensors.outdoor.offset;
|
||||
if (settings.system.unitSystem == UnitSystem::METRIC) {
|
||||
newTemp += this->filteredOutdoorTemp;
|
||||
@@ -101,9 +136,11 @@ protected:
|
||||
vars.temperatures.outdoor = newTemp;
|
||||
Log.sinfoln(FPSTR(L_SENSORS_OUTDOOR), F("New temp: %f"), vars.temperatures.outdoor);
|
||||
}
|
||||
|
||||
this->prevFilteredOutdoorTemp = this->filteredOutdoorTemp;
|
||||
}
|
||||
|
||||
if (indoorTempUpdated) {
|
||||
if (fabs(this->prevFilteredIndoorTemp - this->filteredIndoorTemp) > 0.1f) {
|
||||
float newTemp = settings.sensors.indoor.offset;
|
||||
if (settings.system.unitSystem == UnitSystem::METRIC) {
|
||||
newTemp += this->filteredIndoorTemp;
|
||||
@@ -116,127 +153,378 @@ protected:
|
||||
vars.temperatures.indoor = newTemp;
|
||||
Log.sinfoln(FPSTR(L_SENSORS_INDOOR), F("New temp: %f"), vars.temperatures.indoor);
|
||||
}
|
||||
|
||||
this->prevFilteredIndoorTemp = this->filteredIndoorTemp;
|
||||
}
|
||||
}
|
||||
|
||||
#if USE_BLE
|
||||
void indoorTemperatureBluetoothSensor() {
|
||||
static bool initBleNotify = false;
|
||||
if (!initBleSensor && millis() > 5000) {
|
||||
Log.sinfoln(FPSTR(L_SENSORS_BLE), F("Init BLE"));
|
||||
BLEDevice::init("");
|
||||
|
||||
pBleClient = BLEDevice::createClient();
|
||||
pBleClient->setConnectTimeout(5);
|
||||
|
||||
initBleSensor = true;
|
||||
bool bluetoothSensor(const BLEAddress& address, int8_t* const pRssi, float* const pTemperature, float* const pHumidity = nullptr, float* const pBattery = nullptr) {
|
||||
if (!NimBLEDevice::getInitialized()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!initBleSensor || pBleClient->isConnected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset init notify flag
|
||||
this->initBleNotify = false;
|
||||
NimBLEClient* pClient = nullptr;
|
||||
pClient = NimBLEDevice::getClientByPeerAddress(address);
|
||||
|
||||
// Connect to the remote BLE Server.
|
||||
BLEAddress bleServerAddress(settings.sensors.indoor.bleAddresss);
|
||||
if (!pBleClient->connect(bleServerAddress)) {
|
||||
Log.swarningln(FPSTR(L_SENSORS_BLE), "Failed connecting to device at %s", bleServerAddress.toString().c_str());
|
||||
return;
|
||||
if (pClient == nullptr) {
|
||||
pClient = NimBLEDevice::getDisconnectedClient();
|
||||
}
|
||||
|
||||
Log.sinfoln(FPSTR(L_SENSORS_BLE), "Connected to device at %s", bleServerAddress.toString().c_str());
|
||||
if (pClient == nullptr) {
|
||||
if (NimBLEDevice::getClientListSize() >= NIMBLE_MAX_CONNECTIONS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
NimBLEUUID serviceUUID((uint16_t) 0x181AU);
|
||||
BLERemoteService* pRemoteService = pBleClient->getService(serviceUUID);
|
||||
if (!pRemoteService) {
|
||||
Log.straceln(FPSTR(L_SENSORS_BLE), F("Failed to find service UUID: %s"), serviceUUID.toString().c_str());
|
||||
return;
|
||||
pClient = NimBLEDevice::createClient();
|
||||
pClient->setConnectTimeout(5);
|
||||
}
|
||||
|
||||
Log.straceln(FPSTR(L_SENSORS_BLE), F("Found service UUID: %s"), serviceUUID.toString().c_str());
|
||||
if(pClient->isConnected()) {
|
||||
*pRssi = pClient->getRssi();
|
||||
return true;
|
||||
}
|
||||
|
||||
// 0x2A6E - Notify temperature x0.01C (pvvx)
|
||||
if (!this->initBleNotify) {
|
||||
NimBLEUUID charUUID((uint16_t) 0x2A6E);
|
||||
BLERemoteCharacteristic* pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID);
|
||||
if (pRemoteCharacteristic && pRemoteCharacteristic->canNotify()) {
|
||||
Log.straceln(FPSTR(L_SENSORS_BLE), F("Found characteristic UUID: %s"), charUUID.toString().c_str());
|
||||
if (!pClient->connect(address)) {
|
||||
Log.swarningln(FPSTR(L_SENSORS_BLE), "Device %s: failed connecting", address.toString().c_str());
|
||||
|
||||
this->initBleNotify = pRemoteCharacteristic->subscribe(true, [this](NimBLERemoteCharacteristic*, uint8_t* pData, size_t length, bool isNotify) {
|
||||
if (length != 2) {
|
||||
Log.swarningln(FPSTR(L_SENSORS_BLE), F("Invalid notification data"));
|
||||
return;
|
||||
}
|
||||
NimBLEDevice::deleteClient(pClient);
|
||||
return false;
|
||||
}
|
||||
|
||||
float rawTemp = ((pData[0] | (pData[1] << 8)) * 0.01f);
|
||||
Log.straceln(FPSTR(L_SENSORS_INDOOR), F("Raw temp: %f"), rawTemp);
|
||||
Log.sinfoln(FPSTR(L_SENSORS_BLE), "Device %s: connected", address.toString().c_str());
|
||||
NimBLERemoteService* pService = nullptr;
|
||||
NimBLERemoteCharacteristic* pChar = nullptr;
|
||||
|
||||
if (this->emptyIndoorTemp) {
|
||||
this->filteredIndoorTemp = rawTemp;
|
||||
this->emptyIndoorTemp = false;
|
||||
// ENV Service (0x181A)
|
||||
pService = pClient->getService(NimBLEUUID((uint16_t) 0x181AU));
|
||||
if (!pService) {
|
||||
Log.straceln(
|
||||
FPSTR(L_SENSORS_BLE),
|
||||
F("Device %s: failed to find env service (%s)"),
|
||||
address.toString().c_str(),
|
||||
pService->getUUID().toString().c_str()
|
||||
);
|
||||
|
||||
} else {
|
||||
Log.straceln(
|
||||
FPSTR(L_SENSORS_BLE),
|
||||
F("Device %s: found env service (%s)"),
|
||||
address.toString().c_str(),
|
||||
pService->getUUID().toString().c_str()
|
||||
);
|
||||
|
||||
|
||||
// 0x2A6E - Notify temperature x0.01C (pvvx)
|
||||
bool tempNotifyCreated = false;
|
||||
if (!tempNotifyCreated) {
|
||||
pChar = pService->getCharacteristic(NimBLEUUID((uint16_t) 0x2A6E));
|
||||
|
||||
if (pChar && pChar->canNotify()) {
|
||||
Log.straceln(
|
||||
FPSTR(L_SENSORS_BLE),
|
||||
F("Device %s: found temperature char (%s) in env service"),
|
||||
address.toString().c_str(),
|
||||
pChar->getUUID().toString().c_str()
|
||||
);
|
||||
|
||||
tempNotifyCreated = pChar->subscribe(true, [pTemperature](NimBLERemoteCharacteristic* pChar, uint8_t* pData, size_t length, bool isNotify) {
|
||||
NimBLEClient* pClient = pChar->getRemoteService()->getClient();
|
||||
|
||||
if (length != 2) {
|
||||
Log.swarningln(
|
||||
FPSTR(L_SENSORS_BLE),
|
||||
F("Device %s: invalid notification data at temperature char (%s)"),
|
||||
pClient->getPeerAddress().toString().c_str(),
|
||||
pChar->getUUID().toString().c_str()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
float rawTemp = ((pData[0] | (pData[1] << 8)) * 0.01f);
|
||||
Log.straceln(
|
||||
FPSTR(L_SENSORS_INDOOR),
|
||||
F("Device %s: raw temp %f"),
|
||||
pClient->getPeerAddress().toString().c_str(),
|
||||
rawTemp
|
||||
);
|
||||
|
||||
if (fabs(*pTemperature) < 0.1f) {
|
||||
*pTemperature = rawTemp;
|
||||
|
||||
} else {
|
||||
*pTemperature += (rawTemp - (*pTemperature)) * EXT_SENSORS_FILTER_K;
|
||||
}
|
||||
|
||||
*pTemperature = floor((*pTemperature) * 100) / 100;
|
||||
});
|
||||
|
||||
if (tempNotifyCreated) {
|
||||
Log.straceln(
|
||||
FPSTR(L_SENSORS_BLE),
|
||||
F("Device %s: subscribed to temperature char (%s) in env service"),
|
||||
address.toString().c_str(),
|
||||
pChar->getUUID().toString().c_str()
|
||||
);
|
||||
|
||||
} else {
|
||||
this->filteredIndoorTemp += (rawTemp - this->filteredIndoorTemp) * EXT_SENSORS_FILTER_K;
|
||||
Log.swarningln(
|
||||
FPSTR(L_SENSORS_BLE),
|
||||
F("Device %s: failed to subscribe to temperature char (%s) in env service"),
|
||||
address.toString().c_str(),
|
||||
pChar->getUUID().toString().c_str()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this->filteredIndoorTemp = floor(this->filteredIndoorTemp * 100) / 100;
|
||||
});
|
||||
|
||||
if (this->initBleNotify) {
|
||||
Log.straceln(FPSTR(L_SENSORS_BLE), F("Subscribed to characteristic UUID: %s"), charUUID.toString().c_str());
|
||||
// 0x2A1F - Notify temperature x0.1C (atc1441/pvvx)
|
||||
if (!tempNotifyCreated) {
|
||||
pChar = pService->getCharacteristic(NimBLEUUID((uint16_t) 0x2A1F));
|
||||
|
||||
} else {
|
||||
Log.swarningln(FPSTR(L_SENSORS_BLE), F("Failed to subscribe to characteristic UUID: %s"), charUUID.toString().c_str());
|
||||
if (pChar && pChar->canNotify()) {
|
||||
Log.straceln(
|
||||
FPSTR(L_SENSORS_BLE),
|
||||
F("Device %s: found temperature char (%s) in env service"),
|
||||
address.toString().c_str(),
|
||||
pChar->getUUID().toString().c_str()
|
||||
);
|
||||
|
||||
tempNotifyCreated = pChar->subscribe(true, [pTemperature](NimBLERemoteCharacteristic* pChar, uint8_t* pData, size_t length, bool isNotify) {
|
||||
NimBLEClient* pClient = pChar->getRemoteService()->getClient();
|
||||
|
||||
if (length != 2) {
|
||||
Log.swarningln(
|
||||
FPSTR(L_SENSORS_BLE),
|
||||
F("Device %s: invalid notification data at temperature char (%s)"),
|
||||
pClient->getPeerAddress().toString().c_str(),
|
||||
pChar->getUUID().toString().c_str()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
float rawTemp = ((pData[0] | (pData[1] << 8)) * 0.1f);
|
||||
Log.straceln(
|
||||
FPSTR(L_SENSORS_INDOOR),
|
||||
F("Device %s: raw temp %f"),
|
||||
pClient->getPeerAddress().toString().c_str(),
|
||||
rawTemp
|
||||
);
|
||||
|
||||
if (fabs(*pTemperature) < 0.1f) {
|
||||
*pTemperature = rawTemp;
|
||||
|
||||
} else {
|
||||
*pTemperature += (rawTemp - (*pTemperature)) * EXT_SENSORS_FILTER_K;
|
||||
}
|
||||
|
||||
*pTemperature = floor((*pTemperature) * 100) / 100;
|
||||
});
|
||||
|
||||
if (tempNotifyCreated) {
|
||||
Log.straceln(
|
||||
FPSTR(L_SENSORS_BLE),
|
||||
F("Device %s: subscribed to temperature char (%s) in env service"),
|
||||
address.toString().c_str(),
|
||||
pChar->getUUID().toString().c_str()
|
||||
);
|
||||
|
||||
} else {
|
||||
Log.swarningln(
|
||||
FPSTR(L_SENSORS_BLE),
|
||||
F("Device %s: failed to subscribe to temperature char (%s) in env service"),
|
||||
address.toString().c_str(),
|
||||
pChar->getUUID().toString().c_str()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!tempNotifyCreated) {
|
||||
Log.swarningln(
|
||||
FPSTR(L_SENSORS_BLE),
|
||||
F("Device %s: not found supported temperature chars in env service"),
|
||||
address.toString().c_str()
|
||||
);
|
||||
|
||||
pClient->disconnect();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// 0x2A6F - Notify about humidity x0.01% (pvvx)
|
||||
if (pHumidity != nullptr) {
|
||||
bool humidityNotifyCreated = false;
|
||||
if (!humidityNotifyCreated) {
|
||||
pChar = pService->getCharacteristic(NimBLEUUID((uint16_t) 0x2A6F));
|
||||
|
||||
if (pChar && pChar->canNotify()) {
|
||||
Log.straceln(
|
||||
FPSTR(L_SENSORS_BLE),
|
||||
F("Device %s: found humidity char (%s) in env service"),
|
||||
address.toString().c_str(),
|
||||
pChar->getUUID().toString().c_str()
|
||||
);
|
||||
|
||||
humidityNotifyCreated = pChar->subscribe(true, [pHumidity](NimBLERemoteCharacteristic* pChar, uint8_t* pData, size_t length, bool isNotify) {
|
||||
NimBLEClient* pClient = pChar->getRemoteService()->getClient();
|
||||
|
||||
if (length != 2) {
|
||||
Log.swarningln(
|
||||
FPSTR(L_SENSORS_BLE),
|
||||
F("Device %s: invalid notification data at humidity char (%s)"),
|
||||
pClient->getPeerAddress().toString().c_str(),
|
||||
pChar->getUUID().toString().c_str()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
float rawHumidity = ((pData[0] | (pData[1] << 8)) * 0.01f);
|
||||
Log.straceln(
|
||||
FPSTR(L_SENSORS_INDOOR),
|
||||
F("Device %s: raw humidity %f"),
|
||||
pClient->getPeerAddress().toString().c_str(),
|
||||
rawHumidity
|
||||
);
|
||||
|
||||
if (fabs(*pHumidity) < 0.1f) {
|
||||
*pHumidity = rawHumidity;
|
||||
|
||||
} else {
|
||||
*pHumidity += (rawHumidity - (*pHumidity)) * EXT_SENSORS_FILTER_K;
|
||||
}
|
||||
|
||||
*pHumidity = floor((*pHumidity) * 100) / 100;
|
||||
});
|
||||
|
||||
if (humidityNotifyCreated) {
|
||||
Log.straceln(
|
||||
FPSTR(L_SENSORS_BLE),
|
||||
F("Device %s: subscribed to humidity char (%s) in env service"),
|
||||
address.toString().c_str(),
|
||||
pChar->getUUID().toString().c_str()
|
||||
);
|
||||
|
||||
} else {
|
||||
Log.swarningln(
|
||||
FPSTR(L_SENSORS_BLE),
|
||||
F("Device %s: failed to subscribe to humidity char (%s) in env service"),
|
||||
address.toString().c_str(),
|
||||
pChar->getUUID().toString().c_str()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!humidityNotifyCreated) {
|
||||
Log.swarningln(
|
||||
FPSTR(L_SENSORS_BLE),
|
||||
F("Device %s: not found supported humidity chars in env service"),
|
||||
address.toString().c_str()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 0x2A1F - Notify temperature x0.1C (atc1441/pvvx)
|
||||
if (!this->initBleNotify) {
|
||||
NimBLEUUID charUUID((uint16_t) 0x2A1F);
|
||||
BLERemoteCharacteristic* pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID);
|
||||
if (pRemoteCharacteristic && pRemoteCharacteristic->canNotify()) {
|
||||
Log.straceln(FPSTR(L_SENSORS_BLE), F("Found characteristic UUID: %s"), charUUID.toString().c_str());
|
||||
|
||||
this->initBleNotify = pRemoteCharacteristic->subscribe(true, [this](NimBLERemoteCharacteristic*, uint8_t* pData, size_t length, bool isNotify) {
|
||||
if (length != 2) {
|
||||
Log.swarningln(FPSTR(L_SENSORS_BLE), F("Invalid notification data"));
|
||||
return;
|
||||
// Battery Service (0x180F)
|
||||
if (pBattery != nullptr) {
|
||||
pService = pClient->getService(NimBLEUUID((uint16_t) 0x180F));
|
||||
if (!pService) {
|
||||
Log.straceln(
|
||||
FPSTR(L_SENSORS_BLE),
|
||||
F("Device %s: failed to find battery service (%s)"),
|
||||
address.toString().c_str(),
|
||||
pService->getUUID().toString().c_str()
|
||||
);
|
||||
|
||||
} else {
|
||||
Log.straceln(
|
||||
FPSTR(L_SENSORS_BLE),
|
||||
F("Device %s: found battery service (%s)"),
|
||||
address.toString().c_str(),
|
||||
pService->getUUID().toString().c_str()
|
||||
);
|
||||
|
||||
// 0x2A19 - Notify the battery charge level 0..99% (pvvx)
|
||||
bool batteryNotifyCreated = false;
|
||||
if (!batteryNotifyCreated) {
|
||||
pChar = pService->getCharacteristic(NimBLEUUID((uint16_t) 0x2A19));
|
||||
|
||||
if (pChar && pChar->canNotify()) {
|
||||
Log.straceln(
|
||||
FPSTR(L_SENSORS_BLE),
|
||||
F("Device %s: found battery char (%s) in battery service"),
|
||||
address.toString().c_str(),
|
||||
pChar->getUUID().toString().c_str()
|
||||
);
|
||||
|
||||
batteryNotifyCreated = pChar->subscribe(true, [pBattery](NimBLERemoteCharacteristic* pChar, uint8_t* pData, size_t length, bool isNotify) {
|
||||
NimBLEClient* pClient = pChar->getRemoteService()->getClient();
|
||||
|
||||
if (length != 1) {
|
||||
Log.swarningln(
|
||||
FPSTR(L_SENSORS_BLE),
|
||||
F("Device %s: invalid notification data at battery char (%s)"),
|
||||
pClient->getPeerAddress().toString().c_str(),
|
||||
pChar->getUUID().toString().c_str()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t rawBattery = pData[0];
|
||||
Log.straceln(
|
||||
FPSTR(L_SENSORS_INDOOR),
|
||||
F("Device %s: raw battery %hhu"),
|
||||
pClient->getPeerAddress().toString().c_str(),
|
||||
rawBattery
|
||||
);
|
||||
|
||||
if (fabs(*pBattery) < 0.1f) {
|
||||
*pBattery = rawBattery;
|
||||
|
||||
} else {
|
||||
*pBattery += (rawBattery - (*pBattery)) * EXT_SENSORS_FILTER_K;
|
||||
}
|
||||
|
||||
*pBattery = floor((*pBattery) * 100) / 100;
|
||||
});
|
||||
|
||||
if (batteryNotifyCreated) {
|
||||
Log.straceln(
|
||||
FPSTR(L_SENSORS_BLE),
|
||||
F("Device %s: subscribed to battery char (%s) in battery service"),
|
||||
address.toString().c_str(),
|
||||
pChar->getUUID().toString().c_str()
|
||||
);
|
||||
|
||||
} else {
|
||||
Log.swarningln(
|
||||
FPSTR(L_SENSORS_BLE),
|
||||
F("Device %s: failed to subscribe to battery char (%s) in battery service"),
|
||||
address.toString().c_str(),
|
||||
pChar->getUUID().toString().c_str()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float rawTemp = ((pData[0] | (pData[1] << 8)) * 0.1);
|
||||
Log.straceln(FPSTR(L_SENSORS_INDOOR), F("Raw temp: %f"), rawTemp);
|
||||
|
||||
if (this->emptyIndoorTemp) {
|
||||
this->filteredIndoorTemp = rawTemp;
|
||||
this->emptyIndoorTemp = false;
|
||||
|
||||
} else {
|
||||
this->filteredIndoorTemp += (rawTemp - this->filteredIndoorTemp) * EXT_SENSORS_FILTER_K;
|
||||
}
|
||||
|
||||
this->filteredIndoorTemp = floor(this->filteredIndoorTemp * 100) / 100;
|
||||
});
|
||||
|
||||
if (this->initBleNotify) {
|
||||
Log.straceln(FPSTR(L_SENSORS_BLE), F("Subscribed to characteristic UUID: %s"), charUUID.toString().c_str());
|
||||
|
||||
} else {
|
||||
Log.swarningln(FPSTR(L_SENSORS_BLE), F("Failed to subscribe to characteristic UUID: %s"), charUUID.toString().c_str());
|
||||
if (!batteryNotifyCreated) {
|
||||
Log.swarningln(
|
||||
FPSTR(L_SENSORS_BLE),
|
||||
F("Device %s: not found supported battery chars in battery service"),
|
||||
address.toString().c_str()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!this->initBleNotify) {
|
||||
Log.swarningln(FPSTR(L_SENSORS_BLE), F("Not found supported characteristics"));
|
||||
pBleClient->disconnect();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
void outdoorTemperatureSensor() {
|
||||
void outdoorDallasSensor() {
|
||||
if (!this->initOutdoorSensor) {
|
||||
if (this->initOutdoorSensorTime && millis() - this->initOutdoorSensorTime < EXT_SENSORS_INTERVAL * 10) {
|
||||
return;
|
||||
@@ -265,6 +553,10 @@ protected:
|
||||
Log.sinfoln(FPSTR(L_SENSORS_OUTDOOR), F("Started"));
|
||||
|
||||
} else {
|
||||
if (vars.sensors.outdoor.connected) {
|
||||
vars.sensors.outdoor.connected = false;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -294,9 +586,12 @@ protected:
|
||||
} else {
|
||||
Log.straceln(FPSTR(L_SENSORS_OUTDOOR), F("Raw temp: %f"), rawTemp);
|
||||
|
||||
if (this->emptyOutdoorTemp) {
|
||||
if (!vars.sensors.outdoor.connected) {
|
||||
vars.sensors.outdoor.connected = true;
|
||||
}
|
||||
|
||||
if (fabs(this->filteredOutdoorTemp) < 0.1f) {
|
||||
this->filteredOutdoorTemp = rawTemp;
|
||||
this->emptyOutdoorTemp = false;
|
||||
|
||||
} else {
|
||||
this->filteredOutdoorTemp += (rawTemp - this->filteredOutdoorTemp) * EXT_SENSORS_FILTER_K;
|
||||
@@ -308,7 +603,7 @@ protected:
|
||||
}
|
||||
}
|
||||
|
||||
void indoorTemperatureSensor() {
|
||||
void indoorDallasSensor() {
|
||||
if (!this->initIndoorSensor) {
|
||||
if (this->initIndoorSensorTime && millis() - this->initIndoorSensorTime < EXT_SENSORS_INTERVAL * 10) {
|
||||
return;
|
||||
@@ -337,6 +632,10 @@ protected:
|
||||
Log.sinfoln(FPSTR(L_SENSORS_INDOOR), F("Started"));
|
||||
|
||||
} else {
|
||||
if (vars.sensors.indoor.connected) {
|
||||
vars.sensors.indoor.connected = false;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -366,9 +665,12 @@ protected:
|
||||
} else {
|
||||
Log.straceln(FPSTR(L_SENSORS_INDOOR), F("Raw temp: %f"), rawTemp);
|
||||
|
||||
if (this->emptyIndoorTemp) {
|
||||
if (!vars.sensors.indoor.connected) {
|
||||
vars.sensors.indoor.connected = true;
|
||||
}
|
||||
|
||||
if (fabs(this->filteredIndoorTemp) < 0.1f) {
|
||||
this->filteredIndoorTemp = rawTemp;
|
||||
this->emptyIndoorTemp = false;
|
||||
|
||||
} else {
|
||||
this->filteredIndoorTemp += (rawTemp - this->filteredIndoorTemp) * EXT_SENSORS_FILTER_K;
|
||||
|
||||
@@ -54,6 +54,8 @@ struct Settings {
|
||||
byte faultStateGpio = DEFAULT_OT_FAULT_STATE_GPIO;
|
||||
byte invertFaultState = false;
|
||||
unsigned int memberIdCode = 0;
|
||||
float pressureFactor = 1.0f;
|
||||
float dhwFlowRateFactor = 1.0f;
|
||||
bool dhwPresent = true;
|
||||
bool summerWinterMode = false;
|
||||
bool heatingCh2Enabled = true;
|
||||
@@ -63,6 +65,12 @@ struct Settings {
|
||||
bool modulationSyncWithHeating = false;
|
||||
bool getMinMaxTemp = true;
|
||||
bool nativeHeatingControl = false;
|
||||
bool immergasFix = false;
|
||||
|
||||
struct {
|
||||
bool enable = false;
|
||||
float factor = 0.1f;
|
||||
} filterNumValues;
|
||||
} opentherm;
|
||||
|
||||
struct {
|
||||
@@ -77,13 +85,15 @@ struct Settings {
|
||||
} mqtt;
|
||||
|
||||
struct {
|
||||
bool enable = true;
|
||||
bool enable = false;
|
||||
float target = DEFAULT_HEATING_TARGET_TEMP;
|
||||
unsigned short tresholdTime = 120;
|
||||
bool useEquitherm = false;
|
||||
bool usePid = false;
|
||||
bool onNetworkFault = true;
|
||||
bool onMqttFault = true;
|
||||
bool onIndoorSensorDisconnect = false;
|
||||
bool onOutdoorSensorDisconnect = false;
|
||||
} emergency;
|
||||
|
||||
struct {
|
||||
@@ -124,13 +134,14 @@ struct Settings {
|
||||
struct {
|
||||
SensorType type = SensorType::BOILER;
|
||||
byte gpio = DEFAULT_SENSOR_OUTDOOR_GPIO;
|
||||
uint8_t bleAddress[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
float offset = 0.0f;
|
||||
} outdoor;
|
||||
|
||||
struct {
|
||||
SensorType type = SensorType::MANUAL;
|
||||
byte gpio = DEFAULT_SENSOR_INDOOR_GPIO;
|
||||
uint8_t bleAddresss[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t bleAddress[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
float offset = 0.0f;
|
||||
} indoor;
|
||||
} sensors;
|
||||
@@ -164,7 +175,22 @@ struct Variables {
|
||||
float pressure = 0.0f;
|
||||
float dhwFlowRate = 0.0f;
|
||||
byte faultCode = 0;
|
||||
unsigned short diagnosticCode = 0;
|
||||
int8_t rssi = 0;
|
||||
|
||||
struct {
|
||||
bool connected = false;
|
||||
int8_t rssi = 0;
|
||||
float battery = 0.0f;
|
||||
float humidity = 0.0f;
|
||||
} outdoor;
|
||||
|
||||
struct {
|
||||
bool connected = false;
|
||||
int8_t rssi = 0;
|
||||
float battery = 0.0f;
|
||||
float humidity = 0.0f;
|
||||
} indoor;
|
||||
} sensors;
|
||||
|
||||
struct {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#define PROJECT_NAME "OpenTherm Gateway"
|
||||
#define PROJECT_VERSION "1.4.1"
|
||||
#define PROJECT_REPO "https://github.com/Laxilef/OTGateway"
|
||||
|
||||
#define MQTT_RECONNECT_INTERVAL 15000
|
||||
@@ -23,6 +22,14 @@
|
||||
#define THERMOSTAT_INDOOR_MIN_TEMP 5
|
||||
#define THERMOSTAT_INDOOR_MAX_TEMP 30
|
||||
|
||||
#ifndef BUILD_VERSION
|
||||
#define BUILD_VERSION "0.0.0"
|
||||
#endif
|
||||
|
||||
#ifndef BUILD_ENV
|
||||
#define BUILD_ENV "undefined"
|
||||
#endif
|
||||
|
||||
#ifndef USE_SERIAL
|
||||
#define USE_SERIAL true
|
||||
#endif
|
||||
|
||||
11
src/main.cpp
@@ -135,17 +135,20 @@ void setup() {
|
||||
network = (new NetworkMgr)
|
||||
->setHostname(networkSettings.hostname)
|
||||
->setStaCredentials(
|
||||
#ifdef WOKWI
|
||||
"Wokwi-GUEST", nullptr, 6
|
||||
#else
|
||||
strlen(networkSettings.sta.ssid) ? networkSettings.sta.ssid : nullptr,
|
||||
strlen(networkSettings.sta.password) ? networkSettings.sta.password : nullptr,
|
||||
networkSettings.sta.channel
|
||||
#endif
|
||||
)->setApCredentials(
|
||||
strlen(networkSettings.ap.ssid) ? networkSettings.ap.ssid : nullptr,
|
||||
strlen(networkSettings.ap.password) ? networkSettings.ap.password : nullptr,
|
||||
networkSettings.ap.channel
|
||||
)
|
||||
->setUseDhcp(networkSettings.useDhcp)
|
||||
->setStaticConfig(
|
||||
networkSettings.staticConfig.ip,
|
||||
networkSettings.staticConfig.gateway,
|
||||
networkSettings.staticConfig.subnet,
|
||||
networkSettings.staticConfig.dns
|
||||
);
|
||||
|
||||
// tasks
|
||||
|
||||
153
src/utils.h
@@ -345,6 +345,8 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) {
|
||||
dst["opentherm"]["faultStateGpio"] = src.opentherm.faultStateGpio;
|
||||
dst["opentherm"]["invertFaultState"] = src.opentherm.invertFaultState;
|
||||
dst["opentherm"]["memberIdCode"] = src.opentherm.memberIdCode;
|
||||
dst["opentherm"]["pressureFactor"] = roundd(src.opentherm.pressureFactor, 2);
|
||||
dst["opentherm"]["dhwFlowRateFactor"] = roundd(src.opentherm.dhwFlowRateFactor, 2);
|
||||
dst["opentherm"]["dhwPresent"] = src.opentherm.dhwPresent;
|
||||
dst["opentherm"]["summerWinterMode"] = src.opentherm.summerWinterMode;
|
||||
dst["opentherm"]["heatingCh2Enabled"] = src.opentherm.heatingCh2Enabled;
|
||||
@@ -354,6 +356,9 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) {
|
||||
dst["opentherm"]["modulationSyncWithHeating"] = src.opentherm.modulationSyncWithHeating;
|
||||
dst["opentherm"]["getMinMaxTemp"] = src.opentherm.getMinMaxTemp;
|
||||
dst["opentherm"]["nativeHeatingControl"] = src.opentherm.nativeHeatingControl;
|
||||
dst["opentherm"]["immergasFix"] = src.opentherm.immergasFix;
|
||||
dst["opentherm"]["filterNumValues"]["enable"] = src.opentherm.filterNumValues.enable;
|
||||
dst["opentherm"]["filterNumValues"]["factor"] = roundd(src.opentherm.filterNumValues.factor, 2);
|
||||
|
||||
dst["mqtt"]["enable"] = src.mqtt.enable;
|
||||
dst["mqtt"]["server"] = src.mqtt.server;
|
||||
@@ -371,6 +376,8 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) {
|
||||
dst["emergency"]["usePid"] = src.emergency.usePid;
|
||||
dst["emergency"]["onNetworkFault"] = src.emergency.onNetworkFault;
|
||||
dst["emergency"]["onMqttFault"] = src.emergency.onMqttFault;
|
||||
dst["emergency"]["onIndoorSensorDisconnect"] = src.emergency.onIndoorSensorDisconnect;
|
||||
dst["emergency"]["onOutdoorSensorDisconnect"] = src.emergency.onOutdoorSensorDisconnect;
|
||||
}
|
||||
|
||||
dst["heating"]["enable"] = src.heating.enable;
|
||||
@@ -401,23 +408,35 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) {
|
||||
|
||||
dst["sensors"]["outdoor"]["type"] = static_cast<byte>(src.sensors.outdoor.type);
|
||||
dst["sensors"]["outdoor"]["gpio"] = src.sensors.outdoor.gpio;
|
||||
dst["sensors"]["outdoor"]["offset"] = roundd(src.sensors.outdoor.offset, 2);
|
||||
|
||||
dst["sensors"]["indoor"]["type"] = static_cast<byte>(src.sensors.indoor.type);
|
||||
dst["sensors"]["indoor"]["gpio"] = src.sensors.indoor.gpio;
|
||||
|
||||
char bleAddress[18];
|
||||
sprintf(
|
||||
bleAddress,
|
||||
"%02x:%02x:%02x:%02x:%02x:%02x",
|
||||
src.sensors.indoor.bleAddresss[0],
|
||||
src.sensors.indoor.bleAddresss[1],
|
||||
src.sensors.indoor.bleAddresss[2],
|
||||
src.sensors.indoor.bleAddresss[3],
|
||||
src.sensors.indoor.bleAddresss[4],
|
||||
src.sensors.indoor.bleAddresss[5]
|
||||
src.sensors.outdoor.bleAddress[0],
|
||||
src.sensors.outdoor.bleAddress[1],
|
||||
src.sensors.outdoor.bleAddress[2],
|
||||
src.sensors.outdoor.bleAddress[3],
|
||||
src.sensors.outdoor.bleAddress[4],
|
||||
src.sensors.outdoor.bleAddress[5]
|
||||
);
|
||||
dst["sensors"]["indoor"]["bleAddresss"] = String(bleAddress);
|
||||
dst["sensors"]["outdoor"]["bleAddress"] = String(bleAddress);
|
||||
dst["sensors"]["outdoor"]["offset"] = roundd(src.sensors.outdoor.offset, 2);
|
||||
|
||||
dst["sensors"]["indoor"]["type"] = static_cast<byte>(src.sensors.indoor.type);
|
||||
dst["sensors"]["indoor"]["gpio"] = src.sensors.indoor.gpio;
|
||||
|
||||
sprintf(
|
||||
bleAddress,
|
||||
"%02x:%02x:%02x:%02x:%02x:%02x",
|
||||
src.sensors.indoor.bleAddress[0],
|
||||
src.sensors.indoor.bleAddress[1],
|
||||
src.sensors.indoor.bleAddress[2],
|
||||
src.sensors.indoor.bleAddress[3],
|
||||
src.sensors.indoor.bleAddress[4],
|
||||
src.sensors.indoor.bleAddress[5]
|
||||
);
|
||||
dst["sensors"]["indoor"]["bleAddress"] = String(bleAddress);
|
||||
dst["sensors"]["indoor"]["offset"] = roundd(src.sensors.indoor.offset, 2);
|
||||
|
||||
if (!safe) {
|
||||
@@ -679,6 +698,42 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
|
||||
}
|
||||
}
|
||||
|
||||
if (!src["opentherm"]["pressureFactor"].isNull()) {
|
||||
float value = src["opentherm"]["pressureFactor"].as<float>();
|
||||
|
||||
if (value > 0 && value <= 100 && fabs(value - dst.opentherm.pressureFactor) > 0.0001f) {
|
||||
dst.opentherm.pressureFactor = roundd(value, 2);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!src["opentherm"]["dhwFlowRateFactor"].isNull()) {
|
||||
float value = src["opentherm"]["dhwFlowRateFactor"].as<float>();
|
||||
|
||||
if (value > 0 && value <= 100 && fabs(value - dst.opentherm.dhwFlowRateFactor) > 0.0001f) {
|
||||
dst.opentherm.dhwFlowRateFactor = roundd(value, 2);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (src["opentherm"]["filterNumValues"]["enable"].is<bool>()) {
|
||||
bool value = src["opentherm"]["filterNumValues"]["enable"].as<bool>();
|
||||
|
||||
if (value != dst.opentherm.filterNumValues.enable) {
|
||||
dst.opentherm.filterNumValues.enable = value;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!src["opentherm"]["filterNumValues"]["factor"].isNull()) {
|
||||
float value = src["opentherm"]["filterNumValues"]["factor"].as<float>();
|
||||
|
||||
if (value > 0 && value <= 1 && fabs(value - dst.opentherm.filterNumValues.factor) > 0.0001f) {
|
||||
dst.opentherm.filterNumValues.factor = roundd(value, 2);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (src["opentherm"]["dhwPresent"].is<bool>()) {
|
||||
bool value = src["opentherm"]["dhwPresent"].as<bool>();
|
||||
|
||||
@@ -786,6 +841,15 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
|
||||
}
|
||||
}
|
||||
|
||||
if (src["opentherm"]["immergasFix"].is<bool>()) {
|
||||
bool value = src["opentherm"]["immergasFix"].as<bool>();
|
||||
|
||||
if (value != dst.opentherm.immergasFix) {
|
||||
dst.opentherm.immergasFix = value;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// mqtt
|
||||
if (src["mqtt"]["enable"].is<bool>()) {
|
||||
@@ -883,7 +947,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
|
||||
if (src["emergency"]["useEquitherm"].is<bool>()) {
|
||||
bool value = src["emergency"]["useEquitherm"].as<bool>();
|
||||
|
||||
if (!dst.opentherm.nativeHeatingControl && dst.sensors.outdoor.type != SensorType::MANUAL) {
|
||||
if (!dst.opentherm.nativeHeatingControl && dst.sensors.outdoor.type != SensorType::MANUAL && dst.sensors.outdoor.type != SensorType::BLUETOOTH) {
|
||||
if (value != dst.emergency.useEquitherm) {
|
||||
dst.emergency.useEquitherm = value;
|
||||
changed = true;
|
||||
@@ -903,7 +967,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
|
||||
if (src["emergency"]["usePid"].is<bool>()) {
|
||||
bool value = src["emergency"]["usePid"].as<bool>();
|
||||
|
||||
if (!dst.opentherm.nativeHeatingControl && dst.sensors.indoor.type != SensorType::MANUAL) {
|
||||
if (!dst.opentherm.nativeHeatingControl && dst.sensors.indoor.type != SensorType::MANUAL && dst.sensors.indoor.type != SensorType::BLUETOOTH) {
|
||||
if (value != dst.emergency.usePid) {
|
||||
dst.emergency.usePid = value;
|
||||
changed = true;
|
||||
@@ -937,6 +1001,26 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (src["emergency"]["onIndoorSensorDisconnect"].is<bool>()) {
|
||||
bool value = src["emergency"]["onIndoorSensorDisconnect"].as<bool>();
|
||||
|
||||
if (value != dst.emergency.onIndoorSensorDisconnect) {
|
||||
dst.emergency.onIndoorSensorDisconnect = value;
|
||||
dst.emergency.usePid = false;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (src["emergency"]["onOutdoorSensorDisconnect"].is<bool>()) {
|
||||
bool value = src["emergency"]["onOutdoorSensorDisconnect"].as<bool>();
|
||||
|
||||
if (value != dst.emergency.onOutdoorSensorDisconnect) {
|
||||
dst.emergency.onOutdoorSensorDisconnect = value;
|
||||
dst.emergency.useEquitherm = false;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1167,6 +1251,16 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
|
||||
}
|
||||
break;
|
||||
|
||||
#if USE_BLE
|
||||
case static_cast<byte>(SensorType::BLUETOOTH):
|
||||
if (dst.sensors.outdoor.type != SensorType::BLUETOOTH) {
|
||||
dst.sensors.outdoor.type = SensorType::BLUETOOTH;
|
||||
dst.emergency.useEquitherm = false;
|
||||
changed = true;
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -1189,6 +1283,21 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
|
||||
}
|
||||
}
|
||||
|
||||
#if USE_BLE
|
||||
if (!src["sensors"]["outdoor"]["bleAddress"].isNull()) {
|
||||
String value = src["sensors"]["outdoor"]["bleAddress"].as<String>();
|
||||
int tmp[6];
|
||||
if(sscanf(value.c_str(), "%02x:%02x:%02x:%02x:%02x:%02x", &tmp[0], &tmp[1], &tmp[2], &tmp[3], &tmp[4], &tmp[5]) == 6) {
|
||||
for(uint8_t i = 0; i < 6; i++) {
|
||||
if (dst.sensors.outdoor.bleAddress[i] != (uint8_t) tmp[i]) {
|
||||
dst.sensors.outdoor.bleAddress[i] = (uint8_t) tmp[i];
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!src["sensors"]["outdoor"]["offset"].isNull()) {
|
||||
float value = src["sensors"]["outdoor"]["offset"].as<float>();
|
||||
|
||||
@@ -1222,6 +1331,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
|
||||
case static_cast<byte>(SensorType::BLUETOOTH):
|
||||
if (dst.sensors.indoor.type != SensorType::BLUETOOTH) {
|
||||
dst.sensors.indoor.type = SensorType::BLUETOOTH;
|
||||
dst.emergency.usePid = false;
|
||||
changed = true;
|
||||
}
|
||||
break;
|
||||
@@ -1250,13 +1360,13 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
|
||||
}
|
||||
|
||||
#if USE_BLE
|
||||
if (!src["sensors"]["indoor"]["bleAddresss"].isNull()) {
|
||||
String value = src["sensors"]["indoor"]["bleAddresss"].as<String>();
|
||||
if (!src["sensors"]["indoor"]["bleAddress"].isNull()) {
|
||||
String value = src["sensors"]["indoor"]["bleAddress"].as<String>();
|
||||
int tmp[6];
|
||||
if(sscanf(value.c_str(), "%02x:%02x:%02x:%02x:%02x:%02x", &tmp[0], &tmp[1], &tmp[2], &tmp[3], &tmp[4], &tmp[5]) == 6) {
|
||||
for(uint8_t i = 0; i < 6; i++) {
|
||||
if (dst.sensors.indoor.bleAddresss[i] != (uint8_t) tmp[i]) {
|
||||
dst.sensors.indoor.bleAddresss[i] = (uint8_t) tmp[i];
|
||||
if (dst.sensors.indoor.bleAddress[i] != (uint8_t) tmp[i]) {
|
||||
dst.sensors.indoor.bleAddress[i] = (uint8_t) tmp[i];
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
@@ -1437,8 +1547,17 @@ void varsToJson(const Variables& src, JsonVariant dst) {
|
||||
dst["sensors"]["pressure"] = roundd(src.sensors.pressure, 2);
|
||||
dst["sensors"]["dhwFlowRate"] = roundd(src.sensors.dhwFlowRate, 2);
|
||||
dst["sensors"]["faultCode"] = src.sensors.faultCode;
|
||||
dst["sensors"]["diagnosticCode"] = src.sensors.diagnosticCode;
|
||||
dst["sensors"]["rssi"] = src.sensors.rssi;
|
||||
dst["sensors"]["uptime"] = millis() / 1000ul;
|
||||
dst["sensors"]["outdoor"]["connected"] = src.sensors.outdoor.connected;
|
||||
dst["sensors"]["outdoor"]["rssi"] = src.sensors.outdoor.rssi;
|
||||
dst["sensors"]["outdoor"]["battery"] = roundd(src.sensors.outdoor.battery, 2);
|
||||
dst["sensors"]["outdoor"]["humidity"] = roundd(src.sensors.outdoor.humidity, 2);
|
||||
dst["sensors"]["indoor"]["connected"] = src.sensors.indoor.connected;
|
||||
dst["sensors"]["indoor"]["rssi"] = src.sensors.indoor.rssi;
|
||||
dst["sensors"]["indoor"]["battery"] = roundd(src.sensors.indoor.battery, 2);
|
||||
dst["sensors"]["indoor"]["humidity"] = roundd(src.sensors.indoor.humidity, 2);
|
||||
|
||||
dst["temperatures"]["indoor"] = roundd(src.temperatures.indoor, 2);
|
||||
dst["temperatures"]["outdoor"] = roundd(src.temperatures.outdoor, 2);
|
||||
|
||||
78
src_data/fonts/iconly.svg
Normal file
@@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<font id="iconly" horiz-adv-x="1024">
|
||||
<font-face font-family="iconly"
|
||||
units-per-em="1024" ascent="896"
|
||||
descent="128" />
|
||||
<missing-glyph horiz-adv-x="0" />
|
||||
<glyph glyph-name="plus"
|
||||
unicode=""
|
||||
horiz-adv-x="895.5537099041368" d="M831.585587768127 479.8473854289214H543.729038156083V767.7039350409655C543.729038156083 803.0263324829433 515.0833134620511 831.6720571769752 479.7609160200732 831.6720571769752H415.7927938840635C380.4703964420856 831.6720571769752 351.8246717480538 803.0263324829433 351.8246717480538 767.7039350409655V479.8473854289214H63.9681221360098C28.6457246940319 479.8473854289214 0 451.2016607348895 0 415.8792632929117V351.9111411569019C0 316.588743714924 28.6457246940319 287.9430190208922 63.9681221360098 287.9430190208922H351.8246717480538V0.0864694088482C351.8246717480538 -35.2359280331298 380.4703964420857 -63.8816527271616 415.7927938840635 -63.8816527271616H479.7609160200732C515.0833134620511 -63.8816527271616 543.729038156083 -35.2359280331298 543.729038156083 0.0864694088482V287.9430190208922H831.585587768127C866.9079852101049 287.9430190208922 895.5537099041368 316.588743714924 895.5537099041368 351.9111411569019V415.8792632929117C895.5537099041368 451.2016607348896 866.9079852101049 479.8473854289214 831.585587768127 479.8473854289214z" />
|
||||
<glyph glyph-name="plus-1"
|
||||
unicode="plus"
|
||||
horiz-adv-x="895.5537099041368" d="M831.585587768127 479.8473854289214H543.729038156083V767.7039350409655C543.729038156083 803.0263324829433 515.0833134620511 831.6720571769752 479.7609160200732 831.6720571769752H415.7927938840635C380.4703964420856 831.6720571769752 351.8246717480538 803.0263324829433 351.8246717480538 767.7039350409655V479.8473854289214H63.9681221360098C28.6457246940319 479.8473854289214 0 451.2016607348895 0 415.8792632929117V351.9111411569019C0 316.588743714924 28.6457246940319 287.9430190208922 63.9681221360098 287.9430190208922H351.8246717480538V0.0864694088482C351.8246717480538 -35.2359280331298 380.4703964420857 -63.8816527271616 415.7927938840635 -63.8816527271616H479.7609160200732C515.0833134620511 -63.8816527271616 543.729038156083 -35.2359280331298 543.729038156083 0.0864694088482V287.9430190208922H831.585587768127C866.9079852101049 287.9430190208922 895.5537099041368 316.588743714924 895.5537099041368 351.9111411569019V415.8792632929117C895.5537099041368 451.2016607348896 866.9079852101049 479.8473854289214 831.585587768127 479.8473854289214z" />
|
||||
<glyph glyph-name="minus"
|
||||
unicode=""
|
||||
horiz-adv-x="893.7641930109211" d="M829.9238935101409 480.448527979227H63.8402995007801C28.5884841201931 480.4465329698676 0 451.8580488496745 0 416.6062334690875V352.7659339683073C0 317.5141185877203 28.5884841201931 288.9256344675273 63.8402995007801 288.9256344675273H829.9238935101409C865.1757088907281 288.9256344675273 893.7641930109211 317.5141185877203 893.7641930109211 352.7659339683073V416.6062334690875C893.7641930109211 451.8580488496744 865.1757088907281 480.4465329698675 829.9238935101409 480.4465329698675z" />
|
||||
<glyph glyph-name="minus-1"
|
||||
unicode="minus"
|
||||
horiz-adv-x="893.7641930109211" d="M829.9238935101409 480.448527979227H63.8402995007801C28.5884841201931 480.4465329698676 0 451.8580488496745 0 416.6062334690875V352.7659339683073C0 317.5141185877203 28.5884841201931 288.9256344675273 63.8402995007801 288.9256344675273H829.9238935101409C865.1757088907281 288.9256344675273 893.7641930109211 317.5141185877203 893.7641930109211 352.7659339683073V416.6062334690875C893.7641930109211 451.8580488496744 865.1757088907281 480.4465329698675 829.9238935101409 480.4465329698675z" />
|
||||
<glyph glyph-name="unlocked"
|
||||
unicode=""
|
||||
horiz-adv-x="1024" d="M426.667 341.333C473.6 341.333 512 303.36 512 256S474.027 170.667 426.6670000000001 170.667S341.3330000000001 208.64 341.3330000000001 256S379.733 341.333 426.6670000000001 341.333M768 853.333C650.24 853.333 554.667 757.76 554.667 640V554.667H170.667C123.733 554.667 85.333 516.267 85.333 469.333V42.667C85.333 -4.2669999999999 123.733 -42.6669999999999 170.667 -42.6669999999999H682.667C729.6 -42.6669999999999 768 -4.2669999999999 768 42.6670000000001V469.333C768 516.267 729.6 554.667 682.667 554.667H640V640C640 710.827 697.173 768 768 768S896 710.827 896 640V554.667H981.333V640C981.333 757.76 885.76 853.333 768 853.333M682.667 469.333V42.6669999999999H170.667V469.333H682.667z" />
|
||||
<glyph glyph-name="unlocked-1"
|
||||
unicode="unlocked"
|
||||
horiz-adv-x="1024" d="M426.667 341.333C473.6 341.333 512 303.36 512 256S474.027 170.667 426.6670000000001 170.667S341.3330000000001 208.64 341.3330000000001 256S379.733 341.333 426.6670000000001 341.333M768 853.333C650.24 853.333 554.667 757.76 554.667 640V554.667H170.667C123.733 554.667 85.333 516.267 85.333 469.333V42.667C85.333 -4.2669999999999 123.733 -42.6669999999999 170.667 -42.6669999999999H682.667C729.6 -42.6669999999999 768 -4.2669999999999 768 42.6670000000001V469.333C768 516.267 729.6 554.667 682.667 554.667H640V640C640 710.827 697.173 768 768 768S896 710.827 896 640V554.667H981.333V640C981.333 757.76 885.76 853.333 768 853.333M682.667 469.333V42.6669999999999H170.667V469.333H682.667z" />
|
||||
<glyph glyph-name="locked"
|
||||
unicode=""
|
||||
horiz-adv-x="1024" d="M512 170.667C464.64 170.667 426.6670000000001 209.067 426.6670000000001 256C426.6670000000001 303.36 464.64 341.333 512 341.333A85.333 85.333 0 0 0 597.333 256A85.333 85.333 0 0 0 512 170.667M768 42.667V469.333H256V42.667H768M768 554.667A85.333 85.333 0 0 0 853.333 469.333V42.667A85.333 85.333 0 0 0 768 -42.667H256C208.64 -42.667 170.667 -4.2670000000001 170.667 42.6669999999999V469.333C170.667 516.693 208.64 554.667 256 554.667H298.6670000000001V640A213.333 213.333 0 0 0 512 853.333A213.333 213.333 0 0 0 725.333 640V554.667H768M512 768A128 128 0 0 1 384 640V554.667H640V640A128 128 0 0 1 512 768z" />
|
||||
<glyph glyph-name="locked-1"
|
||||
unicode="locked"
|
||||
horiz-adv-x="1024" d="M512 170.667C464.64 170.667 426.6670000000001 209.067 426.6670000000001 256C426.6670000000001 303.36 464.64 341.333 512 341.333A85.333 85.333 0 0 0 597.333 256A85.333 85.333 0 0 0 512 170.667M768 42.667V469.333H256V42.667H768M768 554.667A85.333 85.333 0 0 0 853.333 469.333V42.667A85.333 85.333 0 0 0 768 -42.667H256C208.64 -42.667 170.667 -4.2670000000001 170.667 42.6669999999999V469.333C170.667 516.693 208.64 554.667 256 554.667H298.6670000000001V640A213.333 213.333 0 0 0 512 853.333A213.333 213.333 0 0 0 725.333 640V554.667H768M512 768A128 128 0 0 1 384 640V554.667H640V640A128 128 0 0 1 512 768z" />
|
||||
<glyph glyph-name="wifi-strength-1"
|
||||
unicode=""
|
||||
horiz-adv-x="1022.9733303792667" d="M511.4866651896334 768.1283337025917C332.0397604864727 768.1283337025917 157.7087214326013 708.0286505428097 16.1967447318741 597.6324456402496C187.9713494571903 381.9559014844185 336.3024823621839 198.2472739029395 511.4866651896334 -20.4132754656288C685.3921313541088 196.1154134663874 862.7071756207173 416.9078232715078 1008.0553023103668 597.6324456402497C866.1177527202434 708.0286505428097 691.3591427821901 768.1283337025917 511.4866651896334 768.1283337025917M511.4866651896334 682.8808891701171C642.3423366997788 682.8808891701171 771.0661477733722 646.2236788332648 882.7410693389065 578.4516956956384L665.3592366333123 307.3637631451326C618.0467201032712 329.95509018927 564.7661931477559 341.8891130454328 511.4866651896333 341.8891130454328C458.2071372315108 341.8891130454328 404.9266102759954 329.9550901892699 357.6140937459543 307.7903350319215L139.3801162641751 578.8782675824273C251.9071826058945 646.6502507200537 380.6309936794878 682.8808891701171 511.4866651896334 682.8808891701171z" />
|
||||
<glyph glyph-name="wifi-strength-1-1"
|
||||
unicode="wifi_strength_1"
|
||||
horiz-adv-x="1022.9733303792667" d="M511.4866651896334 768.1283337025917C332.0397604864727 768.1283337025917 157.7087214326013 708.0286505428097 16.1967447318741 597.6324456402496C187.9713494571903 381.9559014844185 336.3024823621839 198.2472739029395 511.4866651896334 -20.4132754656288C685.3921313541088 196.1154134663874 862.7071756207173 416.9078232715078 1008.0553023103668 597.6324456402497C866.1177527202434 708.0286505428097 691.3591427821901 768.1283337025917 511.4866651896334 768.1283337025917M511.4866651896334 682.8808891701171C642.3423366997788 682.8808891701171 771.0661477733722 646.2236788332648 882.7410693389065 578.4516956956384L665.3592366333123 307.3637631451326C618.0467201032712 329.95509018927 564.7661931477559 341.8891130454328 511.4866651896333 341.8891130454328C458.2071372315108 341.8891130454328 404.9266102759954 329.9550901892699 357.6140937459543 307.7903350319215L139.3801162641751 578.8782675824273C251.9071826058945 646.6502507200537 380.6309936794878 682.8808891701171 511.4866651896334 682.8808891701171z" />
|
||||
<glyph glyph-name="wifi-strength-0"
|
||||
unicode=""
|
||||
horiz-adv-x="1022.9733303792667" d="M511.4866651896334 768.1283337025917C332.4663323732617 768.1283337025917 157.7087214326013 708.4552224295987 17.0498885054521 597.6324456402496C183.283054692083 393.0377795643962 349.5162208787138 184.1813906102245 511.4866651896334 -20.4132754656289C609.5212763518488 103.1956689560682 711.8186093897755 226.8046133777654 809.8542195493837 350.4145567968553V486.8106678482933L511.4866651896334 115.9828355858092L140.6588329271492 580.5835561321903C251.4806107191054 648.7811121592129 379.3522770165138 682.8808891701171 511.4866651896334 682.8808891701171S771.4927196601614 644.5193892808945 882.3144974521176 580.5835561321903L865.2646089466655 555.0092228727087H976.0873857360147C988.8745523657554 567.7963895024495 997.398997119785 584.8452790105086 1010.186163749526 597.6324456402496C865.2646089466655 708.4552224295987 690.5069980060051 768.1283337025917 511.4866651896334 768.1283337025917M895.1016640818584 469.7607793428413V214.0174467480247H980.349108614333V469.7607793428413M895.1016640818584 128.77000221555V43.5225576830754H980.349108614333V128.77000221555" />
|
||||
<glyph glyph-name="wifi-strength-0-1"
|
||||
unicode="wifi_strength_0"
|
||||
horiz-adv-x="1022.9733303792667" d="M511.4866651896334 768.1283337025917C332.4663323732617 768.1283337025917 157.7087214326013 708.4552224295987 17.0498885054521 597.6324456402496C183.283054692083 393.0377795643962 349.5162208787138 184.1813906102245 511.4866651896334 -20.4132754656289C609.5212763518488 103.1956689560682 711.8186093897755 226.8046133777654 809.8542195493837 350.4145567968553V486.8106678482933L511.4866651896334 115.9828355858092L140.6588329271492 580.5835561321903C251.4806107191054 648.7811121592129 379.3522770165138 682.8808891701171 511.4866651896334 682.8808891701171S771.4927196601614 644.5193892808945 882.3144974521176 580.5835561321903L865.2646089466655 555.0092228727087H976.0873857360147C988.8745523657554 567.7963895024495 997.398997119785 584.8452790105086 1010.186163749526 597.6324456402496C865.2646089466655 708.4552224295987 690.5069980060051 768.1283337025917 511.4866651896334 768.1283337025917M895.1016640818584 469.7607793428413V214.0174467480247H980.349108614333V469.7607793428413M895.1016640818584 128.77000221555V43.5225576830754H980.349108614333V128.77000221555" />
|
||||
<glyph glyph-name="wifi-strength-2"
|
||||
unicode=""
|
||||
horiz-adv-x="1022.9733303792667" d="M511.4866651896334 768.1283337025917C332.0397604864727 768.1283337025917 157.7087214326013 708.0286505428097 16.1967447318741 597.6324456402496C187.9713494571903 381.9559014844185 336.3024823621839 198.2472739029395 511.4866651896334 -20.4132754656288C685.3921313541088 196.1154134663874 862.7071756207173 416.9078232715078 1008.0553023103668 597.6324456402497C866.1177527202434 708.0286505428097 691.3591427821901 768.1283337025917 511.4866651896334 768.1283337025917M511.4866651896334 682.8808891701171C642.3423366997788 682.8808891701171 771.0661477733722 646.2236788332648 882.7410693389065 578.4516956956384L745.9183864006797 408.8089514068742C693.0644313319532 438.2194346552781 612.505281564586 469.7607793428413 511.4866651896334 469.7607793428413C410.0414769278917 469.7607793428413 329.9088990473136 437.7928627684892 277.0549439785871 408.8089514068743L139.3801162641751 578.8782675824273C251.9071826058945 646.6502507200537 380.6309936794878 682.8808891701171 511.4866651896334 682.8808891701171z" />
|
||||
<glyph glyph-name="wifi-strength-2-1"
|
||||
unicode="wifi_strength_2"
|
||||
horiz-adv-x="1022.9733303792667" d="M511.4866651896334 768.1283337025917C332.0397604864727 768.1283337025917 157.7087214326013 708.0286505428097 16.1967447318741 597.6324456402496C187.9713494571903 381.9559014844185 336.3024823621839 198.2472739029395 511.4866651896334 -20.4132754656288C685.3921313541088 196.1154134663874 862.7071756207173 416.9078232715078 1008.0553023103668 597.6324456402497C866.1177527202434 708.0286505428097 691.3591427821901 768.1283337025917 511.4866651896334 768.1283337025917M511.4866651896334 682.8808891701171C642.3423366997788 682.8808891701171 771.0661477733722 646.2236788332648 882.7410693389065 578.4516956956384L745.9183864006797 408.8089514068742C693.0644313319532 438.2194346552781 612.505281564586 469.7607793428413 511.4866651896334 469.7607793428413C410.0414769278917 469.7607793428413 329.9088990473136 437.7928627684892 277.0549439785871 408.8089514068743L139.3801162641751 578.8782675824273C251.9071826058945 646.6502507200537 380.6309936794878 682.8808891701171 511.4866651896334 682.8808891701171z" />
|
||||
<glyph glyph-name="wifi-strength-3"
|
||||
unicode=""
|
||||
horiz-adv-x="1022.9733303792667" d="M511.4866651896334 768.1283337025917C332.0397604864727 768.1283337025917 157.7087214326013 708.0286505428097 16.1967447318741 597.6324456402496C187.9713494571903 381.9559014844185 336.3024823621839 198.2472739029395 511.4866651896334 -20.4132754656288C685.3921313541088 196.1154134663874 862.7071756207173 416.9078232715078 1008.0553023103668 597.6324456402497C866.1177527202434 708.0286505428097 691.3591427821901 768.1283337025917 511.4866651896334 768.1283337025917M511.4866651896334 682.8808891701171C642.3423366997788 682.8808891701171 771.0661477733722 646.2236788332648 882.7410693389065 578.4516956956384L800.0500591349871 474.8756459947375C735.688653096887 512.385001107775 634.2434648351453 555.0092228727087 511.4866651896334 555.0092228727087C383.614998892225 555.0092228727087 284.7272439564316 512.385001107775 222.0701274707015 476.5809345445006L139.3801162641751 578.8782675824273C251.9071826058945 646.6502507200537 380.6309936794878 682.8808891701171 511.4866651896334 682.8808891701171z" />
|
||||
<glyph glyph-name="wifi-strength-3-1"
|
||||
unicode="wifi_strength_3"
|
||||
horiz-adv-x="1022.9733303792667" d="M511.4866651896334 768.1283337025917C332.0397604864727 768.1283337025917 157.7087214326013 708.0286505428097 16.1967447318741 597.6324456402496C187.9713494571903 381.9559014844185 336.3024823621839 198.2472739029395 511.4866651896334 -20.4132754656288C685.3921313541088 196.1154134663874 862.7071756207173 416.9078232715078 1008.0553023103668 597.6324456402497C866.1177527202434 708.0286505428097 691.3591427821901 768.1283337025917 511.4866651896334 768.1283337025917M511.4866651896334 682.8808891701171C642.3423366997788 682.8808891701171 771.0661477733722 646.2236788332648 882.7410693389065 578.4516956956384L800.0500591349871 474.8756459947375C735.688653096887 512.385001107775 634.2434648351453 555.0092228727087 511.4866651896334 555.0092228727087C383.614998892225 555.0092228727087 284.7272439564316 512.385001107775 222.0701274707015 476.5809345445006L139.3801162641751 578.8782675824273C251.9071826058945 646.6502507200537 380.6309936794878 682.8808891701171 511.4866651896334 682.8808891701171z" />
|
||||
<glyph glyph-name="down"
|
||||
unicode=""
|
||||
horiz-adv-x="893.7641930109211" d="M824.1383663678829 451.520892267936L868.427574146549 407.2306869845902C887.180662124903 388.477599006236 887.180662124903 358.1514617340061 868.427574146549 339.5978746915919L480.79725561525 -48.2279547569284C462.0441676368958 -66.9810427352825 431.7200253740253 -66.9810427352825 413.1664383316111 -48.2279547569284L25.3366188643721 339.6018647103107C6.5835308860179 358.3549526886648 6.5835308860179 388.6790949515354 25.3366188643721 407.2326819939496L69.6258266430383 451.5218897726157C88.5784155573323 470.4744786869098 119.5010606280227 470.07547681503 138.0546476704369 450.723886028856L367.0817221294855 210.325258221231V783.690948112612C367.0817221294855 810.2245725926238 388.4283222750588 831.5711727381971 414.9619467550705 831.5711727381971H478.8022462558506C505.3358707358623 831.5711727381971 526.6824708814356 810.2245725926238 526.6824708814356 783.690948112612V210.325258221231L755.7095453404842 450.723886028856C774.2631323828984 470.2749777509699 805.1857774535888 470.6739796228497 824.1383663678829 451.5218897726157z" />
|
||||
<glyph glyph-name="down-1"
|
||||
unicode="down"
|
||||
horiz-adv-x="893.7641930109211" d="M824.1383663678829 451.520892267936L868.427574146549 407.2306869845902C887.180662124903 388.477599006236 887.180662124903 358.1514617340061 868.427574146549 339.5978746915919L480.79725561525 -48.2279547569284C462.0441676368958 -66.9810427352825 431.7200253740253 -66.9810427352825 413.1664383316111 -48.2279547569284L25.3366188643721 339.6018647103107C6.5835308860179 358.3549526886648 6.5835308860179 388.6790949515354 25.3366188643721 407.2326819939496L69.6258266430383 451.5218897726157C88.5784155573323 470.4744786869098 119.5010606280227 470.07547681503 138.0546476704369 450.723886028856L367.0817221294855 210.325258221231V783.690948112612C367.0817221294855 810.2245725926238 388.4283222750588 831.5711727381971 414.9619467550705 831.5711727381971H478.8022462558506C505.3358707358623 831.5711727381971 526.6824708814356 810.2245725926238 526.6824708814356 783.690948112612V210.325258221231L755.7095453404842 450.723886028856C774.2631323828984 470.2749777509699 805.1857774535888 470.6739796228497 824.1383663678829 451.5218897726157z" />
|
||||
<glyph glyph-name="wifi-strength-4"
|
||||
unicode=""
|
||||
horiz-adv-x="1022.9733303792667" d="M511.4866651896334 768.1283337025917C332.0397604864727 768.1283337025917 157.7087214326013 708.0286505428097 16.1967447318741 597.6324456402496C187.9713494571903 381.9559014844185 336.3024823621839 198.2472739029395 511.4866651896334 -20.4132754656288C685.3921313541088 196.1154134663874 862.7071756207173 416.9078232715078 1008.0553023103668 597.6324456402497C866.1177527202434 708.0286505428097 691.3591427821901 768.1283337025917 511.4866651896334 768.1283337025917z" />
|
||||
<glyph glyph-name="wifi-strength-4-1"
|
||||
unicode="wifi_strength_4"
|
||||
horiz-adv-x="1022.9733303792667" d="M511.4866651896334 768.1283337025917C332.0397604864727 768.1283337025917 157.7087214326013 708.0286505428097 16.1967447318741 597.6324456402496C187.9713494571903 381.9559014844185 336.3024823621839 198.2472739029395 511.4866651896334 -20.4132754656288C685.3921313541088 196.1154134663874 862.7071756207173 416.9078232715078 1008.0553023103668 597.6324456402497C866.1177527202434 708.0286505428097 691.3591427821901 768.1283337025917 511.4866651896334 768.1283337025917z" />
|
||||
<glyph glyph-name="up"
|
||||
unicode=""
|
||||
horiz-adv-x="893.7641930109211" d="M69.6258266430383 317.8562626928574L25.3366188643721 362.1454704715236C6.5835308860179 380.8985584498777 6.5835308860179 411.2227007127483 25.3366188643721 429.7762877551625L412.9669373956711 817.6021172036826C431.7200253740253 836.3552051820368 462.0441676368958 836.3552051820368 480.59775467931 817.6021172036826L868.2280732106092 429.9717986723836C886.9811611889633 411.2187106940295 886.9811611889633 380.894568431159 868.2280732106092 362.3409813887447L823.938865431943 318.0517736100786C804.9862765176489 299.0991846957845 774.0636314469585 299.4981865676644 755.5100444045443 318.8497773538383L526.6824708814356 559.0528942442421V-14.3127956471388C526.6824708814356 -40.8464201271506 505.3358707358623 -62.1930202727239 478.8022462558506 -62.1930202727239H414.9619467550705C388.4283222750588 -62.1930202727239 367.0817221294855 -40.8464201271506 367.0817221294855 -14.3127956471388V559.0528942442422L138.0546476704369 318.6542664366173C119.5010606280227 299.1031747145034 88.5784155573324 298.7041728426234 69.6258266430383 317.8562626928575z" />
|
||||
<glyph glyph-name="up-1"
|
||||
unicode="up"
|
||||
horiz-adv-x="893.7641930109211" d="M69.6258266430383 317.8562626928574L25.3366188643721 362.1454704715236C6.5835308860179 380.8985584498777 6.5835308860179 411.2227007127483 25.3366188643721 429.7762877551625L412.9669373956711 817.6021172036826C431.7200253740253 836.3552051820368 462.0441676368958 836.3552051820368 480.59775467931 817.6021172036826L868.2280732106092 429.9717986723836C886.9811611889633 411.2187106940295 886.9811611889633 380.894568431159 868.2280732106092 362.3409813887447L823.938865431943 318.0517736100786C804.9862765176489 299.0991846957845 774.0636314469585 299.4981865676644 755.5100444045443 318.8497773538383L526.6824708814356 559.0528942442421V-14.3127956471388C526.6824708814356 -40.8464201271506 505.3358707358623 -62.1930202727239 478.8022462558506 -62.1930202727239H414.9619467550705C388.4283222750588 -62.1930202727239 367.0817221294855 -40.8464201271506 367.0817221294855 -14.3127956471388V559.0528942442422L138.0546476704369 318.6542664366173C119.5010606280227 299.1031747145034 88.5784155573324 298.7041728426234 69.6258266430383 317.8562626928575z" />
|
||||
</font>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
361
src_data/locales/en.json
Normal file
@@ -0,0 +1,361 @@
|
||||
{
|
||||
"values": {
|
||||
"logo": "OpenTherm Gateway",
|
||||
"nav": {
|
||||
"license": "License",
|
||||
"source": "Source code",
|
||||
"help": "Help",
|
||||
"issues": "Issues & questions",
|
||||
"releases": "Releases"
|
||||
},
|
||||
"dbm": "dBm",
|
||||
|
||||
"button": {
|
||||
"upgrade": "Upgrade",
|
||||
"restart": "Restart",
|
||||
"save": "Save",
|
||||
"saved": "Saved",
|
||||
"refresh": "Refresh",
|
||||
"restore": "Restore",
|
||||
"restored": "Restored",
|
||||
"backup": "Backup",
|
||||
"wait": "Please wait...",
|
||||
"uploading": "Uploading...",
|
||||
"success": "Success",
|
||||
"error": "Error"
|
||||
},
|
||||
|
||||
"index": {
|
||||
"title": "OpenTherm Gateway",
|
||||
|
||||
"section": {
|
||||
"network": "Network",
|
||||
"system": "System"
|
||||
},
|
||||
|
||||
"system": {
|
||||
"build": {
|
||||
"title": "Build",
|
||||
"version": "Version",
|
||||
"date": "Date",
|
||||
"sdk": "Core/SDK"
|
||||
},
|
||||
"uptime": "Uptime",
|
||||
"memory": {
|
||||
"title": "Free memory",
|
||||
"maxFreeBlock": "max free block",
|
||||
"min": "min"
|
||||
},
|
||||
"board": "Board",
|
||||
"chip": {
|
||||
"model": "Chip",
|
||||
"cores": "Cores",
|
||||
"freq": "frequency"
|
||||
},
|
||||
"flash": {
|
||||
"size": "Flash size",
|
||||
"realSize": "real size"
|
||||
},
|
||||
"lastResetReason": "Last reset reason"
|
||||
}
|
||||
},
|
||||
|
||||
"dashboard": {
|
||||
"name": "Dashboard",
|
||||
"title": "Dashboard - OpenTherm Gateway",
|
||||
|
||||
"section": {
|
||||
"control": "Control",
|
||||
"states": "States and sensors",
|
||||
"otDiag": "OpenTherm diagnostic"
|
||||
},
|
||||
|
||||
"thermostat": {
|
||||
"heating": "Heating",
|
||||
"dhw": "DHW",
|
||||
"temp.current": "Current",
|
||||
"enable": "Enable",
|
||||
"turbo": "Turbo mode"
|
||||
},
|
||||
|
||||
"state": {
|
||||
"ot": "OpenTherm connected",
|
||||
"mqtt": "MQTT connected",
|
||||
"emergency": "Emergency",
|
||||
"heating": "Heating",
|
||||
"dhw": "DHW",
|
||||
"flame": "Flame",
|
||||
"fault": "Fault",
|
||||
"diag": "Diagnostic",
|
||||
"extpump": "External pump",
|
||||
"outdoorSensorConnected": "Outdoor sensor connected",
|
||||
"outdoorSensorRssi": "Outdoor sensor RSSI",
|
||||
"outdoorSensorHumidity": "Outdoor sensor humidity",
|
||||
"outdoorSensorBattery": "Outdoor sensor battery",
|
||||
"indoorSensorConnected": "Indoor sensor connected",
|
||||
"indoorSensorRssi": "Indoor sensor RSSI",
|
||||
"indoorSensorHumidity": "Indoor sensor humidity",
|
||||
"indoorSensorBattery": "Indoor sensor battery",
|
||||
"modulation": "Modulation",
|
||||
"pressure": "Pressure",
|
||||
"dhwFlowRate": "DHW flow rate",
|
||||
"faultCode": "Fault code",
|
||||
"diagCode": "Diagnostic code",
|
||||
"indoorTemp": "Indoor temp",
|
||||
"outdoorTemp": "Outdoor temp",
|
||||
"heatingTemp": "Heating temp",
|
||||
"heatingSetpointTemp": "Heating setpoint temp",
|
||||
"heatingReturnTemp": "Heating return temp",
|
||||
"dhwTemp": "DHW temp",
|
||||
"exhaustTemp": "Exhaust temp"
|
||||
}
|
||||
},
|
||||
|
||||
"network": {
|
||||
"title": "Network - OpenTherm Gateway",
|
||||
"name": "Network settings",
|
||||
|
||||
"section": {
|
||||
"static": "Static settings",
|
||||
"availableNetworks": "Available networks",
|
||||
"staSettings": "WiFi settings",
|
||||
"apSettings": "AP settings"
|
||||
},
|
||||
|
||||
"scan": {
|
||||
"pos": "#",
|
||||
"info": "Info"
|
||||
},
|
||||
|
||||
"wifi": {
|
||||
"ssid": "SSID",
|
||||
"password": "Password",
|
||||
"channel": "Channel",
|
||||
"signal": "Signal",
|
||||
"connected": "Connected"
|
||||
},
|
||||
|
||||
"params": {
|
||||
"hostname": "Hostname",
|
||||
"dhcp": "Use DHCP",
|
||||
"mac": "MAC",
|
||||
"ip": "IP",
|
||||
"subnet": "Subnet",
|
||||
"gateway": "Gateway",
|
||||
"dns": "DNS"
|
||||
},
|
||||
|
||||
"sta": {
|
||||
"channel.note": "set 0 for auto select"
|
||||
}
|
||||
},
|
||||
|
||||
"settings": {
|
||||
"title": "Settings - OpenTherm Gateway",
|
||||
"name": "Settings",
|
||||
|
||||
"section": {
|
||||
"portal": "Portal settings",
|
||||
"system": "System settings",
|
||||
"diag": "Diagnostic",
|
||||
"heating": "Heating settings",
|
||||
"dhw": "DHW settings",
|
||||
"emergency": "Emergency mode settings",
|
||||
"emergency.events": "Events",
|
||||
"emergency.regulators": "Using regulators",
|
||||
"equitherm": "Equitherm settings",
|
||||
"pid": "PID settings",
|
||||
"ot": "OpenTherm settings",
|
||||
"ot.options": "Options",
|
||||
"mqtt": "MQTT settings",
|
||||
"outdorSensor": "Outdoor sensor settings",
|
||||
"indoorSensor": "Indoor sensor settings",
|
||||
"extPump": "External pump settings"
|
||||
},
|
||||
|
||||
"enable": "Enable",
|
||||
"note": {
|
||||
"restart": "After changing these settings, the device must be restarted for the changes to take effect.",
|
||||
"blankNotUse": "blank - not use",
|
||||
"bleDevice": "BLE device can be used <u>only</u> with some ESP32 boards with BLE support!"
|
||||
},
|
||||
|
||||
"temp": {
|
||||
"min": "Minimum temperature",
|
||||
"max": "Maximum temperature"
|
||||
},
|
||||
|
||||
"portal": {
|
||||
"login": "Login",
|
||||
"password": "Password",
|
||||
"auth": "Require authentication"
|
||||
},
|
||||
|
||||
"system": {
|
||||
"unit": "Unit system",
|
||||
"metric": "Metric <small>(celsius, liters, bar)</small>",
|
||||
"imperial": "Imperial <small>(fahrenheit, gallons, psi)</small>",
|
||||
"statusLedGpio": "Status LED GPIO",
|
||||
"debug": "Debug mode",
|
||||
"serial": {
|
||||
"enable": "Enable Serial port",
|
||||
"baud": {
|
||||
"title": "Serial port baud rate",
|
||||
"note": "Available: 9600, 19200, 38400, 57600, 74880, 115200"
|
||||
}
|
||||
},
|
||||
"telnet": {
|
||||
"enable": "Enable Telnet",
|
||||
"port": {
|
||||
"title": "Telnet port",
|
||||
"note": "Default: 23"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"heating": {
|
||||
"hyst": "Hysteresis <small>(in degrees)</small>",
|
||||
"maxMod": "Max modulation level"
|
||||
},
|
||||
|
||||
"emergency": {
|
||||
"desc": "<b>!</b> Emergency mode can be useful <u>only</u> when using Equitherm and/or PID (when normal work) and when reporting indoor/outdoor temperature via MQTT/API/BLE. In this mode, sensor values that are reported via MQTT/API/BLE are not used.",
|
||||
|
||||
"target": {
|
||||
"title": "Target temperature",
|
||||
"note": "<u>Indoor temperature</u> if Equitherm or PID is <b>enabled</b><br /><u>Heat carrier temperature</u> if Equitherm and PID <b>is disabled</b>"
|
||||
},
|
||||
"treshold": "Treshold time <small>(sec)</small>",
|
||||
|
||||
"events": {
|
||||
"network": "On network fault",
|
||||
"mqtt": "On MQTT fault",
|
||||
"indoorSensorDisconnect": "On loss connection with indoor sensor",
|
||||
"outdoorSensorDisconnect": "On loss connection with outdoor sensor"
|
||||
},
|
||||
|
||||
"regulators": {
|
||||
"equitherm": "Equitherm <small>(requires at least an external (DS18B20) or boiler <u>outdoor</u> sensor)</small>",
|
||||
"pid": "PID <small>(requires at least an external (DS18B20) <u>indoor</u> sensor)</small>"
|
||||
}
|
||||
},
|
||||
|
||||
"equitherm": {
|
||||
"n": "N factor",
|
||||
"k": "K factor",
|
||||
"t": {
|
||||
"title": "T factor",
|
||||
"note": "Not used if PID is enabled"
|
||||
}
|
||||
},
|
||||
|
||||
"pid": {
|
||||
"p": "P factor",
|
||||
"i": "I factor",
|
||||
"d": "D factor",
|
||||
"dt": "DT <small>in seconds</small>"
|
||||
},
|
||||
|
||||
"ot": {
|
||||
"inGpio": "In GPIO",
|
||||
"outGpio": "Out GPIO",
|
||||
"ledGpio": "RX LED GPIO",
|
||||
"memberIdCode": "Master MemberID code",
|
||||
"pressureFactor": {
|
||||
"title": "Coeff. pressure correction",
|
||||
"note": "If the pressure displayed is <b>X10</b> from the real one, set the <b>0.1</b>."
|
||||
},
|
||||
"dhwFlowRateFactor": {
|
||||
"title": "Coeff. DHW flow rate correction",
|
||||
"note": "If the DHW flow rate displayed is <b>X10</b> from the real one, set the <b>0.1</b>."
|
||||
},
|
||||
"fnv": {
|
||||
"title": "Filtering numeric values",
|
||||
"enable": {
|
||||
"title": "Enable filtering",
|
||||
"note": "It can be useful if there is a lot of sharp noise on the charts. The filter used is \"Running Average\"."
|
||||
},
|
||||
"factor": {
|
||||
"title": "Filtration coeff.",
|
||||
"note": "The lower the value, the smoother and <u>longer</u> the change in numeric values."
|
||||
}
|
||||
},
|
||||
|
||||
"options": {
|
||||
"dhwPresent": "DHW present",
|
||||
"summerWinterMode": "Summer/winter mode",
|
||||
"heatingCh2Enabled": "Heating CH2 always enabled",
|
||||
"heatingCh1ToCh2": "Duplicate heating CH1 to CH2",
|
||||
"dhwToCh2": "Duplicate DHW to CH2",
|
||||
"dhwBlocking": "DHW blocking",
|
||||
"modulationSyncWithHeating": "Sync modulation with heating",
|
||||
"getMinMaxTemp": "Get min/max temp from boiler",
|
||||
"immergasFix": "Fix for Immergas boilers"
|
||||
},
|
||||
|
||||
"faultState": {
|
||||
"gpio": "Fault state GPIO",
|
||||
"note": "Can be useful to switch on another boiler <u>via relay</u>. Blank - not use.",
|
||||
"invert": "Invert fault state"
|
||||
},
|
||||
|
||||
"nativeHeating": {
|
||||
"title": "Native heating control (boiler)",
|
||||
"note": "Works <u>ONLY</u> if the boiler requires the desired room temperature and regulates the temperature of the coolant itself. Not compatible with PID and Equitherm regulators and hysteresis in firmware."
|
||||
}
|
||||
},
|
||||
|
||||
"mqtt": {
|
||||
"homeAssistantDiscovery": "Home Assistant Discovery",
|
||||
"server": "Server",
|
||||
"port": "Port",
|
||||
"user": "User",
|
||||
"password": "Password",
|
||||
"prefix": "Prefix",
|
||||
"interval": "Publish interval <small>(sec)</small>"
|
||||
},
|
||||
|
||||
"tempSensor": {
|
||||
"source": {
|
||||
"type": "Source type",
|
||||
"boiler": "From boiler via OpenTherm",
|
||||
"manual": "Manual via MQTT/API",
|
||||
"ext": "External (DS18B20)",
|
||||
"ble": "BLE device"
|
||||
},
|
||||
"gpio": "GPIO",
|
||||
"offset": "Temp offset <small>(calibration)</small>",
|
||||
"bleAddress": "BLE device MAC address"
|
||||
},
|
||||
|
||||
"extPump": {
|
||||
"use": "Use external pump",
|
||||
"gpio": "Relay GPIO",
|
||||
"postCirculationTime": "Post circulation time <small>(min)</small>",
|
||||
"antiStuckInterval": "Anti stuck interval <small>(days)</small>",
|
||||
"antiStuckTime": "Anti stuck time <small>(min)</small>"
|
||||
}
|
||||
},
|
||||
|
||||
"upgrade": {
|
||||
"title": "Upgrade - OpenTherm Gateway",
|
||||
"name": "Upgrade",
|
||||
|
||||
"section": {
|
||||
"backupAndRestore": "Backup & restore",
|
||||
"backupAndRestore.desc": "In this section you can save and restore a backup of ALL settings.",
|
||||
"upgrade": "Upgrade",
|
||||
"upgrade.desc": "In this section you can upgrade the firmware and filesystem of your device.<br />Latest releases can be downloaded from the <a href=\"https://github.com/Laxilef/OTGateway/releases\" target=\"_blank\">Releases page</a> of the project repository."
|
||||
},
|
||||
|
||||
"note": {
|
||||
"disclaimer1": "After a successful upgrade the filesystem, ALL settings will be reset to default values! Save backup before upgrading.",
|
||||
"disclaimer2": "After a successful upgrade, the device will automatically reboot after 10 seconds."
|
||||
},
|
||||
|
||||
"settingsFile": "Settings file",
|
||||
"fw": "Firmware",
|
||||
"fs": "Filesystem"
|
||||
}
|
||||
}
|
||||
}
|
||||
361
src_data/locales/ru.json
Normal file
@@ -0,0 +1,361 @@
|
||||
{
|
||||
"values": {
|
||||
"logo": "OpenTherm Gateway",
|
||||
"nav": {
|
||||
"license": "Лицензия",
|
||||
"source": "Исходный код",
|
||||
"help": "Помощь",
|
||||
"issues": "Проблемы и вопросы",
|
||||
"releases": "Релизы"
|
||||
},
|
||||
"dbm": "дБм",
|
||||
|
||||
"button": {
|
||||
"upgrade": "Обновить",
|
||||
"restart": "Перезагрузка",
|
||||
"save": "Сохранить",
|
||||
"saved": "Сохранено",
|
||||
"refresh": "Обновить",
|
||||
"restore": "Восстановить настройки",
|
||||
"restored": "Восстановлено",
|
||||
"backup": "Сохранить настройки",
|
||||
"wait": "Пожалуйста, подождите...",
|
||||
"uploading": "Загрузка...",
|
||||
"success": "Успешно",
|
||||
"error": "Ошибка"
|
||||
},
|
||||
|
||||
"index": {
|
||||
"title": "OpenTherm Gateway",
|
||||
|
||||
"section": {
|
||||
"network": "Сеть",
|
||||
"system": "Система"
|
||||
},
|
||||
|
||||
"system": {
|
||||
"build": {
|
||||
"title": "Билд",
|
||||
"version": "Версия",
|
||||
"date": "Дата",
|
||||
"sdk": "Ядро/SDK"
|
||||
},
|
||||
"uptime": "Аптайм",
|
||||
"memory": {
|
||||
"title": "ОЗУ",
|
||||
"maxFreeBlock": "макс. блок",
|
||||
"min": "мин."
|
||||
},
|
||||
"board": "Плата",
|
||||
"chip": {
|
||||
"model": "Чип",
|
||||
"cores": "Кол-во ядер",
|
||||
"freq": "частота"
|
||||
},
|
||||
"flash": {
|
||||
"size": "Размер ПЗУ",
|
||||
"realSize": "реальный размер"
|
||||
},
|
||||
"lastResetReason": "Причина перезагрузки"
|
||||
}
|
||||
},
|
||||
|
||||
"dashboard": {
|
||||
"name": "Дашборд",
|
||||
"title": "Дашборд - OpenTherm Gateway",
|
||||
|
||||
"section": {
|
||||
"control": "Управление",
|
||||
"states": "Состояние и сенсоры",
|
||||
"otDiag": "Диагностика OpenTherm"
|
||||
},
|
||||
|
||||
"thermostat": {
|
||||
"heating": "Отопление",
|
||||
"dhw": "ГВС",
|
||||
"temp.current": "Текущая",
|
||||
"enable": "Вкл",
|
||||
"turbo": "Турбо"
|
||||
},
|
||||
|
||||
"state": {
|
||||
"ot": "OpenTherm подключение",
|
||||
"mqtt": "MQTT подключение",
|
||||
"emergency": "Аварийный режим",
|
||||
"heating": "Отопление",
|
||||
"dhw": "ГВС",
|
||||
"flame": "Пламя",
|
||||
"fault": "Ошибка",
|
||||
"diag": "Диагностика",
|
||||
"extpump": "Внешний насос",
|
||||
"outdoorSensorConnected": "Датчик наруж. темп.",
|
||||
"outdoorSensorRssi": "RSSI датчика наруж. темп.",
|
||||
"outdoorSensorHumidity": "Влажность с наруж. датчика темп.",
|
||||
"outdoorSensorBattery": "Заряд наруж. датчика темп.",
|
||||
"indoorSensorConnected": "Датчик внутр. темп.",
|
||||
"indoorSensorRssi": "RSSI датчика внутр. темп.",
|
||||
"indoorSensorHumidity": "Влажность с внутр. датчика темп.",
|
||||
"indoorSensorBattery": "Заряд внутр. датчика темп.",
|
||||
"modulation": "Уровень модуляции",
|
||||
"pressure": "Давление",
|
||||
"dhwFlowRate": "Расход ГВС",
|
||||
"faultCode": "Код ошибки",
|
||||
"diagCode": "Диагностический код",
|
||||
"indoorTemp": "Внутренняя темп.",
|
||||
"outdoorTemp": "Наружная темп.",
|
||||
"heatingTemp": "Темп. отопления",
|
||||
"heatingSetpointTemp": "Уставка темп. отопления",
|
||||
"heatingReturnTemp": "Темп. обратки отопления",
|
||||
"dhwTemp": "Темп. ГВС",
|
||||
"exhaustTemp": "Темп. выхлопных газов"
|
||||
}
|
||||
},
|
||||
|
||||
"network": {
|
||||
"title": "Сеть - OpenTherm Gateway",
|
||||
"name": "Настройки сети",
|
||||
|
||||
"section": {
|
||||
"static": "Статические параметры",
|
||||
"availableNetworks": "Доступные сети",
|
||||
"staSettings": "Настройки подключения",
|
||||
"apSettings": "Настройки точки доступа"
|
||||
},
|
||||
|
||||
"scan": {
|
||||
"pos": "#",
|
||||
"info": "Инфо"
|
||||
},
|
||||
|
||||
"wifi": {
|
||||
"ssid": "Имя сети",
|
||||
"password": "Пароль",
|
||||
"channel": "Канал",
|
||||
"signal": "Сигнал",
|
||||
"connected": "Подключено"
|
||||
},
|
||||
|
||||
"params": {
|
||||
"hostname": "Имя хоста",
|
||||
"dhcp": "Использовать DHCP",
|
||||
"mac": "MAC адрес",
|
||||
"ip": "IP адрес",
|
||||
"subnet": "Адрес подсети",
|
||||
"gateway": "Адрес шлюза",
|
||||
"dns": "DNS адрес"
|
||||
},
|
||||
|
||||
"sta": {
|
||||
"channel.note": "установите 0 для автоматического выбора"
|
||||
}
|
||||
},
|
||||
|
||||
"settings": {
|
||||
"title": "Настройки - OpenTherm Gateway",
|
||||
"name": "Настройки",
|
||||
|
||||
"section": {
|
||||
"portal": "Настройки портала",
|
||||
"system": "Системные настройки",
|
||||
"diag": "Диагностика",
|
||||
"heating": "Настройки отопления",
|
||||
"dhw": "Настройки ГВС",
|
||||
"emergency": "Настройки аварийного режима",
|
||||
"emergency.events": "События",
|
||||
"emergency.regulators": "Используемые регуляторы",
|
||||
"equitherm": "Настройки ПЗА",
|
||||
"pid": "Настройки ПИД",
|
||||
"ot": "Настройки OpenTherm",
|
||||
"ot.options": "Опции",
|
||||
"mqtt": "Настройки MQTT",
|
||||
"outdorSensor": "Настройки наружного датчика температуры",
|
||||
"indoorSensor": "Настройки внутреннего датчика температуры",
|
||||
"extPump": "Настройки дополнительного насоса"
|
||||
},
|
||||
|
||||
"enable": "Вкл",
|
||||
"note": {
|
||||
"restart": "После изменения этих настроек устройство необходимо перезагрузить, чтобы изменения вступили в силу.",
|
||||
"blankNotUse": "пусто - не использовать",
|
||||
"bleDevice": "BLE устройство можно использовать <u>только</u> с некоторыми платами ESP32, которые поддерживают BLE!"
|
||||
},
|
||||
|
||||
"temp": {
|
||||
"min": "Мин. температура",
|
||||
"max": "Макс. температура"
|
||||
},
|
||||
|
||||
"portal": {
|
||||
"login": "Логин",
|
||||
"password": "Пароль",
|
||||
"auth": "Требовать аутентификацию"
|
||||
},
|
||||
|
||||
"system": {
|
||||
"unit": "Система единиц",
|
||||
"metric": "Метрическая <small>(цильсии, литры, бары)</small>",
|
||||
"imperial": "Imperial <small>(фаренгейты, галлоны, psi)</small>",
|
||||
"statusLedGpio": "Статус LED GPIO",
|
||||
"debug": "Отладка",
|
||||
"serial": {
|
||||
"enable": "Вкл. Serial порт",
|
||||
"baud": {
|
||||
"title": "Скорость Serial порта",
|
||||
"note": "Доступно: 9600, 19200, 38400, 57600, 74880, 115200"
|
||||
}
|
||||
},
|
||||
"telnet": {
|
||||
"enable": "Вкл. Telnet",
|
||||
"port": {
|
||||
"title": "Telnet порт",
|
||||
"note": "По умолчанию: 23"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"heating": {
|
||||
"hyst": "Гистерезис <small>(в градусах)</small>",
|
||||
"maxMod": "Макс. уровень модуляции"
|
||||
},
|
||||
|
||||
"emergency": {
|
||||
"desc": "<b>!</b> Аварийный режим может быть полезен <u>только</u> при использовании ПЗА и/или ПИД и при передачи наружной/внутренней температуры через MQTT/API/BLE. В этом режиме значения датчиков, передаваемые через MQTT/API/BLE, не используются.",
|
||||
|
||||
"target": {
|
||||
"title": "Целевая температура",
|
||||
"note": "Целевая <u>внутренняя температура</u> если ПЗА и/или ПИД <b>включены</b><br />Целевая <u>температура теплоносителя</u> если ПЗА и ПИД <b>выключены</b>"
|
||||
},
|
||||
"treshold": "Пороговое время включения <small>(сек)</small>",
|
||||
|
||||
"events": {
|
||||
"network": "При отключении сети",
|
||||
"mqtt": "При отключении MQTT",
|
||||
"indoorSensorDisconnect": "При потере связи с датчиком внутренней темп.",
|
||||
"outdoorSensorDisconnect": "При потере связи с датчиком наружной темп."
|
||||
},
|
||||
|
||||
"regulators": {
|
||||
"equitherm": "ПЗА <small>(требуется внешний (DS18B20) или подключенный к котлу датчик <u>наружной</u> температуры)</small>",
|
||||
"pid": "ПИД <small>(требуется внешний (DS18B20) датчик <u>внутренней</u> температуры)</small>"
|
||||
}
|
||||
},
|
||||
|
||||
"equitherm": {
|
||||
"n": "Коэффициент N",
|
||||
"k": "Коэффициент K",
|
||||
"t": {
|
||||
"title": "Коэффициент T",
|
||||
"note": "Не используется, если ПИД включен"
|
||||
}
|
||||
},
|
||||
|
||||
"pid": {
|
||||
"p": "Коэффициент P",
|
||||
"i": "Коэффициент I",
|
||||
"d": "Коэффициент D",
|
||||
"dt": "DT <small>(сек)</small>"
|
||||
},
|
||||
|
||||
"ot": {
|
||||
"inGpio": "Вход GPIO",
|
||||
"outGpio": "Выход GPIO",
|
||||
"ledGpio": "RX LED GPIO",
|
||||
"memberIdCode": "Master MemberID код",
|
||||
"pressureFactor": {
|
||||
"title": "Коэфф. коррекции давления",
|
||||
"note": "Если давление отображается <b>Х10</b> от реального, установите значение <b>0.1</b>."
|
||||
},
|
||||
"dhwFlowRateFactor": {
|
||||
"title": "Коэфф. коррекции потока ГВС",
|
||||
"note": "Если поток ГВС отображается <b>Х10</b> от реального, установите значение <b>0.1</b>."
|
||||
},
|
||||
"fnv": {
|
||||
"title": "Фильтрация числовых значений",
|
||||
"enable": {
|
||||
"title": "Включить фильтрацию",
|
||||
"note": "Может быть полезно, если на графиках много резкого шума. В качестве фильтра используется \"бегущее среднее\"."
|
||||
},
|
||||
"factor": {
|
||||
"title": "Коэфф. фильтрации",
|
||||
"note": "Чем меньше коэф., тем плавнее и <u>дольше</u> изменение числовых значений."
|
||||
}
|
||||
},
|
||||
|
||||
"options": {
|
||||
"dhwPresent": "Контур ГВС",
|
||||
"summerWinterMode": "Летний/зимний режим",
|
||||
"heatingCh2Enabled": "Канал 2 отопления всегда вкл.",
|
||||
"heatingCh1ToCh2": "Дублировать параметры отопления канала 1 в канал 2",
|
||||
"dhwToCh2": "Дублировать параметры ГВС в канал 2",
|
||||
"dhwBlocking": "DHW blocking",
|
||||
"modulationSyncWithHeating": "Синхронизировать модуляцию с отоплением",
|
||||
"getMinMaxTemp": "Получать мин. и макс. температуру от котла",
|
||||
"immergasFix": "Фикс для котлов Immergas"
|
||||
},
|
||||
|
||||
"faultState": {
|
||||
"gpio": "Fault state GPIO",
|
||||
"note": "Can be useful to switch on another boiler <u>via relay</u>. Blank - not use.",
|
||||
"invert": "Invert fault state"
|
||||
},
|
||||
|
||||
"nativeHeating": {
|
||||
"title": "Передать управление отоплением котлу",
|
||||
"note": "Работает <u>ТОЛЬКО</u> если котел требует и принимает целевую температуру в помещении и сам регулирует температуру теплоносителя на основе встроенного режима кривых. Несовместимо с ПИД, ПЗА и гистерезисом."
|
||||
}
|
||||
},
|
||||
|
||||
"mqtt": {
|
||||
"homeAssistantDiscovery": "Home Assistant Discovery",
|
||||
"server": "Адрес сервера",
|
||||
"port": "Порт",
|
||||
"user": "Имя пользователя",
|
||||
"password": "Пароль",
|
||||
"prefix": "Префикс",
|
||||
"interval": "Интервал публикации <small>(сек)</small>"
|
||||
},
|
||||
|
||||
"tempSensor": {
|
||||
"source": {
|
||||
"type": "Источник данных",
|
||||
"boiler": "От котла через OpenTherm",
|
||||
"manual": "Вручную через MQTT/API",
|
||||
"ext": "Внешний датчик (DS18B20)",
|
||||
"ble": "BLE устройство"
|
||||
},
|
||||
"gpio": "GPIO",
|
||||
"offset": "Смещение температуры <small>(калибровка)</small>",
|
||||
"bleAddress": "MAC адрес BLE устройства"
|
||||
},
|
||||
|
||||
"extPump": {
|
||||
"use": "Использовать доп. насос",
|
||||
"gpio": "GPIO реле",
|
||||
"postCirculationTime": "Время постциркуляции <small>(в минутах)</small>",
|
||||
"antiStuckInterval": "Интервал защиты от блокировки <small>(в днях)</small>",
|
||||
"antiStuckTime": "Время работы насоса <small>(в минутах)</small>"
|
||||
}
|
||||
},
|
||||
|
||||
"upgrade": {
|
||||
"title": "Обновление - OpenTherm Gateway",
|
||||
"name": "Обновление",
|
||||
|
||||
"section": {
|
||||
"backupAndRestore": "Резервное копирование и восстановление",
|
||||
"backupAndRestore.desc": "В этом разделе вы можете сохранить и восстановить резервную копию ВСЕХ настроек.",
|
||||
"upgrade": "Обновление",
|
||||
"upgrade.desc": "В этом разделе вы можете обновить прошивку и файловую систему вашего устройства.<br />Последнюю версию можно загрузить со страницы <a href=\"https://github.com/Laxilef/OTGateway/releases\" target=\"_blank\">Релизы</a> в репозитории проекта."
|
||||
},
|
||||
|
||||
"note": {
|
||||
"disclaimer1": "После успешного обновления файловой системы ВСЕ настройки будут сброшены на стандартные! Создайте резервную копию ПЕРЕД обновлением.",
|
||||
"disclaimer2": "После успешного обновления устройство автоматически перезагрузится через 10 секунд."
|
||||
},
|
||||
|
||||
"settingsFile": "Файл настроек",
|
||||
"fw": "Прошивка",
|
||||
"fs": "Файловая система"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,211 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Network - OpenTherm Gateway</title>
|
||||
<link rel="stylesheet" href="/static/pico.min.css">
|
||||
<link rel="stylesheet" href="/static/app.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header class="container">
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/"><div class="logo">OpenTherm Gateway</div></a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li><a href="https://github.com/Laxilef/OTGateway/wiki" role="button" class="secondary" target="_blank">Help</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<article>
|
||||
<div>
|
||||
<hgroup>
|
||||
<h2>Network settings</h2>
|
||||
<p></p>
|
||||
</hgroup>
|
||||
|
||||
<div id="network-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/network/settings" id="network-settings" class="hidden">
|
||||
<label for="network-hostname">
|
||||
Hostname
|
||||
<input type="text" id="network-hostname" name="hostname" maxlength="24" pattern="[A-Za-z0-9]+[A-Za-z0-9\-]+[A-Za-z0-9]+" required>
|
||||
</label>
|
||||
|
||||
<label for="network-use-dhcp">
|
||||
<input type="checkbox" id="network-use-dhcp" name="useDhcp" value="true">
|
||||
Use DHCP
|
||||
</label>
|
||||
<br />
|
||||
<hr />
|
||||
|
||||
<label for="network-static-ip">
|
||||
Static IP:
|
||||
<input type="text" id="network-static-ip" name="staticConfig[ip]" value="true" maxlength="16" pattern="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" required>
|
||||
</label>
|
||||
|
||||
<label for="network-static-gateway">
|
||||
Static gateway:
|
||||
<input type="text" id="network-static-gateway" name="staticConfig[gateway]" maxlength="16" pattern="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" required>
|
||||
</label>
|
||||
|
||||
<label for="network-static-subnet">
|
||||
Static subnet:
|
||||
<input type="text" id="network-static-subnet" name="staticConfig[subnet]" maxlength="16" pattern="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" required>
|
||||
</label>
|
||||
|
||||
<label for="network-static-dns">
|
||||
Static DNS:
|
||||
<input type="text" id="network-static-dns" name="staticConfig[dns]" maxlength="16" pattern="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" required>
|
||||
</label>
|
||||
|
||||
<button type="submit">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article>
|
||||
<div>
|
||||
<hgroup>
|
||||
<h3>Available networks</h3>
|
||||
<p></p>
|
||||
</hgroup>
|
||||
|
||||
<form action="/api/network/scan" id="network-scan">
|
||||
<div style="max-height: 25rem;" class="overflow-auto">
|
||||
<table id="networks" role="grid">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">#</th>
|
||||
<th scope="col">SSID</th>
|
||||
<th scope="col">Info</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<button type="submit">Refresh</button>
|
||||
</form>
|
||||
|
||||
<hr />
|
||||
<div>
|
||||
<hgroup>
|
||||
<h2>WiFi settings</h2>
|
||||
<p></p>
|
||||
</hgroup>
|
||||
|
||||
<div id="sta-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/network/settings" id="sta-settings" class="hidden">
|
||||
<label for="sta-ssid">
|
||||
SSID:
|
||||
<input type="text" id="sta-ssid" name="sta[ssid]" maxlength="32" required>
|
||||
</label>
|
||||
|
||||
<label for="sta-password">
|
||||
Password:
|
||||
<input type="password" id="sta-password" name="sta[password]" maxlength="64" required>
|
||||
</label>
|
||||
|
||||
<label for="sta-channel">
|
||||
Channel:
|
||||
<input type="number" inputmode="numeric" id="sta-channel" name="sta[channel]" min="0" max="12" step="1" required>
|
||||
<small>set 0 for auto select</small>
|
||||
</label>
|
||||
|
||||
<button type="submit">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article>
|
||||
<div>
|
||||
<hgroup>
|
||||
<h2>AP settings</h2>
|
||||
<p></p>
|
||||
</hgroup>
|
||||
|
||||
<div id="ap-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/network/settings" id="ap-settings" class="hidden">
|
||||
<label for="ap-ssid">
|
||||
SSID:
|
||||
<input type="text" id="ap-ssid" name="ap[ssid]" maxlength="32" required>
|
||||
</label>
|
||||
|
||||
<label for="ap-password">
|
||||
Password:
|
||||
<input type="text" id="ap-password" name="ap[password]" maxlength="64" required>
|
||||
</label>
|
||||
|
||||
<label for="ap-channel">
|
||||
Channel:
|
||||
<input type="number" inputmode="numeric" id="ap-channel" name="ap[channel]" min="1" max="12" step="1" required>
|
||||
</label>
|
||||
|
||||
<button type="submit">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</article>
|
||||
</main>
|
||||
|
||||
<footer class="container">
|
||||
<small>
|
||||
<b>Made by Laxilef</b>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/blob/master/LICENSE" target="_blank" class="secondary">License</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/blob/master/" target="_blank" class="secondary">Source code</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/wiki" target="_blank" class="secondary">Help</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/issues" target="_blank" class="secondary">Issue & questions</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/releases" target="_blank" class="secondary">Releases</a>
|
||||
</small>
|
||||
</footer>
|
||||
|
||||
<script src="/static/app.js"></script>
|
||||
<script>
|
||||
window.onload = async function () {
|
||||
const fillData = (data) => {
|
||||
setInputValue('#network-hostname', data.hostname);
|
||||
setCheckboxValue('#network-use-dhcp', data.useDhcp);
|
||||
setInputValue('#network-static-ip', data.staticConfig.ip);
|
||||
setInputValue('#network-static-gateway', data.staticConfig.gateway);
|
||||
setInputValue('#network-static-subnet', data.staticConfig.subnet);
|
||||
setInputValue('#network-static-dns', data.staticConfig.dns);
|
||||
setBusy('#network-settings-busy', '#network-settings', false);
|
||||
|
||||
setInputValue('#sta-ssid', data.sta.ssid);
|
||||
setInputValue('#sta-password', data.sta.password);
|
||||
setInputValue('#sta-channel', data.sta.channel);
|
||||
setBusy('#sta-settings-busy', '#sta-settings', false);
|
||||
|
||||
setInputValue('#ap-ssid', data.ap.ssid);
|
||||
setInputValue('#ap-password', data.ap.password);
|
||||
setInputValue('#ap-channel', data.ap.channel);
|
||||
setBusy('#ap-settings-busy', '#ap-settings', false);
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/network/settings', { cache: 'no-cache' });
|
||||
if (!response.ok) {
|
||||
throw new Error('Response not valid');
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
fillData(result);
|
||||
|
||||
setupForm('#network-settings', fillData, ['hostname']);
|
||||
setupNetworkScanForm('#network-scan', '#networks');
|
||||
setupForm('#sta-settings', fillData, ['sta.ssid', 'sta.password']);
|
||||
setupForm('#ap-settings', fillData, ['ap.ssid', 'ap.password']);
|
||||
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -3,19 +3,26 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Dashboard - OpenTherm Gateway</title>
|
||||
<link rel="stylesheet" href="/static/pico.min.css">
|
||||
<link rel="stylesheet" href="/static/app.css"/>
|
||||
<title data-i18n>dashboard.title</title>
|
||||
<link rel="stylesheet" href="/static/app.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header class="container">
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/"><div class="logo">OpenTherm Gateway</div></a></li>
|
||||
<li><a href="/">
|
||||
<div class="logo" data-i18n>logo</div>
|
||||
</a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li><a href="https://github.com/Laxilef/OTGateway/wiki" role="button" class="secondary" target="_blank">Help</a></li>
|
||||
<!--<li><a href="https://github.com/Laxilef/OTGateway/wiki" role="button" class="secondary" target="_blank">Help</a></li>-->
|
||||
<li>
|
||||
<select id="lang" aria-label="Lang">
|
||||
<option value="en" selected>EN</option>
|
||||
<option value="ru">RU</option>
|
||||
</select>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
@@ -23,43 +30,43 @@
|
||||
<main class="container">
|
||||
<article>
|
||||
<hgroup>
|
||||
<h2>Dashboard</h2>
|
||||
<h2 data-i18n>dashboard.name</h2>
|
||||
<p></p>
|
||||
</hgroup>
|
||||
|
||||
<div id="dashboard-busy" aria-busy="true"></div>
|
||||
<div id="dashboard-container" class="hidden">
|
||||
<details open>
|
||||
<summary><b>Control</b></summary>
|
||||
<summary><b data-i18n>dashboard.section.control</b></summary>
|
||||
<div class="grid">
|
||||
<div class="thermostat" id="thermostat-heating">
|
||||
<div class="thermostat-header">Heating</div>
|
||||
<div class="thermostat-header" data-i18n>dashboard.thermostat.heating</div>
|
||||
<div class="thermostat-temp">
|
||||
<div class="thermostat-temp-target"><span id="thermostat-heating-target"></span> <span class="temp-unit"></span></div>
|
||||
<div class="thermostat-temp-current">Current: <span id="thermostat-heating-current"></span> <span class="temp-unit"></span></div>
|
||||
<div class="thermostat-temp-current"><span data-i18n>dashboard.thermostat.temp.current</span>: <span id="thermostat-heating-current"></span> <span class="temp-unit"></span></div>
|
||||
</div>
|
||||
<div class="thermostat-minus"><button id="thermostat-heating-minus" class="outline"><i class="icons-down"></i></button></div>
|
||||
<div class="thermostat-plus"><button id="thermostat-heating-plus" class="outline"><i class="icons-up"></i></button></div>
|
||||
<div class="thermostat-control">
|
||||
<input type="checkbox" role="switch" id="thermostat-heating-enabled" value="true">
|
||||
<label htmlFor="thermostat-heating-enabled">Enable</label>
|
||||
<label htmlFor="thermostat-heating-enabled" data-i18n>dashboard.thermostat.enable</label>
|
||||
|
||||
<input type="checkbox" role="switch" id="thermostat-heating-turbo" value="true">
|
||||
<label htmlFor="thermostat-heating-turbo">Turbo mode</label>
|
||||
<label htmlFor="thermostat-heating-turbo" data-i18n>dashboard.thermostat.turbo</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="thermostat" id="thermostat-dhw">
|
||||
<div class="thermostat-header">DHW</div>
|
||||
<div class="thermostat-header" data-i18n>dashboard.thermostat.dhw</div>
|
||||
<div class="thermostat-temp">
|
||||
<div class="thermostat-temp-target"><span id="thermostat-dhw-target"></span> <span class="temp-unit"></span></div>
|
||||
<div class="thermostat-temp-current">Current: <span id="thermostat-dhw-current"></span> <span class="temp-unit"></span></div>
|
||||
<div class="thermostat-temp-current"><span data-i18n>dashboard.thermostat.temp.current</span>: <span id="thermostat-dhw-current"></span> <span class="temp-unit"></span></div>
|
||||
</div>
|
||||
<div class="thermostat-minus"><button class="outline" id="thermostat-dhw-minus"><i class="icons-down"></i></button></div>
|
||||
<div class="thermostat-plus"><button class="outline" id="thermostat-dhw-plus"><i class="icons-up"></i></button></div>
|
||||
<div class="thermostat-control">
|
||||
<input type="checkbox" role="switch" id="thermostat-dhw-enabled" value="true">
|
||||
<label htmlFor="thermostat-dhw-enabled">Enable</label>
|
||||
<label htmlFor="thermostat-dhw-enabled" data-i18n>dashboard.thermostat.enable</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -68,87 +75,123 @@
|
||||
<hr />
|
||||
|
||||
<details>
|
||||
<summary><b>States and sensors</b></summary>
|
||||
<summary><b data-i18n>dashboard.section.states</b></summary>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">OpenTherm connected:</th>
|
||||
<th scope="row" data-i18n>dashboard.state.ot</th>
|
||||
<td><input type="radio" id="ot-connected" aria-invalid="false" checked disabled /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">MQTT connected:</th>
|
||||
<th scope="row" data-i18n>dashboard.state.mqtt</th>
|
||||
<td><input type="radio" id="mqtt-connected" aria-invalid="false" checked disabled /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Emergency:</th>
|
||||
<th scope="row" data-i18n>dashboard.state.emergency</th>
|
||||
<td><input type="radio" id="ot-emergency" aria-invalid="false" checked disabled /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Heating:</th>
|
||||
<th scope="row" data-i18n>dashboard.state.heating</th>
|
||||
<td><input type="radio" id="ot-heating" aria-invalid="false" checked disabled /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">DHW:</th>
|
||||
<th scope="row" data-i18n>dashboard.state.dhw</th>
|
||||
<td><input type="radio" id="ot-dhw" aria-invalid="false" checked disabled /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Flame:</th>
|
||||
<th scope="row" data-i18n>dashboard.state.flame</th>
|
||||
<td><input type="radio" id="ot-flame" aria-invalid="false" checked disabled /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Fault:</th>
|
||||
<th scope="row" data-i18n>dashboard.state.fault</th>
|
||||
<td><input type="radio" id="ot-fault" aria-invalid="false" checked disabled /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Diagnostic:</th>
|
||||
<th scope="row" data-i18n>dashboard.state.diag</th>
|
||||
<td><input type="radio" id="ot-diagnostic" aria-invalid="false" checked disabled /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">External pump:</th>
|
||||
<th scope="row" data-i18n>dashboard.state.extpump</th>
|
||||
<td><input type="radio" id="ot-external-pump" aria-invalid="false" checked disabled /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Modulation:</th>
|
||||
<th scope="row" data-i18n>dashboard.state.outdoorSensorConnected</th>
|
||||
<td><input type="radio" id="outdoor-sensor-connected" aria-invalid="false" checked disabled /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" data-i18n>dashboard.state.outdoorSensorRssi</th>
|
||||
<td><b id="outdoor-sensor-rssi"></b> <span data-i18n>dbm</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" data-i18n>dashboard.state.outdoorSensorHumidity</th>
|
||||
<td><b id="outdoor-sensor-humidity"></b> %</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" data-i18n>dashboard.state.outdoorSensorBattery</th>
|
||||
<td><b id="outdoor-sensor-battery"></b> %</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" data-i18n>dashboard.state.indoorSensorConnected</th>
|
||||
<td><input type="radio" id="indoor-sensor-connected" aria-invalid="false" checked disabled /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" data-i18n>dashboard.state.indoorSensorRssi</th>
|
||||
<td><b id="indoor-sensor-rssi"></b> <span data-i18n>dbm</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" data-i18n>dashboard.state.indoorSensorHumidity</th>
|
||||
<td><b id="indoor-sensor-humidity"></b> %</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" data-i18n>dashboard.state.indoorSensorBattery</th>
|
||||
<td><b id="indoor-sensor-battery"></b> %</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" data-i18n>dashboard.state.modulation</th>
|
||||
<td><b id="ot-modulation"></b> %</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Pressure:</th>
|
||||
<th scope="row" data-i18n>dashboard.state.pressure</th>
|
||||
<td><b id="ot-pressure"></b> <span class="pressure-unit"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">DHW flow rate:</th>
|
||||
<th scope="row" data-i18n>dashboard.state.dhwFlowRate</th>
|
||||
<td><b id="ot-dhw-flow-rate"></b> <span class="volume-unit"></span>/min</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Fault code:</th>
|
||||
<th scope="row" data-i18n>dashboard.state.faultCode</th>
|
||||
<td><b id="ot-fault-code"></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Indoor temp:</th>
|
||||
<th scope="row" data-i18n>dashboard.state.diagCode</th>
|
||||
<td><b id="ot-diag-code"></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" data-i18n>dashboard.state.indoorTemp</th>
|
||||
<td><b id="indoor-temp"></b> <span class="temp-unit"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Outdoor temp:</th>
|
||||
<th scope="row" data-i18n>dashboard.state.outdoorTemp</th>
|
||||
<td><b id="outdoor-temp"></b> <span class="temp-unit"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Heating temp:</th>
|
||||
<th scope="row" data-i18n>dashboard.state.heatingTemp</th>
|
||||
<td><b id="heating-temp"></b> <span class="temp-unit"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Heating setpoint temp:</th>
|
||||
<th scope="row" data-i18n>dashboard.state.heatingSetpointTemp</th>
|
||||
<td><b id="heating-setpoint-temp"></b> <span class="temp-unit"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Heating return temp:</th>
|
||||
<th scope="row" data-i18n>dashboard.state.heatingReturnTemp</th>
|
||||
<td><b id="heating-return-temp"></b> <span class="temp-unit"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">DHW temp:</th>
|
||||
<th scope="row" data-i18n>dashboard.state.dhwTemp</th>
|
||||
<td><b id="dhw-temp"></b> <span class="temp-unit"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Exhaust temp:</th>
|
||||
<th scope="row" data-i18n>dashboard.state.exhaustTemp</th>
|
||||
<td><b id="exhaust-temp"></b> <span class="temp-unit"></span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -158,7 +201,7 @@
|
||||
<hr />
|
||||
|
||||
<details>
|
||||
<summary><b>OpenTherm diagnostic</b></summary>
|
||||
<summary><b data-i18n>dashboard.section.otDiag</b></summary>
|
||||
<pre><b>Vendor:</b> <span id="slave-vendor"></span>
|
||||
<b>Member ID:</b> <span id="slave-member-id"></span>
|
||||
<b>Flags:</b> <span id="slave-flags"></span>
|
||||
@@ -175,11 +218,11 @@
|
||||
<footer class="container">
|
||||
<small>
|
||||
<b>Made by Laxilef</b>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/blob/master/LICENSE" target="_blank" class="secondary">License</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/blob/master/" target="_blank" class="secondary">Source code</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/wiki" target="_blank" class="secondary">Help</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/issues" target="_blank" class="secondary">Issue & questions</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/releases" target="_blank" class="secondary">Releases</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/blob/master/LICENSE" target="_blank" class="secondary" data-i18n>nav.license</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/blob/master/" target="_blank" class="secondary" data-i18n>nav.source</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/wiki" target="_blank" class="secondary" data-i18n>nav.help</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/issues" target="_blank" class="secondary" data-i18n>nav.issues</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/releases" target="_blank" class="secondary" data-i18n>nav.releases</a>
|
||||
</small>
|
||||
</footer>
|
||||
|
||||
@@ -200,12 +243,15 @@
|
||||
}
|
||||
};
|
||||
|
||||
window.onload = async function () {
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
const lang = new Lang(document.getElementById('lang'));
|
||||
lang.build();
|
||||
|
||||
document.querySelector('#thermostat-heating-minus').addEventListener('click', (event) => {
|
||||
if (!prevSettings) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
newSettings.heating.target -= 0.5;
|
||||
modifiedTime = Date.now();
|
||||
|
||||
@@ -226,8 +272,8 @@
|
||||
document.querySelector('#thermostat-heating-plus').addEventListener('click', (event) => {
|
||||
if (!prevSettings) {
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
newSettings.heating.target += 0.5;
|
||||
modifiedTime = Date.now();
|
||||
|
||||
@@ -249,7 +295,7 @@
|
||||
if (!prevSettings) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
newSettings.dhw.target -= 1.0;
|
||||
modifiedTime = Date.now();
|
||||
|
||||
@@ -264,7 +310,7 @@
|
||||
if (!prevSettings) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
newSettings.dhw.target += 1.0;
|
||||
modifiedTime = Date.now();
|
||||
|
||||
@@ -314,7 +360,7 @@
|
||||
console.log(newSettings);
|
||||
}
|
||||
|
||||
let parameters = {cache: 'no-cache'};
|
||||
let parameters = { cache: 'no-cache' };
|
||||
if (modified) {
|
||||
parameters.method = "POST";
|
||||
parameters.body = JSON.stringify(newSettings);
|
||||
@@ -367,6 +413,7 @@
|
||||
setValue('#thermostat-dhw-current', result.temperatures.dhw);
|
||||
|
||||
setState('#ot-connected', result.states.otStatus);
|
||||
setState('#mqtt-connected', result.states.mqtt);
|
||||
setState('#ot-emergency', result.states.emergency);
|
||||
setState('#ot-heating', result.states.heating);
|
||||
setState('#ot-dhw', result.states.dhw);
|
||||
@@ -374,12 +421,31 @@
|
||||
setState('#ot-fault', result.states.fault);
|
||||
setState('#ot-diagnostic', result.states.diagnostic);
|
||||
setState('#ot-external-pump', result.states.externalPump);
|
||||
setState('#mqtt-connected', result.states.mqtt);
|
||||
setState('#outdoor-sensor-connected', result.sensors.outdoor.connected);
|
||||
setState('#indoor-sensor-connected', result.sensors.indoor.connected);
|
||||
|
||||
setValue('#outdoor-sensor-rssi', result.sensors.outdoor.rssi);
|
||||
setValue('#outdoor-sensor-humidity', result.sensors.outdoor.humidity);
|
||||
setValue('#outdoor-sensor-battery', result.sensors.outdoor.battery);
|
||||
setValue('#indoor-sensor-rssi', result.sensors.indoor.rssi);
|
||||
setValue('#indoor-sensor-humidity', result.sensors.indoor.humidity);
|
||||
setValue('#indoor-sensor-battery', result.sensors.indoor.battery);
|
||||
|
||||
setValue('#ot-modulation', result.sensors.modulation);
|
||||
setValue('#ot-pressure', result.sensors.pressure);
|
||||
setValue('#ot-dhw-flow-rate', result.sensors.dhwFlowRate);
|
||||
setValue('#ot-fault-code', result.sensors.faultCode ? ("E" + result.sensors.faultCode) : "-");
|
||||
setValue(
|
||||
'#ot-fault-code',
|
||||
result.sensors.faultCode
|
||||
? (result.sensors.faultCode + " (0x" + dec2hex(result.sensors.faultCode) + ")")
|
||||
: "-"
|
||||
);
|
||||
setValue(
|
||||
'#ot-diag-code',
|
||||
result.sensors.diagnosticCode
|
||||
? (result.sensors.diagnosticCode + " (0x" + dec2hex(result.sensors.diagnosticCode) + ")")
|
||||
: "-"
|
||||
);
|
||||
|
||||
setValue('#indoor-temp', result.temperatures.indoor);
|
||||
setValue('#outdoor-temp', result.temperatures.outdoor);
|
||||
@@ -409,7 +475,7 @@
|
||||
|
||||
setTimeout(onLoadPage, 10000);
|
||||
}, 1000);
|
||||
};
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
@@ -3,19 +3,26 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>OpenTherm Gateway</title>
|
||||
<link rel="stylesheet" href="/static/pico.min.css">
|
||||
<link rel="stylesheet" href="/static/app.css"/>
|
||||
<title data-i18n>index.title</title>
|
||||
<link rel="stylesheet" href="/static/app.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header class="container">
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/"><div class="logo">OpenTherm Gateway</div></a></li>
|
||||
<li><a href="/">
|
||||
<div class="logo" data-i18n>logo</div>
|
||||
</a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li><a href="https://github.com/Laxilef/OTGateway/wiki" role="button" class="secondary" target="_blank">Help</a></li>
|
||||
<!--<li><a href="https://github.com/Laxilef/OTGateway/wiki" role="button" class="secondary" target="_blank">Help</a></li>-->
|
||||
<li>
|
||||
<select id="lang" aria-label="Lang">
|
||||
<option value="en" selected>EN</option>
|
||||
<option value="ru">RU</option>
|
||||
</select>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
@@ -24,7 +31,7 @@
|
||||
<article>
|
||||
<div>
|
||||
<hgroup>
|
||||
<h2>Network</h2>
|
||||
<h2 data-i18n>index.section.network</h2>
|
||||
<p></p>
|
||||
</hgroup>
|
||||
|
||||
@@ -32,45 +39,45 @@
|
||||
<table id="main-table" class="hidden">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">Hostname:</th>
|
||||
<th scope="row" data-i18n>network.params.hostname</th>
|
||||
<td><b id="network-hostname"></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">MAC:</th>
|
||||
<th scope="row" data-i18n>network.params.mac</th>
|
||||
<td><b id="network-mac"></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Connected:</th>
|
||||
<th scope="row" data-i18n>network.wifi.connected</th>
|
||||
<td><input type="radio" id="network-connected" aria-invalid="false" checked disabled /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">SSID:</th>
|
||||
<th scope="row" data-i18n>network.wifi.ssid</th>
|
||||
<td><b id="network-ssid"></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Signal:</th>
|
||||
<th scope="row" data-i18n>network.wifi.signal</th>
|
||||
<td><b id="network-signal"></b> %</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">IP:</th>
|
||||
<th scope="row" data-i18n>network.params.ip</th>
|
||||
<td><b id="network-ip"></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Subnet:</th>
|
||||
<th scope="row" data-i18n>network.params.subnet</th>
|
||||
<td><b id="network-subnet"></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Gateway:</th>
|
||||
<th scope="row" data-i18n>network.params.gateway</th>
|
||||
<td><b id="network-gateway"></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">DNS:</th>
|
||||
<th scope="row" data-i18n>network.params.dns</th>
|
||||
<td><b id="network-dns"></b></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="grid">
|
||||
<a href="/network.html" role="button">Network settings</a>
|
||||
<a href="/network.html" role="button" data-i18n>network.name</a>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
@@ -78,7 +85,7 @@
|
||||
<article>
|
||||
<div>
|
||||
<hgroup>
|
||||
<h2>System</h2>
|
||||
<h2 data-i18n>index.section.system</h2>
|
||||
<p></p>
|
||||
</hgroup>
|
||||
|
||||
@@ -86,37 +93,53 @@
|
||||
<table id="system-table" class="hidden">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">Version:</th>
|
||||
<td><b id="version"></b>, core/sdk: <b id="core-version"></b></td>
|
||||
<th scope="row" data-i18n>index.system.build.version</th>
|
||||
<td><b id="build-version"></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Build date:</th>
|
||||
<td><b id="build-date"></b></td>
|
||||
<th scope="row" data-i18n>index.system.build.title</th>
|
||||
<td>
|
||||
Env: <b id="build-env"></b><br />
|
||||
<span data-i18n>index.system.build.date</span>: <b id="build-date"></b><br />
|
||||
<span data-i18n>index.system.build.sdk</span>: <b id="core-version"></b>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Uptime:</th>
|
||||
<td><b id="uptime-days"></b> days, <b id="uptime-hours"></b> hours, <b id="uptime-min"></b> min., <b id="uptime-sec"></b> sec.</td>
|
||||
<th scope="row" data-i18n>index.system.uptime</th>
|
||||
<td>
|
||||
<b id="uptime-days"></b> days,
|
||||
<b id="uptime-hours"></b> hours,
|
||||
<b id="uptime-min"></b> min.,
|
||||
<b id="uptime-sec"></b> sec.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Free memory:</th>
|
||||
<td><b id="free-heap"></b> of <b id="total-heap"></b> bytes (min: <b id="min-free-heap"></b> bytes)<br />max free block: <b id="max-free-block-heap"></b> bytes (min: <b id="min-max-free-block-heap"></b> bytes)</td>
|
||||
<th scope="row" data-i18n>index.system.memory.title</th>
|
||||
<td>
|
||||
<b id="free-heap"></b> of <b id="total-heap"></b> bytes (<span data-i18n>index.system.memory.min</span>: <b id="min-free-heap"></b> bytes)<br />
|
||||
<span data-i18n>index.system.memory.maxFreeBlock</span>: <b id="max-free-block-heap"></b> bytes (<span data-i18n>index.system.memory.min</span>: <b id="min-max-free-block-heap"></b> bytes)
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Board:</th>
|
||||
<td>Chip <b id="chip-model"></b> (rev. <span id="chip-revision"></span>)<br />Cores: <b id="chip-cores"></b>, frequency: <b id="cpu-freq"></b> mHz<br />Flash size: <b id="flash-size"></b> MB (real: <b id="flash-real-size"></b> MB)</td>
|
||||
<th scope="row" data-i18n>index.system.board</th>
|
||||
<td>
|
||||
<span data-i18n>index.system.chip.model</span>: <b id="chip-model"></b> (rev. <span id="chip-revision"></span>)<br />
|
||||
<span data-i18n>index.system.chip.cores</span>: <b id="chip-cores"></b>, <span data-i18n>index.system.chip.freq</span>: <b id="cpu-freq"></b> mHz<br />
|
||||
<span data-i18n>index.system.flash.size</span>: <b id="flash-size"></b> MB (<span data-i18n>index.system.flash.realSize</span>: <b id="flash-real-size"></b> MB)
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Last reset reason:</th>
|
||||
<th scope="row" data-i18n>index.system.lastResetReason</th>
|
||||
<td><b id="reset-reason"></b></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="grid">
|
||||
<a href="/dashboard.html" role="button">Dashboard</a>
|
||||
<a href="/settings.html" role="button">Settings</a>
|
||||
<a href="/upgrade.html" role="button">Upgrade</a>
|
||||
<a href="/restart.html" role="button" class="secondary restart">Restart</a>
|
||||
<a href="/dashboard.html" role="button" data-i18n>dashboard.name</a>
|
||||
<a href="/settings.html" role="button" data-i18n>settings.name</a>
|
||||
<a href="/upgrade.html" role="button" data-i18n>upgrade.name</a>
|
||||
<a href="/restart.html" role="button" class="secondary restart" data-i18n>button.restart</a>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
@@ -125,17 +148,20 @@
|
||||
<footer class="container">
|
||||
<small>
|
||||
<b>Made by Laxilef</b>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/blob/master/LICENSE" target="_blank" class="secondary">License</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/blob/master/" target="_blank" class="secondary">Source code</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/wiki" target="_blank" class="secondary">Help</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/issues" target="_blank" class="secondary">Issue & questions</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/releases" target="_blank" class="secondary">Releases</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/blob/master/LICENSE" target="_blank" class="secondary" data-i18n>nav.license</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/blob/master/" target="_blank" class="secondary" data-i18n>nav.source</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/wiki" target="_blank" class="secondary" data-i18n>nav.help</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/issues" target="_blank" class="secondary" data-i18n>nav.issues</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/releases" target="_blank" class="secondary" data-i18n>nav.releases</a>
|
||||
</small>
|
||||
</footer>
|
||||
|
||||
<script src="/static/app.js"></script>
|
||||
<script>
|
||||
window.onload = async function () {
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
const lang = new Lang(document.getElementById('lang'));
|
||||
lang.build();
|
||||
|
||||
setTimeout(async function onLoadPage() {
|
||||
try {
|
||||
const response = await fetch('/api/info', { cache: 'no-cache' });
|
||||
@@ -155,8 +181,9 @@
|
||||
setValue('#network-dns', result.network.dns);
|
||||
setBusy('#main-busy', '#main-table', false);
|
||||
|
||||
setValue('#version', result.system.version);
|
||||
setValue('#build-version', result.system.buildVersion);
|
||||
setValue('#build-date', result.system.buildDate);
|
||||
setValue('#build-env', result.system.buildEnv);
|
||||
setValue('#uptime', result.system.uptime);
|
||||
setValue('#uptime-days', Math.floor(result.system.uptime / 86400));
|
||||
setValue('#uptime-hours', Math.floor(result.system.uptime % 86400 / 3600));
|
||||
@@ -168,7 +195,7 @@
|
||||
setValue('#max-free-block-heap', result.system.maxFreeBlockHeap);
|
||||
setValue('#min-max-free-block-heap', result.system.minMaxFreeBlockHeap);
|
||||
setValue('#reset-reason', result.system.resetReason);
|
||||
|
||||
|
||||
setValue('#chip-model', result.system.chipModel);
|
||||
setValue('#chip-revision', result.system.chipRevision);
|
||||
setValue('#chip-cores', result.system.chipCores);
|
||||
@@ -185,7 +212,7 @@
|
||||
|
||||
setTimeout(onLoadPage, 10000);
|
||||
}, 1000);
|
||||
};
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
220
src_data/pages/network.html
Normal file
@@ -0,0 +1,220 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title data-i18n>network.title</title>
|
||||
<link rel="stylesheet" href="/static/app.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header class="container">
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/">
|
||||
<div class="logo" data-i18n>logo</div>
|
||||
</a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<!--<li><a href="https://github.com/Laxilef/OTGateway/wiki" role="button" class="secondary" target="_blank">Help</a></li>-->
|
||||
<li>
|
||||
<select id="lang" aria-label="Lang">
|
||||
<option value="en" selected>EN</option>
|
||||
<option value="ru">RU</option>
|
||||
</select>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<article>
|
||||
<div>
|
||||
<hgroup>
|
||||
<h2 data-i18n>network.name</h2>
|
||||
<p></p>
|
||||
</hgroup>
|
||||
|
||||
<div id="network-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/network/settings" id="network-settings" class="hidden">
|
||||
<label for="network-hostname">
|
||||
<span data-i18n>network.params.hostname</span>
|
||||
<input type="text" id="network-hostname" name="hostname" maxlength="24" pattern="[A-Za-z0-9]+[A-Za-z0-9\-]+[A-Za-z0-9]+" required>
|
||||
</label>
|
||||
|
||||
<label for="network-use-dhcp">
|
||||
<input type="checkbox" id="network-use-dhcp" name="useDhcp" value="true">
|
||||
<span data-i18n>network.params.dhcp</span>
|
||||
</label>
|
||||
<br />
|
||||
<hr />
|
||||
<h4 data-i18n>network.section.static</h4>
|
||||
|
||||
<label for="network-static-ip">
|
||||
<span data-i18n>network.params.ip</span>
|
||||
<input type="text" id="network-static-ip" name="staticConfig[ip]" value="true" maxlength="16" pattern="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" required>
|
||||
</label>
|
||||
|
||||
<label for="network-static-gateway">
|
||||
<span data-i18n>network.params.gateway</span>
|
||||
<input type="text" id="network-static-gateway" name="staticConfig[gateway]" maxlength="16" pattern="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" required>
|
||||
</label>
|
||||
|
||||
<label for="network-static-subnet">
|
||||
<span data-i18n>network.params.subnet</span>
|
||||
<input type="text" id="network-static-subnet" name="staticConfig[subnet]" maxlength="16" pattern="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" required>
|
||||
</label>
|
||||
|
||||
<label for="network-static-dns">
|
||||
<span data-i18n>network.params.dns</span>
|
||||
<input type="text" id="network-static-dns" name="staticConfig[dns]" maxlength="16" pattern="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" required>
|
||||
</label>
|
||||
|
||||
<button type="submit" data-i18n>button.save</button>
|
||||
</form>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article>
|
||||
<div>
|
||||
<hgroup>
|
||||
<h3 data-i18n>network.section.availableNetworks</h3>
|
||||
<p></p>
|
||||
</hgroup>
|
||||
|
||||
<form action="/api/network/scan" id="network-scan">
|
||||
<div style="max-height: 25rem;" class="overflow-auto">
|
||||
<table id="networks" role="grid">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" data-i18n>network.scan.pos</th>
|
||||
<th scope="col" data-i18n>network.wifi.ssid</th>
|
||||
<th scope="col" data-i18n>network.scan.info</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<button type="submit" data-i18n>button.refresh</button>
|
||||
</form>
|
||||
|
||||
<hr />
|
||||
<div>
|
||||
<hgroup>
|
||||
<h2 data-i18n>network.section.staSettings</h2>
|
||||
<p></p>
|
||||
</hgroup>
|
||||
|
||||
<div id="sta-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/network/settings" id="sta-settings" class="hidden">
|
||||
<label for="sta-ssid">
|
||||
<span data-i18n>network.wifi.ssid</span>
|
||||
<input type="text" id="sta-ssid" name="sta[ssid]" maxlength="32" required>
|
||||
</label>
|
||||
|
||||
<label for="sta-password">
|
||||
<span data-i18n>network.wifi.password</span>
|
||||
<input type="password" id="sta-password" name="sta[password]" maxlength="64" required>
|
||||
</label>
|
||||
|
||||
<label for="sta-channel">
|
||||
<span data-i18n>network.wifi.channel</span>
|
||||
<input type="number" inputmode="numeric" id="sta-channel" name="sta[channel]" min="0" max="12" step="1" required>
|
||||
<small data-i18n>network.sta.channel.note</small>
|
||||
</label>
|
||||
|
||||
<button type="submit" data-i18n>button.save</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article>
|
||||
<div>
|
||||
<hgroup>
|
||||
<h2 data-i18n>network.section.apSettings</h2>
|
||||
<p></p>
|
||||
</hgroup>
|
||||
|
||||
<div id="ap-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/network/settings" id="ap-settings" class="hidden">
|
||||
<label for="ap-ssid">
|
||||
<span data-i18n>network.wifi.ssid</span>
|
||||
<input type="text" id="ap-ssid" name="ap[ssid]" maxlength="32" required>
|
||||
</label>
|
||||
|
||||
<label for="ap-password">
|
||||
<span data-i18n>network.wifi.password</span>
|
||||
<input type="text" id="ap-password" name="ap[password]" maxlength="64" required>
|
||||
</label>
|
||||
|
||||
<label for="ap-channel">
|
||||
<span data-i18n>network.wifi.channel</span>
|
||||
<input type="number" inputmode="numeric" id="ap-channel" name="ap[channel]" min="1" max="12" step="1" required>
|
||||
</label>
|
||||
|
||||
<button type="submit" data-i18n>button.save</button>
|
||||
</form>
|
||||
</div>
|
||||
</article>
|
||||
</main>
|
||||
|
||||
<footer class="container">
|
||||
<small>
|
||||
<b>Made by Laxilef</b>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/blob/master/LICENSE" target="_blank" class="secondary" data-i18n>nav.license</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/blob/master/" target="_blank" class="secondary" data-i18n>nav.source</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/wiki" target="_blank" class="secondary" data-i18n>nav.help</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/issues" target="_blank" class="secondary" data-i18n>nav.issues</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/releases" target="_blank" class="secondary" data-i18n>nav.releases</a>
|
||||
</small>
|
||||
</footer>
|
||||
|
||||
<script src="/static/app.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
const lang = new Lang(document.getElementById('lang'));
|
||||
lang.build();
|
||||
|
||||
const fillData = (data) => {
|
||||
setInputValue('#network-hostname', data.hostname);
|
||||
setCheckboxValue('#network-use-dhcp', data.useDhcp);
|
||||
setInputValue('#network-static-ip', data.staticConfig.ip);
|
||||
setInputValue('#network-static-gateway', data.staticConfig.gateway);
|
||||
setInputValue('#network-static-subnet', data.staticConfig.subnet);
|
||||
setInputValue('#network-static-dns', data.staticConfig.dns);
|
||||
setBusy('#network-settings-busy', '#network-settings', false);
|
||||
|
||||
setInputValue('#sta-ssid', data.sta.ssid);
|
||||
setInputValue('#sta-password', data.sta.password);
|
||||
setInputValue('#sta-channel', data.sta.channel);
|
||||
setBusy('#sta-settings-busy', '#sta-settings', false);
|
||||
|
||||
setInputValue('#ap-ssid', data.ap.ssid);
|
||||
setInputValue('#ap-password', data.ap.password);
|
||||
setInputValue('#ap-channel', data.ap.channel);
|
||||
setBusy('#ap-settings-busy', '#ap-settings', false);
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/network/settings', { cache: 'no-cache' });
|
||||
if (!response.ok) {
|
||||
throw new Error('Response not valid');
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
fillData(result);
|
||||
|
||||
setupForm('#network-settings', fillData, ['hostname']);
|
||||
setupNetworkScanForm('#network-scan', '#networks');
|
||||
setupForm('#sta-settings', fillData, ['sta.ssid', 'sta.password']);
|
||||
setupForm('#ap-settings', fillData, ['ap.ssid', 'ap.password']);
|
||||
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
909
src_data/pages/settings.html
Normal file
@@ -0,0 +1,909 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title data-i18n>settings.title</title>
|
||||
<link rel="stylesheet" href="/static/app.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header class="container">
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/">
|
||||
<div class="logo" data-i18n>logo</div>
|
||||
</a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<!--<li><a href="https://github.com/Laxilef/OTGateway/wiki" role="button" class="secondary" target="_blank">Help</a></li>-->
|
||||
<li>
|
||||
<select id="lang" aria-label="Lang">
|
||||
<option value="en" selected>EN</option>
|
||||
<option value="ru">RU</option>
|
||||
</select>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<article>
|
||||
<hgroup>
|
||||
<h2 data-i18n>settings.name</h2>
|
||||
<p></p>
|
||||
</hgroup>
|
||||
|
||||
<details>
|
||||
<summary><b data-i18n>settings.section.portal</b></summary>
|
||||
<div>
|
||||
<div id="portal-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/settings" id="portal-settings" class="hidden">
|
||||
<div class="grid">
|
||||
<label for="portal-login">
|
||||
<span data-i18n>settings.portal.login</span>
|
||||
<input type="text" id="portal-login" name="portal[login]" maxlength="12" required>
|
||||
</label>
|
||||
|
||||
<label for="portal-password">
|
||||
<span data-i18n>settings.portal.password</span>
|
||||
<input type="password" id="portal-password" name="portal[password]" maxlength="32" required>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label for="portal-auth">
|
||||
<input type="checkbox" id="portal-auth" name="portal[auth]" value="true">
|
||||
<span data-i18n>settings.portal.auth</span>
|
||||
</label>
|
||||
<br />
|
||||
|
||||
<button type="submit" data-i18n>button.save</button>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<hr />
|
||||
|
||||
<details>
|
||||
<summary><b data-i18n>settings.section.system</b></summary>
|
||||
<div>
|
||||
<div id="system-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/settings" id="system-settings" class="hidden">
|
||||
<fieldset>
|
||||
<legend data-i18n>settings.system.unit</legend>
|
||||
|
||||
<label>
|
||||
<input type="radio" class="system-unit-system" name="system[unitSystem]" value="0" />
|
||||
<span data-i18n>settings.system.metric</span>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<input type="radio" class="system-unit-system" name="system[unitSystem]" value="1" />
|
||||
<span data-i18n>settings.system.imperial</span>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label for="system-status-led-gpio">
|
||||
<span data-i18n>settings.system.statusLedGpio</span>
|
||||
<input type="number" inputmode="numeric" id="system-status-led-gpio" name="system[statusLedGpio]" min="0" max="254" step="1">
|
||||
<small data-i18n>settings.note.blankNotUse</small>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend data-i18n>settings.section.diag</legend>
|
||||
|
||||
<label for="system-debug">
|
||||
<input type="checkbox" id="system-debug" name="system[debug]" value="true">
|
||||
<span data-i18n>settings.system.debug</span>
|
||||
</label>
|
||||
|
||||
<label for="system-serial-enable">
|
||||
<input type="checkbox" id="system-serial-enable" name="system[serial][enable]" value="true">
|
||||
<span data-i18n>settings.system.serial.enable</span>
|
||||
</label>
|
||||
|
||||
<label for="system-telnet-enable">
|
||||
<input type="checkbox" id="system-telnet-enable" name="system[telnet][enable]" value="true">
|
||||
<span data-i18n>settings.system.telnet.enable</span>
|
||||
</label>
|
||||
|
||||
<div class="grid">
|
||||
<label for="system-serial-baudrate">
|
||||
<span data-i18n>settings.system.serial.baud.title</span>
|
||||
<input type="number" inputmode="numeric" id="system-serial-baudrate" name="system[serial][baudrate]" min="9600" max="115200" step="1" required>
|
||||
<small data-i18n>settings.system.serial.baud.note</small>
|
||||
</label>
|
||||
|
||||
<label for="system-telnet-port">
|
||||
<span data-i18n>settings.system.telnet.port.title</span>
|
||||
<input type="number" inputmode="numeric" id="system-telnet-port" name="system[telnet][port]" min="1" max="65535" step="1" required>
|
||||
<small data-i18n>settings.system.telnet.port.note</small>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<mark data-i18n>settings.note.restart</mark>
|
||||
</fieldset>
|
||||
|
||||
<button type="submit" data-i18n>button.save</button>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
|
||||
<hr />
|
||||
|
||||
<details>
|
||||
<summary><b data-i18n>settings.section.heating</b></summary>
|
||||
<div>
|
||||
<div id="heating-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/settings" id="heating-settings" class="hidden">
|
||||
<div class="grid">
|
||||
<label for="heating-min-temp">
|
||||
<span data-i18n>settings.temp.min</span>
|
||||
<input type="number" inputmode="numeric" id="heating-min-temp" name="heating[minTemp]" min="0" max="0" step="1" required>
|
||||
</label>
|
||||
|
||||
<label for="heating-max-temp">
|
||||
<span data-i18n>settings.temp.max</span>
|
||||
<input type="number" inputmode="numeric" id="heating-max-temp" name="heating[maxTemp]" min="0" max="0" step="1" required>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<label for="heating-hysteresis">
|
||||
<span data-i18n>settings.heating.hyst</span>
|
||||
<input type="number" inputmode="numeric" id="heating-hysteresis" name="heating[hysteresis]" min="0" max="5" step="0.05" required>
|
||||
</label>
|
||||
|
||||
<label for="heating-max-modulation">
|
||||
<span data-i18n>settings.heating.maxMod</span>
|
||||
<input type="number" inputmode="numeric" id="heating-max-modulation" name="heating[maxModulation]" min="1" max="100" step="1" required>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit" data-i18n>button.save</button>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<hr />
|
||||
|
||||
<details>
|
||||
<summary><b data-i18n>settings.section.dhw</b></summary>
|
||||
<div>
|
||||
<div id="dhw-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/settings" id="dhw-settings" class="hidden">
|
||||
<div class="grid">
|
||||
<label for="dhw-min-temp">
|
||||
<span data-i18n>settings.temp.min</span>
|
||||
<input type="number" inputmode="numeric" id="dhw-min-temp" name="dhw[minTemp]" min="0" max="0" step="1" required>
|
||||
</label>
|
||||
|
||||
<label for="dhw-max-temp">
|
||||
<span data-i18n>settings.temp.max</span>
|
||||
<input type="number" inputmode="numeric" id="dhw-max-temp" name="dhw[maxTemp]" min="0" max="0" step="1" required>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit" data-i18n>button.save</button>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<hr />
|
||||
|
||||
<details>
|
||||
<summary><b data-i18n>settings.section.emergency</b></summary>
|
||||
<div>
|
||||
<div id="emergency-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/settings" id="emergency-settings" class="hidden">
|
||||
<fieldset>
|
||||
<label for="emergency-enable">
|
||||
<input type="checkbox" id="emergency-enable" name="emergency[enable]" value="true">
|
||||
<span data-i18n>settings.enable</span>
|
||||
</label>
|
||||
|
||||
<small data-i18n>settings.emergency.desc</small>
|
||||
</fieldset>
|
||||
|
||||
<div class="grid">
|
||||
<label for="emergency-target">
|
||||
<span data-i18n>settings.emergency.target.title</span>
|
||||
<input type="number" inputmode="numeric" id="emergency-target" name="emergency[target]" min="0" max="0" step="1" required>
|
||||
<small data-i18n>settings.emergency.target.note</small>
|
||||
</label>
|
||||
|
||||
<label for="emergency-treshold-time">
|
||||
<span data-i18n>settings.emergency.treshold</span>
|
||||
<input type="number" inputmode="numeric" id="emergency-treshold-time" name="emergency[tresholdTime]" min="60" max="1800" step="1" required>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<fieldset>
|
||||
<legend data-i18n>settings.section.emergency.events</legend>
|
||||
|
||||
<label for="emergency-on-network-fault">
|
||||
<input type="checkbox" id="emergency-on-network-fault" name="emergency[onNetworkFault]" value="true">
|
||||
<span data-i18n>settings.emergency.events.network</span>
|
||||
</label>
|
||||
|
||||
<label for="emergency-on-mqtt-fault">
|
||||
<input type="checkbox" id="emergency-on-mqtt-fault" name="emergency[onMqttFault]" value="true">
|
||||
<span data-i18n>settings.emergency.events.mqtt</span>
|
||||
</label>
|
||||
|
||||
<label for="emergency-on-indoor-sensor-disconnect">
|
||||
<input type="checkbox" id="emergency-on-indoor-sensor-disconnect" name="emergency[onIndoorSensorDisconnect]" value="true">
|
||||
<span data-i18n>settings.emergency.events.indoorSensorDisconnect</span>
|
||||
</label>
|
||||
|
||||
<label for="emergency-on-outdoor-sensor-disconnect">
|
||||
<input type="checkbox" id="emergency-on-outdoor-sensor-disconnect" name="emergency[onOutdoorSensorDisconnect]" value="true">
|
||||
<span data-i18n>settings.emergency.events.outdoorSensorDisconnect</span>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend data-i18n>settings.section.emergency.regulators</legend>
|
||||
|
||||
<label for="emergency-use-equitherm">
|
||||
<input type="checkbox" id="emergency-use-equitherm" name="emergency[useEquitherm]" value="true">
|
||||
<span data-i18n>settings.emergency.regulators.equitherm</span>
|
||||
</label>
|
||||
<label for="emergency-use-pid">
|
||||
<input type="checkbox" id="emergency-use-pid" name="emergency[usePid]" value="true">
|
||||
<span data-i18n>settings.emergency.regulators.pid</span>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<button type="submit" data-i18n>button.save</button>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<hr />
|
||||
|
||||
<details>
|
||||
<summary><b data-i18n>settings.section.equitherm</b></summary>
|
||||
<div>
|
||||
<div id="equitherm-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/settings" id="equitherm-settings" class="hidden">
|
||||
<fieldset>
|
||||
<label for="equitherm-enable">
|
||||
<input type="checkbox" id="equitherm-enable" name="equitherm[enable]" value="true">
|
||||
<span data-i18n>settings.enable</span>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<div class="grid">
|
||||
<label for="equitherm-n-factor">
|
||||
<span data-i18n>settings.equitherm.n</span>
|
||||
<input type="number" inputmode="numeric" id="equitherm-n-factor" name="equitherm[n_factor]" min="0.001" max="10" step="0.001" required>
|
||||
</label>
|
||||
|
||||
<label for="equitherm-k-factor">
|
||||
<span data-i18n>settings.equitherm.k</span>
|
||||
<input type="number" inputmode="numeric" id="equitherm-k-factor" name="equitherm[k_factor]" min="0" max="10" step="0.01" required>
|
||||
</label>
|
||||
|
||||
<label for="equitherm-t-factor">
|
||||
<span data-i18n>settings.equitherm.t.title</span>
|
||||
<input type="number" inputmode="numeric" id="equitherm-t-factor" name="equitherm[t_factor]" min="0" max="10" step="0.01" required>
|
||||
<small data-i18n>settings.equitherm.t.note</small>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit" data-i18n>button.save</button>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<hr />
|
||||
|
||||
<details>
|
||||
<summary><b data-i18n>settings.section.pid</b></summary>
|
||||
<div>
|
||||
<div id="pid-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/settings" id="pid-settings" class="hidden">
|
||||
<fieldset>
|
||||
<label for="pid-enable">
|
||||
<input type="checkbox" id="pid-enable" name="pid[enable]" value="true">
|
||||
<span data-i18n>settings.enable</span>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<div class="grid">
|
||||
<label for="pid-p-factor">
|
||||
<span data-i18n>settings.pid.p</span>
|
||||
<input type="number" inputmode="numeric" id="pid-p-factor" name="pid[p_factor]" min="0.1" max="1000" step="0.1" required>
|
||||
</label>
|
||||
|
||||
<label for="pid-i-factor">
|
||||
<span data-i18n>settings.pid.i</span>
|
||||
<input type="number" inputmode="numeric" id="pid-i-factor" name="pid[i_factor]" min="0" max="100" step="0.0001" required>
|
||||
</label>
|
||||
|
||||
<label for="pid-d-factor">
|
||||
<span data-i18n>settings.pid.d</span>
|
||||
<input type="number" inputmode="numeric" id="pid-d-factor" name="pid[d_factor]" min="0" max="100000" step="1" required>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label for="pid-dt">
|
||||
<span data-i18n>settings.pid.dt</span>
|
||||
<input type="number" inputmode="numeric" id="pid-dt" name="pid[dt]" min="30" max="600" step="1" required>
|
||||
</label>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="grid">
|
||||
<label for="pid-min-temp">
|
||||
<span data-i18n>settings.temp.min</span>
|
||||
<input type="number" inputmode="numeric" id="pid-min-temp" name="pid[minTemp]" min="0" max="0" step="1" required>
|
||||
</label>
|
||||
|
||||
<label for="pid-max-temp">
|
||||
<span data-i18n>settings.temp.max</span>
|
||||
<input type="number" inputmode="numeric" id="pid-max-temp" name="pid[maxTemp]" min="0" max="0" step="1" required>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit" data-i18n>button.save</button>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<hr />
|
||||
|
||||
<details>
|
||||
<summary><b data-i18n>settings.section.ot</b></summary>
|
||||
<div>
|
||||
<div id="opentherm-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/settings" id="opentherm-settings" class="hidden">
|
||||
<fieldset>
|
||||
<legend data-i18n>settings.system.unit</legend>
|
||||
|
||||
<label>
|
||||
<input type="radio" class="opentherm-unit-system" name="opentherm[unitSystem]" value="0" />
|
||||
<span data-i18n>settings.system.metric</span>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<input type="radio" class="opentherm-unit-system" name="opentherm[unitSystem]" value="1" />
|
||||
<span data-i18n>settings.system.imperial</span>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<div class="grid">
|
||||
<label for="opentherm-in-gpio">
|
||||
<span data-i18n>settings.ot.inGpio</span>
|
||||
<input type="number" inputmode="numeric" id="opentherm-in-gpio" name="opentherm[inGpio]" min="0" max="254" step="1">
|
||||
</label>
|
||||
|
||||
<label for="opentherm-in-gpio">
|
||||
<span data-i18n>settings.ot.outGpio</span>
|
||||
<input type="number" inputmode="numeric" id="opentherm-out-gpio" name="opentherm[outGpio]" min="0" max="254" step="1">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<label for="opentherm-rx-led-gpio">
|
||||
<span data-i18n>settings.ot.ledGpio</span>
|
||||
<input type="number" inputmode="numeric" id="opentherm-rx-led-gpio" name="opentherm[rxLedGpio]" min="0" max="254" step="1">
|
||||
<small data-i18n>settings.note.blankNotUse</small>
|
||||
</label>
|
||||
|
||||
<label for="opentherm-member-id-code">
|
||||
<span data-i18n>settings.ot.memberIdCode</span>
|
||||
<input type="number" inputmode="numeric" id="opentherm-member-id-code" name="opentherm[memberIdCode]" min="0" max="65535" step="1" required>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<label for="opentherm-pressure-factor">
|
||||
<span data-i18n>settings.ot.pressureFactor.title</span>
|
||||
<input type="number" inputmode="numeric" id="opentherm-pressure-factor" name="opentherm[pressureFactor]" min="0.1" max="100" step="0.01">
|
||||
<small data-i18n>settings.ot.pressureFactor.note</small>
|
||||
</label>
|
||||
|
||||
<label for="opentherm-dhw-fr-factor">
|
||||
<span data-i18n>settings.ot.dhwFlowRateFactor.title</span>
|
||||
<input type="number" inputmode="numeric" id="opentherm-dhw-fr-factor" name="opentherm[dhwFlowRateFactor]" min="0.1" max="100" step="0.01">
|
||||
<small data-i18n>settings.ot.dhwFlowRateFactor.note</small>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<fieldset>
|
||||
<legend data-i18n>settings.section.ot.options</legend>
|
||||
<label for="opentherm-dhw-present">
|
||||
<input type="checkbox" id="opentherm-dhw-present" name="opentherm[dhwPresent]" value="true">
|
||||
<span data-i18n>settings.ot.options.dhwPresent</span>
|
||||
</label>
|
||||
|
||||
<label for="opentherm-sw-mode">
|
||||
<input type="checkbox" id="opentherm-sw-mode" name="opentherm[summerWinterMode]" value="true">
|
||||
<span data-i18n>settings.ot.options.summerWinterMode</span>
|
||||
</label>
|
||||
|
||||
<label for="opentherm-heating-ch2-enabled">
|
||||
<input type="checkbox" id="opentherm-heating-ch2-enabled" name="opentherm[heatingCh2Enabled]" value="true">
|
||||
<span data-i18n>settings.ot.options.heatingCh2Enabled</span>
|
||||
</label>
|
||||
|
||||
<label for="opentherm-heating-ch1-to-ch2">
|
||||
<input type="checkbox" id="opentherm-heating-ch1-to-ch2" name="opentherm[heatingCh1ToCh2]" value="true">
|
||||
<span data-i18n>settings.ot.options.heatingCh1ToCh2</span>
|
||||
</label>
|
||||
|
||||
<label for="opentherm-dhw-to-ch2">
|
||||
<input type="checkbox" id="opentherm-dhw-to-ch2" name="opentherm[dhwToCh2]" value="true">
|
||||
<span data-i18n>settings.ot.options.dhwToCh2</span>
|
||||
</label>
|
||||
|
||||
<label for="opentherm-dhw-blocking">
|
||||
<input type="checkbox" id="opentherm-dhw-blocking" name="opentherm[dhwBlocking]" value="true">
|
||||
<span data-i18n>settings.ot.options.dhwBlocking</span>
|
||||
</label>
|
||||
|
||||
<label for="opentherm-sync-modulation-with-heating">
|
||||
<input type="checkbox" id="opentherm-sync-modulation-with-heating" name="opentherm[modulationSyncWithHeating]" value="true">
|
||||
<span data-i18n>settings.ot.options.modulationSyncWithHeating</span>
|
||||
</label>
|
||||
|
||||
<label for="opentherm-get-min-max-temp">
|
||||
<input type="checkbox" id="opentherm-get-min-max-temp" name="opentherm[getMinMaxTemp]" value="true">
|
||||
<span data-i18n>settings.ot.options.getMinMaxTemp</span>
|
||||
</label>
|
||||
|
||||
<label for="opentherm-immergas-fix">
|
||||
<input type="checkbox" id="opentherm-immergas-fix" name="opentherm[immergasFix]" value="true">
|
||||
<span data-i18n>settings.ot.options.immergasFix</span>
|
||||
</label>
|
||||
|
||||
<hr />
|
||||
<fieldset>
|
||||
<legend>
|
||||
<span data-i18n>settings.ot.fnv.title</span>
|
||||
</legend>
|
||||
|
||||
<label for="opentherm-fnv-enable">
|
||||
<input type="checkbox" id="opentherm-fnv-enable" name="opentherm[filterNumValues][enable]" value="true">
|
||||
<span data-i18n>settings.ot.fnv.enable.title</span>
|
||||
<br>
|
||||
<small data-i18n>settings.ot.fnv.enable.note</small>
|
||||
</label>
|
||||
|
||||
<label for="opentherm-fnv-factor">
|
||||
<span data-i18n>settings.ot.fnv.factor.title</span>
|
||||
<input type="number" inputmode="numeric" id="opentherm-fnv-factor" name="opentherm[filterNumValues][factor]" min="0.01" max="1" step="0.01">
|
||||
<small data-i18n>settings.ot.fnv.factor.note</small>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<hr />
|
||||
<fieldset>
|
||||
<label for="opentherm-fault-state-gpio">
|
||||
<span data-i18n>settings.ot.faultState.gpio</span>
|
||||
<input type="number" inputmode="numeric" id="opentherm-fault-state-gpio" name="opentherm[faultStateGpio]" min="0" max="254" step="1">
|
||||
<small data-i18n>settings.ot.faultState.note</small>
|
||||
</label>
|
||||
|
||||
<label for="opentherm-invert-fault-state">
|
||||
<input type="checkbox" id="opentherm-invert-fault-state" name="opentherm[invertFaultState]" value="true">
|
||||
<span data-i18n>settings.ot.faultState.invert</span>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<hr />
|
||||
<label for="opentherm-native-heating-control">
|
||||
<input type="checkbox" id="opentherm-native-heating-control" name="opentherm[nativeHeatingControl]" value="true">
|
||||
<span data-i18n>settings.ot.nativeHeating.title</span><br />
|
||||
<small data-i18n>settings.ot.nativeHeating.note</small>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<button type="submit" data-i18n>button.save</button>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<hr />
|
||||
|
||||
<details>
|
||||
<summary><b data-i18n>settings.section.mqtt</b></summary>
|
||||
<div>
|
||||
<div id="mqtt-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/settings" id="mqtt-settings" class="hidden">
|
||||
<fieldset>
|
||||
<label for="mqtt-enable">
|
||||
<input type="checkbox" id="mqtt-enable" name="mqtt[enable]" value="true">
|
||||
<span data-i18n>settings.enable</span>
|
||||
</label>
|
||||
|
||||
<label for="mqtt-ha-discovery">
|
||||
<input type="checkbox" id="mqtt-ha-discovery" name="mqtt[homeAssistantDiscovery]" value="true">
|
||||
<span data-i18n>settings.mqtt.homeAssistantDiscovery</span>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<div class="grid">
|
||||
<label for="mqtt-server">
|
||||
<span data-i18n>settings.mqtt.server</span>
|
||||
<input type="text" id="mqtt-server" name="mqtt[server]" maxlength="80" required>
|
||||
</label>
|
||||
|
||||
<label for="mqtt-port">
|
||||
<span data-i18n>settings.mqtt.port</span>
|
||||
<input type="number" inputmode="numeric" id="mqtt-port" name="mqtt[port]" min="1" max="65535" step="1" required>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<label for="mqtt-user">
|
||||
<span data-i18n>settings.mqtt.user</span>
|
||||
<input type="text" id="mqtt-user" name="mqtt[user]" maxlength="32" required>
|
||||
</label>
|
||||
|
||||
<label for="mqtt-password">
|
||||
<span data-i18n>settings.mqtt.password</span>
|
||||
<input type="password" id="mqtt-password" name="mqtt[password]" maxlength="32">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<label for="mqtt-prefix">
|
||||
<span data-i18n>settings.mqtt.prefix</span>
|
||||
<input type="text" id="mqtt-prefix" name="mqtt[prefix]" maxlength="32" required>
|
||||
</label>
|
||||
|
||||
<label for="mqtt-interval">
|
||||
<span data-i18n>settings.mqtt.interval</span>
|
||||
<input type="number" inputmode="numeric" id="mqtt-interval" name="mqtt[interval]" min="3" max="60" step="1" required>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit" data-i18n>button.save</button>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<hr />
|
||||
|
||||
<details>
|
||||
<summary><b data-i18n>settings.section.outdorSensor</b></summary>
|
||||
<div>
|
||||
<div id="outdoor-sensor-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/settings" id="outdoor-sensor-settings" class="hidden">
|
||||
<fieldset>
|
||||
<legend data-i18n>settings.tempSensor.source.type</legend>
|
||||
|
||||
<label>
|
||||
<input type="radio" class="outdoor-sensor-type" name="sensors[outdoor][type]" value="0" />
|
||||
<span data-i18n>settings.tempSensor.source.boiler</span>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<input type="radio" class="outdoor-sensor-type" name="sensors[outdoor][type]" value="1" />
|
||||
<span data-i18n>settings.tempSensor.source.manual</span>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<input type="radio" class="outdoor-sensor-type" name="sensors[outdoor][type]" value="2" />
|
||||
<span data-i18n>settings.tempSensor.source.ext</span>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<input type="radio" class="outdoor-sensor-type" name="sensors[outdoor][type]" value="3" />
|
||||
<span data-i18n>settings.tempSensor.source.ble</span>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<div class="grid">
|
||||
<label for="outdoor-sensor-gpio">
|
||||
<span data-i18n>settings.tempSensor.gpio</span>
|
||||
<input type="number" inputmode="numeric" id="outdoor-sensor-gpio" name="sensors[outdoor][gpio]" min="0" max="254" step="1">
|
||||
</label>
|
||||
|
||||
<label for="outdoor-sensor-ble-addresss">
|
||||
<span data-i18n>settings.tempSensor.bleAddress</span>
|
||||
<input type="text" id="outdoor-sensor-ble-addresss" name="sensors[outdoor][bleAddress]" pattern="([A-Fa-f0-9]{2}:){5}[A-Fa-f0-9]{2}">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label for="outdoor-sensor-offset">
|
||||
<span data-i18n>settings.tempSensor.offset</span>
|
||||
<input type="number" inputmode="numeric" id="outdoor-sensor-offset" name="sensors[outdoor][offset]" min="-10" max="10" step="0.01" required>
|
||||
</label>
|
||||
|
||||
<fieldset>
|
||||
<mark data-i18n>settings.note.bleDevice</mark>
|
||||
</fieldset>
|
||||
|
||||
<button type="submit" data-i18n>button.save</button>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<hr />
|
||||
|
||||
<details>
|
||||
<summary><b data-i18n>settings.section.indoorSensor</b></summary>
|
||||
<div>
|
||||
<div id="indoor-sensor-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/settings" id="indoor-sensor-settings" class="hidden">
|
||||
<fieldset>
|
||||
<legend data-i18n>settings.tempSensor.source.type</legend>
|
||||
|
||||
<label>
|
||||
<input type="radio" class="indoor-sensor-type" name="sensors[indoor][type]" value="1" />
|
||||
<span data-i18n>settings.tempSensor.source.manual</span>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<input type="radio" class="indoor-sensor-type" name="sensors[indoor][type]" value="2" />
|
||||
<span data-i18n>settings.tempSensor.source.ext</span>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<input type="radio" class="indoor-sensor-type" name="sensors[indoor][type]" value="3" />
|
||||
<span data-i18n>settings.tempSensor.source.ble</span>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<div class="grid">
|
||||
<label for="indoor-sensor-gpio">
|
||||
<span data-i18n>settings.tempSensor.gpio</span>
|
||||
<input type="number" inputmode="numeric" id="indoor-sensor-gpio" name="sensors[indoor][gpio]" min="0" max="254" step="1">
|
||||
</label>
|
||||
|
||||
<label for="indoor-sensor-ble-addresss">
|
||||
<span data-i18n>settings.tempSensor.bleAddress</span>
|
||||
<input type="text" id="indoor-sensor-ble-addresss" name="sensors[indoor][bleAddress]" pattern="([A-Fa-f0-9]{2}:){5}[A-Fa-f0-9]{2}">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label for="indoor-sensor-offset">
|
||||
<span data-i18n>settings.tempSensor.offset</span>
|
||||
<input type="number" inputmode="numeric" id="indoor-sensor-offset" name="sensors[indoor][offset]" min="-10" max="10" step="0.01" required>
|
||||
</label>
|
||||
|
||||
<fieldset>
|
||||
<mark data-i18n>settings.note.bleDevice</mark>
|
||||
</fieldset>
|
||||
|
||||
<button type="submit" data-i18n>button.save</button>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<hr />
|
||||
|
||||
<details>
|
||||
<summary><b data-i18n>settings.section.extPump</b></summary>
|
||||
<div>
|
||||
<div id="extpump-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/settings" id="extpump-settings" class="hidden">
|
||||
<fieldset>
|
||||
<label for="extpump-use">
|
||||
<input type="checkbox" id="extpump-use" name="externalPump[use]" value="true">
|
||||
<span data-i18n>settings.extPump.use</span>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<div class="grid">
|
||||
<label for="extpump-gpio">
|
||||
<span data-i18n>settings.extPump.gpio</span>
|
||||
<input type="number" inputmode="numeric" id="extpump-gpio" name="externalPump[gpio]" min="0" max="254" step="1">
|
||||
</label>
|
||||
|
||||
<label for="extpump-pc-time">
|
||||
<span data-i18n>settings.extPump.postCirculationTime</span>
|
||||
<input type="number" inputmode="numeric" id="extpump-pc-time" name="externalPump[postCirculationTime]" min="1" max="120" step="1" required>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<label for="extpump-as-interval">
|
||||
<span data-i18n>settings.extPump.antiStuckInterval</span>
|
||||
<input type="number" inputmode="numeric" id="extpump-as-interval" name="externalPump[antiStuckInterval]" min="1" max="366" step="1" required>
|
||||
</label>
|
||||
|
||||
<label for="extpump-as-time">
|
||||
<span data-i18n>settings.extPump.antiStuckTime</span>
|
||||
<input type="number" inputmode="numeric" id="extpump-as-time" name="externalPump[antiStuckTime]" min="1" max="20" step="1" required>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit" data-i18n>button.save</button>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
</article>
|
||||
</main>
|
||||
|
||||
<footer class="container">
|
||||
<small>
|
||||
<b>Made by Laxilef</b>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/blob/master/LICENSE" target="_blank" class="secondary" data-i18n>nav.license</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/blob/master/" target="_blank" class="secondary" data-i18n>nav.source</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/wiki" target="_blank" class="secondary" data-i18n>nav.help</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/issues" target="_blank" class="secondary" data-i18n>nav.issues</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/releases" target="_blank" class="secondary" data-i18n>nav.releases</a>
|
||||
</small>
|
||||
</footer>
|
||||
|
||||
<script src="/static/app.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
const lang = new Lang(document.getElementById('lang'));
|
||||
lang.build();
|
||||
|
||||
|
||||
const fillData = (data) => {
|
||||
// System
|
||||
setCheckboxValue('#system-debug', data.system.debug);
|
||||
setCheckboxValue('#system-serial-enable', data.system.serial.enable);
|
||||
setInputValue('#system-serial-baudrate', data.system.serial.baudrate);
|
||||
setCheckboxValue('#system-telnet-enable', data.system.telnet.enable);
|
||||
setInputValue('#system-telnet-port', data.system.telnet.port);
|
||||
setRadioValue('.system-unit-system', data.system.unitSystem);
|
||||
setInputValue('#system-status-led-gpio', data.system.statusLedGpio < 255 ? data.system.statusLedGpio : '');
|
||||
setBusy('#system-settings-busy', '#system-settings', false);
|
||||
|
||||
// Portal
|
||||
setCheckboxValue('#portal-auth', data.portal.auth);
|
||||
setInputValue('#portal-login', data.portal.login);
|
||||
setInputValue('#portal-password', data.portal.password);
|
||||
setBusy('#portal-settings-busy', '#portal-settings', false);
|
||||
|
||||
// Opentherm
|
||||
setRadioValue('.opentherm-unit-system', data.opentherm.unitSystem);
|
||||
setInputValue('#opentherm-in-gpio', data.opentherm.inGpio < 255 ? data.opentherm.inGpio : '');
|
||||
setInputValue('#opentherm-out-gpio', data.opentherm.outGpio < 255 ? data.opentherm.outGpio : '');
|
||||
setInputValue('#opentherm-rx-led-gpio', data.opentherm.rxLedGpio < 255 ? data.opentherm.rxLedGpio : '');
|
||||
setInputValue('#opentherm-fault-state-gpio', data.opentherm.faultStateGpio < 255 ? data.opentherm.faultStateGpio : '');
|
||||
setCheckboxValue('#opentherm-invert-fault-state', data.opentherm.invertFaultState);
|
||||
setInputValue('#opentherm-member-id-code', data.opentherm.memberIdCode);
|
||||
setInputValue('#opentherm-pressure-factor', data.opentherm.pressureFactor);
|
||||
setInputValue('#opentherm-dhw-fr-factor', data.opentherm.dhwFlowRateFactor);
|
||||
setCheckboxValue('#opentherm-dhw-present', data.opentherm.dhwPresent);
|
||||
setCheckboxValue('#opentherm-sw-mode', data.opentherm.summerWinterMode);
|
||||
setCheckboxValue('#opentherm-heating-ch2-enabled', data.opentherm.heatingCh2Enabled);
|
||||
setCheckboxValue('#opentherm-heating-ch1-to-ch2', data.opentherm.heatingCh1ToCh2);
|
||||
setCheckboxValue('#opentherm-dhw-to-ch2', data.opentherm.dhwToCh2);
|
||||
setCheckboxValue('#opentherm-dhw-blocking', data.opentherm.dhwBlocking);
|
||||
setCheckboxValue('#opentherm-sync-modulation-with-heating', data.opentherm.modulationSyncWithHeating);
|
||||
setCheckboxValue('#opentherm-get-min-max-temp', data.opentherm.getMinMaxTemp);
|
||||
setCheckboxValue('#opentherm-native-heating-control', data.opentherm.nativeHeatingControl);
|
||||
setCheckboxValue('#opentherm-immergas-fix', data.opentherm.immergasFix);
|
||||
setCheckboxValue('#opentherm-fnv-enable', data.opentherm.filterNumValues.enable);
|
||||
setInputValue('#opentherm-fnv-factor', data.opentherm.filterNumValues.factor);
|
||||
setBusy('#opentherm-settings-busy', '#opentherm-settings', false);
|
||||
|
||||
// MQTT
|
||||
setCheckboxValue('#mqtt-enable', data.mqtt.enable);
|
||||
setCheckboxValue('#mqtt-ha-discovery', data.mqtt.homeAssistantDiscovery);
|
||||
setInputValue('#mqtt-server', data.mqtt.server);
|
||||
setInputValue('#mqtt-port', data.mqtt.port);
|
||||
setInputValue('#mqtt-user', data.mqtt.user);
|
||||
setInputValue('#mqtt-password', data.mqtt.password);
|
||||
setInputValue('#mqtt-prefix', data.mqtt.prefix);
|
||||
setInputValue('#mqtt-interval', data.mqtt.interval);
|
||||
setBusy('#mqtt-settings-busy', '#mqtt-settings', false);
|
||||
|
||||
// Outdoor sensor
|
||||
setRadioValue('.outdoor-sensor-type', data.sensors.outdoor.type);
|
||||
setInputValue('#outdoor-sensor-gpio', data.sensors.outdoor.gpio < 255 ? data.sensors.outdoor.gpio : '');
|
||||
setInputValue('#outdoor-sensor-offset', data.sensors.outdoor.offset);
|
||||
setInputValue('#outdoor-sensor-ble-addresss', data.sensors.outdoor.bleAddress);
|
||||
setBusy('#outdoor-sensor-settings-busy', '#outdoor-sensor-settings', false);
|
||||
|
||||
// Indoor sensor
|
||||
setRadioValue('.indoor-sensor-type', data.sensors.indoor.type);
|
||||
setInputValue('#indoor-sensor-gpio', data.sensors.indoor.gpio < 255 ? data.sensors.indoor.gpio : '');
|
||||
setInputValue('#indoor-sensor-offset', data.sensors.indoor.offset);
|
||||
setInputValue('#indoor-sensor-ble-addresss', data.sensors.indoor.bleAddress);
|
||||
setBusy('#indoor-sensor-settings-busy', '#indoor-sensor-settings', false);
|
||||
|
||||
// Extpump
|
||||
setCheckboxValue('#extpump-use', data.externalPump.use);
|
||||
setInputValue('#extpump-gpio', data.externalPump.gpio < 255 ? data.externalPump.gpio : '');
|
||||
setInputValue('#extpump-pc-time', data.externalPump.postCirculationTime);
|
||||
setInputValue('#extpump-as-interval', data.externalPump.antiStuckInterval);
|
||||
setInputValue('#extpump-as-time', data.externalPump.antiStuckTime);
|
||||
setBusy('#extpump-settings-busy', '#extpump-settings', false);
|
||||
|
||||
// Heating
|
||||
setInputValue('#heating-min-temp', data.heating.minTemp, {
|
||||
"min": data.system.unitSystem == 0 ? 0 : 32,
|
||||
"max": data.system.unitSystem == 0 ? 99 : 211
|
||||
});
|
||||
setInputValue('#heating-max-temp', data.heating.maxTemp, {
|
||||
"min": data.system.unitSystem == 0 ? 1 : 33,
|
||||
"max": data.system.unitSystem == 0 ? 100 : 212
|
||||
});
|
||||
setInputValue('#heating-hysteresis', data.heating.hysteresis);
|
||||
setInputValue('#heating-max-modulation', data.heating.maxModulation);
|
||||
setBusy('#heating-settings-busy', '#heating-settings', false);
|
||||
|
||||
// DHW
|
||||
setInputValue('#dhw-min-temp', data.dhw.minTemp, {
|
||||
"min": data.system.unitSystem == 0 ? 0 : 32,
|
||||
"max": data.system.unitSystem == 0 ? 99 : 211
|
||||
});
|
||||
setInputValue('#dhw-max-temp', data.dhw.maxTemp, {
|
||||
"min": data.system.unitSystem == 0 ? 1 : 33,
|
||||
"max": data.system.unitSystem == 0 ? 100 : 212
|
||||
});
|
||||
setBusy('#dhw-settings-busy', '#dhw-settings', false);
|
||||
|
||||
// Emergency mode
|
||||
setCheckboxValue('#emergency-enable', data.emergency.enable);
|
||||
setInputValue('#emergency-treshold-time', data.emergency.tresholdTime);
|
||||
setCheckboxValue('#emergency-use-equitherm', data.emergency.useEquitherm);
|
||||
setCheckboxValue('#emergency-use-pid', data.emergency.usePid);
|
||||
setCheckboxValue('#emergency-on-network-fault', data.emergency.onNetworkFault);
|
||||
setCheckboxValue('#emergency-on-mqtt-fault', data.emergency.onMqttFault);
|
||||
setCheckboxValue('#emergency-on-indoor-sensor-disconnect', data.emergency.onIndoorSensorDisconnect);
|
||||
setCheckboxValue('#emergency-on-outdoor-sensor-disconnect', data.emergency.onOutdoorSensorDisconnect);
|
||||
setInputValue('#emergency-target', data.emergency.target, {
|
||||
"min": (!data.emergency.useEquitherm && !data.emergency.usePid) ? data.heating.minTemp : 10,
|
||||
"max": (!data.emergency.useEquitherm && !data.emergency.usePid) ? data.heating.maxTemp : 30,
|
||||
});
|
||||
setBusy('#emergency-settings-busy', '#emergency-settings', false);
|
||||
|
||||
// Equitherm
|
||||
setCheckboxValue('#equitherm-enable', data.equitherm.enable);
|
||||
setInputValue('#equitherm-n-factor', data.equitherm.n_factor);
|
||||
setInputValue('#equitherm-k-factor', data.equitherm.k_factor);
|
||||
setInputValue('#equitherm-t-factor', data.equitherm.t_factor);
|
||||
setBusy('#equitherm-settings-busy', '#equitherm-settings', false);
|
||||
|
||||
// PID
|
||||
setCheckboxValue('#pid-enable', data.pid.enable);
|
||||
setInputValue('#pid-p-factor', data.pid.p_factor);
|
||||
setInputValue('#pid-i-factor', data.pid.i_factor);
|
||||
setInputValue('#pid-d-factor', data.pid.d_factor);
|
||||
setInputValue('#pid-dt', data.pid.dt);
|
||||
setInputValue('#pid-min-temp', data.pid.minTemp, {
|
||||
"min": 0,
|
||||
"max": data.system.unitSystem == 0 ? 99 : 211
|
||||
});
|
||||
setInputValue('#pid-max-temp', data.pid.maxTemp, {
|
||||
"min": 1,
|
||||
"max": data.system.unitSystem == 0 ? 100 : 212
|
||||
});
|
||||
setBusy('#pid-settings-busy', '#pid-settings', false);
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/settings', { cache: 'no-cache' });
|
||||
if (!response.ok) {
|
||||
throw new Error('Response not valid');
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
fillData(result);
|
||||
|
||||
setupForm('#portal-settings', fillData, ['portal.login', 'portal.password']);
|
||||
setupForm('#system-settings', fillData);
|
||||
setupForm('#heating-settings', fillData);
|
||||
setupForm('#dhw-settings', fillData);
|
||||
setupForm('#emergency-settings', fillData);
|
||||
setupForm('#equitherm-settings', fillData);
|
||||
setupForm('#pid-settings', fillData);
|
||||
setupForm('#opentherm-settings', fillData);
|
||||
setupForm('#mqtt-settings', fillData, ['mqtt.user', 'mqtt.password', 'mqtt.prefix']);
|
||||
setupForm('#outdoor-sensor-settings', fillData);
|
||||
setupForm('#indoor-sensor-settings', fillData, ['sensors.indoor.bleAddress']);
|
||||
setupForm('#extpump-settings', fillData);
|
||||
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
111
src_data/pages/upgrade.html
Normal file
@@ -0,0 +1,111 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title data-i18n>upgrade.title</title>
|
||||
<link rel="stylesheet" href="/static/app.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header class="container">
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/">
|
||||
<div class="logo" data-i18n>logo</div>
|
||||
</a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<!--<li><a href="https://github.com/Laxilef/OTGateway/wiki" role="button" class="secondary" target="_blank">Help</a></li>-->
|
||||
<li>
|
||||
<select id="lang" aria-label="Lang">
|
||||
<option value="en" selected>EN</option>
|
||||
<option value="ru">RU</option>
|
||||
</select>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<article>
|
||||
<div>
|
||||
<hgroup>
|
||||
<h2 data-i18n>upgrade.section.backupAndRestore</h2>
|
||||
<p data-i18n>upgrade.section.backupAndRestore.desc</p>
|
||||
</hgroup>
|
||||
|
||||
<form action="/api/backup/restore" id="restore">
|
||||
<label for="restore-file">
|
||||
<span data-i18n>upgrade.settingsFile</span>
|
||||
<input type="file" name="settings" id="restore-file" accept=".json">
|
||||
</label>
|
||||
|
||||
<div class="grid">
|
||||
<button type="submit" data-i18n>button.restore</button>
|
||||
<button type="button" class="secondary" onclick="window.location='/api/backup/save';" data-i18n>button.backup</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article>
|
||||
<div>
|
||||
<hgroup>
|
||||
<h2 data-i18n>upgrade.section.upgrade</h2>
|
||||
<p data-i18n>upgrade.section.upgrade.desc</p>
|
||||
</hgroup>
|
||||
|
||||
<form action="/api/upgrade" id="upgrade">
|
||||
<fieldset class="primary">
|
||||
<label for="firmware-file">
|
||||
<span data-i18n>upgrade.fw</span>:
|
||||
<div class="grid">
|
||||
<input type="file" name="firmware" id="firmware-file" accept=".bin">
|
||||
<button type="button" class="upgrade-firmware-result hidden" disabled></button>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label for="filesystem-file">
|
||||
<span data-i18n>upgrade.fs</span>:
|
||||
<div class="grid">
|
||||
<input type="file" name="filesystem" id="filesystem-file" accept=".bin">
|
||||
<button type="button" class="upgrade-filesystem-result hidden" disabled></button>
|
||||
</div>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<ul>
|
||||
<li><mark data-i18n>upgrade.note.disclaimer1</mark></li>
|
||||
<li><mark data-i18n>upgrade.note.disclaimer2</mark></li>
|
||||
</ul>
|
||||
|
||||
<button type="submit" data-i18n>button.upgrade</button>
|
||||
</form>
|
||||
</div>
|
||||
</article>
|
||||
</main>
|
||||
|
||||
<footer class="container">
|
||||
<small>
|
||||
<b>Made by Laxilef</b>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/blob/master/LICENSE" target="_blank" class="secondary" data-i18n>nav.license</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/blob/master/" target="_blank" class="secondary" data-i18n>nav.source</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/wiki" target="_blank" class="secondary" data-i18n>nav.help</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/issues" target="_blank" class="secondary" data-i18n>nav.issues</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/releases" target="_blank" class="secondary" data-i18n>nav.releases</a>
|
||||
</small>
|
||||
</footer>
|
||||
|
||||
<script src="/static/app.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
const lang = new Lang(document.getElementById('lang'));
|
||||
lang.build();
|
||||
|
||||
setupRestoreBackupForm('#restore');
|
||||
setupUpgradeForm('#upgrade');
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
1
src_data/scripts/i18n.min.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
(function(){var e,t,n,r=function(e,t){return function(){return e.apply(t,arguments)}};e=function(){function e(){this.translate=r(this.translate,this);this.data={values:{},contexts:[]};this.globalContext={}}e.prototype.translate=function(e,t,n,r,i){var s,o,u,a;if(i==null){i=this.globalContext}u=function(e){var t;t=typeof e;return t==="function"||t==="object"&&!!e};if(u(t)){s=null;a=null;o=t;i=n||this.globalContext}else{if(typeof t==="number"){s=null;a=t;o=n;i=r||this.globalContext}else{s=t;if(typeof n==="number"){a=n;o=r;i=i}else{a=null;o=n;i=r||this.globalContext}}}if(u(e)){if(u(e["i18n"])){e=e["i18n"]}return this.translateHash(e,i)}else{return this.translateText(e,a,o,i,s)}};e.prototype.add=function(e){var t,n,r,i,s,o,u,a;if(e.values!=null){o=e.values;for(n in o){r=o[n];this.data.values[n]=r}}if(e.contexts!=null){u=e.contexts;a=[];for(i=0,s=u.length;i<s;i++){t=u[i];a.push(this.data.contexts.push(t))}return a}};e.prototype.setContext=function(e,t){return this.globalContext[e]=t};e.prototype.clearContext=function(e){return this.lobalContext[e]=null};e.prototype.reset=function(){this.data={values:{},contexts:[]};return this.globalContext={}};e.prototype.resetData=function(){return this.data={values:{},contexts:[]}};e.prototype.resetContext=function(){return this.globalContext={}};e.prototype.translateHash=function(e,t){var n,r;for(n in e){r=e[n];if(typeof r==="string"){e[n]=this.translateText(r,null,null,t)}}return e};e.prototype.translateText=function(e,t,n,r,i){var s,o;if(r==null){r=this.globalContext}if(this.data==null){return this.useOriginalText(i||e,t,n)}s=this.getContextData(this.data,r);if(s!=null){o=this.findTranslation(e,t,n,s.values,i)}if(o==null){o=this.findTranslation(e,t,n,this.data.values,i)}if(o==null){return this.useOriginalText(i||e,t,n)}return o};e.prototype.findTranslation=function(e,t,n,r){var i,s,o,u,a;o=r[e];if(o==null){return null}if(t==null){if(typeof o==="string"){return this.applyFormatting(o,t,n)}}else{if(o instanceof Array||o.length){for(u=0,a=o.length;u<a;u++){s=o[u];if((t>=s[0]||s[0]===null)&&(t<=s[1]||s[1]===null)){i=this.applyFormatting(s[2].replace("-%n",String(-t)),t,n);return this.applyFormatting(i.replace("%n",String(t)),t,n)}}}}return null};e.prototype.getContextData=function(e,t){var n,r,i,s,o,u,a,f;if(e.contexts==null){return null}a=e.contexts;for(o=0,u=a.length;o<u;o++){n=a[o];r=true;f=n.matches;for(i in f){s=f[i];r=r&&s===t[i]}if(r){return n}}return null};e.prototype.useOriginalText=function(e,t,n){if(t==null){return this.applyFormatting(e,t,n)}return this.applyFormatting(e.replace("%n",String(t)),t,n)};e.prototype.applyFormatting=function(e,t,n){var r,i;for(r in n){i=new RegExp("%{"+r+"}","g");e=e.replace(i,n[r])}return e};return e}();n=new e;t=n.translate;t.translator=n;t.create=function(n){var r;r=new e;if(n!=null){r.add(n)}r.translate.create=t.create;return r.translate};(typeof module!=="undefined"&&module!==null?module.exports=t:void 0)||(this.i18n=t)}).call(this)
|
||||
128
src_data/scripts/lang.js
Normal file
@@ -0,0 +1,128 @@
|
||||
class Lang {
|
||||
constructor(switcher, defaultLocale = null) {
|
||||
if (!(switcher instanceof Object)) {
|
||||
throw new SyntaxError("switcher must be an element object");
|
||||
}
|
||||
|
||||
this.switcher = switcher;
|
||||
this.defaultLocale = defaultLocale;
|
||||
this.supportedLocales = [];
|
||||
this.currentLocale = null;
|
||||
}
|
||||
|
||||
async build() {
|
||||
this.bindSwitcher();
|
||||
|
||||
const userLocale = localStorage.getItem('locale');
|
||||
if (this.localeIsSupported(userLocale)) {
|
||||
await this.setLocale(userLocale);
|
||||
|
||||
} else {
|
||||
const initialLocale = this.getSuitableLocale(this.browserLocales(true));
|
||||
await this.setLocale(initialLocale);
|
||||
}
|
||||
|
||||
this.translatePage();
|
||||
}
|
||||
|
||||
bindSwitcher() {
|
||||
this.supportedLocales = [];
|
||||
for (const option of this.switcher.options) {
|
||||
this.supportedLocales.push(option.value);
|
||||
}
|
||||
|
||||
if (!this.localeIsSupported(this.defaultLocale)) {
|
||||
const selected = this.switcher.selectedIndex ?? 0;
|
||||
this.defaultLocale = this.switcher.options[selected].value;
|
||||
}
|
||||
|
||||
this.switcher.addEventListener('change', async (element) => {
|
||||
await this.setLocale(element.target.value);
|
||||
this.translatePage();
|
||||
});
|
||||
}
|
||||
|
||||
async setLocale(newLocale) {
|
||||
if (this.currentLocale == newLocale) {
|
||||
return;
|
||||
}
|
||||
|
||||
i18n.translator.reset();
|
||||
i18n.translator.add(await this.fetchTranslations(newLocale));
|
||||
|
||||
this.currentLocale = newLocale;
|
||||
localStorage.setItem('locale', this.currentLocale);
|
||||
|
||||
if (document.documentElement) {
|
||||
document.documentElement.setAttribute("lang", this.currentLocale);
|
||||
}
|
||||
|
||||
if (this.switcher.value != this.currentLocale) {
|
||||
this.switcher.value = this.currentLocale;
|
||||
}
|
||||
}
|
||||
|
||||
async fetchTranslations(locale) {
|
||||
const response = await fetch(`/static/locales/${locale}.json`);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.values instanceof Object) {
|
||||
data.values = this.flattenKeys({keys: data.values, prefix: ''});
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
translatePage() {
|
||||
document
|
||||
.querySelectorAll("[data-i18n]")
|
||||
.forEach((element) => this.translateElement(element));
|
||||
}
|
||||
|
||||
translateElement(element) {
|
||||
let key = element.getAttribute("data-i18n");
|
||||
if (!key && element.innerHTML) {
|
||||
key = element.innerHTML;
|
||||
element.setAttribute("data-i18n", key);
|
||||
}
|
||||
|
||||
if (!key) {
|
||||
return;
|
||||
}
|
||||
|
||||
const arg = element.getAttribute("data-i18n-arg") || null;
|
||||
const options = JSON.parse(element.getAttribute("data-i18n-options")) || null;
|
||||
|
||||
element.innerHTML = i18n(key, arg, options);
|
||||
}
|
||||
|
||||
|
||||
localeIsSupported(locale) {
|
||||
return locale !== null && this.supportedLocales.indexOf(locale) > -1;
|
||||
}
|
||||
|
||||
getSuitableLocale(locales) {
|
||||
return locales.find(this.localeIsSupported, this) || this.defaultLocale;
|
||||
}
|
||||
|
||||
browserLocales(codeOnly = false) {
|
||||
return navigator.languages.map((locale) =>
|
||||
codeOnly ? locale.split("-")[0] : locale,
|
||||
);
|
||||
}
|
||||
|
||||
flattenKeys({ keys, prefix }) {
|
||||
let result = {};
|
||||
for (let key in keys) {
|
||||
const type = typeof keys[key];
|
||||
if (type === 'string') {
|
||||
result[`${prefix}${key}`] = keys[key];
|
||||
}
|
||||
else if (type === 'object') {
|
||||
result = { ...result, ...this.flattenKeys({ keys: keys[key], prefix: `${prefix}${key}.` }) }
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,22 +14,19 @@ function setupForm(formSelector, onResultCallback = null, noCastItems = []) {
|
||||
let button = form.querySelector('button[type="submit"]');
|
||||
let defaultText;
|
||||
|
||||
if (button) {
|
||||
defaultText = button.textContent;
|
||||
}
|
||||
|
||||
form.addEventListener('submit', async (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
if (button) {
|
||||
button.textContent = 'Please wait...';
|
||||
defaultText = button.textContent;
|
||||
button.textContent = i18n("button.wait");
|
||||
button.setAttribute('disabled', true);
|
||||
button.setAttribute('aria-busy', true);
|
||||
}
|
||||
|
||||
const onSuccess = (result) => {
|
||||
if (button) {
|
||||
button.textContent = 'Saved';
|
||||
button.textContent = i18n('button.saved');
|
||||
button.classList.add('success');
|
||||
button.removeAttribute('aria-busy');
|
||||
|
||||
@@ -43,7 +40,7 @@ function setupForm(formSelector, onResultCallback = null, noCastItems = []) {
|
||||
|
||||
const onFailed = () => {
|
||||
if (button) {
|
||||
button.textContent = 'Error';
|
||||
button.textContent = i18n('button.error');
|
||||
button.classList.add('failed');
|
||||
button.removeAttribute('aria-busy');
|
||||
|
||||
@@ -100,17 +97,14 @@ function setupNetworkScanForm(formSelector, tableSelector) {
|
||||
let button = form.querySelector('button[type="submit"]');
|
||||
let defaultText;
|
||||
|
||||
if (button) {
|
||||
defaultText = button.innerHTML;
|
||||
}
|
||||
|
||||
const onSubmitFn = async (event) => {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
if (button) {
|
||||
button.innerHTML = 'Please wait...';
|
||||
defaultText = button.innerHTML;
|
||||
button.innerHTML = i18n('button.wait');
|
||||
button.setAttribute('disabled', true);
|
||||
button.setAttribute('aria-busy', true);
|
||||
}
|
||||
@@ -262,22 +256,19 @@ function setupRestoreBackupForm(formSelector) {
|
||||
let button = form.querySelector('button[type="submit"]');
|
||||
let defaultText;
|
||||
|
||||
if (button) {
|
||||
defaultText = button.textContent;
|
||||
}
|
||||
|
||||
form.addEventListener('submit', async (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
if (button) {
|
||||
button.textContent = 'Please wait...';
|
||||
defaultText = button.textContent;
|
||||
button.textContent = i18n('button.wait');
|
||||
button.setAttribute('disabled', true);
|
||||
button.setAttribute('aria-busy', true);
|
||||
}
|
||||
|
||||
const onSuccess = (response) => {
|
||||
if (button) {
|
||||
button.textContent = 'Restored';
|
||||
button.textContent = i18n('button.restored');
|
||||
button.classList.add('success');
|
||||
button.removeAttribute('aria-busy');
|
||||
|
||||
@@ -291,7 +282,7 @@ function setupRestoreBackupForm(formSelector) {
|
||||
|
||||
const onFailed = (response) => {
|
||||
if (button) {
|
||||
button.textContent = 'Error';
|
||||
button.textContent = i18n('button.error');
|
||||
button.classList.add('failed');
|
||||
button.removeAttribute('aria-busy');
|
||||
|
||||
@@ -349,10 +340,6 @@ function setupUpgradeForm(formSelector) {
|
||||
let button = form.querySelector('button[type="submit"]');
|
||||
let defaultText;
|
||||
|
||||
if (button) {
|
||||
defaultText = button.textContent;
|
||||
}
|
||||
|
||||
const statusToText = (status) => {
|
||||
switch (status) {
|
||||
case 0:
|
||||
@@ -437,7 +424,7 @@ function setupUpgradeForm(formSelector) {
|
||||
|
||||
const onFailed = (response) => {
|
||||
if (button) {
|
||||
button.textContent = 'Error';
|
||||
button.textContent = i18n('button.error');
|
||||
button.classList.add('failed');
|
||||
button.removeAttribute('aria-busy');
|
||||
|
||||
@@ -456,7 +443,8 @@ function setupUpgradeForm(formSelector) {
|
||||
hide('.upgrade-filesystem-result');
|
||||
|
||||
if (button) {
|
||||
button.textContent = 'Uploading...';
|
||||
defaultText = button.textContent;
|
||||
button.textContent = i18n('button.uploading');
|
||||
button.setAttribute('disabled', true);
|
||||
button.setAttribute('aria-busy', true);
|
||||
}
|
||||
@@ -621,7 +609,7 @@ function memberIdToVendor(memberId) {
|
||||
27: "Baxi",
|
||||
29: "Itho Daalderop",
|
||||
33: "Viessmann",
|
||||
41: "Italtherm",
|
||||
41: "Italtherm/Radiant",
|
||||
56: "Baxi Luna Duo-Tec",
|
||||
131: "Nefit",
|
||||
148: "Navien",
|
||||
@@ -677,4 +665,13 @@ function form2json(data, noCastItems = []) {
|
||||
|
||||
let object = Array.from(data).reduce(method, {});
|
||||
return JSON.stringify(object);
|
||||
}
|
||||
|
||||
function dec2hex(i) {
|
||||
let hex = parseInt(i).toString(16);
|
||||
if (hex.length % 2 != 0) {
|
||||
hex = "0" + hex;
|
||||
}
|
||||
|
||||
return hex.toUpperCase();
|
||||
}
|
||||
@@ -1,833 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Settings - OpenTherm Gateway</title>
|
||||
<link rel="stylesheet" href="/static/pico.min.css">
|
||||
<link rel="stylesheet" href="/static/app.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header class="container">
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/"><div class="logo">OpenTherm Gateway</div></a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li><a href="https://github.com/Laxilef/OTGateway/wiki" role="button" class="secondary" target="_blank">Help</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<article>
|
||||
<hgroup>
|
||||
<h2>Settings</h2>
|
||||
<p></p>
|
||||
</hgroup>
|
||||
|
||||
<details>
|
||||
<summary><b>Portal settings</b></summary>
|
||||
<div>
|
||||
<div id="portal-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/settings" id="portal-settings" class="hidden">
|
||||
<div class="grid">
|
||||
<label for="portal-login">
|
||||
Login
|
||||
<input type="text" id="portal-login" name="portal[login]" maxlength="12" required>
|
||||
</label>
|
||||
|
||||
<label for="portal-password">
|
||||
Password
|
||||
<input type="password" id="portal-password" name="portal[password]" maxlength="32" required>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label for="portal-auth">
|
||||
<input type="checkbox" id="portal-auth" name="portal[auth]" value="true">
|
||||
Require authentication
|
||||
</label>
|
||||
<br />
|
||||
|
||||
<button type="submit">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<hr />
|
||||
|
||||
<details>
|
||||
<summary><b>System settings</b></summary>
|
||||
<div>
|
||||
<div id="system-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/settings" id="system-settings" class="hidden">
|
||||
<fieldset>
|
||||
<legend>Unit system</legend>
|
||||
|
||||
<label>
|
||||
<input type="radio" class="system-unit-system" name="system[unitSystem]" value="0" />
|
||||
Metric (celsius, liters, bar)
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<input type="radio" class="system-unit-system" name="system[unitSystem]" value="1" />
|
||||
Imperial (fahrenheit, gallons, psi)
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label for="system-status-led-gpio">
|
||||
Status LED GPIO
|
||||
<input type="number" inputmode="numeric" id="system-status-led-gpio" name="system[statusLedGpio]" min="0" max="254" step="1">
|
||||
<small>blank - not use</small>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Diagnostic</legend>
|
||||
|
||||
<label for="system-debug">
|
||||
<input type="checkbox" id="system-debug" name="system[debug]" value="true">
|
||||
Debug mode
|
||||
</label>
|
||||
|
||||
<label for="system-serial-enable">
|
||||
<input type="checkbox" id="system-serial-enable" name="system[serial][enable]" value="true">
|
||||
Enable Serial port
|
||||
</label>
|
||||
|
||||
<label for="system-telnet-enable">
|
||||
<input type="checkbox" id="system-telnet-enable" name="system[telnet][enable]" value="true">
|
||||
Enable Telnet
|
||||
</label>
|
||||
|
||||
<div class="grid">
|
||||
<label for="system-serial-baudrate">
|
||||
Serial port baud rate
|
||||
<input type="number" inputmode="numeric" id="system-serial-baudrate" name="system[serial][baudrate]" min="9600" max="115200" step="1" required>
|
||||
<small>Available: 9600, 19200, 38400, 57600, 74880, 115200</small>
|
||||
</label>
|
||||
|
||||
<label for="system-telnet-port">
|
||||
Telnet port
|
||||
<input type="number" inputmode="numeric" id="system-telnet-port" name="system[telnet][port]" min="1" max="65535" step="1" required>
|
||||
<small>Default: 23</small>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<mark>After changing this settings, the ESP must be restarted for the changes to take effect.</mark>
|
||||
</fieldset>
|
||||
|
||||
<button type="submit">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
|
||||
<hr />
|
||||
|
||||
<details>
|
||||
<summary><b>Heating settings</b></summary>
|
||||
<div>
|
||||
<div id="heating-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/settings" id="heating-settings" class="hidden">
|
||||
<div class="grid">
|
||||
<label for="heating-min-temp">
|
||||
Minimum temperature
|
||||
<input type="number" inputmode="numeric" id="heating-min-temp" name="heating[minTemp]" min="0" max="0" step="1" required>
|
||||
</label>
|
||||
|
||||
<label for="heating-max-temp">
|
||||
Maximum temperature
|
||||
<input type="number" inputmode="numeric" id="heating-max-temp" name="heating[maxTemp]" min="0" max="0" step="1" required>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<label for="heating-hysteresis">
|
||||
Hysteresis
|
||||
<input type="number" inputmode="numeric" id="heating-hysteresis" name="heating[hysteresis]" min="0" max="5" step="0.05" required>
|
||||
</label>
|
||||
|
||||
<label for="heating-max-modulation">
|
||||
Max modulation level
|
||||
<input type="number" inputmode="numeric" id="heating-max-modulation" name="heating[maxModulation]" min="1" max="100" step="1" required>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<hr />
|
||||
|
||||
<details>
|
||||
<summary><b>DHW settings</b></summary>
|
||||
<div>
|
||||
<div id="dhw-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/settings" id="dhw-settings" class="hidden">
|
||||
<div class="grid">
|
||||
<label for="dhw-min-temp">
|
||||
Minimum temperature
|
||||
<input type="number" inputmode="numeric" id="dhw-min-temp" name="dhw[minTemp]" min="0" max="0" step="1" required>
|
||||
</label>
|
||||
|
||||
<label for="dhw-max-temp">
|
||||
Maximum temperature
|
||||
<input type="number" inputmode="numeric" id="dhw-max-temp" name="dhw[maxTemp]" min="0" max="0" step="1" required>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<hr />
|
||||
|
||||
<details>
|
||||
<summary><b>Emergency mode settings</b></summary>
|
||||
<div>
|
||||
<div id="emergency-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/settings" id="emergency-settings" class="hidden">
|
||||
<fieldset>
|
||||
<label for="emergency-enable">
|
||||
<input type="checkbox" id="emergency-enable" name="emergency[enable]" value="true">
|
||||
Enable
|
||||
</label>
|
||||
|
||||
<small>
|
||||
<b>!</b> Emergency mode can be useful <u>only</u> when using Equitherm and/or PID (when normal work) and when reporting indoor/outdoor temperature via MQTT or API. In this mode, sensor values that are reported via MQTT/API are not used.
|
||||
</small>
|
||||
</fieldset>
|
||||
|
||||
<div class="grid">
|
||||
<label for="emergency-target">
|
||||
Target temperature
|
||||
<input type="number" inputmode="numeric" id="emergency-target" name="emergency[target]" min="0" max="0" step="1" required>
|
||||
<small>
|
||||
<u>Indoor temperature</u> if Equitherm or PID is <b>enabled</b><br />
|
||||
<u>Heat carrier temperature</u> if Equitherm and PID <b>is disabled</b>
|
||||
</small>
|
||||
</label>
|
||||
|
||||
<label for="emergency-treshold-time">
|
||||
Treshold time <small>(sec)</small>
|
||||
<input type="number" inputmode="numeric" id="emergency-treshold-time" name="emergency[tresholdTime]" min="60" max="1800" step="1" required>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<fieldset>
|
||||
<legend>Events</legend>
|
||||
|
||||
<label for="emergency-on-network-fault">
|
||||
<input type="checkbox" id="emergency-on-network-fault" name="emergency[onNetworkFault]" value="true">
|
||||
On network fault
|
||||
</label>
|
||||
|
||||
<label for="emergency-on-mqtt-fault">
|
||||
<input type="checkbox" id="emergency-on-mqtt-fault" name="emergency[onMqttFault]" value="true">
|
||||
On MQTT fault
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Using regulators</legend>
|
||||
|
||||
<label for="emergency-use-equitherm">
|
||||
<input type="checkbox" id="emergency-use-equitherm" name="emergency[useEquitherm]" value="true">
|
||||
<span>
|
||||
Equitherm <small>(requires at least an external/boiler <u>outdoor</u> sensor)</small>
|
||||
</span>
|
||||
</label>
|
||||
<label for="emergency-use-pid">
|
||||
<input type="checkbox" id="emergency-use-pid" name="emergency[usePid]" value="true">
|
||||
<span>
|
||||
PID <small>(requires at least an external/BLE <u>indoor</u> sensor)</small>
|
||||
</span>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<button type="submit">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<hr />
|
||||
|
||||
<details>
|
||||
<summary><b>Equitherm settings</b></summary>
|
||||
<div>
|
||||
<div id="equitherm-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/settings" id="equitherm-settings" class="hidden">
|
||||
<fieldset>
|
||||
<label for="equitherm-enable">
|
||||
<input type="checkbox" id="equitherm-enable" name="equitherm[enable]" value="true">
|
||||
Enable
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<div class="grid">
|
||||
<label for="equitherm-n-factor">
|
||||
N factor
|
||||
<input type="number" inputmode="numeric" id="equitherm-n-factor" name="equitherm[n_factor]" min="0.001" max="10" step="0.001" required>
|
||||
</label>
|
||||
|
||||
<label for="equitherm-k-factor">
|
||||
K factor
|
||||
<input type="number" inputmode="numeric" id="equitherm-k-factor" name="equitherm[k_factor]" min="0" max="10" step="0.01" required>
|
||||
</label>
|
||||
|
||||
<label for="equitherm-t-factor">
|
||||
T factor
|
||||
<input type="number" inputmode="numeric" id="equitherm-t-factor" name="equitherm[t_factor]" min="0" max="10" step="0.01" required>
|
||||
<small>Not used if PID is enabled</small>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<hr />
|
||||
|
||||
<details>
|
||||
<summary><b>PID settings</b></summary>
|
||||
<div>
|
||||
<div id="pid-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/settings" id="pid-settings" class="hidden">
|
||||
<fieldset>
|
||||
<label for="pid-enable">
|
||||
<input type="checkbox" id="pid-enable" name="pid[enable]" value="true">
|
||||
Enable
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<div class="grid">
|
||||
<label for="pid-p-factor">
|
||||
P factor
|
||||
<input type="number" inputmode="numeric" id="pid-p-factor" name="pid[p_factor]" min="0.1" max="1000" step="0.1" required>
|
||||
</label>
|
||||
|
||||
<label for="pid-i-factor">
|
||||
I factor
|
||||
<input type="number" inputmode="numeric" id="pid-i-factor" name="pid[i_factor]" min="0" max="100" step="0.0001" required>
|
||||
</label>
|
||||
|
||||
<label for="pid-d-factor">
|
||||
D factor
|
||||
<input type="number" inputmode="numeric" id="pid-d-factor" name="pid[d_factor]" min="0" max="100000" step="1" required>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label for="pid-dt">
|
||||
DT <small>in seconds</small>
|
||||
<input type="number" inputmode="numeric" id="pid-dt" name="pid[dt]" min="30" max="600" step="1" required>
|
||||
</label>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="grid">
|
||||
<label for="pid-min-temp">
|
||||
Minimum temperature
|
||||
<input type="number" inputmode="numeric" id="pid-min-temp" name="pid[minTemp]" min="0" max="0" step="1" required>
|
||||
</label>
|
||||
|
||||
<label for="pid-max-temp">
|
||||
Maximum temperature
|
||||
<input type="number" inputmode="numeric" id="pid-max-temp" name="pid[maxTemp]" min="0" max="0" step="1" required>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<hr />
|
||||
|
||||
<details>
|
||||
<summary><b>OpenTherm settings</b></summary>
|
||||
<div>
|
||||
<div id="opentherm-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/settings" id="opentherm-settings" class="hidden">
|
||||
<fieldset>
|
||||
<legend>Unit system</legend>
|
||||
|
||||
<label>
|
||||
<input type="radio" class="opentherm-unit-system" name="opentherm[unitSystem]" value="0" />
|
||||
Metric (celsius)
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<input type="radio" class="opentherm-unit-system" name="opentherm[unitSystem]" value="1" />
|
||||
Imperial (fahrenheit)
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<div class="grid">
|
||||
<label for="opentherm-in-gpio">
|
||||
In GPIO
|
||||
<input type="number" inputmode="numeric" id="opentherm-in-gpio" name="opentherm[inGpio]" min="0" max="254" step="1">
|
||||
</label>
|
||||
|
||||
<label for="opentherm-in-gpio">
|
||||
Out GPIO
|
||||
<input type="number" inputmode="numeric" id="opentherm-out-gpio" name="opentherm[outGpio]" min="0" max="254" step="1">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<label for="opentherm-rx-led-gpio">
|
||||
RX LED GPIO
|
||||
<input type="number" inputmode="numeric" id="opentherm-rx-led-gpio" name="opentherm[rxLedGpio]" min="0" max="254" step="1">
|
||||
<small>blank - not use</small>
|
||||
</label>
|
||||
|
||||
<label for="opentherm-member-id-code">
|
||||
Master MemberID code
|
||||
<input type="number" inputmode="numeric" id="opentherm-member-id-code" name="opentherm[memberIdCode]" min="0" max="65535" step="1" required>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<fieldset>
|
||||
<legend>Options</legend>
|
||||
<label for="opentherm-dhw-present">
|
||||
<input type="checkbox" id="opentherm-dhw-present" name="opentherm[dhwPresent]" value="true">
|
||||
DHW present
|
||||
</label>
|
||||
|
||||
<label for="opentherm-sw-mode">
|
||||
<input type="checkbox" id="opentherm-sw-mode" name="opentherm[summerWinterMode]" value="true">
|
||||
Summer/winter mode
|
||||
</label>
|
||||
|
||||
<label for="opentherm-heating-ch2-enabled">
|
||||
<input type="checkbox" id="opentherm-heating-ch2-enabled" name="opentherm[heatingCh2Enabled]" value="true">
|
||||
Heating CH2 always enabled
|
||||
</label>
|
||||
|
||||
<label for="opentherm-heating-ch1-to-ch2">
|
||||
<input type="checkbox" id="opentherm-heating-ch1-to-ch2" name="opentherm[heatingCh1ToCh2]" value="true">
|
||||
Duplicate heating CH1 to CH2
|
||||
</label>
|
||||
|
||||
<label for="opentherm-dhw-to-ch2">
|
||||
<input type="checkbox" id="opentherm-dhw-to-ch2" name="opentherm[dhwToCh2]" value="true">
|
||||
Duplicate DHW to CH2
|
||||
</label>
|
||||
|
||||
<label for="opentherm-dhw-blocking">
|
||||
<input type="checkbox" id="opentherm-dhw-blocking" name="opentherm[dhwBlocking]" value="true">
|
||||
DHW blocking
|
||||
</label>
|
||||
|
||||
<label for="opentherm-sync-modulation-with-heating">
|
||||
<input type="checkbox" id="opentherm-sync-modulation-with-heating" name="opentherm[modulationSyncWithHeating]" value="true">
|
||||
Sync modulation with heating
|
||||
</label>
|
||||
|
||||
<label for="opentherm-get-min-max-temp">
|
||||
<input type="checkbox" id="opentherm-get-min-max-temp" name="opentherm[getMinMaxTemp]" value="true">
|
||||
Get min/max temp from boiler
|
||||
</label>
|
||||
|
||||
<hr />
|
||||
<fieldset>
|
||||
<label for="opentherm-fault-state-gpio">
|
||||
Fault state GPIO
|
||||
<input type="number" inputmode="numeric" id="opentherm-fault-state-gpio" name="opentherm[faultStateGpio]" min="0" max="254" step="1">
|
||||
<small>Can be useful to switch on another boiler <u>via relay</u>. Blank - not use.</small>
|
||||
</label>
|
||||
|
||||
<label for="opentherm-invert-fault-state">
|
||||
<input type="checkbox" id="opentherm-invert-fault-state" name="opentherm[invertFaultState]" value="true">
|
||||
Invert fault state
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<hr />
|
||||
<label for="opentherm-native-heating-control">
|
||||
<input type="checkbox" id="opentherm-native-heating-control" name="opentherm[nativeHeatingControl]" value="true">
|
||||
Native heating control (boiler)<br />
|
||||
<small>Works <u>ONLY</u> if the boiler requires the desired room temperature and regulates the temperature of the coolant itself. Not compatible with PID and Equitherm regulators and hysteresis in firmware.</small>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<button type="submit">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<hr />
|
||||
|
||||
<details>
|
||||
<summary><b>MQTT settings</b></summary>
|
||||
<div>
|
||||
<div id="mqtt-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/settings" id="mqtt-settings" class="hidden">
|
||||
<fieldset>
|
||||
<label for="mqtt-enable">
|
||||
<input type="checkbox" id="mqtt-enable" name="mqtt[enable]" value="true">
|
||||
Enable
|
||||
</label>
|
||||
|
||||
<label for="mqtt-ha-discovery">
|
||||
<input type="checkbox" id="mqtt-ha-discovery" name="mqtt[homeAssistantDiscovery]" value="true">
|
||||
Home Assistant Discovery
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<div class="grid">
|
||||
<label for="mqtt-server">
|
||||
Server
|
||||
<input type="text" id="mqtt-server" name="mqtt[server]" maxlength="80" required>
|
||||
</label>
|
||||
|
||||
<label for="mqtt-port">
|
||||
Port
|
||||
<input type="number" inputmode="numeric" id="mqtt-port" name="mqtt[port]" min="1" max="65535" step="1" required>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<label for="mqtt-user">
|
||||
User
|
||||
<input type="text" id="mqtt-user" name="mqtt[user]" maxlength="32" required>
|
||||
</label>
|
||||
|
||||
<label for="mqtt-password">
|
||||
Password
|
||||
<input type="password" id="mqtt-password" name="mqtt[password]" maxlength="32">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<label for="mqtt-prefix">
|
||||
Prefix
|
||||
<input type="text" id="mqtt-prefix" name="mqtt[prefix]" maxlength="32" required>
|
||||
</label>
|
||||
|
||||
<label for="mqtt-interval">
|
||||
Publish interval <small>(sec)</small>
|
||||
<input type="number" inputmode="numeric" id="mqtt-interval" name="mqtt[interval]" min="3" max="60" step="1" required>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<hr />
|
||||
|
||||
<details>
|
||||
<summary><b>Outdoor sensor settings</b></summary>
|
||||
<div>
|
||||
<div id="outdoor-sensor-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/settings" id="outdoor-sensor-settings" class="hidden">
|
||||
<fieldset>
|
||||
<legend>Source type</legend>
|
||||
|
||||
<label>
|
||||
<input type="radio" class="outdoor-sensor-type" name="sensors[outdoor][type]" value="0" />
|
||||
From boiler via OpenTherm
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<input type="radio" class="outdoor-sensor-type" name="sensors[outdoor][type]" value="1" />
|
||||
Manual via MQTT/API
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<input type="radio" class="outdoor-sensor-type" name="sensors[outdoor][type]" value="2" />
|
||||
External (DS18B20)
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<label for="outdoor-sensor-gpio">
|
||||
GPIO
|
||||
<input type="number" inputmode="numeric" id="outdoor-sensor-gpio" name="sensors[outdoor][gpio]" min="0" max="254" step="1">
|
||||
</label>
|
||||
|
||||
<label for="outdoor-sensor-offset">
|
||||
Temp offset (calibration)
|
||||
<input type="number" inputmode="numeric" id="outdoor-sensor-offset" name="sensors[outdoor][offset]" min="-10" max="10" step="0.01" required>
|
||||
</label>
|
||||
|
||||
<button type="submit">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<hr />
|
||||
|
||||
<details>
|
||||
<summary><b>Indoor sensor settings</b></summary>
|
||||
<div>
|
||||
<div id="indoor-sensor-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/settings" id="indoor-sensor-settings" class="hidden">
|
||||
<fieldset>
|
||||
<legend>Source type</legend>
|
||||
|
||||
<label>
|
||||
<input type="radio" class="indoor-sensor-type" name="sensors[indoor][type]" value="1" />
|
||||
Manual via MQTT/API
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<input type="radio" class="indoor-sensor-type" name="sensors[indoor][type]" value="2" />
|
||||
External (DS18B20)
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<input type="radio" class="indoor-sensor-type" name="sensors[indoor][type]" value="3" />
|
||||
BLE device <i>(ONLY for some ESP32 which support BLE)</i>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<label for="indoor-sensor-gpio">
|
||||
GPIO
|
||||
<input type="number" inputmode="numeric" id="indoor-sensor-gpio" name="sensors[indoor][gpio]" min="0" max="254" step="1">
|
||||
</label>
|
||||
|
||||
<div class="grid">
|
||||
<label for="indoor-sensor-offset">
|
||||
Temp offset (calibration)
|
||||
<input type="number" inputmode="numeric" id="indoor-sensor-offset" name="sensors[indoor][offset]" min="-10" max="10" step="0.01" required>
|
||||
</label>
|
||||
|
||||
<label for="indoor-sensor-ble-addresss">
|
||||
BLE addresss
|
||||
<input type="text" id="indoor-sensor-ble-addresss" name="sensors[indoor][bleAddresss]" pattern="([A-Fa-f0-9]{2}:){5}[A-Fa-f0-9]{2}">
|
||||
<small>ONLY for some ESP32 which support BLE</small>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<hr />
|
||||
|
||||
<details>
|
||||
<summary><b>External pump settings</b></summary>
|
||||
<div>
|
||||
<div id="extpump-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/settings" id="extpump-settings" class="hidden">
|
||||
<fieldset>
|
||||
<label for="extpump-use">
|
||||
<input type="checkbox" id="extpump-use" name="externalPump[use]" value="true">
|
||||
Use external pump
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<div class="grid">
|
||||
<label for="extpump-gpio">
|
||||
Relay GPIO
|
||||
<input type="number" inputmode="numeric" id="extpump-gpio" name="externalPump[gpio]" min="0" max="254" step="1">
|
||||
</label>
|
||||
|
||||
<label for="extpump-pc-time">
|
||||
Post circulation time <small>(min)</small>
|
||||
<input type="number" inputmode="numeric" id="extpump-pc-time" name="externalPump[postCirculationTime]" min="1" max="120" step="1" required>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<label for="extpump-as-interval">
|
||||
Anti stuck interval <small>(days)</small>
|
||||
<input type="number" inputmode="numeric" id="extpump-as-interval" name="externalPump[antiStuckInterval]" min="1" max="366" step="1" required>
|
||||
</label>
|
||||
|
||||
<label for="extpump-as-time">
|
||||
Anti stuck time <small>(min)</small>
|
||||
<input type="number" inputmode="numeric" id="extpump-as-time" name="externalPump[antiStuckTime]" min="1" max="20" step="1" required>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
</article>
|
||||
</main>
|
||||
|
||||
<footer class="container">
|
||||
<small>
|
||||
<b>Made by Laxilef</b>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/blob/master/LICENSE" target="_blank" class="secondary">License</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/blob/master/" target="_blank" class="secondary">Source code</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/wiki" target="_blank" class="secondary">Help</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/issues" target="_blank" class="secondary">Issue & questions</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/releases" target="_blank" class="secondary">Releases</a>
|
||||
</small>
|
||||
</footer>
|
||||
|
||||
<script src="/static/app.js"></script>
|
||||
<script>
|
||||
window.onload = async function () {
|
||||
const fillData = (data) => {
|
||||
// System
|
||||
setCheckboxValue('#system-debug', data.system.debug);
|
||||
setCheckboxValue('#system-serial-enable', data.system.serial.enable);
|
||||
setInputValue('#system-serial-baudrate', data.system.serial.baudrate);
|
||||
setCheckboxValue('#system-telnet-enable', data.system.telnet.enable);
|
||||
setInputValue('#system-telnet-port', data.system.telnet.port);
|
||||
setRadioValue('.system-unit-system', data.system.unitSystem);
|
||||
setInputValue('#system-status-led-gpio', data.system.statusLedGpio < 255 ? data.system.statusLedGpio : '');
|
||||
setBusy('#system-settings-busy', '#system-settings', false);
|
||||
|
||||
// Portal
|
||||
setCheckboxValue('#portal-auth', data.portal.auth);
|
||||
setInputValue('#portal-login', data.portal.login);
|
||||
setInputValue('#portal-password', data.portal.password);
|
||||
setBusy('#portal-settings-busy', '#portal-settings', false);
|
||||
|
||||
// Opentherm
|
||||
setRadioValue('.opentherm-unit-system', data.opentherm.unitSystem);
|
||||
setInputValue('#opentherm-in-gpio', data.opentherm.inGpio < 255 ? data.opentherm.inGpio : '');
|
||||
setInputValue('#opentherm-out-gpio', data.opentherm.outGpio < 255 ? data.opentherm.outGpio : '');
|
||||
setInputValue('#opentherm-rx-led-gpio', data.opentherm.rxLedGpio < 255 ? data.opentherm.rxLedGpio : '');
|
||||
setInputValue('#opentherm-fault-state-gpio', data.opentherm.faultStateGpio < 255 ? data.opentherm.faultStateGpio : '');
|
||||
setCheckboxValue('#opentherm-invert-fault-state', data.opentherm.invertFaultState);
|
||||
setInputValue('#opentherm-member-id-code', data.opentherm.memberIdCode);
|
||||
setCheckboxValue('#opentherm-dhw-present', data.opentherm.dhwPresent);
|
||||
setCheckboxValue('#opentherm-sw-mode', data.opentherm.summerWinterMode);
|
||||
setCheckboxValue('#opentherm-heating-ch2-enabled', data.opentherm.heatingCh2Enabled);
|
||||
setCheckboxValue('#opentherm-heating-ch1-to-ch2', data.opentherm.heatingCh1ToCh2);
|
||||
setCheckboxValue('#opentherm-dhw-to-ch2', data.opentherm.dhwToCh2);
|
||||
setCheckboxValue('#opentherm-dhw-blocking', data.opentherm.dhwBlocking);
|
||||
setCheckboxValue('#opentherm-sync-modulation-with-heating', data.opentherm.modulationSyncWithHeating);
|
||||
setCheckboxValue('#opentherm-get-min-max-temp', data.opentherm.getMinMaxTemp);
|
||||
setCheckboxValue('#opentherm-native-heating-control', data.opentherm.nativeHeatingControl);
|
||||
setBusy('#opentherm-settings-busy', '#opentherm-settings', false);
|
||||
|
||||
// MQTT
|
||||
setCheckboxValue('#mqtt-enable', data.mqtt.enable);
|
||||
setCheckboxValue('#mqtt-ha-discovery', data.mqtt.homeAssistantDiscovery);
|
||||
setInputValue('#mqtt-server', data.mqtt.server);
|
||||
setInputValue('#mqtt-port', data.mqtt.port);
|
||||
setInputValue('#mqtt-user', data.mqtt.user);
|
||||
setInputValue('#mqtt-password', data.mqtt.password);
|
||||
setInputValue('#mqtt-prefix', data.mqtt.prefix);
|
||||
setInputValue('#mqtt-interval', data.mqtt.interval);
|
||||
setBusy('#mqtt-settings-busy', '#mqtt-settings', false);
|
||||
|
||||
// Outdoor sensor
|
||||
setRadioValue('.outdoor-sensor-type', data.sensors.outdoor.type);
|
||||
setInputValue('#outdoor-sensor-gpio', data.sensors.outdoor.gpio < 255 ? data.sensors.outdoor.gpio : '');
|
||||
setInputValue('#outdoor-sensor-offset', data.sensors.outdoor.offset);
|
||||
setBusy('#outdoor-sensor-settings-busy', '#outdoor-sensor-settings', false);
|
||||
|
||||
// Indoor sensor
|
||||
setRadioValue('.indoor-sensor-type', data.sensors.indoor.type);
|
||||
setInputValue('#indoor-sensor-gpio', data.sensors.indoor.gpio < 255 ? data.sensors.indoor.gpio : '');
|
||||
setInputValue('#indoor-sensor-offset', data.sensors.indoor.offset);
|
||||
setInputValue('#indoor-sensor-ble-addresss', data.sensors.indoor.bleAddresss);
|
||||
setBusy('#indoor-sensor-settings-busy', '#indoor-sensor-settings', false);
|
||||
|
||||
// Extpump
|
||||
setCheckboxValue('#extpump-use', data.externalPump.use);
|
||||
setInputValue('#extpump-gpio', data.externalPump.gpio < 255 ? data.externalPump.gpio : '');
|
||||
setInputValue('#extpump-pc-time', data.externalPump.postCirculationTime);
|
||||
setInputValue('#extpump-as-interval', data.externalPump.antiStuckInterval);
|
||||
setInputValue('#extpump-as-time', data.externalPump.antiStuckTime);
|
||||
setBusy('#extpump-settings-busy', '#extpump-settings', false);
|
||||
|
||||
// Heating
|
||||
setInputValue('#heating-min-temp', data.heating.minTemp, {
|
||||
"min": data.system.unitSystem == 0 ? 0 : 32,
|
||||
"max": data.system.unitSystem == 0 ? 99 : 211
|
||||
});
|
||||
setInputValue('#heating-max-temp', data.heating.maxTemp, {
|
||||
"min": data.system.unitSystem == 0 ? 1 : 33,
|
||||
"max": data.system.unitSystem == 0 ? 100 : 212
|
||||
});
|
||||
setInputValue('#heating-hysteresis', data.heating.hysteresis);
|
||||
setInputValue('#heating-max-modulation', data.heating.maxModulation);
|
||||
setBusy('#heating-settings-busy', '#heating-settings', false);
|
||||
|
||||
// DHW
|
||||
setInputValue('#dhw-min-temp', data.dhw.minTemp, {
|
||||
"min": data.system.unitSystem == 0 ? 0 : 32,
|
||||
"max": data.system.unitSystem == 0 ? 99 : 211
|
||||
});
|
||||
setInputValue('#dhw-max-temp', data.dhw.maxTemp, {
|
||||
"min": data.system.unitSystem == 0 ? 1 : 33,
|
||||
"max": data.system.unitSystem == 0 ? 100 : 212
|
||||
});
|
||||
setBusy('#dhw-settings-busy', '#dhw-settings', false);
|
||||
|
||||
// Emergency mode
|
||||
setCheckboxValue('#emergency-enable', data.emergency.enable);
|
||||
setInputValue('#emergency-treshold-time', data.emergency.tresholdTime);
|
||||
setCheckboxValue('#emergency-use-equitherm', data.emergency.useEquitherm);
|
||||
setCheckboxValue('#emergency-use-pid', data.emergency.usePid);
|
||||
setCheckboxValue('#emergency-on-network-fault', data.emergency.onNetworkFault);
|
||||
setCheckboxValue('#emergency-on-mqtt-fault', data.emergency.onMqttFault);
|
||||
setInputValue('#emergency-target', data.emergency.target, {
|
||||
"min": (!data.emergency.useEquitherm && !data.emergency.usePid) ? data.heating.minTemp : 10,
|
||||
"max": (!data.emergency.useEquitherm && !data.emergency.usePid) ? data.heating.maxTemp : 30,
|
||||
});
|
||||
setBusy('#emergency-settings-busy', '#emergency-settings', false);
|
||||
|
||||
// Equitherm
|
||||
setCheckboxValue('#equitherm-enable', data.equitherm.enable);
|
||||
setInputValue('#equitherm-n-factor', data.equitherm.n_factor);
|
||||
setInputValue('#equitherm-k-factor', data.equitherm.k_factor);
|
||||
setInputValue('#equitherm-t-factor', data.equitherm.t_factor);
|
||||
setBusy('#equitherm-settings-busy', '#equitherm-settings', false);
|
||||
|
||||
// PID
|
||||
setCheckboxValue('#pid-enable', data.pid.enable);
|
||||
setInputValue('#pid-p-factor', data.pid.p_factor);
|
||||
setInputValue('#pid-i-factor', data.pid.i_factor);
|
||||
setInputValue('#pid-d-factor', data.pid.d_factor);
|
||||
setInputValue('#pid-dt', data.pid.dt);
|
||||
setInputValue('#pid-min-temp', data.pid.minTemp, {
|
||||
"min": 0,
|
||||
"max": data.system.unitSystem == 0 ? 99 : 211
|
||||
});
|
||||
setInputValue('#pid-max-temp', data.pid.maxTemp, {
|
||||
"min": 1,
|
||||
"max": data.system.unitSystem == 0 ? 100 : 212
|
||||
});
|
||||
setBusy('#pid-settings-busy', '#pid-settings', false);
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/settings', { cache: 'no-cache' });
|
||||
if (!response.ok) {
|
||||
throw new Error('Response not valid');
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
fillData(result);
|
||||
|
||||
setupForm('#portal-settings', fillData, ['portal.login', 'portal.password']);
|
||||
setupForm('#system-settings', fillData);
|
||||
setupForm('#heating-settings', fillData);
|
||||
setupForm('#dhw-settings', fillData);
|
||||
setupForm('#emergency-settings', fillData);
|
||||
setupForm('#equitherm-settings', fillData);
|
||||
setupForm('#pid-settings', fillData);
|
||||
setupForm('#opentherm-settings', fillData);
|
||||
setupForm('#mqtt-settings', fillData, ['mqtt.user', 'mqtt.password', 'mqtt.prefix']);
|
||||
setupForm('#outdoor-sensor-settings', fillData);
|
||||
setupForm('#indoor-sensor-settings', fillData, ['sensors.indoor.bleAddresss']);
|
||||
setupForm('#extpump-settings', fillData);
|
||||
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -25,9 +25,9 @@
|
||||
--pico-block-spacing-horizontal: calc(var(--pico-spacing) * 1.5);
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1000px;
|
||||
}
|
||||
.container {
|
||||
max-width: 1000px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1536px) {
|
||||
@@ -36,77 +36,82 @@
|
||||
--pico-block-spacing-horizontal: calc(var(--pico-spacing) * 1.75);
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1000px;
|
||||
}
|
||||
.container {
|
||||
max-width: 1000px;
|
||||
}
|
||||
}
|
||||
|
||||
header, main, footer {
|
||||
padding-top: 1rem !important;
|
||||
padding-bottom: 1rem !important;
|
||||
header,
|
||||
main,
|
||||
footer {
|
||||
padding-top: 1rem !important;
|
||||
padding-bottom: 1rem !important;
|
||||
}
|
||||
|
||||
article {
|
||||
margin-bottom: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
footer {
|
||||
text-align: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
nav li a:has(> div.logo) {
|
||||
margin-bottom: 0;
|
||||
/*nav li a:has(> div.logo) {
|
||||
margin-bottom: 0;
|
||||
}*/
|
||||
nav li :where(a,[role=link]) {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
details > div {
|
||||
padding: 0 var(--pico-form-element-spacing-horizontal);
|
||||
details>div {
|
||||
padding: 0 var(--pico-form-element-spacing-horizontal);
|
||||
}
|
||||
|
||||
pre {
|
||||
padding: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
|
||||
.hidden {
|
||||
display: none !important;
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
button.success {
|
||||
background-color: var(--pico-form-element-valid-border-color);
|
||||
border-color: var(--pico-form-element-valid-border-color);
|
||||
background-color: var(--pico-form-element-valid-border-color);
|
||||
border-color: var(--pico-form-element-valid-border-color);
|
||||
}
|
||||
|
||||
button.failed {
|
||||
background-color: var(--pico-form-element-invalid-border-color);
|
||||
border-color: var(--pico-form-element-invalid-border-color);
|
||||
background-color: var(--pico-form-element-invalid-border-color);
|
||||
border-color: var(--pico-form-element-invalid-border-color);
|
||||
}
|
||||
|
||||
tr.network:hover {
|
||||
--pico-background-color: var(--pico-primary-focus);
|
||||
cursor: pointer;
|
||||
--pico-background-color: var(--pico-primary-focus);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.primary {
|
||||
border: 0.25rem solid var(--pico-form-element-invalid-border-color);
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
border: 0.25rem solid var(--pico-form-element-invalid-border-color);
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: inline-block;
|
||||
padding: calc(var(--pico-nav-link-spacing-vertical) - var(--pico-border-width) * 2) var(--pico-nav-link-spacing-horizontal);
|
||||
vertical-align: baseline;
|
||||
line-height: var(--pico-line-height);
|
||||
background-color: var(--pico-code-kbd-background-color);
|
||||
border-radius: var(--pico-border-radius);
|
||||
display: inline-block;
|
||||
padding: calc(var(--pico-nav-link-spacing-vertical) - var(--pico-border-width) * 2) var(--pico-nav-link-spacing-horizontal);
|
||||
vertical-align: baseline;
|
||||
line-height: var(--pico-line-height);
|
||||
background-color: var(--pico-code-kbd-background-color);
|
||||
border-radius: var(--pico-border-radius);
|
||||
color: var(--pico-code-kbd-color);
|
||||
font-weight: bolder;
|
||||
font-size: 1.3rem;
|
||||
font-size: 1.3rem;
|
||||
font-family: var(--pico-font-family-monospace);
|
||||
}
|
||||
|
||||
.thermostat {
|
||||
display: grid;
|
||||
display: grid;
|
||||
grid-template-columns: 0.5fr 2fr 0.5fr;
|
||||
grid-template-rows: 0.25fr 1fr 0.25fr;
|
||||
gap: 0px 0px;
|
||||
@@ -118,8 +123,8 @@ tr.network:hover {
|
||||
"thermostat-minus thermostat-temp thermostat-plus"
|
||||
"thermostat-control thermostat-control thermostat-control";
|
||||
|
||||
border: .25rem solid var(--pico-blockquote-border-color);
|
||||
padding: 0.5rem;
|
||||
border: .25rem solid var(--pico-blockquote-border-color);
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.thermostat-header {
|
||||
@@ -127,14 +132,14 @@ tr.network:hover {
|
||||
align-self: end;
|
||||
grid-area: thermostat-header;
|
||||
|
||||
font-size: 1rem;
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
border-bottom: .25rem solid var(--pico-primary-hover-border);
|
||||
margin: 0 0 1rem 0;
|
||||
margin: 0 0 1rem 0;
|
||||
}
|
||||
|
||||
.thermostat-temp {
|
||||
display: grid;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr 0.5fr;
|
||||
gap: 0px 0px;
|
||||
@@ -150,7 +155,7 @@ tr.network:hover {
|
||||
align-self: center;
|
||||
grid-area: thermostat-temp-target;
|
||||
|
||||
font-weight: bold;
|
||||
font-weight: bold;
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
|
||||
@@ -159,7 +164,7 @@ tr.network:hover {
|
||||
align-self: start;
|
||||
grid-area: thermostat-temp-current;
|
||||
|
||||
color: var(--pico-secondary);
|
||||
color: var(--pico-secondary);
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
@@ -180,15 +185,19 @@ tr.network:hover {
|
||||
align-self: start;
|
||||
grid-area: thermostat-control;
|
||||
|
||||
margin: 1.25rem 0;
|
||||
margin: 1.25rem 0;
|
||||
}
|
||||
|
||||
[class*=" icons-"],[class=icons],[class^=icons-] {font-size: 1.35rem; }
|
||||
*:has(> [class*=" icons-"], > [class=icons], > [class^=icons-]):has(+ * > [class*=" icons-"], + * > [class=icons], + * > [class^=icons-]) { margin: 0 0.5rem 0 0; }
|
||||
[data-tooltip]:has(> [class*=" icons-"], > [class=icons], > [class^=icons-]) { border: 0!important; }
|
||||
[class*=" icons-"],
|
||||
[class=icons],
|
||||
[class^=icons-] {
|
||||
font-size: 1.35rem;
|
||||
}
|
||||
|
||||
*:has(> [class*=" icons-"], > [class=icons], > [class^=icons-]):has(+ * > [class*=" icons-"], + * > [class=icons], + * > [class^=icons-]) {
|
||||
margin: 0 0.5rem 0 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Icons icon font. Generated by Iconly: https://iconly.io/
|
||||
*/
|
||||
@font-face{font-display:auto;font-family:"Icons";font-style:normal;font-weight:400;src:url(./fonts/iconly.eot?1717885802370);src:url(./fonts/iconly.eot?#iefix) format("embedded-opentype"),url(./fonts/iconly.woff2?1717885802370) format("woff2"),url(./fonts/iconly.woff?1717885802370) format("woff"),url(./fonts/iconly.ttf?1717885802370) format("truetype")}[class*=" icons-"],[class=icons],[class^=icons-]{display:inline-block;font-family:"Icons"!important;font-weight:400;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}.icons-plus:before{content:"\e000"}.icons-minus:before{content:"\e001"}.icons-unlocked:before{content:"\e002"}.icons-locked:before{content:"\e003"}.icons-wifi-strength-1:before{content:"\e004"}.icons-wifi-strength-0:before{content:"\e005"}.icons-wifi-strength-2:before{content:"\e006"}.icons-wifi-strength-3:before{content:"\e008"}.icons-down:before{content:"\e009"}.icons-wifi-strength-4:before{content:"\e00a"}.icons-up:before{content:"\e00c"}
|
||||
[data-tooltip]:has(> [class*=" icons-"], > [class=icons], > [class^=icons-]) {
|
||||
border: 0 !important;
|
||||
}
|
||||
68
src_data/styles/iconly.css
Normal file
@@ -0,0 +1,68 @@
|
||||
/*!
|
||||
* Icons icon font. Generated by Iconly: https://iconly.io/
|
||||
*/
|
||||
|
||||
@font-face {
|
||||
font-display: auto;
|
||||
font-family: "Icons";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url("/static/fonts/iconly.eot?1718563596894");
|
||||
src: url("/static/fonts/iconly.eot?#iefix") format("embedded-opentype"), url("/static/fonts/iconly.woff2?1718563596894") format("woff2"), url("/static/fonts/iconly.woff?1718563596894") format("woff"), url("/static/fonts/iconly.ttf?1718563596894") format("truetype"), url("/static/fonts/iconly.svg?1718563596894#Icons") format("svg");
|
||||
}
|
||||
|
||||
[class="icons"], [class^="icons-"], [class*=" icons-"] {
|
||||
display: inline-block;
|
||||
font-family: "Icons" !important;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-variant: normal;
|
||||
text-rendering: auto;
|
||||
line-height: 1;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.icons-plus:before {
|
||||
content: "\e000";
|
||||
}
|
||||
|
||||
.icons-minus:before {
|
||||
content: "\e001";
|
||||
}
|
||||
|
||||
.icons-unlocked:before {
|
||||
content: "\e002";
|
||||
}
|
||||
|
||||
.icons-locked:before {
|
||||
content: "\e003";
|
||||
}
|
||||
|
||||
.icons-wifi-strength-1:before {
|
||||
content: "\e004";
|
||||
}
|
||||
|
||||
.icons-wifi-strength-0:before {
|
||||
content: "\e005";
|
||||
}
|
||||
|
||||
.icons-wifi-strength-2:before {
|
||||
content: "\e006";
|
||||
}
|
||||
|
||||
.icons-wifi-strength-3:before {
|
||||
content: "\e008";
|
||||
}
|
||||
|
||||
.icons-down:before {
|
||||
content: "\e009";
|
||||
}
|
||||
|
||||
.icons-wifi-strength-4:before {
|
||||
content: "\e00a";
|
||||
}
|
||||
|
||||
.icons-up:before {
|
||||
content: "\e00c";
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Upgrade - OpenTherm Gateway</title>
|
||||
<link rel="stylesheet" href="/static/pico.min.css">
|
||||
<link rel="stylesheet" href="/static/app.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header class="container">
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/"><div class="logo">OpenTherm Gateway</div></a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li><a href="https://github.com/Laxilef/OTGateway/wiki" role="button" class="secondary" target="_blank">Help</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<article>
|
||||
<div>
|
||||
<hgroup>
|
||||
<h2>Backup & restore</h2>
|
||||
<p>
|
||||
In this section you can save and restore a backup of ALL settings.
|
||||
</p>
|
||||
</hgroup>
|
||||
|
||||
<form action="/api/backup/restore" id="restore">
|
||||
<label for="restore-file">
|
||||
Settings file:
|
||||
<input type="file" name="settings" id="restore-file" accept=".json">
|
||||
</label>
|
||||
|
||||
<div class="grid">
|
||||
<button type="submit">Restore</button>
|
||||
<button type="button" class="secondary" onclick="window.location='/api/backup/save';">Backup</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article>
|
||||
<div>
|
||||
<hgroup>
|
||||
<h2>Upgrade</h2>
|
||||
<p>
|
||||
In this section you can upgrade the firmware and filesystem of your device.<br />
|
||||
Latest releases can be downloaded from the <a href="https://github.com/Laxilef/OTGateway/releases" target="_blank">Releases page</a> of the project repository.
|
||||
</p>
|
||||
</hgroup>
|
||||
|
||||
<form action="/api/upgrade" id="upgrade">
|
||||
<fieldset class="primary">
|
||||
<label for="firmware-file">
|
||||
Firmware:
|
||||
<div class="grid">
|
||||
<input type="file" name="firmware" id="firmware-file" accept=".bin">
|
||||
<button type="button" class="upgrade-firmware-result hidden" disabled></button>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label for="filesystem-file">
|
||||
Filesystem:
|
||||
<div class="grid">
|
||||
<input type="file" name="filesystem" id="filesystem-file" accept=".bin">
|
||||
<button type="button" class="upgrade-filesystem-result hidden" disabled></button>
|
||||
</div>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<ul>
|
||||
<li><mark>After a successful upgrade the filesystem, ALL settings will be reset to default values! Save backup before upgrading.</mark></li>
|
||||
<li><mark>After a successful upgrade, the device will automatically reboot after 10 seconds.</mark></li>
|
||||
</ul>
|
||||
|
||||
<button type="submit">Upgrade</button>
|
||||
</form>
|
||||
</div>
|
||||
</article>
|
||||
</main>
|
||||
|
||||
<footer class="container">
|
||||
<small>
|
||||
<b>Made by Laxilef</b>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/blob/master/LICENSE" target="_blank" class="secondary">License</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/blob/master/" target="_blank" class="secondary">Source code</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/wiki" target="_blank" class="secondary">Help</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/issues" target="_blank" class="secondary">Issue & questions</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/releases" target="_blank" class="secondary">Releases</a>
|
||||
</small>
|
||||
</footer>
|
||||
|
||||
<script src="/static/app.js"></script>
|
||||
<script>
|
||||
window.onload = async function () {
|
||||
setupRestoreBackupForm('#restore');
|
||||
setupUpgradeForm('#upgrade');
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -14,6 +14,9 @@ def post_build(source, target, env):
|
||||
|
||||
|
||||
def before_buildfs(source, target, env):
|
||||
env.Execute("npm install --silent")
|
||||
env.Execute("npx gulp build_all --no-deprecation")
|
||||
"""
|
||||
src = os.path.join(env["PROJECT_DIR"], "src_data")
|
||||
dst = os.path.join(env["PROJECT_DIR"], "data")
|
||||
|
||||
@@ -32,6 +35,7 @@ def before_buildfs(source, target, env):
|
||||
shutil.copyfileobj(f_in, f_out)
|
||||
|
||||
print("Compressed '%s' to '%s'" % (src_path, dst_path))
|
||||
"""
|
||||
|
||||
def after_buildfs(source, target, env):
|
||||
copy_to_build_dir({
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"version": 1,
|
||||
"editor": "wokwi",
|
||||
"parts": [
|
||||
{ "type": "wokwi-esp32-devkit-v1", "id": "esp", "top": 0, "left": 0, "attrs": {} }
|
||||
],
|
||||
"connections": [
|
||||
[ "esp:TX0", "$serialMonitor:RX", "", [] ],
|
||||
[ "esp:RX0", "$serialMonitor:TX", "", [] ]
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
[wokwi]
|
||||
version = 1
|
||||
elf = "../../.pio/build/nodemcu_32s/firmware.elf"
|
||||
firmware = "../../.pio/build/nodemcu_32s/firmware.bin"
|
||||
|
||||
[[net.forward]]
|
||||
from = "localhost:9080"
|
||||
to = "target:80"
|
||||