mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2026-04-29 12:05:12 +00:00
Compare commits
66 Commits
a813d38108
...
v3.7.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eaa277fef0 | ||
|
|
e418b7d8e7 | ||
|
|
a4e3be6a69 | ||
|
|
a1bc5bb055 | ||
|
|
f444ca31e0 | ||
|
|
1b70b55989 | ||
|
|
1487f30c43 | ||
|
|
e00eb8e64f | ||
|
|
f41bb3671c | ||
|
|
22c75e6df3 | ||
|
|
5ab10b7aa6 | ||
|
|
ee5fd4d0eb | ||
|
|
46f35bc67c | ||
|
|
ec85a7ec24 | ||
|
|
02f2389587 | ||
|
|
7f140021aa | ||
|
|
6796962c1e | ||
|
|
df6de21cf4 | ||
|
|
df9f75a5c9 | ||
|
|
7bd8710eb6 | ||
|
|
32f2c6d341 | ||
|
|
86919c1684 | ||
|
|
86e29515e7 | ||
|
|
46eb4185d7 | ||
|
|
8da6761a48 | ||
|
|
9233f0dfcc | ||
|
|
292f743b14 | ||
|
|
dd6dfffd57 | ||
|
|
ec705a5307 | ||
|
|
f45f071710 | ||
|
|
f3858546de | ||
|
|
d0ac0b7804 | ||
|
|
d8284ec09f | ||
|
|
6e982acde8 | ||
|
|
8c94ce99b2 | ||
|
|
fc057d18c9 | ||
|
|
18e9b99413 | ||
|
|
a47e0e8266 | ||
|
|
f412ddc716 | ||
|
|
29110e96e5 | ||
|
|
b65866217a | ||
|
|
611e3b1243 | ||
|
|
2ca0a0c634 | ||
|
|
7eb1f061b7 | ||
|
|
50459a23fe | ||
|
|
5bf53c3389 | ||
|
|
4b7aa95be3 | ||
|
|
70943f5758 | ||
|
|
3bc280b817 | ||
|
|
62b15a5319 | ||
|
|
8dd18802d6 | ||
|
|
57a516a83a | ||
|
|
a57fdaa4b3 | ||
|
|
4841e42286 | ||
|
|
8c2d2b06ed | ||
|
|
38c8b1b7f0 | ||
|
|
6fb5933a02 | ||
|
|
c0944433be | ||
|
|
478e6362c9 | ||
|
|
4d6354db78 | ||
|
|
beab0f0c77 | ||
|
|
c17749bd22 | ||
|
|
2bad769c5c | ||
|
|
8ad89ca64b | ||
|
|
9244d8daec | ||
|
|
02d01334b2 |
@@ -1,6 +1,7 @@
|
||||
name: 'github-releases-to-discord'
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
|
||||
2
.github/workflows/pre_release.yml
vendored
2
.github/workflows/pre_release.yml
vendored
@@ -50,6 +50,8 @@ jobs:
|
||||
- name: Build all PIO target environments from default_envs
|
||||
run: |
|
||||
platformio run
|
||||
env:
|
||||
NO_BUILD_WEBUI: true
|
||||
|
||||
- name: Create GitHub Release
|
||||
id: 'automatic_releases'
|
||||
|
||||
2
.github/workflows/tagged_release.yml
vendored
2
.github/workflows/tagged_release.yml
vendored
@@ -44,6 +44,8 @@ jobs:
|
||||
- name: Build all PIO target environments from default_envs
|
||||
run: |
|
||||
platformio run
|
||||
env:
|
||||
NO_BUILD_WEBUI: true
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: emsesp/action-automatic-releases@v1.0.0
|
||||
|
||||
11
.github/workflows/test_release.yml
vendored
11
.github/workflows/test_release.yml
vendored
@@ -12,24 +12,30 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Enable Corepack
|
||||
run: corepack enable
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Use Node.js 20.x
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20.x'
|
||||
|
||||
- name: Get EMS-ESP source code and version
|
||||
id: build_info
|
||||
run: |
|
||||
version=`grep -E '^#define EMSESP_APP_VERSION' ./src/version.h | awk -F'"' '{print $2}'`
|
||||
echo "VERSION=$version" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Install PlatformIO
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -U platformio
|
||||
|
||||
- name: Build WebUI
|
||||
run: |
|
||||
cd interface
|
||||
@@ -38,9 +44,13 @@ jobs:
|
||||
sed -i "s/= 'pl'/= 'en'/" ./src/i18n/i18n-util.ts
|
||||
yarn build
|
||||
yarn webUI
|
||||
|
||||
- name: Build all target environments from default_envs
|
||||
run: |
|
||||
platformio run
|
||||
env:
|
||||
NO_BUILD_WEBUI: true
|
||||
|
||||
- name: Create GitHub Release
|
||||
id: 'automatic_releases'
|
||||
uses: emsesp/action-automatic-releases@v1.0.0
|
||||
@@ -52,3 +62,4 @@ jobs:
|
||||
files: |
|
||||
CHANGELOG_LATEST.md
|
||||
./build/firmware/*.*
|
||||
|
||||
|
||||
16
.gitignore
vendored
16
.gitignore
vendored
@@ -12,17 +12,15 @@ cppcheck.out.xml
|
||||
# platformio
|
||||
.pio
|
||||
pio_local.ini
|
||||
*_old
|
||||
|
||||
# OS specific
|
||||
.DS_Store
|
||||
*Thumbs.db
|
||||
|
||||
# web specfic
|
||||
# web specific
|
||||
build/
|
||||
dist/
|
||||
/data/www
|
||||
/lib/framework/WWWData.h
|
||||
/interface/build
|
||||
node_modules
|
||||
/interface/.eslintcache
|
||||
@@ -35,6 +33,9 @@ stats.html
|
||||
analyse.html
|
||||
interface/vite.config.ts.timestamp*
|
||||
*.local
|
||||
src/ESP32React/WWWData.h
|
||||
.yarn/*
|
||||
.yarnrc.yml
|
||||
|
||||
# i18n generated files
|
||||
interface/src/i18n/i18n-react.tsx
|
||||
@@ -66,3 +67,12 @@ words-found-verbose.txt
|
||||
|
||||
# sonarlint
|
||||
compile_commands.json
|
||||
|
||||
# pioarduino + hybrid
|
||||
managed_components
|
||||
dependencies.lock
|
||||
CMakeLists.txt
|
||||
.dummy/*
|
||||
logs/*
|
||||
sdkconfig.*
|
||||
sdkconfig_tasmota_esp32
|
||||
|
||||
73
CHANGELOG.md
73
CHANGELOG.md
@@ -5,6 +5,77 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [3.7.2] 22 March 2025
|
||||
|
||||
## Added
|
||||
|
||||
- change enum_heatingtype for remote control [#2268](https://github.com/emsesp/EMS-ESP32/issues/2268)
|
||||
- system service commands [#2182](https://github.com/emsesp/EMS-ESP32/issues/2182)
|
||||
- read 0x02A5 for thermostat CT200 [#2277](https://github.com/emsesp/EMS-ESP32/issues/2277)
|
||||
- add "duplicate" option to Custom Entities [#2266](https://github.com/emsesp/EMS-ESP32/discussion/2266)
|
||||
- mask bits for bool custom entities
|
||||
- thermostat `reduce threshold` [#2288](https://github.com/emsesp/EMS-ESP32/issues/2288)
|
||||
- thermostat `absent` [#1957](https://github.com/emsesp/EMS-ESP32/issues/1957)
|
||||
- CR11 thermostat [#2295](https://github.com/emsesp/EMS-ESP32/issues/2295)
|
||||
- Show ESP32's CPU temp in Hardware Status
|
||||
- vacation mode for the CR50 [#2403](https://github.com/emsesp/EMS-ESP32/issues/2403)
|
||||
- new Console command "set admin password" to set WebUI admin password
|
||||
- support nested conditions in scheduler [#2451](https://github.com/emsesp/EMS-ESP32/issues/2451)
|
||||
- allow mixed case in scheduler expressions [#2457](https://github.com/emsesp/EMS-ESP32/issues/2457)
|
||||
- Suprapur-o [#2470](https://github.com/emsesp/EMS-ESP32/issues/2470)
|
||||
|
||||
## Fixed
|
||||
|
||||
- long numbers of custom entities [#2267](https://github.com/emsesp/EMS-ESP32/issues/2267)
|
||||
- modbus command path to `api/` [#2276](https://github.com/emsesp/EMS-ESP32/issues/2276)
|
||||
- info command for devices without entity-commands [#2274](https://github.com/emsesp/EMS-ESP32/issues/2274)
|
||||
- CW100 settings telegram 0x241 [#2290](https://github.com/emsesp/EMS-ESP32/issues/2290)
|
||||
- modbus signed 8bit values [#2294](https://github.com/emsesp/EMS-ESP32/issues/2294)
|
||||
- thermostat date [#2313](https://github.com/emsesp/EMS-ESP32/issues/2313)
|
||||
- Updated unknown compressor stati "enum_hpactivity" [#2311](https://github.com/emsesp/EMS-ESP32/pull/2311)
|
||||
- Underline Tab headers in WebUI
|
||||
- console unit tests fixed due to changed shell output
|
||||
- tx-queue overflow in some heatpump systems [#2455](https://github.com/emsesp/EMS-ESP32/issues/2455)
|
||||
|
||||
## Changed
|
||||
|
||||
- show operation in pretty telegram between src and dst [#2263](https://github.com/emsesp/EMS-ESP32/discussions/2263)
|
||||
- update eModbus to 1.7.2 [#2254](https://github.com/emsesp/EMS-ESP32/issues/2254)
|
||||
- modbus timeout default to 300 sec, change setting from ms to sec [#2254](https://github.com/emsesp/EMS-ESP32/issues/2254)
|
||||
- update AsyncTCP and ESPAsyncWebServer to latest versions
|
||||
- update Arduino pio platform to 3.10.0 and optimized flash using build flags
|
||||
- Version checker in WebUI improved
|
||||
- rename `remoteseltemp` to `cooltemp` [#2456](https://github.com/emsesp/EMS-ESP32/issues/2456)
|
||||
|
||||
## [3.7.1] 29 November 2024
|
||||
|
||||
## Added
|
||||
|
||||
- include HA "unit_of_meas", "stat_cla" and "dev_cla" attributes for Number sensors [#2149](https://github.com/emsesp/EMS-ESP32/issues/2149)
|
||||
- Bosch CS6800i AW - Silent Mode + Electrical Power Reduction (HP) [#2147](https://github.com/emsesp/EMS-ESP32/issues/2147)
|
||||
- `/api/system/showeralert` and `/api/system/showertimer` [#2182](https://github.com/emsesp/EMS-ESP32/issues/2182)
|
||||
- MX400 [#2198](https://github.com/emsesp/EMS-ESP32/issues/2198)
|
||||
- SM200 values [#2212](https://github.com/emsesp/EMS-ESP32/discussions/2212)
|
||||
|
||||
## Fixed
|
||||
|
||||
- Modbus integration in 3.7.0 missing offset [#2148](https://github.com/emsesp/EMS-ESP32/issues/2148)
|
||||
- fix changing TZ in NTPsettings without clearing enable+server, added DST support [#2142](https://github.com/emsesp/EMS-ESP32/issues/2142)
|
||||
- Support MQTT Discovery (AD) with Domoticz [#2177](https://github.com/emsesp/EMS-ESP32/issues/2177)
|
||||
- wwExtra (dhw extra) changed from temperature reading to number
|
||||
- auxheaterstatus [#2192](https://github.com/emsesp/EMS-ESP32/issues/2192)
|
||||
- lastCode character check [#2189](https://github.com/emsesp/EMS-ESP32/issues/2189)
|
||||
- reading too many telegram parts
|
||||
- heatpump cost UOMs [#2188](https://github.com/emsesp/EMS-ESP32/issues/2188)
|
||||
- analog dac output and inputs on dac pins [#2201](https://github.com/emsesp/EMS-ESP32/discussions/2201)
|
||||
- api memory leak [#2216](https://github.com/emsesp/EMS-ESP32/issues/2216)
|
||||
- modbus multiple mixers [#2229](https://github.com/emsesp/EMS-ESP32/issues/2229)
|
||||
- Last Will (LWT) not set on MQTT Connect [#2247](https://github.com/emsesp/EMS-ESP32/issues/2247)
|
||||
|
||||
## Changed
|
||||
|
||||
- name of wwstarts2 [#2217](https://github.com/emsesp/EMS-ESP32/discussions/2217)
|
||||
|
||||
## [3.7.0] 27 October 2024
|
||||
|
||||
## **IMPORTANT! BREAKING CHANGES with 3.6.5**
|
||||
@@ -146,7 +217,7 @@ For more details go to [docs.emsesp.org](https://docs.emsesp.org/).
|
||||
|
||||
## **IMPORTANT! BREAKING CHANGES**
|
||||
|
||||
Writeable Text entities have moved from type `sensor` to `text` in Home Assistant to make them also editable within an HA dashboard. Examples are `datetime`, `holidays`, `switchtime`, `vacations`, `maintenancedate`... You will need to manually remove any old discovery topics from your MQTT broker using an application like MQTT Explorer.
|
||||
Writeable Text entities have moved from type `sensor` to `text` in Home Assistant to make them also editable within an HA dashboard. Examples are `datetime`, `holidays`, `switchtime`, `vacations`, `maintenancedate`. You will need to manually remove any old discovery topics from your MQTT broker using an application like MQTT Explorer.
|
||||
|
||||
## Added
|
||||
|
||||
|
||||
@@ -1,34 +1 @@
|
||||
# Changelog
|
||||
|
||||
For more details go to [docs.emsesp.org](https://docs.emsesp.org/).
|
||||
|
||||
## [3.7.1]
|
||||
|
||||
## **IMPORTANT! BREAKING CHANGES since v3.7.0**
|
||||
|
||||
## Added
|
||||
|
||||
- include HA "unit_of_meas", "stat_cla" and "dev_cla" attributes for Number sensors [#2149](https://github.com/emsesp/EMS-ESP32/issues/2149)
|
||||
- Bosch CS6800i AW - Silent Mode + Electrical Power Reduction (HP) [#2147](https://github.com/emsesp/EMS-ESP32/issues/2147)
|
||||
- `/api/system/showeralert` and `/api/system/showertimer` [#2182](https://github.com/emsesp/EMS-ESP32/issues/2182)
|
||||
- MX400 [#2198](https://github.com/emsesp/EMS-ESP32/issues/2198)
|
||||
- SM200 values [#2212](https://github.com/emsesp/EMS-ESP32/discussions/2212)
|
||||
|
||||
## Fixed
|
||||
|
||||
- Modbus integration in 3.7.0 missing offset [#2148](https://github.com/emsesp/EMS-ESP32/issues/2148)
|
||||
- fix changing TZ in NTPsettings without clearing enable+server, added DST support [#2142](https://github.com/emsesp/EMS-ESP32/issues/2142)
|
||||
- Support MQTT Discovery (AD) with Domoticz [#2177](https://github.com/emsesp/EMS-ESP32/issues/2177)
|
||||
- wwExtra (dhw extra) changed from temperature reading to number
|
||||
- auxheaterstatus [#2192](https://github.com/emsesp/EMS-ESP32/issues/2192)
|
||||
- lastCode character check [#2189](https://github.com/emsesp/EMS-ESP32/issues/2189)
|
||||
- reading too many telegram parts
|
||||
- heatpump cost UOMs [#2188](https://github.com/emsesp/EMS-ESP32/issues/2188)
|
||||
- analog dac output and inputs on dac pins [#2201](https://github.com/emsesp/EMS-ESP32/discussions/2201)
|
||||
- api memory leak [#2216](https://github.com/emsesp/EMS-ESP32/issues/2216)
|
||||
- modbus multiple mixers [#2229](https://github.com/emsesp/EMS-ESP32/issues/2229)
|
||||
- Last Will (LWT) not set on MQTT Connect [#2247](https://github.com/emsesp/EMS-ESP32/issues/2247)
|
||||
|
||||
## Changed
|
||||
|
||||
- name of wwstarts2 [#2217](https://github.com/emsesp/EMS-ESP32/discussions/2217)
|
||||
|
||||
51
Makefile
51
Makefile
@@ -16,13 +16,23 @@ T := $(shell $(MAKE) $(MAKECMDGOALS) --no-print-directory \
|
||||
ECHO="COUNTTHIS" | grep -c "COUNTTHIS")
|
||||
N := x
|
||||
C = $(words $N)$(eval N := x $N)
|
||||
ECHO = python $(I)/echo_progress.py --stepno=$C --nsteps=$T
|
||||
ECHO = python3 $(I)/scripts/echo_progress.py --stepno=$C --nsteps=$T
|
||||
endif
|
||||
|
||||
# number of parallel compiles
|
||||
JOBS ?= $(shell nproc)
|
||||
# determine number of parallel compiles based on OS
|
||||
UNAME_S := $(shell uname -s)
|
||||
ifeq ($(UNAME_S),Linux)
|
||||
EXTRA_CPPFLAGS = -D LINUX
|
||||
JOBS ?= $(shell nproc)
|
||||
endif
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
EXTRA_CPPFLAGS = -D OSX -Wno-tautological-constant-out-of-range-compare
|
||||
JOBS ?= $(shell sysctl -n hw.ncpu)
|
||||
endif
|
||||
MAKEFLAGS += -j $(JOBS) -l $(JOBS)
|
||||
|
||||
# $(info Number of jobs: $(JOBS))
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
# Project Structure
|
||||
#----------------------------------------------------------------------
|
||||
@@ -32,26 +42,20 @@ MAKEFLAGS += -j $(JOBS) -l $(JOBS)
|
||||
# INCLUDES is a list of directories containing header files
|
||||
# LIBRARIES is a list of directories containing libraries, this must be the top level containing include and lib
|
||||
#----------------------------------------------------------------------
|
||||
|
||||
#TARGET := $(notdir $(CURDIR))
|
||||
TARGET := emsesp
|
||||
BUILD := build
|
||||
SOURCES := src src/* lib_standalone lib/uuid-common/src lib/uuid-console/src lib/uuid-log/src src/devices lib/ArduinoJson/src lib/PButton lib/semver lib/espMqttClient/src lib/espMqttClient/src/*
|
||||
INCLUDES := src lib_standalone lib/espMqttClient/src lib/espMqttClient/src/Transport lib/ArduinoJson/src lib/uuid-common/src lib/uuid-console/src lib/uuid-log/src lib/uuid-telnet/src lib/uuid-syslog/src lib/semver lib/* src/devices
|
||||
SOURCES := src/core src/devices src/web src/test lib_standalone lib/semver lib/espMqttClient/src lib/espMqttClient/src/* lib/ArduinoJson/src lib/uuid-common/src lib/uuid-console/src lib/uuid-log/src lib/PButton
|
||||
INCLUDES := src/core src/devices src/web src/test lib/* lib_standalone lib/semver lib/espMqttClient/src lib/espMqttClient/src/Transport lib/ArduinoJson/src lib/uuid-common/src lib/uuid-console/src lib/uuid-log/src lib/uuid-telnet/src lib/uuid-syslog/src
|
||||
LIBRARIES :=
|
||||
|
||||
CPPCHECK = cppcheck
|
||||
# CHECKFLAGS = -q --force --std=c++17
|
||||
CHECKFLAGS = -q --force --std=c++11
|
||||
CHECKFLAGS = -q --force --std=gnu++17
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
# Languages Standard
|
||||
#----------------------------------------------------------------------
|
||||
C_STANDARD := -std=c17
|
||||
CXX_STANDARD := -std=gnu++14
|
||||
|
||||
# C_STANDARD := -std=c11
|
||||
# CXX_STANDARD := -std=c++11
|
||||
CXX_STANDARD := -std=gnu++17
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
# Defined Symbols
|
||||
@@ -60,7 +64,7 @@ DEFINES += -DARDUINOJSON_ENABLE -DARDUINOJSON_ENABLE_ARDUINO_STRING -DARDUINOJSO
|
||||
DEFINES += -DEMSESP_STANDALONE -DEMSESP_TEST -DEMSESP_DEBUG -DEMC_RX_BUFFER_SIZE=1500
|
||||
DEFINES += $(ARGS)
|
||||
|
||||
DEFAULTS = -DEMSESP_DEFAULT_LOCALE=\"en\" -DEMSESP_DEFAULT_TX_MODE=8 -DEMSESP_DEFAULT_VERSION=\"3.7.1-dev\" -DEMSESP_DEFAULT_BOARD_PROFILE=\"S3\"
|
||||
DEFAULTS = -DEMSESP_DEFAULT_LOCALE=\"en\" -DEMSESP_DEFAULT_TX_MODE=8 -DEMSESP_DEFAULT_VERSION=\"3.7.3-dev\" -DEMSESP_DEFAULT_BOARD_PROFILE=\"S32S3\"
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
# Sources & Files
|
||||
@@ -94,14 +98,19 @@ CXX := /usr/bin/g++
|
||||
# LDFLAGS Linker Flags
|
||||
#----------------------------------------------------------------------
|
||||
CPPFLAGS += $(DEFINES) $(DEFAULTS) $(INCLUDE)
|
||||
CPPFLAGS += -ggdb
|
||||
CPPFLAGS += -g3
|
||||
CPPFLAGS += -Os
|
||||
CPPFLAGS += -ggdb -g3 -O3
|
||||
CPPFLAGS += -MMD
|
||||
CPPFLAGS += -flto=auto -fno-lto
|
||||
CPPFLAGS += -Wall -Wextra -Werror
|
||||
CPPFLAGS += -Wswitch-enum
|
||||
CPPFLAGS += -Wno-unused-parameter
|
||||
CPPFLAGS += -Wno-missing-braces
|
||||
|
||||
CPPFLAGS += $(EXTRA_CPPFLAGS)
|
||||
|
||||
CFLAGS += $(CPPFLAGS)
|
||||
CFLAGS += -Wall -Wextra -Werror -Wswitch-enum
|
||||
CFLAGS += -Wno-tautological-constant-out-of-range-compare -Wno-unused-parameter -Wno-inconsistent-missing-override -Wno-missing-braces -Wno-unused-lambda-capture -Wno-sign-compare
|
||||
CXXFLAGS += $(CFLAGS) -MMD
|
||||
CXXFLAGS += $(CPPFLAGS)
|
||||
LDFLAGS =
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
# Compiler & Linker Commands
|
||||
@@ -142,7 +151,7 @@ COMPILE.cpp = $(CXX) $(CXX_STANDARD) $(CXXFLAGS) $(DEPFLAGS) -c $< -o $@
|
||||
.SILENT: $(OUTPUT)
|
||||
|
||||
all: $(OUTPUT)
|
||||
@$(ECHO) All done
|
||||
@$(ECHO) Build complete.
|
||||
|
||||
$(OUTPUT): $(OBJS)
|
||||
@mkdir -p $(@D)
|
||||
|
||||
45
boards/c3_mini_4M.json
Normal file
45
boards/c3_mini_4M.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "esp32c3_out.ld"
|
||||
},
|
||||
"core": "esp32",
|
||||
"extra_flags": [
|
||||
"-DTASMOTA_SDK",
|
||||
"-DARDUINO_LOLIN_C3_MINI",
|
||||
"-DARDUINO_USB_MODE=1",
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=1"
|
||||
],
|
||||
"f_cpu": "160000000L",
|
||||
"f_flash": "80000000L",
|
||||
"flash_mode": "qio",
|
||||
"hwids": [
|
||||
[
|
||||
"0X303A",
|
||||
"0x1001"
|
||||
]
|
||||
],
|
||||
"mcu": "esp32c3",
|
||||
"variant": "lolin_c3_mini"
|
||||
},
|
||||
"connectivity": [
|
||||
"wifi"
|
||||
],
|
||||
"debug": {
|
||||
"openocd_target": "esp32c3.cfg"
|
||||
},
|
||||
"frameworks": [
|
||||
"arduino",
|
||||
"espidf"
|
||||
],
|
||||
"name": "WEMOS LOLIN C3 Mini",
|
||||
"upload": {
|
||||
"flash_size": "4MB",
|
||||
"maximum_ram_size": 327680,
|
||||
"maximum_size": 4194304,
|
||||
"require_upload_port": true,
|
||||
"speed": 460800
|
||||
},
|
||||
"url": "https://www.wemos.cc/en/latest/c3/c3_mini.html",
|
||||
"vendor": "WEMOS"
|
||||
}
|
||||
34
boards/esp32dev.json
Normal file
34
boards/esp32dev.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"build": {
|
||||
"core": "esp32",
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "40000000L",
|
||||
"flash_mode": "dio",
|
||||
"mcu": "esp32",
|
||||
"variant": "esp32"
|
||||
},
|
||||
"connectivity": [
|
||||
"wifi",
|
||||
"ethernet"
|
||||
],
|
||||
"debug": {
|
||||
"openocd_board": "esp32.cfg"
|
||||
},
|
||||
"frameworks": [
|
||||
"arduino",
|
||||
"espidf"
|
||||
],
|
||||
"name": "Espressif ESP32 Dev Module",
|
||||
"upload": {
|
||||
"flash_size": "16MB",
|
||||
"maximum_ram_size": 327680,
|
||||
"maximum_size": 16777216,
|
||||
"require_upload_port": true,
|
||||
"speed": 460800
|
||||
},
|
||||
"download": {
|
||||
"speed": 230400
|
||||
},
|
||||
"url": "https://en.wikipedia.org/wiki/ESP32",
|
||||
"vendor": "Espressif"
|
||||
}
|
||||
47
boards/s2_4M_P.json
Normal file
47
boards/s2_4M_P.json
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "esp32s2_out.ld"
|
||||
},
|
||||
"core": "esp32",
|
||||
"extra_flags": [
|
||||
"-DBOARD_HAS_PSRAM",
|
||||
"-DTASMOTA_SDK",
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||
"-DARDUINO_USB_MODE=0"
|
||||
],
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "80000000L",
|
||||
"flash_mode": "dio",
|
||||
"hwids": [
|
||||
[
|
||||
"0X303A",
|
||||
"0x80C2"
|
||||
]
|
||||
],
|
||||
"mcu": "esp32s2",
|
||||
"variant": "lolin_s2_mini"
|
||||
},
|
||||
"connectivity": [
|
||||
"wifi"
|
||||
],
|
||||
"debug": {
|
||||
"openocd_target": "esp32s2.cfg"
|
||||
},
|
||||
"frameworks": [
|
||||
"arduino",
|
||||
"espidf"
|
||||
],
|
||||
"name": "WEMOS LOLIN S2 Mini",
|
||||
"upload": {
|
||||
"flash_size": "4MB",
|
||||
"maximum_ram_size": 327680,
|
||||
"maximum_size": 4194304,
|
||||
"use_1200bps_touch": true,
|
||||
"wait_for_upload_port": true,
|
||||
"require_upload_port": true,
|
||||
"speed": 921600
|
||||
},
|
||||
"url": "https://www.wemos.cc/en/latest/s2/s2_mini.html",
|
||||
"vendor": "WEMOS"
|
||||
}
|
||||
44
boards/s3_16M_P.json
Normal file
44
boards/s3_16M_P.json
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "esp32s3_out.ld",
|
||||
"memory_type": "qio_opi"
|
||||
},
|
||||
"core": "esp32",
|
||||
"extra_flags": [
|
||||
"-DBOARD_HAS_PSRAM",
|
||||
"-DARDUINO_USB_MODE=1",
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||
"-DARDUINO_RUNNING_CORE=1",
|
||||
"-DARDUINO_EVENT_RUNNING_CORE=1"
|
||||
],
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "80000000L",
|
||||
"flash_mode": "qio",
|
||||
"mcu": "esp32s3",
|
||||
"variant": "esp32s3"
|
||||
},
|
||||
"connectivity": [
|
||||
"wifi"
|
||||
],
|
||||
"debug": {
|
||||
"openocd_target": "esp32s3.cfg"
|
||||
},
|
||||
"frameworks": [
|
||||
"arduino",
|
||||
"espidf"
|
||||
],
|
||||
"name": "Espressif ESP32-S3 16M Flash OPI PSRAM, 4608KB Code/OTA, 2MB FS",
|
||||
"upload": {
|
||||
"flash_size": "16MB",
|
||||
"maximum_ram_size": 327680,
|
||||
"maximum_size": 16777216,
|
||||
"require_upload_port": true,
|
||||
"speed": 460800
|
||||
},
|
||||
"download": {
|
||||
"speed": 230400
|
||||
},
|
||||
"url": "https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/hw-reference/esp32s3/",
|
||||
"vendor": "Espressif"
|
||||
}
|
||||
37
boards/s3_32M_P.json
Normal file
37
boards/s3_32M_P.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino":{
|
||||
"memory_type": "opi_opi"
|
||||
},
|
||||
"core": "esp32",
|
||||
"extra_flags": "-DBOARD_HAS_PSRAM",
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "80000000L",
|
||||
"flash_mode": "opi",
|
||||
"mcu": "esp32s3",
|
||||
"variant": "esp32s3"
|
||||
},
|
||||
"connectivity": [
|
||||
"wifi"
|
||||
],
|
||||
"debug": {
|
||||
"openocd_target": "esp32s3.cfg"
|
||||
},
|
||||
"frameworks": [
|
||||
"arduino",
|
||||
"espidf"
|
||||
],
|
||||
"name": "Espressif ESP32-S3 32M Flash OPI PSRAM, 4608KB Code/OTA, 2MB FS",
|
||||
"upload": {
|
||||
"flash_size": "32MB",
|
||||
"maximum_ram_size": 327680,
|
||||
"maximum_size": 16777216,
|
||||
"require_upload_port": true,
|
||||
"speed": 460800
|
||||
},
|
||||
"download": {
|
||||
"speed": 230400
|
||||
},
|
||||
"url": "https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/hw-reference/esp32s3/",
|
||||
"vendor": "Espressif"
|
||||
}
|
||||
35
boards/s_16M.json
Normal file
35
boards/s_16M.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"build": {
|
||||
"core": "esp32",
|
||||
"extra_flags": "-DTASMOTA_SDK",
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "40000000L",
|
||||
"flash_mode": "dio",
|
||||
"mcu": "esp32",
|
||||
"variant": "esp32"
|
||||
},
|
||||
"connectivity": [
|
||||
"wifi",
|
||||
"ethernet"
|
||||
],
|
||||
"debug": {
|
||||
"openocd_target": "esp32.cfg"
|
||||
},
|
||||
"frameworks": [
|
||||
"arduino",
|
||||
"espidf"
|
||||
],
|
||||
"name": "Espressif ESP32 16M Flash, 4608KB Code/OTA, 2MB FS",
|
||||
"upload": {
|
||||
"flash_size": "16MB",
|
||||
"maximum_ram_size": 327680,
|
||||
"maximum_size": 16777216,
|
||||
"require_upload_port": true,
|
||||
"speed": 460800
|
||||
},
|
||||
"download": {
|
||||
"speed": 230400
|
||||
},
|
||||
"url": "https://en.wikipedia.org/wiki/ESP32",
|
||||
"vendor": "Espressif"
|
||||
}
|
||||
35
boards/s_16M_P.json
Normal file
35
boards/s_16M_P.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"build": {
|
||||
"core": "esp32",
|
||||
"extra_flags": "-DBOARD_HAS_PSRAM",
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "80000000L",
|
||||
"flash_mode": "dio",
|
||||
"mcu": "esp32",
|
||||
"variant": "esp32"
|
||||
},
|
||||
"connectivity": [
|
||||
"wifi",
|
||||
"ethernet"
|
||||
],
|
||||
"debug": {
|
||||
"openocd_target": "esp32.cfg"
|
||||
},
|
||||
"frameworks": [
|
||||
"arduino",
|
||||
"espidf"
|
||||
],
|
||||
"name": "Espressif ESP32 16M Flash DIO PSRAM, 4608KB Code/OTA, 2MB FS",
|
||||
"upload": {
|
||||
"flash_size": "16MB",
|
||||
"maximum_ram_size": 327680,
|
||||
"maximum_size": 16777216,
|
||||
"require_upload_port": true,
|
||||
"speed": 460800
|
||||
},
|
||||
"download": {
|
||||
"speed": 230400
|
||||
},
|
||||
"url": "https://en.wikipedia.org/wiki/ESP32",
|
||||
"vendor": "Espressif"
|
||||
}
|
||||
34
boards/s_4M.json
Normal file
34
boards/s_4M.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"build": {
|
||||
"core": "esp32",
|
||||
"extra_flags": "-DTASMOTA_SDK",
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "40000000L",
|
||||
"flash_mode": "dio",
|
||||
"mcu": "esp32",
|
||||
"variant": "esp32"
|
||||
},
|
||||
"connectivity": [
|
||||
"wifi"
|
||||
],
|
||||
"debug": {
|
||||
"openocd_target": "esp32.cfg"
|
||||
},
|
||||
"frameworks": [
|
||||
"arduino",
|
||||
"espidf"
|
||||
],
|
||||
"name": "Tasmota ESP32 4M Flash, 4608KB Code/OTA, 2MB FS",
|
||||
"upload": {
|
||||
"flash_size": "4MB",
|
||||
"maximum_ram_size": 327680,
|
||||
"maximum_size": 4194304,
|
||||
"require_upload_port": true,
|
||||
"speed": 460800
|
||||
},
|
||||
"download": {
|
||||
"speed": 230400
|
||||
},
|
||||
"url": "https://en.wikipedia.org/wiki/ESP32",
|
||||
"vendor": "Espressif"
|
||||
}
|
||||
@@ -28,6 +28,10 @@
|
||||
"**/i18n/**",
|
||||
"/project-words.txt",
|
||||
"Makefile",
|
||||
"src/modbus_entity_parameters.hpp"
|
||||
"**/*.ini",
|
||||
"**/*.json",
|
||||
"src/core/modbus_entity_parameters.hpp",
|
||||
"sdkconfig.*",
|
||||
"managed_components/**"
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -29,7 +29,7 @@ telegram_type_id,name,is_fetched
|
||||
0x3B,Energy,
|
||||
0x3D,RC35Set,
|
||||
0x3E,RC35Monitor,
|
||||
0x3F,RC30Timer,
|
||||
0x3F,RC35Timer,
|
||||
0x40,RC30Temp,
|
||||
0x41,RC30Monitor,
|
||||
0x42,RC35Timer2,
|
||||
@@ -81,6 +81,7 @@ telegram_type_id,name,is_fetched
|
||||
0x0166,JunkersSet,
|
||||
0x0167,JunkersSet,
|
||||
0x0168,JunkersSet,
|
||||
0x016E,Absent,fetched
|
||||
0x016F,JunkersMonitor,
|
||||
0x0170,JunkersMonitor,
|
||||
0x0171,JunkersMonitor,
|
||||
@@ -93,8 +94,9 @@ telegram_type_id,name,is_fetched
|
||||
0x023A,RC300OutdoorTemp,fetched
|
||||
0x023E,PVSettings,fetched
|
||||
0x0240,RC300Settings,fetched
|
||||
0x0241,RC300Settings,fetched
|
||||
0x0267,RC300Floordry,
|
||||
0x0269,RC300Holiday1,fetched
|
||||
0x0269,RC300Holiday,fetched
|
||||
0x0291,HPMode,fetched
|
||||
0x0292,HPMode,fetched
|
||||
0x0293,HPMode,fetched
|
||||
@@ -107,7 +109,7 @@ telegram_type_id,name,is_fetched
|
||||
0x02A0,RC300Curves,
|
||||
0x02A1,RC300Curves,
|
||||
0x02A2,RC300Curves,
|
||||
0x02A5,RC300Monitor,
|
||||
0x02A5,RC300Monitor,fetched
|
||||
0x02A6,RC300Monitor,
|
||||
0x02A7,CRFMonitor,
|
||||
0x02A8,RC300Monitor,
|
||||
@@ -159,6 +161,7 @@ telegram_type_id,name,is_fetched
|
||||
0x0380,SM100CollectorConfig,fetched
|
||||
0x038E,SM100Energy,fetched
|
||||
0x0391,SM100Time,fetched
|
||||
0x043F,CRHolidays,fetched
|
||||
0x0467,HPSet,
|
||||
0x0468,HPSet,
|
||||
0x0469,HPSet,
|
||||
|
||||
|
@@ -1,28 +0,0 @@
|
||||
"""
|
||||
Print makefile progress
|
||||
From https://stackoverflow.com/questions/451413/make-makefile-progress-indication
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import math
|
||||
import sys
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--stepno", type=int, required=True)
|
||||
parser.add_argument("--nsteps", type=int, required=True)
|
||||
parser.add_argument("remainder", nargs=argparse.REMAINDER)
|
||||
args = parser.parse_args()
|
||||
|
||||
nchars = int(math.log(args.nsteps, 10)) + 1
|
||||
fmt_str = "[{:Xd}/{:Xd}]({:6.2f}%)".replace("X", str(nchars))
|
||||
progress = 100 * args.stepno / args.nsteps
|
||||
sys.stdout.write(fmt_str.format(args.stepno, args.nsteps, progress))
|
||||
for item in args.remainder:
|
||||
sys.stdout.write(" ")
|
||||
sys.stdout.write(item)
|
||||
sys.stdout.write("\n")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,3 +1,3 @@
|
||||
nodeLinker: node-modules
|
||||
|
||||
yarnPath: .yarn/releases/yarn-4.5.3.cjs
|
||||
yarnPath: .yarn/releases/yarn-4.7.0.cjs
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "EMS-ESP",
|
||||
"version": "3.7.1",
|
||||
"version": "3.7.2",
|
||||
"description": "EMS-ESP WebUI",
|
||||
"homepage": "https://emsesp.org",
|
||||
"author": "proddy, emsesp.org",
|
||||
@@ -21,46 +21,46 @@
|
||||
"lint": "eslint . --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@alova/adapter-xhr": "2.0.10",
|
||||
"@emotion/react": "^11.13.5",
|
||||
"@emotion/styled": "^11.13.5",
|
||||
"@mui/icons-material": "^6.1.9",
|
||||
"@mui/material": "^6.1.9",
|
||||
"@table-library/react-table-library": "4.1.7",
|
||||
"alova": "3.2.5",
|
||||
"@alova/adapter-xhr": "2.1.1",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.0",
|
||||
"@mui/icons-material": "^6.4.8",
|
||||
"@mui/material": "^6.4.8",
|
||||
"@table-library/react-table-library": "4.1.12",
|
||||
"alova": "3.2.10",
|
||||
"async-validator": "^4.2.5",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"mime-types": "^2.1.35",
|
||||
"preact": "^10.25.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-icons": "^5.3.0",
|
||||
"react-router": "^7.0.1",
|
||||
"react-toastify": "^10.0.6",
|
||||
"preact": "^10.26.4",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-router": "^7.4.0",
|
||||
"react-toastify": "^11.0.5",
|
||||
"typesafe-i18n": "^5.26.2",
|
||||
"typescript": "^5.7.2"
|
||||
"typescript": "^5.8.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.26.0",
|
||||
"@eslint/js": "^9.15.0",
|
||||
"@babel/core": "^7.26.10",
|
||||
"@eslint/js": "^9.23.0",
|
||||
"@preact/compat": "^18.3.1",
|
||||
"@preact/preset-vite": "^2.9.2",
|
||||
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
||||
"@preact/preset-vite": "^2.10.1",
|
||||
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
|
||||
"@types/formidable": "^3",
|
||||
"@types/node": "^22.10.1",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"concurrently": "^9.1.0",
|
||||
"eslint": "^9.15.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"@types/node": "^22.13.11",
|
||||
"@types/react": "^19.0.12",
|
||||
"@types/react-dom": "^19.0.4",
|
||||
"concurrently": "^9.1.2",
|
||||
"eslint": "^9.23.0",
|
||||
"eslint-config-prettier": "^10.1.1",
|
||||
"formidable": "^3.5.2",
|
||||
"prettier": "^3.4.1",
|
||||
"rollup-plugin-visualizer": "^5.12.0",
|
||||
"terser": "^5.36.0",
|
||||
"typescript-eslint": "8.16.0",
|
||||
"vite": "^6.0.1",
|
||||
"prettier": "^3.5.3",
|
||||
"rollup-plugin-visualizer": "^5.14.0",
|
||||
"terser": "^5.39.0",
|
||||
"typescript-eslint": "8.27.0",
|
||||
"vite": "^6.2.2",
|
||||
"vite-plugin-imagemin": "^0.6.1",
|
||||
"vite-tsconfig-paths": "^5.1.3"
|
||||
"vite-tsconfig-paths": "^5.1.4"
|
||||
},
|
||||
"packageManager": "yarn@4.5.3"
|
||||
"packageManager": "yarn@4.7.0"
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import zlib from 'zlib';
|
||||
|
||||
const ARDUINO_INCLUDES = '#include <Arduino.h>\n\n';
|
||||
const INDENT = ' ';
|
||||
const outputPath = '../lib/framework/WWWData.h';
|
||||
const outputPath = '../src/ESP32React/WWWData.h';
|
||||
const sourcePath = './dist';
|
||||
const bytesPerLine = 20;
|
||||
var totalSize = 0;
|
||||
|
||||
@@ -13,8 +13,9 @@
|
||||
local('Roboto'),
|
||||
local('Roboto-Regular'),
|
||||
url(../fonts/re.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0104-0107, U+0118-0119, U+011E-011F, U+0130-0131,
|
||||
U+0141-0144, U+0152-0153, U+015A-015B, U+015E-015F, U+0179-017C, U+02BB-02BC,
|
||||
U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193,
|
||||
U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
unicode-range:
|
||||
U+0000-00FF, U+0104-0107, U+0118-0119, U+011E-011F, U+0130-0131, U+0141-0144,
|
||||
U+0152-0153, U+015A-015B, U+015E-015F, U+0179-017C, U+02BB-02BC, U+02C6, U+02DA,
|
||||
U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
|
||||
U+FEFF, U+FFFD;
|
||||
}
|
||||
|
||||
@@ -1,27 +1,44 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Slide, ToastContainer } from 'react-toastify';
|
||||
import 'react-toastify/dist/ReactToastify.min.css';
|
||||
import { ToastContainer, Zoom } from 'react-toastify';
|
||||
|
||||
import AppRouting from 'AppRouting';
|
||||
import CustomTheme from 'CustomTheme';
|
||||
import TypesafeI18n from 'i18n/i18n-react';
|
||||
import { detectLocale } from 'i18n/i18n-util';
|
||||
import type { Locales } from 'i18n/i18n-types';
|
||||
import { loadLocaleAsync } from 'i18n/i18n-util.async';
|
||||
import { localStorageDetector } from 'typesafe-i18n/detectors';
|
||||
import { detectLocale, navigatorDetector } from 'typesafe-i18n/detectors';
|
||||
|
||||
const detectedLocale = detectLocale(localStorageDetector);
|
||||
const availableLocales = [
|
||||
'de',
|
||||
'en',
|
||||
'it',
|
||||
'fr',
|
||||
'nl',
|
||||
'no',
|
||||
'pl',
|
||||
'sk',
|
||||
'sv',
|
||||
'tr',
|
||||
'cz'
|
||||
];
|
||||
|
||||
const App = () => {
|
||||
const [wasLoaded, setWasLoaded] = useState(false);
|
||||
const [locale, setLocale] = useState<Locales>('en');
|
||||
|
||||
useEffect(() => {
|
||||
void loadLocaleAsync(detectedLocale).then(() => setWasLoaded(true));
|
||||
// determine locale, take from session if set other default to browser language
|
||||
const browserLocale = detectLocale('en', availableLocales, navigatorDetector);
|
||||
const newLocale = (localStorage.getItem('lang') || browserLocale) as Locales;
|
||||
localStorage.setItem('lang', newLocale);
|
||||
setLocale(newLocale);
|
||||
void loadLocaleAsync(newLocale).then(() => setWasLoaded(true));
|
||||
}, []);
|
||||
|
||||
if (!wasLoaded) return null;
|
||||
|
||||
return (
|
||||
<TypesafeI18n locale={detectedLocale}>
|
||||
<TypesafeI18n locale={locale}>
|
||||
<CustomTheme>
|
||||
<AppRouting />
|
||||
<ToastContainer
|
||||
@@ -29,14 +46,17 @@ const App = () => {
|
||||
autoClose={3000}
|
||||
hideProgressBar={false}
|
||||
newestOnTop={false}
|
||||
closeOnClick={true}
|
||||
closeOnClick
|
||||
rtl={false}
|
||||
pauseOnFocusLoss={false}
|
||||
pauseOnFocusLoss
|
||||
draggable={false}
|
||||
pauseOnHover={false}
|
||||
transition={Slide}
|
||||
transition={Zoom}
|
||||
closeButton={false}
|
||||
theme="light"
|
||||
theme="dark"
|
||||
toastStyle={{
|
||||
border: '1px solid #177ac9'
|
||||
}}
|
||||
/>
|
||||
</CustomTheme>
|
||||
</TypesafeI18n>
|
||||
|
||||
@@ -15,7 +15,6 @@ import DownloadUpload from 'app/settings/DownloadUpload';
|
||||
import MqttSettings from 'app/settings/MqttSettings';
|
||||
import NTPSettings from 'app/settings/NTPSettings';
|
||||
import Settings from 'app/settings/Settings';
|
||||
import Version from 'app/settings/Version';
|
||||
import Network from 'app/settings/network/Network';
|
||||
import Security from 'app/settings/security/Security';
|
||||
import APStatus from 'app/status/APStatus';
|
||||
@@ -26,6 +25,7 @@ import NTPStatus from 'app/status/NTPStatus';
|
||||
import NetworkStatus from 'app/status/NetworkStatus';
|
||||
import Status from 'app/status/Status';
|
||||
import SystemLog from 'app/status/SystemLog';
|
||||
import Version from 'app/status/Version';
|
||||
import { Layout } from 'components';
|
||||
import { AuthenticatedContext } from 'contexts/authentication';
|
||||
|
||||
@@ -48,17 +48,17 @@ const AuthenticatedRouting = () => {
|
||||
<Route path="/status/ntp" element={<NTPStatus />} />
|
||||
<Route path="/status/ap" element={<APStatus />} />
|
||||
<Route path="/status/network" element={<NetworkStatus />} />
|
||||
<Route path="/status/version" element={<Version />} />
|
||||
|
||||
{me.admin && (
|
||||
<>
|
||||
<Route path="/settings" element={<Settings />} />
|
||||
<Route path="/settings/version" element={<Version />} />
|
||||
<Route path="/settings/application" element={<ApplicationSettings />} />
|
||||
<Route path="/settings/mqtt" element={<MqttSettings />} />
|
||||
<Route path="/settings/ntp" element={<NTPSettings />} />
|
||||
<Route path="/settings/ap" element={<APSettings />} />
|
||||
<Route path="/settings/modules" element={<Modules />} />
|
||||
<Route path="/settings/upload" element={<DownloadUpload />} />
|
||||
<Route path="/settings/downloadUpload" element={<DownloadUpload />} />
|
||||
|
||||
<Route path="/settings/network/*" element={<Network />} />
|
||||
<Route path="/settings/security/*" element={<Security />} />
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import type { FC } from 'react';
|
||||
|
||||
import { CssBaseline } from '@mui/material';
|
||||
import {
|
||||
ThemeProvider,
|
||||
createTheme,
|
||||
responsiveFontSizes
|
||||
} from '@mui/material/styles';
|
||||
import { CssBaseline, ThemeProvider, responsiveFontSizes } from '@mui/material';
|
||||
import { createTheme } from '@mui/material/styles';
|
||||
|
||||
import type { RequiredChildrenProps } from 'utils';
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import type {
|
||||
Action,
|
||||
Activity,
|
||||
CoreData,
|
||||
DashboardItem,
|
||||
DashboardData,
|
||||
DeviceData,
|
||||
DeviceEntity,
|
||||
Entities,
|
||||
@@ -22,7 +22,7 @@ import type {
|
||||
|
||||
// Dashboard
|
||||
export const readDashboard = () =>
|
||||
alovaInstance.Get<DashboardItem[]>('/rest/dashboardData', {
|
||||
alovaInstance.Get<DashboardData>('/rest/dashboardData', {
|
||||
responseType: 'arraybuffer' // uses msgpack
|
||||
});
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ export const alovaInstance = createAlova({
|
||||
method.config.headers.Authorization =
|
||||
'Bearer ' + localStorage.getItem(ACCESS_TOKEN);
|
||||
}
|
||||
// for simulating vrey slow networks
|
||||
// for simulating very slow networks
|
||||
// return new Promise((resolve) => {
|
||||
// const random = 3000 + Math.random() * 2000;
|
||||
// setTimeout(resolve, Math.floor(random));
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { LogSettings, SystemStatus } from 'types';
|
||||
|
||||
import { alovaInstance, alovaInstanceGH } from './endpoints';
|
||||
|
||||
// systemStatus - also used to ping in Restart monitor for pinging
|
||||
// systemStatus - also used to ping in System Monitor for pinging
|
||||
export const readSystemStatus = () =>
|
||||
alovaInstance.Get<SystemStatus>('/rest/systemStatus');
|
||||
|
||||
@@ -14,16 +14,25 @@ export const updateLogSettings = (data: LogSettings) =>
|
||||
export const fetchLogES = () => alovaInstance.Get('/es/log');
|
||||
|
||||
// Get versions from GitHub
|
||||
// cache for 10 minutes to stop getting the IP blocked by GitHub
|
||||
export const getStableVersion = () =>
|
||||
alovaInstanceGH.Get('latest', {
|
||||
transform(response: { data: { name: string } }) {
|
||||
return response.data.name.substring(1);
|
||||
cacheFor: 60 * 10 * 1000,
|
||||
transform(response: { data: { name: string; published_at: string } }) {
|
||||
return {
|
||||
name: response.data.name.substring(1),
|
||||
published_at: response.data.published_at
|
||||
};
|
||||
}
|
||||
});
|
||||
export const getDevVersion = () =>
|
||||
alovaInstanceGH.Get('tags/latest', {
|
||||
transform(response: { data: { name: string } }) {
|
||||
return response.data.name.split(/\s+/).splice(-1)[0].substring(1);
|
||||
cacheFor: 60 * 10 * 1000,
|
||||
transform(response: { data: { name: string; published_at: string } }) {
|
||||
return {
|
||||
name: response.data.name.split(/\s+/).splice(-1)[0].substring(1),
|
||||
published_at: response.data.published_at
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ const CustomEntities = () => {
|
||||
if (!dialogOpen && !numChanges) {
|
||||
void fetchEntities();
|
||||
}
|
||||
}, 3000);
|
||||
});
|
||||
|
||||
const { send: writeEntities } = useRequest(
|
||||
(data: Entities) => writeCustomEntities(data),
|
||||
@@ -83,7 +83,7 @@ const CustomEntities = () => {
|
||||
|
||||
const entity_theme = useTheme({
|
||||
Table: `
|
||||
--data-table-library_grid-template-columns: repeat(1, minmax(60px, 1fr)) minmax(80px, auto) 80px 80px 80px 90px;
|
||||
--data-table-library_grid-template-columns: repeat(1, minmax(60px, 1fr)) minmax(80px, auto) 80px 80px 80px 120px;
|
||||
`,
|
||||
BaseRow: `
|
||||
font-size: 14px;
|
||||
@@ -195,6 +195,25 @@ const CustomEntities = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const onDialogDup = (item: EntityItem) => {
|
||||
setCreating(true);
|
||||
setSelectedEntityItem({
|
||||
id: Math.floor(Math.random() * (Math.floor(200) - 100) + 100),
|
||||
name: item.name + '_',
|
||||
ram: item.ram,
|
||||
device_id: item.device_id,
|
||||
type_id: item.type_id,
|
||||
offset: item.offset,
|
||||
factor: item.factor,
|
||||
uom: item.uom,
|
||||
value_type: item.value_type,
|
||||
writeable: item.writeable,
|
||||
deleted: false,
|
||||
value: item.value
|
||||
});
|
||||
setDialogOpen(true);
|
||||
};
|
||||
|
||||
const addEntityItem = () => {
|
||||
setCreating(true);
|
||||
setSelectedEntityItem({
|
||||
@@ -220,7 +239,7 @@ const CustomEntities = () => {
|
||||
: typeof value === 'number'
|
||||
? new Intl.NumberFormat().format(value) +
|
||||
(uom === 0 ? '' : ' ' + DeviceValueUOM_s[uom])
|
||||
: (value as string);
|
||||
: (value as string) + (uom === 0 ? '' : ' ' + DeviceValueUOM_s[uom]);
|
||||
}
|
||||
|
||||
function showHex(value: number, digit: number) {
|
||||
@@ -296,6 +315,7 @@ const CustomEntities = () => {
|
||||
creating={creating}
|
||||
onClose={onDialogClose}
|
||||
onSave={onDialogSave}
|
||||
onDup={onDialogDup}
|
||||
selectedItem={selectedEntityItem}
|
||||
validator={entityItemValidation(entities, selectedEntityItem)}
|
||||
/>
|
||||
|
||||
@@ -12,11 +12,11 @@ import {
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Grid2 as Grid,
|
||||
InputAdornment,
|
||||
MenuItem,
|
||||
TextField
|
||||
} from '@mui/material';
|
||||
import Grid from '@mui/material/Grid2';
|
||||
|
||||
import { dialogStyle } from 'CustomTheme';
|
||||
import type Schema from 'async-validator';
|
||||
@@ -34,6 +34,7 @@ interface CustomEntitiesDialogProps {
|
||||
creating: boolean;
|
||||
onClose: () => void;
|
||||
onSave: (ei: EntityItem) => void;
|
||||
onDup: (ei: EntityItem) => void;
|
||||
selectedItem: EntityItem;
|
||||
validator: Schema;
|
||||
}
|
||||
@@ -43,6 +44,7 @@ const CustomEntitiesDialog = ({
|
||||
creating,
|
||||
onClose,
|
||||
onSave,
|
||||
onDup,
|
||||
selectedItem,
|
||||
validator
|
||||
}: CustomEntitiesDialogProps) => {
|
||||
@@ -59,7 +61,11 @@ const CustomEntitiesDialog = ({
|
||||
setEditItem({
|
||||
...selectedItem,
|
||||
device_id: selectedItem.device_id.toString(16).toUpperCase(),
|
||||
type_id: selectedItem.type_id.toString(16).toUpperCase()
|
||||
type_id: selectedItem.type_id.toString(16).toUpperCase(),
|
||||
factor:
|
||||
selectedItem.value_type === DeviceValueType.BOOL
|
||||
? selectedItem.factor.toString(16).toUpperCase()
|
||||
: selectedItem.factor
|
||||
});
|
||||
}
|
||||
}, [open, selectedItem]);
|
||||
@@ -80,6 +86,12 @@ const CustomEntitiesDialog = ({
|
||||
if (typeof editItem.type_id === 'string') {
|
||||
editItem.type_id = parseInt(editItem.type_id, 16);
|
||||
}
|
||||
if (
|
||||
editItem.value_type === DeviceValueType.BOOL &&
|
||||
typeof editItem.factor === 'string'
|
||||
) {
|
||||
editItem.factor = parseInt(editItem.factor, 16);
|
||||
}
|
||||
onSave(editItem);
|
||||
} catch (error) {
|
||||
setFieldErrors(error as ValidateFieldsError);
|
||||
@@ -91,6 +103,10 @@ const CustomEntitiesDialog = ({
|
||||
onSave(editItem);
|
||||
};
|
||||
|
||||
const dup = () => {
|
||||
onDup(editItem);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog sx={dialogStyle} open={open} onClose={handleClose}>
|
||||
<DialogTitle>
|
||||
@@ -128,18 +144,36 @@ const CustomEntitiesDialog = ({
|
||||
</TextField>
|
||||
</Grid>
|
||||
{editItem.ram === 1 && (
|
||||
<Grid>
|
||||
<TextField
|
||||
name="value"
|
||||
label={LL.DEFAULT(0) + ' ' + LL.VALUE(0)}
|
||||
type="string"
|
||||
value={editItem.value as string}
|
||||
variant="outlined"
|
||||
onChange={updateFormValue}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
/>
|
||||
</Grid>
|
||||
<>
|
||||
<Grid>
|
||||
<TextField
|
||||
name="value"
|
||||
label={LL.DEFAULT(0) + ' ' + LL.VALUE(0)}
|
||||
type="string"
|
||||
value={editItem.value as string}
|
||||
variant="outlined"
|
||||
onChange={updateFormValue}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid>
|
||||
<TextField
|
||||
name="uom"
|
||||
label={LL.UNIT()}
|
||||
value={editItem.uom}
|
||||
margin="normal"
|
||||
onChange={updateFormValue}
|
||||
select
|
||||
>
|
||||
{DeviceValueUOM_s.map((val, i) => (
|
||||
<MenuItem key={val} value={i}>
|
||||
{val}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
</Grid>
|
||||
</>
|
||||
)}
|
||||
{editItem.ram === 0 && (
|
||||
<>
|
||||
@@ -255,7 +289,7 @@ const CustomEntitiesDialog = ({
|
||||
<TextField
|
||||
name="factor"
|
||||
label={LL.FACTOR()}
|
||||
value={numberValue(editItem.factor)}
|
||||
value={numberValue(editItem.factor as number)}
|
||||
variant="outlined"
|
||||
onChange={updateFormValue}
|
||||
sx={{ width: '11ch' }}
|
||||
@@ -291,16 +325,42 @@ const CustomEntitiesDialog = ({
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="factor"
|
||||
label="Bytes"
|
||||
value={numberValue(editItem.factor)}
|
||||
label={LL.BYTES()}
|
||||
value={numberValue(editItem.factor as number)}
|
||||
sx={{ width: '11ch' }}
|
||||
variant="outlined"
|
||||
onChange={updateFormValue}
|
||||
margin="normal"
|
||||
type="number"
|
||||
slotProps={{
|
||||
htmlInput: { step: '1', min: '1', max: '255' }
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
{editItem.value_type === DeviceValueType.BOOL && (
|
||||
<Grid>
|
||||
<ValidatedTextField
|
||||
fieldErrors={fieldErrors}
|
||||
name="factor"
|
||||
label={LL.BITMASK()}
|
||||
value={editItem.factor as string}
|
||||
sx={{ width: '11ch' }}
|
||||
variant="outlined"
|
||||
onChange={updateFormValue}
|
||||
margin="normal"
|
||||
type="string"
|
||||
slotProps={{
|
||||
input: {
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">0x</InputAdornment>
|
||||
)
|
||||
},
|
||||
htmlInput: { style: { textTransform: 'uppercase' } }
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Grid>
|
||||
@@ -316,6 +376,15 @@ const CustomEntitiesDialog = ({
|
||||
>
|
||||
{LL.REMOVE()}
|
||||
</Button>
|
||||
<Button
|
||||
sx={{ ml: 1 }}
|
||||
startIcon={<AddIcon />}
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
onClick={dup}
|
||||
>
|
||||
{LL.DUPLICATE()}
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
<Button
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Grid2 as Grid,
|
||||
InputAdornment,
|
||||
Link,
|
||||
MenuItem,
|
||||
@@ -24,7 +25,6 @@ import {
|
||||
ToggleButtonGroup,
|
||||
Typography
|
||||
} from '@mui/material';
|
||||
import Grid from '@mui/material/Grid2';
|
||||
|
||||
import {
|
||||
Body,
|
||||
@@ -38,7 +38,7 @@ import {
|
||||
import { useTheme } from '@table-library/react-table-library/theme';
|
||||
import { dialogStyle } from 'CustomTheme';
|
||||
import { useRequest } from 'alova/client';
|
||||
import RestartMonitor from 'app/status/RestartMonitor';
|
||||
import SystemMonitor from 'app/status/SystemMonitor';
|
||||
import {
|
||||
BlockNavigation,
|
||||
ButtonRow,
|
||||
@@ -593,7 +593,7 @@ const Customizations = () => {
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid>
|
||||
<Typography variant="subtitle2" color="primary">
|
||||
<Typography variant="subtitle2" color="grey">
|
||||
{LL.SHOWING()} {shown_data.length}/{deviceEntities.length}
|
||||
{LL.ENTITIES(deviceEntities.length)}
|
||||
</Typography>
|
||||
@@ -737,7 +737,7 @@ const Customizations = () => {
|
||||
return (
|
||||
<SectionContent>
|
||||
{blocker ? <BlockNavigation blocker={blocker} /> : null}
|
||||
{restarting ? <RestartMonitor /> : renderContent()}
|
||||
{restarting ? <SystemMonitor /> : renderContent()}
|
||||
{selectedDeviceEntity && (
|
||||
<SettingsCustomizationsDialog
|
||||
open={dialogOpen}
|
||||
|
||||
@@ -10,10 +10,10 @@ import {
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Grid2 as Grid,
|
||||
TextField,
|
||||
Typography
|
||||
} from '@mui/material';
|
||||
import Grid from '@mui/material/Grid2';
|
||||
|
||||
import { dialogStyle } from 'CustomTheme';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
import { IconContext } from 'react-icons/lib';
|
||||
import { Link } from 'react-router';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
|
||||
import EditIcon from '@mui/icons-material/Edit';
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
import HelpOutlineIcon from '@mui/icons-material/HelpOutline';
|
||||
import UnfoldLessIcon from '@mui/icons-material/UnfoldLess';
|
||||
import UnfoldMoreIcon from '@mui/icons-material/UnfoldMore';
|
||||
import {
|
||||
@@ -12,16 +14,20 @@ import {
|
||||
IconButton,
|
||||
ToggleButton,
|
||||
ToggleButtonGroup,
|
||||
Tooltip,
|
||||
Typography
|
||||
} from '@mui/material';
|
||||
import Grid from '@mui/material/Grid2';
|
||||
|
||||
import { Body, Cell, Row, Table } from '@table-library/react-table-library/table';
|
||||
import { useTheme } from '@table-library/react-table-library/theme';
|
||||
import { CellTree, useTree } from '@table-library/react-table-library/tree';
|
||||
import { useRequest } from 'alova/client';
|
||||
import { FormLoader, SectionContent, useLayoutTitle } from 'components';
|
||||
import {
|
||||
ButtonTooltip,
|
||||
FormLoader,
|
||||
MessageBox,
|
||||
SectionContent,
|
||||
useLayoutTitle
|
||||
} from 'components';
|
||||
import { AuthenticatedContext } from 'contexts/authentication';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import { useInterval, usePersistState } from 'utils';
|
||||
@@ -54,13 +60,12 @@ const Dashboard = () => {
|
||||
const {
|
||||
data,
|
||||
send: fetchDashboard,
|
||||
error,
|
||||
loading
|
||||
error
|
||||
} = useRequest(readDashboard, {
|
||||
initialData: []
|
||||
initialData: { connected: true, nodes: [] }
|
||||
}).onSuccess((event) => {
|
||||
if (event.data.length !== parentNodes) {
|
||||
setParentNodes(event.data.length); // count number of parents/devices
|
||||
if (event.data.nodes.length !== parentNodes) {
|
||||
setParentNodes(event.data.nodes.length); // count number of parents/devices
|
||||
}
|
||||
});
|
||||
|
||||
@@ -120,7 +125,7 @@ const Dashboard = () => {
|
||||
});
|
||||
|
||||
const tree = useTree(
|
||||
{ nodes: data },
|
||||
{ nodes: data.nodes },
|
||||
{
|
||||
onChange: undefined // not used but needed
|
||||
},
|
||||
@@ -149,11 +154,11 @@ const Dashboard = () => {
|
||||
if (!deviceValueDialogOpen) {
|
||||
void fetchDashboard();
|
||||
}
|
||||
}, 3000);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
showAll
|
||||
? tree.fns.onAddAll(data.map((item: DashboardItem) => item.id)) // expand tree
|
||||
? tree.fns.onAddAll(data.nodes.map((item: DashboardItem) => item.id)) // expand tree
|
||||
: tree.fns.onRemoveAll(); // collapse tree
|
||||
}, [parentNodes]);
|
||||
|
||||
@@ -223,120 +228,133 @@ const Dashboard = () => {
|
||||
return <FormLoader onRetry={fetchDashboard} errorMessage={error?.message} />;
|
||||
}
|
||||
|
||||
const hasFavEntities = data.nodes.filter(
|
||||
(item: DashboardItem) => item.id <= 90
|
||||
).length;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
backgroundColor: 'black',
|
||||
pt: 1,
|
||||
pl: 2
|
||||
}}
|
||||
>
|
||||
<Grid container spacing={0} justifyContent="flex-start">
|
||||
<Grid size={11}>
|
||||
<Typography mb={2} variant="body1" color="warning">
|
||||
{LL.DASHBOARD_1()}.
|
||||
</Typography>
|
||||
</Grid>
|
||||
{!data.connected && (
|
||||
<MessageBox mb={2} level="error" message={LL.EMS_BUS_WARNING()} />
|
||||
)}
|
||||
|
||||
<Grid size={1} alignItems="end">
|
||||
<ToggleButtonGroup
|
||||
color="primary"
|
||||
size="small"
|
||||
value={showAll}
|
||||
exclusive
|
||||
onChange={handleShowAll}
|
||||
>
|
||||
{data.connected && data.nodes.length > 0 && !hasFavEntities && (
|
||||
<MessageBox mb={2} level="warning">
|
||||
<Typography>
|
||||
{LL.NO_DATA_1()}
|
||||
<Link to="/customizations" style={{ color: 'white' }}>
|
||||
{LL.CUSTOMIZATIONS()}
|
||||
</Link>
|
||||
{LL.NO_DATA_2()}
|
||||
{LL.NO_DATA_3()}
|
||||
<Link to="/devices" style={{ color: 'white' }}>
|
||||
{LL.DEVICES()}
|
||||
</Link>
|
||||
.
|
||||
</Typography>
|
||||
</MessageBox>
|
||||
)}
|
||||
|
||||
{data.nodes.length > 0 && (
|
||||
<>
|
||||
<ToggleButtonGroup
|
||||
color="primary"
|
||||
size="small"
|
||||
value={showAll}
|
||||
exclusive
|
||||
onChange={handleShowAll}
|
||||
>
|
||||
<ButtonTooltip title={LL.ALLVALUES()} arrow>
|
||||
<ToggleButton value={true}>
|
||||
<UnfoldMoreIcon sx={{ fontSize: 18 }} />
|
||||
</ToggleButton>
|
||||
</ButtonTooltip>
|
||||
<ButtonTooltip title={LL.COMPACT()} arrow>
|
||||
<ToggleButton value={false}>
|
||||
<UnfoldLessIcon sx={{ fontSize: 18 }} />
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
</ButtonTooltip>
|
||||
</ToggleButtonGroup>
|
||||
<ButtonTooltip title={LL.DASHBOARD_1()} arrow>
|
||||
<HelpOutlineIcon color="primary" sx={{ ml: 1, fontSize: 20 }} />
|
||||
</ButtonTooltip>
|
||||
|
||||
<Box
|
||||
padding={1}
|
||||
justifyContent="center"
|
||||
flexDirection="column"
|
||||
sx={{
|
||||
borderRadius: 1,
|
||||
border: '1px solid grey'
|
||||
}}
|
||||
>
|
||||
<IconContext.Provider
|
||||
value={{
|
||||
color: 'lightblue',
|
||||
size: '16',
|
||||
style: { verticalAlign: 'middle' }
|
||||
}}
|
||||
>
|
||||
{!loading && data.length === 0 ? (
|
||||
<Typography variant="subtitle2" color="secondary">
|
||||
{LL.NO_DATA()}
|
||||
</Typography>
|
||||
) : (
|
||||
<Table
|
||||
data={{ nodes: data }}
|
||||
theme={dashboard_theme}
|
||||
layout={{ custom: true }}
|
||||
tree={tree}
|
||||
<Box
|
||||
padding={1}
|
||||
justifyContent="center"
|
||||
flexDirection="column"
|
||||
sx={{
|
||||
borderRadius: 1,
|
||||
border: '1px solid grey'
|
||||
}}
|
||||
>
|
||||
<IconContext.Provider
|
||||
value={{
|
||||
color: 'lightblue',
|
||||
size: '18',
|
||||
style: { verticalAlign: 'middle' }
|
||||
}}
|
||||
>
|
||||
{(tableList: DashboardItem[]) => (
|
||||
<Body>
|
||||
{tableList.map((di: DashboardItem) => (
|
||||
<Row
|
||||
key={di.id}
|
||||
item={di}
|
||||
onClick={() => editDashboardValue(di)}
|
||||
>
|
||||
{di.id > 99 ? (
|
||||
<>
|
||||
<Cell>{showName(di)}</Cell>
|
||||
<Cell>
|
||||
<Tooltip
|
||||
placement="left"
|
||||
title={formatValue(LL, di.dv?.v, di.dv?.u)}
|
||||
arrow
|
||||
>
|
||||
<span>{formatValue(LL, di.dv?.v, di.dv?.u)}</span>
|
||||
</Tooltip>
|
||||
</Cell>
|
||||
<Table
|
||||
data={{ nodes: data.nodes }}
|
||||
theme={dashboard_theme}
|
||||
layout={{ custom: true }}
|
||||
tree={tree}
|
||||
>
|
||||
{(tableList: DashboardItem[]) => (
|
||||
<Body>
|
||||
{tableList.map((di: DashboardItem) => (
|
||||
<Row
|
||||
key={di.id}
|
||||
item={di}
|
||||
onClick={() => editDashboardValue(di)}
|
||||
>
|
||||
{di.id > 99 ? (
|
||||
<>
|
||||
<Cell>{showName(di)}</Cell>
|
||||
<Cell>
|
||||
<ButtonTooltip
|
||||
title={formatValue(LL, di.dv?.v, di.dv?.u)}
|
||||
>
|
||||
<span>{formatValue(LL, di.dv?.v, di.dv?.u)}</span>
|
||||
</ButtonTooltip>
|
||||
</Cell>
|
||||
|
||||
<Cell>
|
||||
{me.admin &&
|
||||
di.dv?.c &&
|
||||
!hasMask(di.dv.id, DeviceEntityMask.DV_READONLY) && (
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => editDashboardValue(di)}
|
||||
>
|
||||
<EditIcon
|
||||
color="primary"
|
||||
sx={{ fontSize: 16 }}
|
||||
/>
|
||||
</IconButton>
|
||||
)}
|
||||
</Cell>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<CellTree item={di}>{showName(di)}</CellTree>
|
||||
<Cell />
|
||||
<Cell />
|
||||
</>
|
||||
)}
|
||||
</Row>
|
||||
))}
|
||||
</Body>
|
||||
)}
|
||||
</Table>
|
||||
)}
|
||||
</IconContext.Provider>
|
||||
</Box>
|
||||
<Cell>
|
||||
{me.admin &&
|
||||
di.dv?.c &&
|
||||
!hasMask(
|
||||
di.dv.id,
|
||||
DeviceEntityMask.DV_READONLY
|
||||
) && (
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => editDashboardValue(di)}
|
||||
>
|
||||
<EditIcon
|
||||
color="primary"
|
||||
sx={{ fontSize: 16 }}
|
||||
/>
|
||||
</IconButton>
|
||||
)}
|
||||
</Cell>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<CellTree item={di}>{showName(di)}</CellTree>
|
||||
<Cell />
|
||||
<Cell />
|
||||
</>
|
||||
)}
|
||||
</Row>
|
||||
))}
|
||||
</Body>
|
||||
)}
|
||||
</Table>
|
||||
</IconContext.Provider>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -10,15 +10,16 @@ import { useNavigate } from 'react-router';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import CommentsDisabledOutlinedIcon from '@mui/icons-material/CommentsDisabledOutlined';
|
||||
import ConstructionIcon from '@mui/icons-material/Construction';
|
||||
import EditIcon from '@mui/icons-material/Edit';
|
||||
import EditOffOutlinedIcon from '@mui/icons-material/EditOffOutlined';
|
||||
import FormatListNumberedIcon from '@mui/icons-material/FormatListNumbered';
|
||||
import DownloadIcon from '@mui/icons-material/GetApp';
|
||||
import HighlightOffIcon from '@mui/icons-material/HighlightOff';
|
||||
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
|
||||
import KeyboardArrowDownOutlinedIcon from '@mui/icons-material/KeyboardArrowDownOutlined';
|
||||
import KeyboardArrowUpOutlinedIcon from '@mui/icons-material/KeyboardArrowUpOutlined';
|
||||
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
import StarIcon from '@mui/icons-material/Star';
|
||||
import StarBorderOutlinedIcon from '@mui/icons-material/StarBorderOutlined';
|
||||
import UnfoldMoreOutlinedIcon from '@mui/icons-material/UnfoldMoreOutlined';
|
||||
@@ -30,17 +31,16 @@ import {
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Grid2 as Grid,
|
||||
IconButton,
|
||||
InputAdornment,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemText,
|
||||
Tooltip,
|
||||
type TooltipProps,
|
||||
Typography,
|
||||
styled,
|
||||
tooltipClasses
|
||||
TextField,
|
||||
ToggleButton,
|
||||
Typography
|
||||
} from '@mui/material';
|
||||
import Grid from '@mui/material/Grid2';
|
||||
|
||||
import { useRowSelect } from '@table-library/react-table-library/select';
|
||||
import { SortToggleType, useSort } from '@table-library/react-table-library/sort';
|
||||
@@ -57,7 +57,12 @@ import { useTheme } from '@table-library/react-table-library/theme';
|
||||
import type { Action, State } from '@table-library/react-table-library/types/common';
|
||||
import { dialogStyle } from 'CustomTheme';
|
||||
import { useRequest } from 'alova/client';
|
||||
import { MessageBox, SectionContent, useLayoutTitle } from 'components';
|
||||
import {
|
||||
ButtonTooltip,
|
||||
MessageBox,
|
||||
SectionContent,
|
||||
useLayoutTitle
|
||||
} from 'components';
|
||||
import { AuthenticatedContext } from 'contexts/authentication';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import { useInterval } from 'utils';
|
||||
@@ -80,6 +85,7 @@ const Devices = () => {
|
||||
const [deviceValueDialogOpen, setDeviceValueDialogOpen] = useState(false);
|
||||
const [showDeviceInfo, setShowDeviceInfo] = useState(false);
|
||||
const [selectedDevice, setSelectedDevice] = useState<number>();
|
||||
const [search, setSearch] = useState('');
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
@@ -221,20 +227,6 @@ const Devices = () => {
|
||||
}
|
||||
]);
|
||||
|
||||
const ButtonTooltip = styled(({ className, ...props }: TooltipProps) => (
|
||||
<Tooltip {...props} arrow classes={{ popper: className }} />
|
||||
))(({ theme }) => ({
|
||||
[`& .${tooltipClasses.arrow}`]: {
|
||||
color: theme.palette.success.main
|
||||
},
|
||||
[`& .${tooltipClasses.tooltip}`]: {
|
||||
backgroundColor: theme.palette.success.main,
|
||||
color: 'rgba(0, 0, 0, 0.87)',
|
||||
boxShadow: theme.shadows[1],
|
||||
fontSize: 10
|
||||
}
|
||||
}));
|
||||
|
||||
const getSortIcon = (state: State, sortKey: unknown) => {
|
||||
if (state.sortKey === sortKey && state.reverse) {
|
||||
return <KeyboardArrowDownOutlinedIcon />;
|
||||
@@ -284,6 +276,7 @@ const Devices = () => {
|
||||
|
||||
const resetDeviceSelect = () => {
|
||||
device_select.fns.onRemoveAll();
|
||||
setSearch('');
|
||||
};
|
||||
|
||||
const escFunction = useCallback(
|
||||
@@ -419,7 +412,7 @@ const Devices = () => {
|
||||
if (!deviceValueDialogOpen) {
|
||||
selectedDevice ? void sendDeviceData(selectedDevice) : void sendCoreData();
|
||||
}
|
||||
}, 3000);
|
||||
});
|
||||
|
||||
const deviceValueDialogSave = async (devicevalue: DeviceValue) => {
|
||||
const id = Number(device_select.state.id);
|
||||
@@ -522,7 +515,7 @@ const Devices = () => {
|
||||
<IconContext.Provider
|
||||
value={{
|
||||
color: 'lightblue',
|
||||
size: '16',
|
||||
size: '18',
|
||||
style: { verticalAlign: 'middle' }
|
||||
}}
|
||||
>
|
||||
@@ -604,8 +597,12 @@ const Devices = () => {
|
||||
);
|
||||
|
||||
const shown_data = onlyFav
|
||||
? deviceData.nodes.filter((dv) => hasMask(dv.id, DeviceEntityMask.DV_FAVORITE))
|
||||
: deviceData.nodes;
|
||||
? deviceData.nodes.filter(
|
||||
(dv) =>
|
||||
hasMask(dv.id, DeviceEntityMask.DV_FAVORITE) &&
|
||||
dv.id.slice(2).includes(search)
|
||||
)
|
||||
: deviceData.nodes.filter((dv) => dv.id.slice(2).includes(search));
|
||||
|
||||
const deviceIndex = coreData.devices.findIndex(
|
||||
(d) => d.id === device_select.state.id
|
||||
@@ -628,56 +625,84 @@ const Devices = () => {
|
||||
border: '1px solid #177ac9'
|
||||
}}
|
||||
>
|
||||
<Box sx={{ border: '1px solid #177ac9' }}>
|
||||
<Typography noWrap variant="subtitle1" color="warning.main" sx={{ ml: 1 }}>
|
||||
{coreData.devices[deviceIndex].n} (
|
||||
{coreData.devices[deviceIndex].tn})
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ p: 1 }}>
|
||||
<Grid container justifyContent="space-between">
|
||||
<Typography sx={{ ml: 1 }} variant="subtitle2" color="grey">
|
||||
{LL.SHOWING() +
|
||||
' ' +
|
||||
shown_data.length +
|
||||
'/' +
|
||||
coreData.devices[deviceIndex].e +
|
||||
' ' +
|
||||
LL.ENTITIES(shown_data.length)}
|
||||
<ButtonTooltip title="Info">
|
||||
<IconButton onClick={() => setShowDeviceInfo(true)}>
|
||||
<InfoOutlinedIcon color="primary" sx={{ fontSize: 18 }} />
|
||||
</IconButton>
|
||||
</ButtonTooltip>
|
||||
{me.admin && (
|
||||
<ButtonTooltip title={LL.CUSTOMIZATIONS()}>
|
||||
<IconButton onClick={customize}>
|
||||
<FormatListNumberedIcon color="primary" sx={{ fontSize: 18 }} />
|
||||
</IconButton>
|
||||
</ButtonTooltip>
|
||||
)}
|
||||
<ButtonTooltip title={LL.EXPORT()}>
|
||||
<IconButton onClick={handleDownloadCsv}>
|
||||
<DownloadIcon color="primary" sx={{ fontSize: 18 }} />
|
||||
</IconButton>
|
||||
</ButtonTooltip>
|
||||
<ButtonTooltip title={LL.FAVORITES()}>
|
||||
<IconButton onClick={() => setOnlyFav(!onlyFav)}>
|
||||
{onlyFav ? (
|
||||
<StarIcon color="primary" sx={{ fontSize: 18 }} />
|
||||
) : (
|
||||
<StarBorderOutlinedIcon color="primary" sx={{ fontSize: 18 }} />
|
||||
)}
|
||||
</IconButton>
|
||||
</ButtonTooltip>
|
||||
<Typography noWrap variant="subtitle1" color="warning.main">
|
||||
{coreData.devices[deviceIndex].n} (
|
||||
{coreData.devices[deviceIndex].tn})
|
||||
</Typography>
|
||||
<Grid justifyContent="flex-end">
|
||||
<ButtonTooltip title={LL.CANCEL()}>
|
||||
<ButtonTooltip title={LL.CLOSE()}>
|
||||
<IconButton onClick={resetDeviceSelect}>
|
||||
<HighlightOffIcon color="primary" sx={{ fontSize: 18 }} />
|
||||
</IconButton>
|
||||
</ButtonTooltip>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<TextField
|
||||
size="small"
|
||||
variant="outlined"
|
||||
sx={{ width: '22ch' }}
|
||||
placeholder={LL.SEARCH()}
|
||||
onChange={(event) => {
|
||||
setSearch(event.target.value);
|
||||
}}
|
||||
slotProps={{
|
||||
input: {
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon color="primary" sx={{ fontSize: 16 }} />
|
||||
</InputAdornment>
|
||||
)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<ButtonTooltip title={LL.DEVICE_DETAILS()}>
|
||||
<IconButton onClick={() => setShowDeviceInfo(true)}>
|
||||
<InfoOutlinedIcon color="primary" sx={{ fontSize: 18 }} />
|
||||
</IconButton>
|
||||
</ButtonTooltip>
|
||||
{me.admin && (
|
||||
<ButtonTooltip title={LL.CUSTOMIZATIONS()}>
|
||||
<IconButton onClick={customize}>
|
||||
<ConstructionIcon color="primary" sx={{ fontSize: 18 }} />
|
||||
</IconButton>
|
||||
</ButtonTooltip>
|
||||
)}
|
||||
<ButtonTooltip title={LL.EXPORT()}>
|
||||
<IconButton onClick={handleDownloadCsv}>
|
||||
<DownloadIcon color="primary" sx={{ fontSize: 18 }} />
|
||||
</IconButton>
|
||||
</ButtonTooltip>
|
||||
|
||||
<ButtonTooltip title={LL.FAVORITES()}>
|
||||
<ToggleButton
|
||||
value="1"
|
||||
size="small"
|
||||
selected={onlyFav}
|
||||
onChange={() => {
|
||||
setOnlyFav(!onlyFav);
|
||||
}}
|
||||
>
|
||||
{onlyFav ? (
|
||||
<StarIcon color="primary" sx={{ fontSize: 18 }} />
|
||||
) : (
|
||||
<StarBorderOutlinedIcon color="primary" sx={{ fontSize: 18 }} />
|
||||
)}{' '}
|
||||
</ToggleButton>
|
||||
</ButtonTooltip>
|
||||
|
||||
<span style={{ color: 'grey', fontSize: '12px' }}>
|
||||
|
||||
{LL.SHOWING() +
|
||||
' ' +
|
||||
shown_data.length +
|
||||
'/' +
|
||||
coreData.devices[deviceIndex].e +
|
||||
' ' +
|
||||
LL.ENTITIES(shown_data.length)}
|
||||
</span>
|
||||
</Box>
|
||||
|
||||
<Table
|
||||
|
||||
@@ -11,12 +11,12 @@ import {
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
FormHelperText,
|
||||
Grid2 as Grid,
|
||||
InputAdornment,
|
||||
MenuItem,
|
||||
TextField,
|
||||
Typography
|
||||
} from '@mui/material';
|
||||
import Grid from '@mui/material/Grid2';
|
||||
|
||||
import { dialogStyle } from 'CustomTheme';
|
||||
import type Schema from 'async-validator';
|
||||
|
||||
@@ -51,20 +51,6 @@ const Help = () => {
|
||||
}
|
||||
});
|
||||
|
||||
// const { send: sendExportAllValues } = useRequest(
|
||||
// () => callAction({ action: 'export', param: 'allvalues' }),
|
||||
// {
|
||||
// immediate: false
|
||||
// }
|
||||
// )
|
||||
// .onSuccess((event) => {
|
||||
// saveFile(event.data, 'allvalues', '.txt');
|
||||
// toast.info(LL.DOWNLOAD_SUCCESSFUL());
|
||||
// })
|
||||
// .onError((error) => {
|
||||
// toast.error(error.message);
|
||||
// });
|
||||
|
||||
const { send: sendAPI } = useRequest((data: APIcall) => API(data), {
|
||||
immediate: false
|
||||
})
|
||||
@@ -114,7 +100,12 @@ const Help = () => {
|
||||
{me.admin && (
|
||||
<List sx={{ borderRadius: 3, border: '2px solid grey' }}>
|
||||
<ListItem>
|
||||
<ListItemButton component="a" href="https://docs.emsesp.org">
|
||||
<ListItemButton
|
||||
component="a"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href="https://docs.emsesp.org"
|
||||
>
|
||||
<ListItemAvatar>
|
||||
<Avatar sx={{ bgcolor: '#72caf9' }}>
|
||||
<MenuBookIcon />
|
||||
@@ -125,7 +116,12 @@ const Help = () => {
|
||||
</ListItem>
|
||||
|
||||
<ListItem>
|
||||
<ListItemButton component="a" href="https://discord.gg/3J3GgnzpyT">
|
||||
<ListItemButton
|
||||
component="a"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href="https://discord.gg/3J3GgnzpyT"
|
||||
>
|
||||
<ListItemAvatar>
|
||||
<Avatar sx={{ bgcolor: '#72caf9' }}>
|
||||
<CommentIcon />
|
||||
@@ -138,6 +134,8 @@ const Help = () => {
|
||||
<ListItem>
|
||||
<ListItemButton
|
||||
component="a"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href="https://github.com/emsesp/EMS-ESP32/issues/new/choose"
|
||||
>
|
||||
<ListItemAvatar>
|
||||
@@ -165,21 +163,16 @@ const Help = () => {
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
{/* <Button
|
||||
sx={{ ml: 2 }}
|
||||
startIcon={<DownloadIcon />}
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
onClick={() => sendExportAllValues()}
|
||||
>
|
||||
{LL.DOWNLOAD(1)} {LL.ALLVALUES()}
|
||||
</Button> */}
|
||||
|
||||
<Divider sx={{ mt: 4 }} />
|
||||
|
||||
<Typography color="white" variant="subtitle1" align="center" mt={1}>
|
||||
©
|
||||
<Link target="_blank" href="https://emsesp.org" color="primary">
|
||||
<Link
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href="https://emsesp.org"
|
||||
color="primary"
|
||||
>
|
||||
{'emsesp.org'}
|
||||
</Link>
|
||||
</Typography>
|
||||
|
||||
@@ -10,9 +10,9 @@ import {
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Grid2 as Grid,
|
||||
TextField
|
||||
} from '@mui/material';
|
||||
import Grid from '@mui/material/Grid2';
|
||||
|
||||
import { dialogStyle } from 'CustomTheme';
|
||||
import { BlockFormControlLabel } from 'components';
|
||||
|
||||
@@ -13,12 +13,12 @@ import {
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Grid2 as Grid,
|
||||
TextField,
|
||||
ToggleButton,
|
||||
ToggleButtonGroup,
|
||||
Typography
|
||||
} from '@mui/material';
|
||||
import Grid from '@mui/material/Grid2';
|
||||
|
||||
import { dialogStyle } from 'CustomTheme';
|
||||
import type Schema from 'async-validator';
|
||||
|
||||
@@ -90,7 +90,7 @@ const Sensors = () => {
|
||||
if (!temperatureDialogOpen && !analogDialogOpen) {
|
||||
void fetchSensorData();
|
||||
}
|
||||
}, 3000);
|
||||
});
|
||||
|
||||
const common_theme = useTheme({
|
||||
BaseRow: `
|
||||
|
||||
@@ -10,12 +10,12 @@ import {
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Grid2 as Grid,
|
||||
InputAdornment,
|
||||
MenuItem,
|
||||
TextField,
|
||||
Typography
|
||||
} from '@mui/material';
|
||||
import Grid from '@mui/material/Grid2';
|
||||
|
||||
import { dialogStyle } from 'CustomTheme';
|
||||
import type Schema from 'async-validator';
|
||||
|
||||
@@ -9,11 +9,11 @@ import {
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Grid2 as Grid,
|
||||
InputAdornment,
|
||||
TextField,
|
||||
Typography
|
||||
} from '@mui/material';
|
||||
import Grid from '@mui/material/Grid2';
|
||||
|
||||
import { dialogStyle } from 'CustomTheme';
|
||||
import type Schema from 'async-validator';
|
||||
|
||||
@@ -34,7 +34,12 @@ export function formatValue(
|
||||
if (value === undefined || typeof value === 'boolean') {
|
||||
return '';
|
||||
}
|
||||
return value as string;
|
||||
return (
|
||||
(value as string) +
|
||||
(value === '' || uom === undefined || uom === 0
|
||||
? ''
|
||||
: ' ' + DeviceValueUOM_s[uom])
|
||||
);
|
||||
}
|
||||
|
||||
switch (uom) {
|
||||
|
||||
@@ -21,6 +21,7 @@ export interface Settings {
|
||||
dallas_gpio: number;
|
||||
dallas_parasite: boolean;
|
||||
led_gpio: number;
|
||||
led_type: number;
|
||||
hide_led: boolean;
|
||||
low_clock: boolean;
|
||||
notoken_api: boolean;
|
||||
@@ -71,7 +72,7 @@ export interface Device {
|
||||
d: number; // deviceid
|
||||
p: number; // productid
|
||||
v: string; // version
|
||||
e: number; // entities
|
||||
e: number; // total number of entities
|
||||
url?: string; // lowercase type name used in API URL
|
||||
}
|
||||
|
||||
@@ -123,6 +124,11 @@ export interface DashboardItem {
|
||||
nodes?: DashboardItem[]; // children nodes, optional
|
||||
}
|
||||
|
||||
export interface DashboardData {
|
||||
connected: boolean; // true if connected to EMS bus
|
||||
nodes: DashboardItem[];
|
||||
}
|
||||
|
||||
export interface DeviceValue {
|
||||
id: string; // index, contains mask+name
|
||||
v?: unknown; // value, Number, String or Boolean - can be undefined
|
||||
@@ -262,6 +268,7 @@ export const BOARD_PROFILES: BoardProfiles = {
|
||||
export interface BoardProfile {
|
||||
board_profile: string;
|
||||
led_gpio: number;
|
||||
led_type: number;
|
||||
dallas_gpio: number;
|
||||
rx_gpio: number;
|
||||
tx_gpio: number;
|
||||
@@ -368,7 +375,7 @@ export interface EntityItem {
|
||||
device_id: number | string;
|
||||
type_id: number | string;
|
||||
offset: number;
|
||||
factor: number;
|
||||
factor: number | string;
|
||||
uom: number;
|
||||
value_type: number;
|
||||
value?: unknown;
|
||||
@@ -380,7 +387,7 @@ export interface EntityItem {
|
||||
o_device_id?: number | string;
|
||||
o_type_id?: number | string;
|
||||
o_offset?: number;
|
||||
o_factor?: number;
|
||||
o_factor?: number | string;
|
||||
o_uom?: number;
|
||||
o_value_type?: number;
|
||||
o_deleted?: boolean;
|
||||
|
||||
@@ -382,10 +382,7 @@ export const entityItemValidation = (entity: EntityItem[], entityItem: EntityIte
|
||||
{ required: true, message: 'Offset is required' },
|
||||
{ type: 'number', min: 0, max: 255, message: 'Must be between 0 and 255' }
|
||||
],
|
||||
factor: [
|
||||
{ required: true, message: 'is required' },
|
||||
{ type: 'number', message: 'Must be a number' }
|
||||
]
|
||||
factor: [{ required: true, message: 'is required' }]
|
||||
});
|
||||
|
||||
export const uniqueTemperatureNameValidator = (
|
||||
|
||||
@@ -9,17 +9,17 @@ import {
|
||||
Button,
|
||||
Checkbox,
|
||||
Divider,
|
||||
Grid2 as Grid,
|
||||
InputAdornment,
|
||||
MenuItem,
|
||||
TextField,
|
||||
Typography
|
||||
} from '@mui/material';
|
||||
import Grid from '@mui/material/Grid2';
|
||||
|
||||
import { readSystemStatus } from 'api/system';
|
||||
|
||||
import { useRequest } from 'alova/client';
|
||||
import RestartMonitor from 'app/status/RestartMonitor';
|
||||
import SystemMonitor from 'app/status/SystemMonitor';
|
||||
import type { ValidateFieldsError } from 'async-validator';
|
||||
import {
|
||||
BlockFormControlLabel,
|
||||
@@ -126,9 +126,6 @@ const ApplicationSettings = () => {
|
||||
const SecondsInputProps = {
|
||||
endAdornment: <InputAdornment position="end">{LL.SECONDS()}</InputAdornment>
|
||||
};
|
||||
const MilliSecondsInputProps = {
|
||||
endAdornment: <InputAdornment position="end">ms</InputAdornment>
|
||||
};
|
||||
const MinutesInputProps = {
|
||||
endAdornment: <InputAdornment position="end">{LL.MINUTES()}</InputAdornment>
|
||||
};
|
||||
@@ -207,7 +204,16 @@ const ApplicationSettings = () => {
|
||||
disabled={!hardwareData.psram}
|
||||
/>
|
||||
}
|
||||
label={LL.ENABLE_MODBUS()}
|
||||
label={
|
||||
<Typography color={!hardwareData.psram ? 'grey' : 'default'}>
|
||||
{LL.ENABLE_MODBUS()}
|
||||
{!hardwareData.psram && (
|
||||
<Typography variant="caption">
|
||||
({LL.IS_REQUIRED('PSRAM')})
|
||||
</Typography>
|
||||
)}
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
{data.modbus_enabled && (
|
||||
<Grid container spacing={2} rowSpacing={0}>
|
||||
@@ -241,7 +247,7 @@ const ApplicationSettings = () => {
|
||||
name="modbus_timeout"
|
||||
label="Timeout"
|
||||
slotProps={{
|
||||
input: MilliSecondsInputProps
|
||||
input: SecondsInputProps
|
||||
}}
|
||||
variant="outlined"
|
||||
value={numberValue(data.modbus_timeout)}
|
||||
@@ -544,6 +550,23 @@ const ApplicationSettings = () => {
|
||||
margin="normal"
|
||||
/>
|
||||
</Grid>
|
||||
{data.led_gpio !== 0 && (
|
||||
<Grid>
|
||||
<TextField
|
||||
name="led_type"
|
||||
label={'LED ' + LL.TYPE()}
|
||||
value={data.led_type}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
onChange={updateFormValue}
|
||||
margin="normal"
|
||||
select
|
||||
>
|
||||
<MenuItem value={0}>LED</MenuItem>
|
||||
<MenuItem value={1}>RGB-LED</MenuItem>
|
||||
</TextField>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid>
|
||||
<TextField
|
||||
name="phy_type"
|
||||
@@ -853,7 +876,7 @@ const ApplicationSettings = () => {
|
||||
return (
|
||||
<SectionContent>
|
||||
{blocker ? <BlockNavigation blocker={blocker} /> : null}
|
||||
{restarting ? <RestartMonitor /> : content()}
|
||||
{restarting ? <SystemMonitor /> : content()}
|
||||
</SectionContent>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,15 +2,14 @@ import { useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import DownloadIcon from '@mui/icons-material/GetApp';
|
||||
import { Box, Button, Typography } from '@mui/material';
|
||||
import Grid from '@mui/material/Grid2';
|
||||
import { Box, Button, Grid2 as Grid, Typography } from '@mui/material';
|
||||
|
||||
import * as SystemApi from 'api/system';
|
||||
import { API, callAction } from 'api/app';
|
||||
|
||||
import { useRequest } from 'alova/client';
|
||||
import type { APIcall } from 'app/main/types';
|
||||
import RestartMonitor from 'app/status/RestartMonitor';
|
||||
import SystemMonitor from 'app/status/SystemMonitor';
|
||||
import {
|
||||
FormLoader,
|
||||
SectionContent,
|
||||
@@ -109,6 +108,15 @@ const DownloadUpload = () => {
|
||||
{LL.SCHEDULE(0)}
|
||||
</Button>
|
||||
</Grid>
|
||||
<Button
|
||||
sx={{ ml: 2, mt: 2 }}
|
||||
startIcon={<DownloadIcon />}
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
onClick={() => sendExportData('allvalues')}
|
||||
>
|
||||
{LL.ALLVALUES()}
|
||||
</Button>
|
||||
|
||||
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
|
||||
{LL.UPLOAD()}
|
||||
@@ -118,13 +126,13 @@ const DownloadUpload = () => {
|
||||
<Typography variant="body1">{LL.UPLOAD_TEXT()}.</Typography>
|
||||
</Box>
|
||||
|
||||
<SingleUpload doRestart={doRestart} />
|
||||
<SingleUpload text={LL.UPLOAD_DRAG()} doRestart={doRestart} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionContent>{restarting ? <RestartMonitor /> : content()}</SectionContent>
|
||||
<SectionContent>{restarting ? <SystemMonitor /> : content()}</SectionContent>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -5,12 +5,12 @@ import WarningIcon from '@mui/icons-material/Warning';
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
Grid2 as Grid,
|
||||
InputAdornment,
|
||||
MenuItem,
|
||||
TextField,
|
||||
Typography
|
||||
} from '@mui/material';
|
||||
import Grid from '@mui/material/Grid2';
|
||||
|
||||
import * as MqttApi from 'api/mqtt';
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import AccessTimeIcon from '@mui/icons-material/AccessTime';
|
||||
import BuildIcon from '@mui/icons-material/Build';
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import DeviceHubIcon from '@mui/icons-material/DeviceHub';
|
||||
import ImportExportIcon from '@mui/icons-material/ImportExport';
|
||||
@@ -21,7 +20,7 @@ import {
|
||||
List
|
||||
} from '@mui/material';
|
||||
|
||||
import { API, callAction } from 'api/app';
|
||||
import { API } from 'api/app';
|
||||
|
||||
import { dialogStyle } from 'CustomTheme';
|
||||
import { useRequest } from 'alova/client';
|
||||
@@ -40,11 +39,6 @@ const Settings = () => {
|
||||
immediate: false
|
||||
});
|
||||
|
||||
// call checkUpgrade with no param to fetch EMS-ESP version
|
||||
const { data } = useRequest(() => callAction({ action: 'checkUpgrade' }), {
|
||||
initialData: { emsesp_version: '...' }
|
||||
});
|
||||
|
||||
const doFormat = async () => {
|
||||
await sendAPI({ device: 'system', cmd: 'format', id: 0 }).then(() => {
|
||||
setConfirmFactoryReset(false);
|
||||
@@ -83,14 +77,6 @@ const Settings = () => {
|
||||
const content = () => (
|
||||
<>
|
||||
<List sx={{ borderRadius: 3, border: '2px solid grey' }}>
|
||||
<ListMenuItem
|
||||
icon={BuildIcon}
|
||||
bgcolor="#72caf9"
|
||||
label="EMS-ESP Firmware"
|
||||
text={'v' + data.emsesp_version}
|
||||
to="version"
|
||||
/>
|
||||
|
||||
<ListMenuItem
|
||||
icon={TuneIcon}
|
||||
bgcolor="#134ba2"
|
||||
@@ -151,7 +137,7 @@ const Settings = () => {
|
||||
bgcolor="#5d89f7"
|
||||
label={LL.DOWNLOAD_UPLOAD()}
|
||||
text={LL.DOWNLOAD_UPLOAD_1()}
|
||||
to="upload"
|
||||
to="downloadUpload"
|
||||
/>
|
||||
</List>
|
||||
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { Navigate, Route, Routes, useNavigate } from 'react-router';
|
||||
import {
|
||||
Navigate,
|
||||
Route,
|
||||
Routes,
|
||||
matchRoutes,
|
||||
useLocation,
|
||||
useNavigate
|
||||
} from 'react-router';
|
||||
|
||||
import { Tab } from '@mui/material';
|
||||
|
||||
import { RouterTabs, useLayoutTitle, useRouterTab } from 'components';
|
||||
import { RouterTabs, useLayoutTitle } from 'components';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import type { WiFiNetwork } from 'types';
|
||||
|
||||
@@ -15,7 +22,20 @@ const Network = () => {
|
||||
const { LL } = useI18nContext();
|
||||
useLayoutTitle(LL.NETWORK(0));
|
||||
|
||||
const { routerTab } = useRouterTab();
|
||||
// this also works!
|
||||
// const routerTab = useMatch(`settings/network/:path/*`)?.pathname || false;
|
||||
const matchedRoutes = matchRoutes(
|
||||
[
|
||||
{
|
||||
path: '/settings/network/settings',
|
||||
element: <NetworkSettings />,
|
||||
dog: 'woof'
|
||||
},
|
||||
{ path: '/settings/network/scan', element: <WiFiNetworkScanner /> }
|
||||
],
|
||||
useLocation()
|
||||
);
|
||||
const routerTab = matchedRoutes?.[0].route.path || false;
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
@@ -24,7 +44,7 @@ const Network = () => {
|
||||
const selectNetwork = useCallback(
|
||||
(network: WiFiNetwork) => {
|
||||
setSelectedNetwork(network);
|
||||
void navigate('/settings');
|
||||
void navigate('/settings/network/settings');
|
||||
},
|
||||
[navigate]
|
||||
);
|
||||
@@ -42,13 +62,19 @@ const Network = () => {
|
||||
}}
|
||||
>
|
||||
<RouterTabs value={routerTab}>
|
||||
<Tab value="settings" label={LL.SETTINGS_OF(LL.NETWORK(1))} />
|
||||
<Tab value="scan" label={LL.NETWORK_SCAN()} />
|
||||
<Tab
|
||||
value="/settings/network/settings"
|
||||
label={LL.SETTINGS_OF(LL.NETWORK(1))}
|
||||
/>
|
||||
<Tab value="/settings/network/scan" label={LL.NETWORK_SCAN()} />
|
||||
</RouterTabs>
|
||||
<Routes>
|
||||
<Route path="scan" element={<WiFiNetworkScanner />} />
|
||||
<Route path="settings" element={<NetworkSettings />} />
|
||||
<Route path="*" element={<Navigate replace to="settings" />} />
|
||||
<Route
|
||||
path="*"
|
||||
element={<Navigate replace to="/settings/network/settings" />}
|
||||
/>
|
||||
</Routes>
|
||||
</WiFiConnectionContext.Provider>
|
||||
);
|
||||
|
||||
@@ -43,7 +43,7 @@ import { updateValueDirty, useRest } from 'utils';
|
||||
import { validate } from 'validators';
|
||||
import { createNetworkSettingsValidator } from 'validators/network';
|
||||
|
||||
import RestartMonitor from '../../status/RestartMonitor';
|
||||
import SystemMonitor from '../../status/SystemMonitor';
|
||||
import { WiFiConnectionContext } from './WiFiConnectionContext';
|
||||
import { isNetworkOpen, networkSecurityMode } from './WiFiNetworkSelector';
|
||||
|
||||
@@ -400,7 +400,7 @@ const NetworkSettings = () => {
|
||||
return (
|
||||
<SectionContent>
|
||||
{blocker ? <BlockNavigation blocker={blocker} /> : null}
|
||||
{restarting ? <RestartMonitor /> : content()}
|
||||
{restarting ? <SystemMonitor /> : content()}
|
||||
</SectionContent>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Navigate, Route, Routes } from 'react-router';
|
||||
import { Navigate, Route, Routes, matchRoutes, useLocation } from 'react-router';
|
||||
|
||||
import { Tab } from '@mui/material';
|
||||
|
||||
import { RouterTabs, useLayoutTitle, useRouterTab } from 'components';
|
||||
import { RouterTabs, useLayoutTitle } from 'components';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
import ManageUsers from './ManageUsers';
|
||||
@@ -12,18 +12,31 @@ const Security = () => {
|
||||
const { LL } = useI18nContext();
|
||||
useLayoutTitle(LL.SECURITY(0));
|
||||
|
||||
const { routerTab } = useRouterTab();
|
||||
const matchedRoutes = matchRoutes(
|
||||
[
|
||||
{ path: '/settings/security/settings', element: <ManageUsers />, dog: 'woof' },
|
||||
{ path: '/settings/security/users', element: <SecuritySettings /> }
|
||||
],
|
||||
useLocation()
|
||||
);
|
||||
const routerTab = matchedRoutes?.[0].route.path || false;
|
||||
|
||||
return (
|
||||
<>
|
||||
<RouterTabs value={routerTab}>
|
||||
<Tab value="settings" label={LL.SETTINGS_OF(LL.SECURITY(1))} />
|
||||
<Tab value="users" label={LL.MANAGE_USERS()} />
|
||||
<Tab
|
||||
value="/settings/security/settings"
|
||||
label={LL.SETTINGS_OF(LL.SECURITY(1))}
|
||||
/>
|
||||
<Tab value="/settings/security/users" label={LL.MANAGE_USERS()} />
|
||||
</RouterTabs>
|
||||
<Routes>
|
||||
<Route path="users" element={<ManageUsers />} />
|
||||
<Route path="settings" element={<SecuritySettings />} />
|
||||
<Route path="*" element={<Navigate replace to="settings" />} />
|
||||
<Route
|
||||
path="*"
|
||||
element={<Navigate replace to="/settings/security/settings" />}
|
||||
/>
|
||||
</Routes>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -14,11 +14,12 @@ import type { Theme } from '@mui/material';
|
||||
|
||||
import * as APApi from 'api/ap';
|
||||
|
||||
import { useAutoRequest } from 'alova/client';
|
||||
import { useRequest } from 'alova/client';
|
||||
import { FormLoader, SectionContent, useLayoutTitle } from 'components';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import type { APStatusType } from 'types';
|
||||
import { APNetworkStatus } from 'types';
|
||||
import { useInterval } from 'utils';
|
||||
|
||||
export const apStatusHighlight = ({ status }: APStatusType, theme: Theme) => {
|
||||
switch (status) {
|
||||
@@ -34,11 +35,11 @@ export const apStatusHighlight = ({ status }: APStatusType, theme: Theme) => {
|
||||
};
|
||||
|
||||
const APStatus = () => {
|
||||
const {
|
||||
data,
|
||||
send: loadData,
|
||||
error
|
||||
} = useAutoRequest(APApi.readAPStatus, { pollingTime: 3000 });
|
||||
const { data, send: loadData, error } = useRequest(APApi.readAPStatus);
|
||||
|
||||
useInterval(() => {
|
||||
void loadData();
|
||||
});
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
useLayoutTitle(LL.ACCESS_POINT(0));
|
||||
|
||||
@@ -8,20 +8,21 @@ import {
|
||||
Table
|
||||
} from '@table-library/react-table-library/table';
|
||||
import { useTheme as tableTheme } from '@table-library/react-table-library/theme';
|
||||
import { useAutoRequest } from 'alova/client';
|
||||
import { useRequest } from 'alova/client';
|
||||
import { FormLoader, SectionContent, useLayoutTitle } from 'components';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import type { Translation } from 'i18n/i18n-types';
|
||||
import { useInterval } from 'utils';
|
||||
|
||||
import { readActivity } from '../../api/app';
|
||||
import type { Stat } from '../main/types';
|
||||
|
||||
const SystemActivity = () => {
|
||||
const {
|
||||
data,
|
||||
send: loadData,
|
||||
error
|
||||
} = useAutoRequest(readActivity, { pollingTime: 3000 });
|
||||
const { data, send: loadData, error } = useRequest(readActivity);
|
||||
|
||||
useInterval(() => {
|
||||
void loadData();
|
||||
});
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
|
||||
@@ -17,9 +17,10 @@ import {
|
||||
|
||||
import * as SystemApi from 'api/system';
|
||||
|
||||
import { useAutoRequest } from 'alova/client';
|
||||
import { useRequest } from 'alova/client';
|
||||
import { FormLoader, SectionContent, useLayoutTitle } from 'components';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import { useInterval } from 'utils';
|
||||
|
||||
import BBQKeesIcon from './bbqkees.svg';
|
||||
|
||||
@@ -32,11 +33,11 @@ const HardwareStatus = () => {
|
||||
|
||||
useLayoutTitle(LL.HARDWARE());
|
||||
|
||||
const {
|
||||
data,
|
||||
send: loadData,
|
||||
error
|
||||
} = useAutoRequest(SystemApi.readSystemStatus, { pollingTime: 3000 });
|
||||
const { data, send: loadData, error } = useRequest(SystemApi.readSystemStatus);
|
||||
|
||||
useInterval(() => {
|
||||
void loadData();
|
||||
});
|
||||
|
||||
const content = () => {
|
||||
if (!data) {
|
||||
@@ -98,7 +99,13 @@ const HardwareStatus = () => {
|
||||
' @ ' +
|
||||
data.cpu_freq_mhz +
|
||||
' Mhz' +
|
||||
(data.temperature ? ', T: ' + data.temperature + ' °C' : '')
|
||||
// bit of a hack : if the CPU temp is higher than 90 (=32 Fahrenheit if using Celsius), show F, otherwise C
|
||||
(data.temperature
|
||||
? ', T: ' +
|
||||
data.temperature +
|
||||
' °' +
|
||||
(data.temperature > 90 ? 'F' : 'C')
|
||||
: '')
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
|
||||
@@ -15,11 +15,12 @@ import type { Theme } from '@mui/material';
|
||||
|
||||
import * as MqttApi from 'api/mqtt';
|
||||
|
||||
import { useAutoRequest } from 'alova/client';
|
||||
import { useRequest } from 'alova/client';
|
||||
import { FormLoader, SectionContent, useLayoutTitle } from 'components';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import type { MqttStatusType } from 'types';
|
||||
import { MqttDisconnectReason } from 'types';
|
||||
import { useInterval } from 'utils';
|
||||
|
||||
export const mqttStatusHighlight = (
|
||||
{ enabled, connected }: MqttStatusType,
|
||||
@@ -54,11 +55,11 @@ export const mqttQueueHighlight = (
|
||||
};
|
||||
|
||||
const MqttStatus = () => {
|
||||
const {
|
||||
data,
|
||||
send: loadData,
|
||||
error
|
||||
} = useAutoRequest(MqttApi.readMqttStatus, { pollingTime: 3000 });
|
||||
const { data, send: loadData, error } = useRequest(MqttApi.readMqttStatus);
|
||||
|
||||
useInterval(() => {
|
||||
void loadData();
|
||||
});
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
useLayoutTitle('MQTT');
|
||||
|
||||
@@ -28,19 +28,20 @@ import type { Theme } from '@mui/material';
|
||||
import * as NTPApi from 'api/ntp';
|
||||
|
||||
import { dialogStyle } from 'CustomTheme';
|
||||
import { useAutoRequest, useRequest } from 'alova/client';
|
||||
import { useRequest } from 'alova/client';
|
||||
import { ButtonRow, FormLoader, SectionContent, useLayoutTitle } from 'components';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import type { NTPStatusType, Time } from 'types';
|
||||
import { NTPSyncStatus } from 'types';
|
||||
import { useInterval } from 'utils';
|
||||
import { formatDateTime, formatLocalDateTime } from 'utils';
|
||||
|
||||
const NTPStatus = () => {
|
||||
const {
|
||||
data,
|
||||
send: loadData,
|
||||
error
|
||||
} = useAutoRequest(NTPApi.readNTPStatus, { pollingTime: 3000 });
|
||||
const { data, send: loadData, error } = useRequest(NTPApi.readNTPStatus);
|
||||
|
||||
useInterval(() => {
|
||||
void loadData();
|
||||
});
|
||||
|
||||
const [localTime, setLocalTime] = useState<string>('');
|
||||
const [settingTime, setSettingTime] = useState<boolean>(false);
|
||||
|
||||
@@ -18,11 +18,12 @@ import type { Theme } from '@mui/material';
|
||||
|
||||
import * as NetworkApi from 'api/network';
|
||||
|
||||
import { useAutoRequest } from 'alova/client';
|
||||
import { useRequest } from 'alova/client';
|
||||
import { FormLoader, SectionContent, useLayoutTitle } from 'components';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import type { NetworkStatusType } from 'types';
|
||||
import { NetworkConnectionStatus } from 'types';
|
||||
import { useInterval } from 'utils';
|
||||
|
||||
const isConnected = ({ status }: NetworkStatusType) =>
|
||||
status === NetworkConnectionStatus.WIFI_STATUS_CONNECTED ||
|
||||
@@ -81,11 +82,11 @@ const IPs = (status: NetworkStatusType) => {
|
||||
};
|
||||
|
||||
const NetworkStatus = () => {
|
||||
const {
|
||||
data,
|
||||
send: loadData,
|
||||
error
|
||||
} = useAutoRequest(NetworkApi.readNetworkStatus, { pollingTime: 3000 });
|
||||
const { data, send: loadData, error } = useRequest(NetworkApi.readNetworkStatus);
|
||||
|
||||
useInterval(() => {
|
||||
void loadData();
|
||||
});
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
useLayoutTitle(LL.NETWORK(1));
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import {
|
||||
Box,
|
||||
CircularProgress,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
Typography
|
||||
} from '@mui/material';
|
||||
|
||||
import { readSystemStatus } from 'api/system';
|
||||
|
||||
import { dialogStyle } from 'CustomTheme';
|
||||
import { useAutoRequest } from 'alova/client';
|
||||
import MessageBox from 'components/MessageBox';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
const RestartMonitor = () => {
|
||||
const [errorMessage, setErrorMessage] = useState<string>();
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
let count = 0;
|
||||
|
||||
const { data } = useAutoRequest(readSystemStatus, {
|
||||
pollingTime: 1000,
|
||||
force: true,
|
||||
initialData: { status: 'Getting ready...' },
|
||||
async middleware(_, next) {
|
||||
if (count++ >= 1) {
|
||||
// skip first request (1 second) to allow AsyncWS to send its response
|
||||
await next();
|
||||
}
|
||||
}
|
||||
})
|
||||
.onSuccess((event) => {
|
||||
if (event.data.status === 'ready' || event.data.status === undefined) {
|
||||
document.location.href = '/';
|
||||
}
|
||||
})
|
||||
.onError((error) => {
|
||||
setErrorMessage(error.message);
|
||||
});
|
||||
|
||||
return (
|
||||
<Dialog fullWidth={true} sx={dialogStyle} open={true}>
|
||||
<DialogContent dividers>
|
||||
<Box m={0} py={0} display="flex" alignItems="center" flexDirection="column">
|
||||
<Typography
|
||||
color="secondary"
|
||||
variant="h6"
|
||||
fontWeight={400}
|
||||
textAlign="center"
|
||||
>
|
||||
{data?.status === 'uploading'
|
||||
? LL.WAIT_FIRMWARE()
|
||||
: data?.status === 'restarting'
|
||||
? LL.APPLICATION_RESTARTING()
|
||||
: data?.status === 'ready'
|
||||
? LL.RESTARTING_PRE()
|
||||
: LL.RESTARTING_POST()}
|
||||
…
|
||||
</Typography>
|
||||
<Typography mt={2} variant="h6" fontWeight={400} textAlign="center">
|
||||
{LL.PLEASE_WAIT()}
|
||||
</Typography>
|
||||
|
||||
{errorMessage ? (
|
||||
<MessageBox my={2} level="error" message={errorMessage} />
|
||||
) : (
|
||||
<Box py={2}>
|
||||
<CircularProgress size={32} />
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default RestartMonitor;
|
||||
@@ -2,6 +2,7 @@ import { useContext, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import AccessTimeIcon from '@mui/icons-material/AccessTime';
|
||||
import BuildIcon from '@mui/icons-material/Build';
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import DeviceHubIcon from '@mui/icons-material/DeviceHub';
|
||||
import DirectionsBusIcon from '@mui/icons-material/DirectionsBus';
|
||||
@@ -30,15 +31,17 @@ import { API } from 'api/app';
|
||||
import { readSystemStatus } from 'api/system';
|
||||
|
||||
import { dialogStyle } from 'CustomTheme';
|
||||
import { useAutoRequest, useRequest } from 'alova/client';
|
||||
import { useRequest } from 'alova/client';
|
||||
import { type APIcall, busConnectionStatus } from 'app/main/types';
|
||||
import { FormLoader, SectionContent, useLayoutTitle } from 'components';
|
||||
import ListMenuItem from 'components/layout/ListMenuItem';
|
||||
import { AuthenticatedContext } from 'contexts/authentication';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import { NTPSyncStatus, NetworkConnectionStatus } from 'types';
|
||||
import { useInterval } from 'utils';
|
||||
import { formatDateTime } from 'utils/time';
|
||||
|
||||
import RestartMonitor from './RestartMonitor';
|
||||
import SystemMonitor from './SystemMonitor';
|
||||
|
||||
const SystemStatus = () => {
|
||||
const { LL } = useI18nContext();
|
||||
@@ -58,9 +61,8 @@ const SystemStatus = () => {
|
||||
data,
|
||||
send: loadData,
|
||||
error
|
||||
} = useAutoRequest(readSystemStatus, {
|
||||
} = useRequest(readSystemStatus, {
|
||||
initialData: [],
|
||||
pollingTime: 3000,
|
||||
async middleware(_, next) {
|
||||
if (!restarting) {
|
||||
await next();
|
||||
@@ -68,6 +70,10 @@ const SystemStatus = () => {
|
||||
}
|
||||
});
|
||||
|
||||
useInterval(() => {
|
||||
void loadData();
|
||||
});
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
const formatDurationSec = (duration_sec: number) => {
|
||||
@@ -134,7 +140,12 @@ const SystemStatus = () => {
|
||||
case NTPSyncStatus.NTP_INACTIVE:
|
||||
return LL.INACTIVE(0);
|
||||
case NTPSyncStatus.NTP_ACTIVE:
|
||||
return LL.ACTIVE();
|
||||
return (
|
||||
LL.ACTIVE() +
|
||||
(data.ntp_time !== undefined
|
||||
? ' (' + formatDateTime(data.ntp_time) + ')'
|
||||
: '')
|
||||
);
|
||||
default:
|
||||
return LL.UNKNOWN();
|
||||
}
|
||||
@@ -243,6 +254,14 @@ const SystemStatus = () => {
|
||||
return (
|
||||
<>
|
||||
<List sx={{ borderRadius: 3, border: '2px solid grey' }}>
|
||||
<ListMenuItem
|
||||
icon={BuildIcon}
|
||||
bgcolor="#72caf9"
|
||||
label="EMS-ESP Firmware"
|
||||
text={'v' + data.emsesp_version}
|
||||
to="version"
|
||||
/>
|
||||
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
<Avatar sx={{ bgcolor: '#c5572c', color: 'white' }}>
|
||||
@@ -301,7 +320,7 @@ const SystemStatus = () => {
|
||||
icon={DeviceHubIcon}
|
||||
bgcolor={activeHighlight(data.mqtt_status)}
|
||||
label="MQTT"
|
||||
text={data.mqtt_status ? LL.ACTIVE() : LL.INACTIVE(0)}
|
||||
text={data.mqtt_status ? LL.CONNECTED(0) : LL.INACTIVE(0)}
|
||||
to="/status/mqtt"
|
||||
/>
|
||||
|
||||
@@ -339,7 +358,7 @@ const SystemStatus = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionContent>{restarting ? <RestartMonitor /> : content()}</SectionContent>
|
||||
<SectionContent>{restarting ? <SystemMonitor /> : content()}</SectionContent>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -8,12 +8,12 @@ import {
|
||||
Box,
|
||||
Button,
|
||||
Checkbox,
|
||||
Grid2 as Grid,
|
||||
IconButton,
|
||||
MenuItem,
|
||||
TextField,
|
||||
styled
|
||||
} from '@mui/material';
|
||||
import Grid from '@mui/material/Grid2';
|
||||
|
||||
import { API } from 'api/app';
|
||||
import { fetchLogES, readLogSettings, updateLogSettings } from 'api/system';
|
||||
@@ -101,6 +101,7 @@ const SystemLog = () => {
|
||||
const [readOpen, setReadOpen] = useState(false);
|
||||
const [logEntries, setLogEntries] = useState<LogEntry[]>([]);
|
||||
const [autoscroll, setAutoscroll] = useState(true);
|
||||
const [lastId, setLastId] = useState<number>(-1);
|
||||
|
||||
const ALPHA_NUMERIC_DASH_REGEX = /^[a-fA-F0-9 ]+$/;
|
||||
|
||||
@@ -115,10 +116,13 @@ const SystemLog = () => {
|
||||
immediate: true,
|
||||
interceptByGlobalResponded: false
|
||||
})
|
||||
.onMessage((message: { id: number; data: string }) => {
|
||||
.onMessage((message: { data: string }) => {
|
||||
const rawData = message.data;
|
||||
const logentry = JSON.parse(rawData) as LogEntry;
|
||||
setLogEntries((log) => [...log, logentry]);
|
||||
if (lastId < logentry.i) {
|
||||
setLogEntries((log) => [...log, logentry]);
|
||||
setLastId(logentry.i);
|
||||
}
|
||||
})
|
||||
.onError(() => {
|
||||
toast.error('No connection to Log service');
|
||||
@@ -197,7 +201,7 @@ const SystemLog = () => {
|
||||
name="level"
|
||||
label={LL.LOG_LEVEL()}
|
||||
value={data.level}
|
||||
sx={{ width: '10ch' }}
|
||||
sx={{ width: '14ch' }}
|
||||
variant="outlined"
|
||||
onChange={updateFormValue}
|
||||
margin="normal"
|
||||
|
||||
125
interface/src/app/status/SystemMonitor.tsx
Normal file
125
interface/src/app/status/SystemMonitor.tsx
Normal file
@@ -0,0 +1,125 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import { Box, Button, Dialog, DialogContent, Typography } from '@mui/material';
|
||||
|
||||
import { callAction } from 'api/app';
|
||||
import { readSystemStatus } from 'api/system';
|
||||
|
||||
import { dialogStyle } from 'CustomTheme';
|
||||
import { useRequest } from 'alova/client';
|
||||
import MessageBox from 'components/MessageBox';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
import { SystemStatusCodes } from 'types';
|
||||
import { useInterval } from 'utils';
|
||||
|
||||
import { LinearProgressWithLabel } from '../../components/upload/LinearProgressWithLabel';
|
||||
|
||||
const SystemMonitor = () => {
|
||||
const [errorMessage, setErrorMessage] = useState<string>();
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
let count = 0;
|
||||
|
||||
const { send: setSystemStatus } = useRequest(
|
||||
(status: string) => callAction({ action: 'systemStatus', param: status }),
|
||||
{
|
||||
immediate: false
|
||||
}
|
||||
);
|
||||
|
||||
const { data, send } = useRequest(readSystemStatus, {
|
||||
force: true,
|
||||
async middleware(_, next) {
|
||||
if (count++ >= 1) {
|
||||
// skip first request (1 second) to allow AsyncWS to send its response
|
||||
await next();
|
||||
}
|
||||
}
|
||||
})
|
||||
.onSuccess((event) => {
|
||||
if (
|
||||
event.data.status === SystemStatusCodes.SYSTEM_STATUS_NORMAL ||
|
||||
event.data.status === undefined
|
||||
) {
|
||||
document.location.href = '/';
|
||||
} else if (
|
||||
event.data.status === SystemStatusCodes.SYSTEM_STATUS_ERROR_UPLOAD
|
||||
) {
|
||||
setErrorMessage('Please check system logs for possible causes');
|
||||
}
|
||||
})
|
||||
.onError((error) => {
|
||||
setErrorMessage(error.message);
|
||||
});
|
||||
|
||||
useInterval(() => {
|
||||
void send();
|
||||
}, 1000); // check every 1 second
|
||||
|
||||
const onCancel = async () => {
|
||||
setErrorMessage(undefined);
|
||||
await setSystemStatus(
|
||||
SystemStatusCodes.SYSTEM_STATUS_NORMAL as unknown as string
|
||||
);
|
||||
document.location.href = '/';
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog fullWidth={true} sx={dialogStyle} open={true}>
|
||||
<DialogContent dividers>
|
||||
<Box m={0} py={0} display="flex" alignItems="center" flexDirection="column">
|
||||
<Typography
|
||||
color="secondary"
|
||||
variant="h6"
|
||||
fontWeight={400}
|
||||
textAlign="center"
|
||||
>
|
||||
{data?.status >= SystemStatusCodes.SYSTEM_STATUS_UPLOADING
|
||||
? LL.WAIT_FIRMWARE()
|
||||
: data?.status === SystemStatusCodes.SYSTEM_STATUS_PENDING_RESTART
|
||||
? LL.APPLICATION_RESTARTING()
|
||||
: data?.status === SystemStatusCodes.SYSTEM_STATUS_NORMAL
|
||||
? LL.RESTARTING_PRE()
|
||||
: data?.status === SystemStatusCodes.SYSTEM_STATUS_ERROR_UPLOAD
|
||||
? 'Upload Failed'
|
||||
: LL.RESTARTING_POST()}
|
||||
</Typography>
|
||||
|
||||
{errorMessage ? (
|
||||
<MessageBox my={2} level="error" message={errorMessage}>
|
||||
<Button
|
||||
size="small"
|
||||
sx={{ ml: 2 }}
|
||||
startIcon={<CancelIcon />}
|
||||
variant="contained"
|
||||
color="error"
|
||||
onClick={onCancel}
|
||||
>
|
||||
{LL.RESET(0)}
|
||||
</Button>
|
||||
</MessageBox>
|
||||
) : (
|
||||
<>
|
||||
<Typography mt={2} variant="h6" fontWeight={400} textAlign="center">
|
||||
{LL.PLEASE_WAIT()}…
|
||||
</Typography>
|
||||
{data && data.status >= SystemStatusCodes.SYSTEM_STATUS_UPLOADING && (
|
||||
<Box width="100%" pl={2} pr={2} py={2}>
|
||||
<LinearProgressWithLabel
|
||||
value={Math.round(
|
||||
data?.status - SystemStatusCodes.SYSTEM_STATUS_UPLOADING
|
||||
)}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default SystemMonitor;
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
@@ -9,27 +9,38 @@ import WarningIcon from '@mui/icons-material/Warning';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Checkbox,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
FormControlLabel,
|
||||
Grid2 as Grid,
|
||||
Link,
|
||||
Typography
|
||||
} from '@mui/material';
|
||||
import Grid from '@mui/material/Grid2';
|
||||
|
||||
import * as SystemApi from 'api/system';
|
||||
import { callAction } from 'api/app';
|
||||
import { API, callAction } from 'api/app';
|
||||
import { getDevVersion, getStableVersion } from 'api/system';
|
||||
|
||||
import { dialogStyle } from 'CustomTheme';
|
||||
import { useRequest } from 'alova/client';
|
||||
import RestartMonitor from 'app/status/RestartMonitor';
|
||||
import { FormLoader, SectionContent, useLayoutTitle } from 'components';
|
||||
import type { APIcall } from 'app/main/types';
|
||||
import SystemMonitor from 'app/status/SystemMonitor';
|
||||
import {
|
||||
FormLoader,
|
||||
SectionContent,
|
||||
SingleUpload,
|
||||
useLayoutTitle
|
||||
} from 'components';
|
||||
import { AuthenticatedContext } from 'contexts/authentication';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
const Version = () => {
|
||||
const { LL } = useI18nContext();
|
||||
const { LL, locale } = useI18nContext();
|
||||
const { me } = useContext(AuthenticatedContext);
|
||||
|
||||
const [restarting, setRestarting] = useState<boolean>(false);
|
||||
const [openInstallDialog, setOpenInstallDialog] = useState<boolean>(false);
|
||||
const [usingDevVersion, setUsingDevVersion] = useState<boolean>(false);
|
||||
@@ -60,8 +71,10 @@ const Version = () => {
|
||||
send: loadData,
|
||||
error
|
||||
} = useRequest(SystemApi.readSystemStatus).onSuccess((event) => {
|
||||
// older version of EMS-ESP didn't have the psram set, so we can't do an OTA upgrade
|
||||
setDownloadOnly(event.data.psram === undefined);
|
||||
// older version of EMS-ESP on 4MB boards, can't use OTA because of SSL support in HttpClient
|
||||
if (event.data.arduino_version.startsWith('Tasmota')) {
|
||||
setDownloadOnly(true);
|
||||
}
|
||||
setUsingDevVersion(event.data.emsesp_version.includes('dev'));
|
||||
});
|
||||
|
||||
@@ -78,7 +91,7 @@ const Version = () => {
|
||||
|
||||
useEffect(() => {
|
||||
if (latestVersion && latestDevVersion) {
|
||||
sendCheckUpgrade(latestDevVersion + ',' + latestVersion)
|
||||
sendCheckUpgrade(latestDevVersion.name + ',' + latestVersion.name)
|
||||
.catch((error: Error) => {
|
||||
toast.error('Failed to check for upgrades: ' + error.message);
|
||||
})
|
||||
@@ -88,19 +101,59 @@ const Version = () => {
|
||||
}
|
||||
}, [latestVersion, latestDevVersion]);
|
||||
|
||||
const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' });
|
||||
const DIVISIONS = [
|
||||
{ amount: 60, name: 'seconds' },
|
||||
{ amount: 60, name: 'minutes' },
|
||||
{ amount: 24, name: 'hours' },
|
||||
{ amount: 7, name: 'days' },
|
||||
{ amount: 4.34524, name: 'weeks' },
|
||||
{ amount: 12, name: 'months' },
|
||||
{ amount: Number.POSITIVE_INFINITY, name: 'years' }
|
||||
];
|
||||
function formatTimeAgo(date) {
|
||||
let duration = (date.getTime() - new Date().getTime()) / 1000;
|
||||
for (let i = 0; i < DIVISIONS.length; i++) {
|
||||
const division = DIVISIONS[i];
|
||||
if (Math.abs(duration) < division.amount) {
|
||||
return rtf.format(
|
||||
Math.round(duration),
|
||||
division.name as Intl.RelativeTimeFormatUnit
|
||||
);
|
||||
}
|
||||
duration /= division.amount;
|
||||
}
|
||||
}
|
||||
|
||||
const { send: sendAPI } = useRequest((data: APIcall) => API(data), {
|
||||
immediate: false
|
||||
});
|
||||
|
||||
const doRestart = async () => {
|
||||
setRestarting(true);
|
||||
await sendAPI({ device: 'system', cmd: 'restart', id: 0 }).catch(
|
||||
(error: Error) => {
|
||||
toast.error(error.message);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const getBinURL = () => {
|
||||
if (!latestVersion || !latestDevVersion) {
|
||||
if (!internetLive) {
|
||||
return '';
|
||||
}
|
||||
const filename =
|
||||
'EMS-ESP-' +
|
||||
(usingDevVersion ? latestDevVersion : latestVersion).replaceAll('.', '_') +
|
||||
(usingDevVersion ? latestDevVersion.name : latestVersion.name).replaceAll(
|
||||
'.',
|
||||
'_'
|
||||
) +
|
||||
'-' +
|
||||
getPlatform() +
|
||||
'.bin';
|
||||
return usingDevVersion
|
||||
? DEV_URL + filename
|
||||
: STABLE_URL + 'v' + latestVersion + '/' + filename;
|
||||
: STABLE_URL + 'v' + latestVersion.name + '/' + filename;
|
||||
};
|
||||
|
||||
const getPlatform = () => {
|
||||
@@ -133,7 +186,9 @@ const Version = () => {
|
||||
</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<Typography mb={2}>
|
||||
{LL.INSTALL_VERSION(usingDevVersion ? latestDevVersion : latestVersion)}
|
||||
{LL.INSTALL_VERSION(
|
||||
usingDevVersion ? latestDevVersion?.name : latestVersion?.name
|
||||
)}
|
||||
</Typography>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
@@ -177,25 +232,20 @@ const Version = () => {
|
||||
setUsingDevVersion(data.emsesp_version.includes('dev'));
|
||||
};
|
||||
|
||||
const switchToDev = () => {
|
||||
setUsingDevVersion(true);
|
||||
setUpgradeAvailable(true);
|
||||
};
|
||||
|
||||
const showButtons = () => {
|
||||
if (!upgradeAvailable) {
|
||||
const showButtons = (showDev?: boolean) => {
|
||||
if (!me.admin) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (downloadOnly) {
|
||||
return (
|
||||
<Button
|
||||
sx={{ ml: 2 }}
|
||||
startIcon={<DownloadIcon />}
|
||||
variant="outlined"
|
||||
onClick={() => setOpenInstallDialog(false)}
|
||||
color="warning"
|
||||
size="small"
|
||||
sx={{ ml: 2 }}
|
||||
>
|
||||
<Link underline="none" target="_blank" href={getBinURL()} color="warning">
|
||||
{LL.DOWNLOAD(1)}
|
||||
@@ -212,7 +262,10 @@ const Version = () => {
|
||||
size="small"
|
||||
onClick={() => showFirmwareDialog()}
|
||||
>
|
||||
{LL.UPGRADE()}…
|
||||
{upgradeAvailable || (!usingDevVersion && showDev)
|
||||
? LL.UPGRADE()
|
||||
: LL.REINSTALL()}
|
||||
…
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
@@ -222,27 +275,29 @@ const Version = () => {
|
||||
return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
|
||||
}
|
||||
|
||||
const isDev = data.emsesp_version.includes('dev');
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box p={2} border="1px solid grey" borderRadius={2}>
|
||||
<Typography sx={{ pb: 2 }} variant="h6" color="primary">
|
||||
Firmware Version
|
||||
<Typography mb={2} variant="h6" color="primary">
|
||||
{LL.THIS_VERSION()}
|
||||
</Typography>
|
||||
|
||||
<Grid container spacing={4}>
|
||||
<Grid mb={1}>
|
||||
<Typography mb={1} color="secondary">
|
||||
{LL.VERSION()}
|
||||
</Typography>
|
||||
<Typography mb={1} color="secondary">
|
||||
Platform
|
||||
</Typography>
|
||||
<Typography mb={1} color="secondary">
|
||||
Release Type
|
||||
</Typography>
|
||||
<Grid
|
||||
container
|
||||
direction="row"
|
||||
rowSpacing={1}
|
||||
sx={{
|
||||
justifyContent: 'flex-start',
|
||||
alignItems: 'baseline'
|
||||
}}
|
||||
>
|
||||
<Grid size={{ xs: 4, md: 2 }}>
|
||||
<Typography color="secondary">{LL.VERSION()}</Typography>
|
||||
</Grid>
|
||||
<Grid mb={1}>
|
||||
<Typography mb={1}>
|
||||
<Grid size={{ xs: 8, md: 10 }}>
|
||||
<Typography>
|
||||
{data.emsesp_version}
|
||||
{data.build_flags && (
|
||||
<Typography variant="caption">
|
||||
@@ -250,49 +305,121 @@ const Version = () => {
|
||||
</Typography>
|
||||
)}
|
||||
</Typography>
|
||||
<Typography mb={1}>{getPlatform()}</Typography>
|
||||
<Typography mb={1}>
|
||||
{data.emsesp_version.includes('dev')
|
||||
? LL.DEVELOPMENT()
|
||||
: LL.STABLE()}
|
||||
</Grid>
|
||||
|
||||
<Grid size={{ xs: 4, md: 2 }}>
|
||||
<Typography color="secondary">{LL.PLATFORM()}</Typography>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 8, md: 10 }}>
|
||||
<Typography>
|
||||
{getPlatform()}
|
||||
<Typography variant="caption">
|
||||
({data.psram ? '+PSRAM' : '-PSRAM'})
|
||||
</Typography>
|
||||
</Typography>
|
||||
</Grid>
|
||||
|
||||
<Grid size={{ xs: 4, md: 2 }}>
|
||||
<Typography color="secondary">{LL.RELEASE_TYPE()}</Typography>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 8, md: 10 }}>
|
||||
<FormControlLabel
|
||||
disabled={!isDev}
|
||||
control={
|
||||
<Checkbox
|
||||
sx={{
|
||||
'&.Mui-checked': {
|
||||
color: 'lightblue'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
slotProps={{
|
||||
typography: {
|
||||
color: 'grey'
|
||||
}
|
||||
}}
|
||||
checked={!isDev}
|
||||
label={LL.STABLE()}
|
||||
sx={{ '& .MuiSvgIcon-root': { fontSize: 16 } }}
|
||||
/>
|
||||
<FormControlLabel
|
||||
disabled={isDev}
|
||||
control={
|
||||
<Checkbox
|
||||
sx={{
|
||||
'&.Mui-checked': {
|
||||
color: 'lightblue'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
slotProps={{
|
||||
typography: {
|
||||
color: 'grey'
|
||||
}
|
||||
}}
|
||||
checked={isDev}
|
||||
label={LL.DEVELOPMENT()}
|
||||
sx={{ '& .MuiSvgIcon-root': { fontSize: 16 } }}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Typography sx={{ pb: 2 }} variant="h6" color="primary">
|
||||
{LL.AVAILABLE_VERSION()}
|
||||
</Typography>
|
||||
|
||||
{internetLive ? (
|
||||
<>
|
||||
<Grid container spacing={4}>
|
||||
<Grid mb={1}>
|
||||
<Typography mb={1} color="secondary">
|
||||
{LL.STABLE()}
|
||||
</Typography>
|
||||
<Typography mb={1} color="secondary">
|
||||
{LL.DEVELOPMENT()}
|
||||
<Typography mt={2} mb={2} variant="h6" color="primary">
|
||||
{LL.AVAILABLE_VERSION()}
|
||||
</Typography>
|
||||
|
||||
<Grid
|
||||
container
|
||||
direction="row"
|
||||
rowSpacing={1}
|
||||
sx={{
|
||||
justifyContent: 'flex-start',
|
||||
alignItems: 'baseline'
|
||||
}}
|
||||
>
|
||||
<Grid size={{ xs: 4, md: 2 }}>
|
||||
<Typography color="secondary">{LL.STABLE()}</Typography>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 8, md: 10 }}>
|
||||
<Typography>
|
||||
<Link target="_blank" href={STABLE_RELNOTES_URL} color="primary">
|
||||
{latestVersion.name}
|
||||
</Link>
|
||||
{latestVersion.published_at && (
|
||||
<Typography component="span" variant="caption">
|
||||
(
|
||||
{formatTimeAgo(new Date(latestVersion.published_at))})
|
||||
</Typography>
|
||||
)}
|
||||
{!usingDevVersion && showButtons(false)}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid mb={1}>
|
||||
<Typography mb={1}>
|
||||
{latestVersion}
|
||||
<Link target="_blank" href={STABLE_RELNOTES_URL} color="primary">
|
||||
(changelog)
|
||||
</Link>
|
||||
{!usingDevVersion && showButtons()}
|
||||
</Typography>
|
||||
<Typography mb={1}>
|
||||
{latestDevVersion}
|
||||
|
||||
<Grid size={{ xs: 4, md: 2 }}>
|
||||
<Typography color="secondary">{LL.DEVELOPMENT()}</Typography>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 8, md: 10 }}>
|
||||
<Typography>
|
||||
<Link target="_blank" href={DEV_RELNOTES_URL} color="primary">
|
||||
(changelog)
|
||||
{latestDevVersion.name}
|
||||
</Link>
|
||||
{usingDevVersion && showButtons()}
|
||||
{latestDevVersion.published_at && (
|
||||
<Typography component="span" variant="caption">
|
||||
(
|
||||
{formatTimeAgo(new Date(latestDevVersion.published_at))})
|
||||
</Typography>
|
||||
)}
|
||||
{showButtons(true)}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
{upgradeAvailable ? (
|
||||
<Typography color="warning">
|
||||
<Typography mt={2} color="warning">
|
||||
<InfoOutlinedIcon
|
||||
color="warning"
|
||||
sx={{ verticalAlign: 'middle', mr: 2 }}
|
||||
@@ -300,7 +427,7 @@ const Version = () => {
|
||||
{LL.UPGRADE_AVAILABLE()}
|
||||
</Typography>
|
||||
) : (
|
||||
<Typography color="success">
|
||||
<Typography mt={2} color="success">
|
||||
<CheckIcon
|
||||
color="success"
|
||||
sx={{ verticalAlign: 'middle', mr: 2 }}
|
||||
@@ -308,36 +435,29 @@ const Version = () => {
|
||||
{LL.LATEST_VERSION()}
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
{!data.emsesp_version.includes('dev') && !usingDevVersion && (
|
||||
<Typography variant="caption">
|
||||
<Button
|
||||
sx={{ mt: 2 }}
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
size="small"
|
||||
onClick={() => switchToDev()}
|
||||
>
|
||||
{LL.SWITCH_DEV()}
|
||||
</Button>
|
||||
</Typography>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<Typography mb={1} color="warning">
|
||||
<Typography mt={2} color="warning">
|
||||
<WarningIcon color="warning" sx={{ verticalAlign: 'middle', mr: 2 }} />
|
||||
device cannot access internet
|
||||
{LL.INTERNET_CONNECTION_REQUIRED()}
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
{renderInstallDialog()}
|
||||
{me.admin && (
|
||||
<>
|
||||
{renderInstallDialog()}
|
||||
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
|
||||
{LL.UPLOAD()}
|
||||
</Typography>
|
||||
<SingleUpload text={LL.UPLOAD_DROP_TEXT()} doRestart={doRestart} />
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<SectionContent>{restarting ? <RestartMonitor /> : content()}</SectionContent>
|
||||
<SectionContent>{restarting ? <SystemMonitor /> : content()}</SectionContent>
|
||||
);
|
||||
};
|
||||
|
||||
17
interface/src/components/ButtonTooltip.tsx
Normal file
17
interface/src/components/ButtonTooltip.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Tooltip, type TooltipProps, styled, tooltipClasses } from '@mui/material';
|
||||
|
||||
export const ButtonTooltip = styled(({ className, ...props }: TooltipProps) => (
|
||||
<Tooltip {...props} placement="top" arrow classes={{ popper: className }} />
|
||||
))(({ theme }) => ({
|
||||
[`& .${tooltipClasses.arrow}`]: {
|
||||
color: theme.palette.success.main
|
||||
},
|
||||
[`& .${tooltipClasses.tooltip}`]: {
|
||||
backgroundColor: theme.palette.success.main,
|
||||
color: 'rgba(0, 0, 0, 0.87)',
|
||||
boxShadow: theme.shadows[1],
|
||||
fontSize: 10
|
||||
}
|
||||
}));
|
||||
|
||||
export default ButtonTooltip;
|
||||
@@ -11,7 +11,7 @@ type MessageBoxLevel = 'warning' | 'success' | 'info' | 'error';
|
||||
|
||||
export interface MessageBoxProps extends BoxProps {
|
||||
level: MessageBoxLevel;
|
||||
message: string;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
const LEVEL_ICONS: {
|
||||
@@ -53,8 +53,8 @@ const MessageBox: FC<MessageBoxProps> = ({
|
||||
{...rest}
|
||||
>
|
||||
<Icon />
|
||||
<Typography sx={{ ml: 2, flexGrow: 1 }} variant="body1">
|
||||
{message}
|
||||
<Typography sx={{ ml: 2 }} variant="body1">
|
||||
{message ?? ''}
|
||||
</Typography>
|
||||
{children}
|
||||
</Box>
|
||||
|
||||
@@ -7,3 +7,4 @@ export { default as SectionContent } from './SectionContent';
|
||||
export { default as ButtonRow } from './ButtonRow';
|
||||
export { default as MessageBox } from './MessageBox';
|
||||
export { default as BlockNavigation } from './routing/BlockNavigation';
|
||||
export { default as ButtonTooltip } from './ButtonTooltip';
|
||||
|
||||
@@ -73,11 +73,6 @@ const LayoutMenu = () => {
|
||||
>
|
||||
<ListItemText
|
||||
primary={LL.MODULES()}
|
||||
primaryTypographyProps={{
|
||||
fontWeight: '600',
|
||||
mb: '2px',
|
||||
color: 'lightblue'
|
||||
}}
|
||||
// secondary={
|
||||
// LL.CUSTOMIZATIONS() +
|
||||
// ', ' +
|
||||
@@ -92,6 +87,13 @@ const LayoutMenu = () => {
|
||||
// color: menuOpen ? 'rgba(0,0,0,0)' : 'rgba(255,255,255,0.5)'
|
||||
// }}
|
||||
sx={{ my: 0 }}
|
||||
slotProps={{
|
||||
primary: {
|
||||
fontWeight: '600',
|
||||
mb: '2px',
|
||||
color: 'lightblue'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<KeyboardArrowDown
|
||||
sx={{
|
||||
@@ -132,7 +134,6 @@ const LayoutMenu = () => {
|
||||
)}
|
||||
</Box>
|
||||
</List>
|
||||
|
||||
<List style={{ marginTop: `auto` }}>
|
||||
<LayoutMenuItem
|
||||
icon={AssessmentIcon}
|
||||
@@ -158,7 +159,6 @@ const LayoutMenu = () => {
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
</List>
|
||||
|
||||
<Popover
|
||||
id={id}
|
||||
open={open}
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||
import { Box, Button, CircularProgress, Typography } from '@mui/material';
|
||||
import { Box, Button, CircularProgress } from '@mui/material';
|
||||
|
||||
import { MessageBox } from 'components';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
interface FormLoaderProps {
|
||||
message?: string;
|
||||
errorMessage?: string;
|
||||
onRetry?: () => void;
|
||||
}
|
||||
|
||||
const FormLoader = ({
|
||||
errorMessage,
|
||||
onRetry,
|
||||
message = 'Loading…'
|
||||
}: FormLoaderProps) => {
|
||||
const FormLoader = ({ errorMessage, onRetry }: FormLoaderProps) => {
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
if (errorMessage) {
|
||||
@@ -22,6 +17,7 @@ const FormLoader = ({
|
||||
<MessageBox my={2} level="error" message={errorMessage}>
|
||||
{onRetry && (
|
||||
<Button
|
||||
sx={{ ml: 2 }}
|
||||
startIcon={<RefreshIcon />}
|
||||
variant="contained"
|
||||
color="error"
|
||||
@@ -38,9 +34,6 @@ const FormLoader = ({
|
||||
<Box py={2}>
|
||||
<CircularProgress size={100} />
|
||||
</Box>
|
||||
<Typography variant="h6" fontWeight={400} textAlign="center">
|
||||
{message}
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
import { Box, CircularProgress, Typography } from '@mui/material';
|
||||
import { Box, CircularProgress } from '@mui/material';
|
||||
import type { Theme } from '@mui/material';
|
||||
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
interface LoadingSpinnerProps {
|
||||
height?: number | string;
|
||||
}
|
||||
|
||||
const LoadingSpinner = ({ height = '100%' }: LoadingSpinnerProps) => {
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
return (
|
||||
<Box
|
||||
display="flex"
|
||||
@@ -26,9 +22,6 @@ const LoadingSpinner = ({ height = '100%' }: LoadingSpinnerProps) => {
|
||||
})}
|
||||
size={100}
|
||||
/>
|
||||
<Typography variant="h4" color="textSecondary">
|
||||
{LL.LOADING()}…
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,5 +2,3 @@ export { default as RouterTabs } from './RouterTabs';
|
||||
export { default as RequireAdmin } from './RequireAdmin';
|
||||
export { default as RequireAuthenticated } from './RequireAuthenticated';
|
||||
export { default as RequireUnauthenticated } from './RequireUnauthenticated';
|
||||
|
||||
export * from './useRouterTab';
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import { useMatch, useResolvedPath } from 'react-router';
|
||||
|
||||
export const useRouterTab = () => {
|
||||
const routerTabPathMatch = useMatch(useResolvedPath(':tab').pathname);
|
||||
const routerTab = routerTabPathMatch?.params?.tab || false;
|
||||
|
||||
return { routerTab } as const;
|
||||
};
|
||||
@@ -10,7 +10,7 @@ import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
import './dragNdrop.css';
|
||||
|
||||
const DragNdrop = ({ onFileSelected }) => {
|
||||
const DragNdrop = ({ text, onFileSelected }) => {
|
||||
const [file, setFile] = useState<File>();
|
||||
const [dragged, setDragged] = useState(false);
|
||||
const inputRef = useRef<HTMLInputElement | null>(null);
|
||||
@@ -73,7 +73,7 @@ const DragNdrop = ({ onFileSelected }) => {
|
||||
>
|
||||
<div className="upload-info">
|
||||
<CloudUploadIcon sx={{ marginRight: 4 }} color="primary" fontSize="large" />
|
||||
<p>{LL.UPLOAD_DRAG()}</p>
|
||||
<p>{text}</p>
|
||||
</div>
|
||||
|
||||
<input
|
||||
|
||||
23
interface/src/components/upload/LinearProgressWithLabel.tsx
Normal file
23
interface/src/components/upload/LinearProgressWithLabel.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import {
|
||||
Box,
|
||||
LinearProgress,
|
||||
type LinearProgressProps,
|
||||
Typography
|
||||
} from '@mui/material';
|
||||
|
||||
export function LinearProgressWithLabel(
|
||||
props: LinearProgressProps & { value: number }
|
||||
) {
|
||||
return (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Box sx={{ width: '100%', mr: 1 }}>
|
||||
<LinearProgress variant="determinate" {...props} />
|
||||
</Box>
|
||||
<Box sx={{ minWidth: 35 }}>
|
||||
<Typography variant="body2" color="text.secondary">{`${Math.round(
|
||||
props.value
|
||||
)}%`}</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -2,13 +2,7 @@ import { useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
LinearProgress,
|
||||
type LinearProgressProps,
|
||||
Typography
|
||||
} from '@mui/material';
|
||||
import { Box, Button, Typography } from '@mui/material';
|
||||
|
||||
import * as SystemApi from 'api/system';
|
||||
|
||||
@@ -16,23 +10,9 @@ import { useRequest } from 'alova/client';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
import DragNdrop from './DragNdrop';
|
||||
import { LinearProgressWithLabel } from './LinearProgressWithLabel';
|
||||
|
||||
function LinearProgressWithLabel(props: LinearProgressProps & { value: number }) {
|
||||
return (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Box sx={{ width: '100%', mr: 1 }}>
|
||||
<LinearProgress variant="determinate" {...props} />
|
||||
</Box>
|
||||
<Box sx={{ minWidth: 35 }}>
|
||||
<Typography variant="body2" color="text.secondary">{`${Math.round(
|
||||
props.value
|
||||
)}%`}</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
const SingleUpload = ({ doRestart }) => {
|
||||
const SingleUpload = ({ text, doRestart }) => {
|
||||
const [md5, setMd5] = useState<string>();
|
||||
const [file, setFile] = useState<File>();
|
||||
const { LL } = useI18nContext();
|
||||
@@ -93,7 +73,7 @@ const SingleUpload = ({ doRestart }) => {
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<DragNdrop onFileSelected={setFile} />
|
||||
<DragNdrop text={text} onFileSelected={setFile} />
|
||||
)}
|
||||
|
||||
{md5 && (
|
||||
|
||||
@@ -187,7 +187,7 @@ const cz: Translation = {
|
||||
COMPACT: 'Kompaktní',
|
||||
DOWNLOAD_SETTINGS_TEXT: 'Vytvořte zálohu svého nastavení a konfigurace',
|
||||
UPLOAD_TEXT: 'Nahrajte nový soubor firmwaru (.bin) nebo záložní soubor (.json)',
|
||||
UPLOAD_DROP_TEXT: 'Přetáhněte soubor nebo klikněte sem',
|
||||
UPLOAD_DROP_TEXT: 'Drop a firmware .bin file or click here', // TODO translate
|
||||
ERROR: 'Neočekávaná chyba, zkuste to prosím znovu',
|
||||
TIME_SET: 'Čas nastaven',
|
||||
MANAGE_USERS: 'Spravovat uživatele',
|
||||
@@ -332,18 +332,27 @@ const cz: Translation = {
|
||||
SPECIAL_FUNCTIONS: 'Speciální funkce',
|
||||
WAIT_FIRMWARE: 'Firmware se nahrává a instaluje',
|
||||
INSTALL_VERSION: 'Tímto se instalovat verze {0}. Jste si jistí?',
|
||||
SWITCH_DEV: 'přepnout na vývojovou verzi',
|
||||
UPGRADE_AVAILABLE: 'Je k dispozici aktualizace firmwaru!',
|
||||
LATEST_VERSION: 'Používáte nejnovější verzi firmwaru.',
|
||||
LATEST_VERSION: 'Používáte nejnovější verzi firmwaru',
|
||||
PLEASE_WAIT: 'Prosím čekejte',
|
||||
RESTARTING_PRE: 'Inicializace',
|
||||
RESTARTING_POST: 'Příprava',
|
||||
AUTO_SCROLL: 'Automatické rolování',
|
||||
DASHBOARD: 'Dashboard',
|
||||
NO_DATA: 'Žádná data nejsou k dispozici',
|
||||
DASHBOARD_1: 'Přizpůsobte si dashboard označením EMS entit jako Oblíbené pomocí modulu Přizpůsobení',
|
||||
DEVELOPER_MODE: 'Režim vývojáře',
|
||||
UPGRADE: 'Upgrade' // TODO translate
|
||||
BYTES: 'Bytes', // TODO translate
|
||||
BITMASK: 'Bit Mask',// TODO translate
|
||||
DUPLICATE: 'Duplikát',
|
||||
UPGRADE: 'Upgrade',
|
||||
DASHBOARD_1: 'Všechny aktivní entity EMS jsou označené jako oblíbené. Všechny vlastní entity, harmonogramy a externí sensory jsou zobrazeny níže.',
|
||||
NO_DATA_1: 'Nebyly nalezeny žádné oblíbené entity. Použijte',
|
||||
NO_DATA_2: 'modul sloužící k jejich výběru.',
|
||||
NO_DATA_3: 'Pro zobrazení všech dostupných entit navštivte',
|
||||
THIS_VERSION: 'Tato verze',
|
||||
PLATFORM: 'Platforma',
|
||||
RELEASE_TYPE: 'Typ sestavení',
|
||||
REINSTALL: 'Přeinstalovat',
|
||||
INTERNET_CONNECTION_REQUIRED: 'Pro automatickou kontrolu a instalaci aktualizací je třeba internetové připojení',
|
||||
};
|
||||
|
||||
export default cz;
|
||||
|
||||
@@ -187,7 +187,7 @@ const de: Translation = {
|
||||
COMPACT: 'Kompakte Darstellung',
|
||||
DOWNLOAD_SETTINGS_TEXT: 'Erstellen Sie eine Sicherung Ihrer Konfigurationen und Einstellungen',
|
||||
UPLOAD_TEXT: 'Laden Sie eine neue Firmware-Datei (.bin) oder eine Sicherungsdatei (.json) hoch',
|
||||
UPLOAD_DROP_TEXT: 'Klicken Sie hier, oder ziehen Sie eine Datei hierher.',
|
||||
UPLOAD_DROP_TEXT: 'Legen Sie eine Firmware-Datei (.bin) ab oder klicken Sie hier',
|
||||
ERROR: 'Unerwarteter Fehler, bitte versuchen Sie es erneut.',
|
||||
TIME_SET: 'Zeit gesetzt',
|
||||
MANAGE_USERS: 'Nutzerverwaltung',
|
||||
@@ -332,18 +332,27 @@ const de: Translation = {
|
||||
SPECIAL_FUNCTIONS: 'Sonderfunktionen',
|
||||
WAIT_FIRMWARE: 'Die Firmware wird hochgeladen und installiert.',
|
||||
INSTALL_VERSION: 'Dadurch wird die Version {0} heruntergeladen. Sind Sie sicher?',
|
||||
SWITCH_DEV: 'Wechseln Sie zur Entwicklungsversion!',
|
||||
UPGRADE_AVAILABLE: 'Es ist ein Firmware-Upgrade verfügbar.',
|
||||
LATEST_VERSION: 'Sie verwenden die neueste Firmware-Version.',
|
||||
LATEST_VERSION: 'Sie verwenden die neueste Firmware-Version',
|
||||
PLEASE_WAIT: 'Bitte warten',
|
||||
RESTARTING_PRE: 'Initialisierung',
|
||||
RESTARTING_POST: 'Vorbereitung',
|
||||
AUTO_SCROLL: 'Automatisches Scrollen',
|
||||
DASHBOARD: 'Dashboard',
|
||||
NO_DATA: 'Keine Daten verfügbar',
|
||||
DASHBOARD_1: 'Passen Sie Ihr Dashboard an, indem Sie EMS-Entitäten mithilfe des Moduls „Anpassungen“ als Favorit markieren',
|
||||
DEVELOPER_MODE: 'Entwicklermodus',
|
||||
UPGRADE: 'Upgrade' // TODO translate
|
||||
BYTES: 'Bytes',
|
||||
BITMASK: 'Bit Maske',
|
||||
DUPLICATE: 'Kopieren',
|
||||
UPGRADE: 'Aktualisieren',
|
||||
DASHBOARD_1: 'Alle EMS-Entitäten, die aktiv und als Favorit markiert sind, sowie alle benutzerdefinierten Entitäten, Zeitpläne und externen Sensordaten werden unten angezeigt.',
|
||||
NO_DATA_1: 'Keine favorisierten EMS-Entitäten gefunden! Verwenden Sie das Modul',
|
||||
NO_DATA_2: ', um sie zu markieren.',
|
||||
NO_DATA_3: 'Um alle verfügbaren Entitäten anzuzeigen, gehen Sie zu',
|
||||
THIS_VERSION: 'Diese Version',
|
||||
PLATFORM: 'Plattform',
|
||||
RELEASE_TYPE: 'Release Typ',
|
||||
REINSTALL: 'Neu installieren',
|
||||
INTERNET_CONNECTION_REQUIRED: 'Internetverbindung erforderlich für automatische Version-Überprüfung und -Aktualisierung',
|
||||
};
|
||||
|
||||
export default de;
|
||||
|
||||
@@ -187,7 +187,7 @@ const en: Translation = {
|
||||
COMPACT: 'Compact',
|
||||
DOWNLOAD_SETTINGS_TEXT: 'Create a backup of your configuration and settings',
|
||||
UPLOAD_TEXT: 'Upload a new firmware file (.bin) or a backup file (.json)',
|
||||
UPLOAD_DROP_TEXT: 'Drop file or click here',
|
||||
UPLOAD_DROP_TEXT: 'Drop a firmware .bin file or click here',
|
||||
ERROR: 'Unexpected Error, please try again',
|
||||
TIME_SET: 'Time set',
|
||||
MANAGE_USERS: 'Manage Users',
|
||||
@@ -332,18 +332,27 @@ const en: Translation = {
|
||||
SPECIAL_FUNCTIONS: 'Special Functions',
|
||||
WAIT_FIRMWARE: 'Firmware is uploading and installing',
|
||||
INSTALL_VERSION: 'This will install version {0}. Are you sure?',
|
||||
SWITCH_DEV: 'switch to the development version',
|
||||
UPGRADE_AVAILABLE: 'There is a firmware upgrade available!',
|
||||
LATEST_VERSION: 'You are using the latest firmware version.',
|
||||
LATEST_VERSION: 'You are using the latest firmware version',
|
||||
PLEASE_WAIT: 'Please wait',
|
||||
RESTARTING_PRE: 'Initializing',
|
||||
RESTARTING_POST: 'Preparing',
|
||||
AUTO_SCROLL: 'Auto Scroll',
|
||||
DASHBOARD: 'Dashboard',
|
||||
NO_DATA: 'No data available',
|
||||
DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorite using the Customizations module',
|
||||
DASHBOARD_1: 'All EMS entities that are active and marked as Favorite, plus all Custom Entities, Schedules and external Sensors data are displayed below.',
|
||||
DEVELOPER_MODE: 'Developer Mode',
|
||||
UPGRADE: 'Upgrade'
|
||||
BYTES: 'Bytes',
|
||||
BITMASK: 'Bit Mask',
|
||||
DUPLICATE: 'Duplicate',
|
||||
UPGRADE: 'Upgrade',
|
||||
NO_DATA_1: 'No favorite EMS entities found yet. Use the',
|
||||
NO_DATA_2: 'module to mark them.',
|
||||
NO_DATA_3: 'To see all available entities go to',
|
||||
THIS_VERSION: 'This Version',
|
||||
PLATFORM: 'Platform',
|
||||
RELEASE_TYPE: 'Release Type',
|
||||
REINSTALL: 'Re-install',
|
||||
INTERNET_CONNECTION_REQUIRED: 'Internet connection required for automatic version checking and upgrading',
|
||||
};
|
||||
|
||||
export default en;
|
||||
|
||||
@@ -187,7 +187,7 @@ const fr: Translation = {
|
||||
COMPACT: 'Compact',
|
||||
DOWNLOAD_SETTINGS_TEXT: 'Create a backup of your configuration and settings', // TODO translate
|
||||
UPLOAD_TEXT: 'Upload a new firmware file (.bin) or a backup file (.json)', // TODO translate
|
||||
UPLOAD_DROP_TEXT: 'Déposer le fichier ou cliquer ici',
|
||||
UPLOAD_DROP_TEXT: 'Drop a firmware .bin file or click here', // TODO translate
|
||||
ERROR: 'Erreur inattendue, veuillez réessayer',
|
||||
TIME_SET: 'Time set',
|
||||
MANAGE_USERS: 'Gérer les utilisateurs',
|
||||
@@ -332,18 +332,27 @@ const fr: Translation = {
|
||||
SPECIAL_FUNCTIONS: 'Special Functions',
|
||||
WAIT_FIRMWARE: 'Firmware is uploading and installing', // TODO translate
|
||||
INSTALL_VERSION: 'This will install version {0}. Are you sure?', // TODO translate
|
||||
SWITCH_DEV: 'switch to the development version', // TODO translate
|
||||
UPGRADE_AVAILABLE: 'There is a firmware upgrade available!', // TODO translate
|
||||
LATEST_VERSION: 'You are using the latest firmware version.', // TODO translate
|
||||
LATEST_VERSION: 'You are using the latest firmware version', // TODO translate
|
||||
PLEASE_WAIT: 'Please wait', // TODO translate
|
||||
RESTARTING_PRE: 'Initializing', // TODO translate
|
||||
RESTARTING_POST: 'Preparing', // TODO translate
|
||||
AUTO_SCROLL: 'Auto Scroll', // TODO translate
|
||||
DASHBOARD: 'Dashboard', // TODO translate
|
||||
NO_DATA: 'No data available', // TODO translate
|
||||
DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorite using the Customizations module', // TODO translate
|
||||
DEVELOPER_MODE: 'Developer Mode', // TODO translate
|
||||
UPGRADE: 'Upgrade' // TODO translate
|
||||
BYTES: 'Bytes', // TODO translate
|
||||
BITMASK: 'Bit Mask',// TODO translate
|
||||
DUPLICATE: 'Duplicate', // TODO translate
|
||||
UPGRADE: 'Upgrade', // TODO translate
|
||||
DASHBOARD_1: 'All EMS entities that are active and marked as Favorite, plus all Custom Entities, Schedules and external Sensors data are displayed below.', // TODO translate
|
||||
NO_DATA_1: 'No favorite EMS entities found yet. Use the', // TODO translate
|
||||
NO_DATA_2: 'module to mark them.', // TODO translate
|
||||
NO_DATA_3: 'To see all available entities go to', // TODO translate
|
||||
THIS_VERSION: 'This Version', // TODO translate
|
||||
PLATFORM: 'Platform', // TODO translate
|
||||
RELEASE_TYPE: 'Release Type', // TODO translate
|
||||
REINSTALL: 'Re-install', // TODO translate
|
||||
INTERNET_CONNECTION_REQUIRED: 'Internet connection required for automatic version checking and upgrading',
|
||||
};
|
||||
|
||||
export default fr;
|
||||
|
||||
@@ -187,7 +187,7 @@ const it: Translation = {
|
||||
COMPACT: 'Compact',
|
||||
DOWNLOAD_SETTINGS_TEXT: 'Create a backup of your configuration and settings', // TODO translate
|
||||
UPLOAD_TEXT: 'Upload a new firmware file (.bin) or a backup file (.json)', // TODO translate
|
||||
UPLOAD_DROP_TEXT: 'Trascina il file o clicca qui',
|
||||
UPLOAD_DROP_TEXT: 'Drop a firmware .bin file or click here', // TODO translate
|
||||
ERROR: 'Errore Inaspettato, prego tenta ancora',
|
||||
TIME_SET: 'Imposta Ora',
|
||||
MANAGE_USERS: 'Gestione Utenti',
|
||||
@@ -332,18 +332,27 @@ const it: Translation = {
|
||||
SPECIAL_FUNCTIONS: 'Special Functions', // TODO translate
|
||||
WAIT_FIRMWARE: 'Firmware is uploading and installing', // TODO translate
|
||||
INSTALL_VERSION: 'This will install version {0}. Are you sure?', // TODO translate
|
||||
SWITCH_DEV: 'switch to the development version', // TODO translate
|
||||
UPGRADE_AVAILABLE: 'There is a firmware upgrade available!', // TODO translate
|
||||
LATEST_VERSION: 'You are using the latest firmware version.', // TODO translate
|
||||
LATEST_VERSION: 'You are using the latest firmware version', // TODO translate
|
||||
PLEASE_WAIT: 'Please wait', // TODO translate
|
||||
RESTARTING_PRE: 'Initializing', // TODO translate
|
||||
RESTARTING_POST: 'Preparing', // TODO translate
|
||||
AUTO_SCROLL: 'Auto Scroll', // TODO translate
|
||||
DASHBOARD: 'Dashboard', // TODO translate
|
||||
NO_DATA: 'No data available', // TODO translate
|
||||
DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorite using the Customizations module', // TODO translate
|
||||
DEVELOPER_MODE: 'Developer Mode', // TODO translate
|
||||
UPGRADE: 'Upgrade' // TODO translate
|
||||
BYTES: 'Bytes', // TODO translate
|
||||
BITMASK: 'Bit Mask',// TODO translate
|
||||
DUPLICATE: 'Duplicate', // TODO translate
|
||||
UPGRADE: 'Upgrade', // TODO translate
|
||||
DASHBOARD_1: 'All EMS entities that are active and marked as Favorite, plus all Custom Entities, Schedules and external Sensors data are displayed below.', // TODO translate
|
||||
NO_DATA_1: 'No favorite EMS entities found yet. Use the', // TODO translate
|
||||
NO_DATA_2: 'module to mark them.', // TODO translate
|
||||
NO_DATA_3: 'To see all available entities go to', // TODO translate
|
||||
THIS_VERSION: 'This Version', // TODO translate
|
||||
PLATFORM: 'Platform', // TODO translate
|
||||
RELEASE_TYPE: 'Release Type', // TODO translate
|
||||
REINSTALL: 'Re-install', // TODO translate
|
||||
INTERNET_CONNECTION_REQUIRED: 'Internet connection required for automatic version checking and upgrading',
|
||||
};
|
||||
|
||||
export default it;
|
||||
|
||||
@@ -72,7 +72,7 @@ const nl: Translation = {
|
||||
TX_ISSUES: 'Tx bus probleem. Probeer een andere Tx verzendmodus',
|
||||
DISCONNECTED: 'Niet verbonden',
|
||||
EMS_SCAN: 'Weet je zeker dat je een volledige EMS bus scan uit wilt voeren?',
|
||||
DATA_TRAFFIC: 'Data Traffic', // TODO translate
|
||||
DATA_TRAFFIC: 'Dataverkeer',
|
||||
EMS_DEVICE: 'EMS Apparaat',
|
||||
SUCCESS: 'SUCCESS',
|
||||
FAIL: 'MISLUKT',
|
||||
@@ -115,7 +115,7 @@ const nl: Translation = {
|
||||
READONLY: 'Activeer read-only modus (blokkeert alle outgaande EMS Tx schrijf commandos)',
|
||||
UNDERCLOCK_CPU: 'Underclock CPU snelheid',
|
||||
REMOTE_TIMEOUT: 'Remote timeout',
|
||||
REMOTE_TIMEOUT_EN: 'Disable remote on missing room temperature', // TODO translate
|
||||
REMOTE_TIMEOUT_EN: 'Schakel de afstandsbediening uit bij ontbrekende kamertemperatuur',
|
||||
HEATINGOFF: 'Start ketel met geforceerde verwarming uit',
|
||||
MIN_DURATION: 'Wait time',
|
||||
ENABLE_SHOWER_TIMER: 'Activeer Douche Timer (tijdmeting)',
|
||||
@@ -174,7 +174,7 @@ const nl: Translation = {
|
||||
FACTORY_RESET: 'Fabrieksinstellingen',
|
||||
SYSTEM_FACTORY_TEXT: 'Gateway is gereset en start nu weer op met fabrieksinstellingen',
|
||||
SYSTEM_FACTORY_TEXT_DIALOG: 'Weet je zeker dat je een reset naar fabrieksinstellingen uit wilt voeren?',
|
||||
AVAILABLE_VERSION: 'Latest Available Versions', // TODO translate
|
||||
AVAILABLE_VERSION: 'Nieuwste beschikbare versies',
|
||||
STABLE: 'Stable',
|
||||
DEVELOPMENT: 'Development',
|
||||
UPTIME: 'Systeem Uptime',
|
||||
@@ -185,9 +185,9 @@ const nl: Translation = {
|
||||
FILESYSTEM: 'File System (Used / Free)',
|
||||
BUFFER_SIZE: 'Max Buffer Size',
|
||||
COMPACT: 'Compact',
|
||||
DOWNLOAD_SETTINGS_TEXT: 'Create a backup of your configuration and settings', // TODO translate
|
||||
UPLOAD_TEXT: 'Upload a new firmware file (.bin) or a backup file (.json)', // TODO translate
|
||||
UPLOAD_DROP_TEXT: 'Sleep bestand hierheen of klik hier',
|
||||
DOWNLOAD_SETTINGS_TEXT: 'Maak een back-up van uw configuratie en instellingen',
|
||||
UPLOAD_TEXT: 'Upload een nieuw firmwarebestand (.bin) of een back-upbestand (.json)',
|
||||
UPLOAD_DROP_TEXT: 'Sleep en firmware .bin bestand hierheen of klik hier',
|
||||
ERROR: 'Onverwachte fout, probeer opnieuw',
|
||||
TIME_SET: 'Tijd ingesteld',
|
||||
MANAGE_USERS: 'Beheer Gebruikers',
|
||||
@@ -319,31 +319,40 @@ const nl: Translation = {
|
||||
SECURITY_1: 'Gebruikers toevoegen of verwijderen',
|
||||
DOWNLOAD_UPLOAD_1: 'Download en upload instellingen en firmware',
|
||||
MODULES: 'Module',
|
||||
MODULES_1: 'Externe modules activeren of deactiveren', // TODO translate
|
||||
MODULES_1: 'Externe modules activeren of deactiveren',
|
||||
MODULES_UPDATED: 'Modules geüpdatet',
|
||||
MODULES_DESCRIPTION: 'Klik op de module om EMS-ESP library modules te activeren of te deactiveren',
|
||||
MODULES_NONE: 'Geen externe modules gedetecteerd',
|
||||
RENAME: 'Hernoemen',
|
||||
ENABLE_MODBUS: 'Activeer Modbus',
|
||||
VIEW_LOG: 'View log to diagnose issues', // TODO translate
|
||||
UPLOAD_DRAG: 'drag and drop a file here or click to select one', // TODO translate
|
||||
SERVICES: 'Services', // TODO translate
|
||||
ALLVALUES: 'All Values', // TODO translate
|
||||
SPECIAL_FUNCTIONS: 'Special Functions', // TODO translate
|
||||
WAIT_FIRMWARE: 'Firmware is uploading and installing', // TODO translate
|
||||
INSTALL_VERSION: 'This will install version {0}. Are you sure?', // TODO translate
|
||||
SWITCH_DEV: 'switch to the development version', // TODO translate
|
||||
UPGRADE_AVAILABLE: 'There is a firmware upgrade available!', // TODO translate
|
||||
LATEST_VERSION: 'You are using the latest firmware version.', // TODO translate
|
||||
PLEASE_WAIT: 'Please wait', // TODO translate
|
||||
RESTARTING_PRE: 'Initializing', // TODO translate
|
||||
RESTARTING_POST: 'Preparing', // TODO translate
|
||||
AUTO_SCROLL: 'Auto Scroll', // TODO translate
|
||||
DASHBOARD: 'Dashboard', // TODO translate
|
||||
NO_DATA: 'No data available', // TODO translate
|
||||
DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorite using the Customizations module', // TODO translate
|
||||
DEVELOPER_MODE: 'Developer Mode', // TODO translate
|
||||
UPGRADE: 'Upgrade' // TODO translate
|
||||
VIEW_LOG: 'Log weergeven om problemen te diagnosticeren',
|
||||
UPLOAD_DRAG: 'sleep hier een bestand en zet het neer of klik om er een te selecteren',
|
||||
SERVICES: 'Services',
|
||||
ALLVALUES: 'All waarden',
|
||||
SPECIAL_FUNCTIONS: 'Speciale functies',
|
||||
WAIT_FIRMWARE: 'Firmware wordt geüpload en geïnstalleerd',
|
||||
INSTALL_VERSION: 'Hiermee wordt versie {0} geïnstalleerd. Weet je het zeker?',
|
||||
UPGRADE_AVAILABLE: 'Er is een firmware-upgrade beschikbaar!',
|
||||
LATEST_VERSION: 'U gebruikt de nieuwste firmwareversie',
|
||||
PLEASE_WAIT: 'Een ogenblik geduld',
|
||||
RESTARTING_PRE: 'Initialiseren',
|
||||
RESTARTING_POST: 'Voorbereiding',
|
||||
AUTO_SCROLL: 'Automatisch Scrollen',
|
||||
DASHBOARD: 'Dashboard',
|
||||
DEVELOPER_MODE: 'Ontwikkelaarsmodus',
|
||||
BYTES: 'Bytes', // TODO translate
|
||||
BITMASK: 'Bit Mask',// TODO translate
|
||||
DUPLICATE: 'Duplicaat',
|
||||
UPGRADE: 'Upgraden',
|
||||
DASHBOARD_1: 'Alle EMS-entiteiten die actief zijn en als favoriet zijn gemarkeerd, plus alle aangepaste entiteiten en externe sensorgegevens worden hieronder weergegeven.',
|
||||
NO_DATA_1: 'Er zijn nog geen favoriete EMS-entiteiten gevonden. Gebruik de',
|
||||
NO_DATA_2: 'module om ze te markeren.',
|
||||
NO_DATA_3: 'Om alle beschikbare entiteiten te zien, ga naar',
|
||||
THIS_VERSION: 'Deze Versie',
|
||||
PLATFORM: 'Platform',
|
||||
RELEASE_TYPE: 'Release Typ',
|
||||
REINSTALL: 'Opnieuw Installeren',
|
||||
INTERNET_CONNECTION_REQUIRED: 'Internetverbinding vereist voor automatische versiecontrole en -upgrade',
|
||||
};
|
||||
|
||||
export default nl;
|
||||
export default nl;
|
||||
@@ -187,7 +187,7 @@ const no: Translation = {
|
||||
COMPACT: 'Komprimere',
|
||||
DOWNLOAD_SETTINGS_TEXT: 'Create a backup of your configuration and settings', // TODO translate
|
||||
UPLOAD_TEXT: 'Upload a new firmware file (.bin) or a backup file (.json)', // TODO translate
|
||||
UPLOAD_DROP_TEXT: 'Slipp fil eller klikk her',
|
||||
UPLOAD_DROP_TEXT: 'Drop a firmware .bin file or click here', // TODO translate
|
||||
ERROR: 'Ukjent feil, prøv igjen',
|
||||
TIME_SET: 'Still in tid',
|
||||
MANAGE_USERS: 'Administrer Brukere',
|
||||
@@ -332,18 +332,27 @@ const no: Translation = {
|
||||
SPECIAL_FUNCTIONS: 'Special Functions', // TODO translate
|
||||
WAIT_FIRMWARE: 'Firmware is uploading and installing', // TODO translate
|
||||
INSTALL_VERSION: 'This will install version {0}. Are you sure?', // TODO translate
|
||||
SWITCH_DEV: 'switch to the development version', // TODO translate
|
||||
UPGRADE_AVAILABLE: 'There is a firmware upgrade available!', // TODO translate
|
||||
LATEST_VERSION: 'You are using the latest firmware version.', // TODO translate
|
||||
LATEST_VERSION: 'You are using the latest firmware version', // TODO translate
|
||||
PLEASE_WAIT: 'Please wait', // TODO translate
|
||||
RESTARTING_PRE: 'Initializing', // TODO translate
|
||||
RESTARTING_POST: 'Preparing', // TODO translate
|
||||
AUTO_SCROLL: 'Auto Scroll', // TODO translate
|
||||
DASHBOARD: 'Dashboard', // TODO translate
|
||||
NO_DATA: 'No data available', // TODO translate
|
||||
DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorite using the Customizations module', // TODO translate
|
||||
DEVELOPER_MODE: 'Developer Mode', // TODO translate
|
||||
UPGRADE: 'Upgrade' // TODO translate
|
||||
BYTES: 'Bytes', // TODO translate
|
||||
BITMASK: 'Bit Mask',// TODO translate
|
||||
DUPLICATE: 'Duplicate', // TODO translate
|
||||
UPGRADE: 'Upgrade', // TODO translate
|
||||
DASHBOARD_1: 'All EMS entities that are active and marked as Favorite, plus all Custom Entities, Schedules and external Sensors data are displayed below.', // TODO translate
|
||||
NO_DATA_1: 'No favorite EMS entities found yet. Use the', // TODO translate
|
||||
NO_DATA_2: 'module to mark them.', // TODO translate
|
||||
NO_DATA_3: 'To see all available entities go to', // TODO translate
|
||||
THIS_VERSION: 'This Version', // TODO translate
|
||||
PLATFORM: 'Platform', // TODO translate
|
||||
RELEASE_TYPE: 'Release Type', // TODO translate
|
||||
REINSTALL: 'Re-install', // TODO translate
|
||||
INTERNET_CONNECTION_REQUIRED: 'Internet connection required for automatic version checking and upgrading',
|
||||
};
|
||||
|
||||
export default no;
|
||||
|
||||
@@ -187,7 +187,7 @@ const pl: BaseTranslation = {
|
||||
COMPACT: 'Kompaktowy',
|
||||
DOWNLOAD_SETTINGS_TEXT: 'Create a backup of your configuration and settings', // TODO translate
|
||||
UPLOAD_TEXT: 'Upload a new firmware file (.bin) or a backup file (.json)', // TODO translate
|
||||
UPLOAD_DROP_TEXT: 'Przeciągnij tutaj plik lub kliknij',
|
||||
UPLOAD_DROP_TEXT: 'Drop a firmware .bin file or click here', // TODO translate
|
||||
ERROR: 'Nieoczekiwany błąd, spróbuj ponownie!',
|
||||
TIME_SET: 'Zegar został ustawiony.',
|
||||
MANAGE_USERS: 'Zarządzanie użytkownikami',
|
||||
@@ -332,18 +332,27 @@ const pl: BaseTranslation = {
|
||||
SPECIAL_FUNCTIONS: 'Special Functions', // TODO translate
|
||||
WAIT_FIRMWARE: 'Firmware is uploading and installing', // TODO translate
|
||||
INSTALL_VERSION: 'This will install version {0}. Are you sure?', // TODO translate
|
||||
SWITCH_DEV: 'switch to the development version', // TODO translate
|
||||
UPGRADE_AVAILABLE: 'There is a firmware upgrade available!', // TODO translate
|
||||
LATEST_VERSION: 'You are using the latest firmware version.', // TODO translate
|
||||
LATEST_VERSION: 'You are using the latest firmware version', // TODO translate
|
||||
PLEASE_WAIT: 'Please wait', // TODO translate
|
||||
RESTARTING_PRE: 'Initializing', // TODO translate
|
||||
RESTARTING_POST: 'Preparing', // TODO translate
|
||||
AUTO_SCROLL: 'Auto Scroll', // TODO translate
|
||||
DASHBOARD: 'Dashboard', // TODO translate
|
||||
NO_DATA: 'No data available', // TODO translate
|
||||
DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorite using the Customizations module', // TODO translate
|
||||
DEVELOPER_MODE: 'Developer Mode', // TODO translate
|
||||
UPGRADE: 'Upgrade' // TODO translate
|
||||
BYTES: 'Bytes', // TODO translate
|
||||
BITMASK: 'Bit Mask',// TODO translate
|
||||
DUPLICATE: 'Duplicate', // TODO translate
|
||||
UPGRADE: 'Upgrade', // TODO translate
|
||||
DASHBOARD_1: 'All EMS entities that are active and marked as Favorite, plus all Custom Entities, Schedules and external Sensors data are displayed below.', // TODO translate
|
||||
NO_DATA_1: 'No favorite EMS entities found yet. Use the', // TODO translate
|
||||
NO_DATA_2: 'module to mark them.', // TODO translate
|
||||
NO_DATA_3: 'To see all available entities go to', // TODO translate
|
||||
THIS_VERSION: 'This Version', // TODO translate
|
||||
PLATFORM: 'Platform', // TODO translate
|
||||
RELEASE_TYPE: 'Release Type', // TODO translate
|
||||
REINSTALL: 'Re-install', // TODO translate
|
||||
INTERNET_CONNECTION_REQUIRED: 'Internet connection required for automatic version checking and upgrading', // TODO translate
|
||||
};
|
||||
|
||||
export default pl;
|
||||
|
||||
@@ -57,7 +57,7 @@ const sk: Translation = {
|
||||
OFFSET: 'Ofset',
|
||||
FACTOR: 'Faktor',
|
||||
FREQ: 'Frekvencia',
|
||||
DUTY_CYCLE: 'Duty Cycle',
|
||||
DUTY_CYCLE: 'Pracovný cyklus',
|
||||
UNIT: 'UoM',
|
||||
STARTVALUE: 'Počiatočná hodnota',
|
||||
WARN_GPIO: 'Upozornenie: Buďte opatrní pri priraďovaní GPIO!',
|
||||
@@ -75,7 +75,7 @@ const sk: Translation = {
|
||||
DATA_TRAFFIC: 'Dátová prevádzka',
|
||||
EMS_DEVICE: 'EMS zariadenie',
|
||||
SUCCESS: 'ÚSPEŠNÉ',
|
||||
FAIL: 'ZLÝHANIE',
|
||||
FAIL: 'ZLYHANIE',
|
||||
QUALITY: 'KVALITA',
|
||||
SCAN: 'Scan',
|
||||
STATUS_NAMES: [
|
||||
@@ -114,10 +114,10 @@ const sk: Translation = {
|
||||
BYPASS_TOKEN: 'Vynechajte autorizáciu prístupového tokenu pri volaniach API',
|
||||
READONLY: 'Povoliť režim len na čítanie (blokuje všetky odchádzajúce príkazy EMS Tx Write)',
|
||||
UNDERCLOCK_CPU: 'Podtaktovanie rýchlosti procesora',
|
||||
REMOTE_TIMEOUT: 'Remote timeout',
|
||||
REMOTE_TIMEOUT_EN: 'Disable remote on missing room temperature', // TODO translate
|
||||
REMOTE_TIMEOUT: 'Vzdialený časový limit',
|
||||
REMOTE_TIMEOUT_EN: 'Deaktivujte diaľkové ovládanie pri chýbajúcej teplote v miestnosti',
|
||||
HEATINGOFF: 'Spustiť kotol s vynúteným vykurovaním',
|
||||
MIN_DURATION: 'Wait time',
|
||||
MIN_DURATION: 'Čakacia doba',
|
||||
ENABLE_SHOWER_TIMER: 'Povoliť časovač sprchovania',
|
||||
ENABLE_SHOWER_ALERT: 'Povoliť upozornenie na sprchu',
|
||||
TRIGGER_TIME: 'Čas spustenia',
|
||||
@@ -127,7 +127,7 @@ const sk: Translation = {
|
||||
BOOLEAN_FORMAT_API: 'Boolean formát API/MQTT',
|
||||
ENUM_FORMAT: 'Enum formát API/MQTT',
|
||||
INDEX: 'Index',
|
||||
ENABLE_PARASITE: 'Povoliť 1-wire parazité napájanie DS18B20',
|
||||
ENABLE_PARASITE: 'Povoliť 1-wire parazitné napájanie DS18B20',
|
||||
LOGGING: 'Logovanie',
|
||||
LOG_HEX: 'Záznam telegramov EMS v hexadecimálnej sústave',
|
||||
ENABLE_SYSLOG: 'Povoliť Syslog',
|
||||
@@ -174,20 +174,20 @@ const sk: Translation = {
|
||||
FACTORY_RESET: 'Továrenské nastavenia',
|
||||
SYSTEM_FACTORY_TEXT: 'Zariadenie bolo obnovené z výroby a teraz sa reštartuje',
|
||||
SYSTEM_FACTORY_TEXT_DIALOG: 'Naozaj chcete resetovať EMS-ESP na predvolené výrobné nastavenia?',
|
||||
AVAILABLE_VERSION: 'Latest Available Versions', // TODO translate
|
||||
AVAILABLE_VERSION: 'Najnovšie dostupné verzie',
|
||||
STABLE: 'Stabilná',
|
||||
DEVELOPMENT: 'Vývojárska',
|
||||
UPTIME: 'Beh systému',
|
||||
FREE_MEMORY: 'Voľné Memory',
|
||||
FREE_MEMORY: 'Voľná pamäť',
|
||||
PSRAM: 'PSRAM (Veľkosť / Voľné)',
|
||||
FLASH: 'Flash chip (Veľkosť , Rýchlosť)',
|
||||
APPSIZE: 'Applikácia (Oddiel: Použité / Voľné)',
|
||||
APPSIZE: 'Aplikácia (Oddiel: Použité / Voľné)',
|
||||
FILESYSTEM: 'Súborový systém (Použité / Voľné)',
|
||||
BUFFER_SIZE: 'Buffer-max.veľkosť',
|
||||
BUFFER_SIZE: 'Buffer-max. veľkosť',
|
||||
COMPACT: 'Kompaktné',
|
||||
DOWNLOAD_SETTINGS_TEXT: 'Vytvorte zálohu svojej konfigurácie a nastavení',
|
||||
UPLOAD_TEXT: 'Nahrajte nový súbor firmvéru (.bin) alebo súbor zálohy (.json)',
|
||||
UPLOAD_DROP_TEXT: 'Potiahnúť a pripnúť súbor alebo kliknúť sem',
|
||||
UPLOAD_DROP_TEXT: 'Drop a firmware .bin file or click here', // TODO translate
|
||||
ERROR: 'Neočakávaná chyba, prosím skúste to znova',
|
||||
TIME_SET: 'Nastavený čas',
|
||||
MANAGE_USERS: 'Správa používateľov',
|
||||
@@ -313,11 +313,11 @@ const sk: Translation = {
|
||||
UNCHANGED: 'Nezmenené',
|
||||
ALWAYS: 'Vždy',
|
||||
ACTIVITY: 'Aktivita',
|
||||
CONFIGURE: 'Konfiguracia {0}',
|
||||
CONFIGURE: 'Konfigurácia {0}',
|
||||
SYSTEM_MEMORY: 'Systémová pamäť',
|
||||
APPLICATION_SETTINGS_1: 'Zmeniť nastavenia aplikácie EMS-ESP',
|
||||
SECURITY_1: 'Pridať, alebo odstrániť použivateľov',
|
||||
DOWNLOAD_UPLOAD_1: 'Stiahnúť a nahrať nastavenia a firmware',
|
||||
SECURITY_1: 'Pridať, alebo odstrániť používateľov',
|
||||
DOWNLOAD_UPLOAD_1: 'Stiahnuť a nahrať nastavenia a firmware',
|
||||
MODULES: 'Moduly',
|
||||
MODULES_1: 'Aktivujte alebo deaktivujte externé moduly',
|
||||
MODULES_UPDATED: 'Aktualizované moduly',
|
||||
@@ -332,18 +332,27 @@ const sk: Translation = {
|
||||
SPECIAL_FUNCTIONS: 'Špeciálne funkcie',
|
||||
WAIT_FIRMWARE: 'Firmvér sa nahráva a inštaluje',
|
||||
INSTALL_VERSION: 'Týmto sa inštalovať verzia {0}. Si si istý?',
|
||||
SWITCH_DEV: 'prejsť na vývojovú verziu',
|
||||
UPGRADE_AVAILABLE: 'K dispozícii je aktualizácia firmvéru!',
|
||||
LATEST_VERSION: 'Používate poslednú verziu firmvéru.',
|
||||
LATEST_VERSION: 'Používate poslednú verziu firmvéru',
|
||||
PLEASE_WAIT: 'Čakajte prosím',
|
||||
RESTARTING_PRE: 'Prebieha inicializácia',
|
||||
RESTARTING_POST: 'Príprava',
|
||||
AUTO_SCROLL: 'Automatické rolovanie',
|
||||
DASHBOARD: 'Panel',
|
||||
NO_DATA: 'Nie sú k dispozícii žiadne údaje',
|
||||
DASHBOARD_1: 'Prispôsobte si svoj informačný panel tak, že označíte entity EMS ako Obľúbené pomocou modulu Prispôsobenia',
|
||||
DEVELOPER_MODE: 'Developer Mode', // TODO translate
|
||||
UPGRADE: 'Upgrade' // TODO translate
|
||||
DEVELOPER_MODE: 'Režim vývojára',
|
||||
BYTES: 'Bytes', // TODO translate
|
||||
BITMASK: 'Bit Mask',// TODO translate
|
||||
DUPLICATE: 'Duplicitné',
|
||||
UPGRADE: 'Inovovať',
|
||||
DASHBOARD_1: 'Všetky entity EMS, ktoré sú aktívne a označené ako obľúbené, plus všetky vlastné entity, plány a údaje externých senzorov sú zobrazené nižšie.',
|
||||
NO_DATA_1: 'Nenašli sa žiadne obľúbené entity EMS. Použite',
|
||||
NO_DATA_2: 'modul na ich označenie.',
|
||||
NO_DATA_3: 'Ak chcete zobraziť všetky dostupné entity, prejdite na',
|
||||
THIS_VERSION: 'Táto verzia',
|
||||
PLATFORM: 'Platforma',
|
||||
RELEASE_TYPE: 'Typ vydania',
|
||||
REINSTALL: 'Preinštalovať',
|
||||
INTERNET_CONNECTION_REQUIRED: 'Internetové pripojenie je potrebné pre automatickú kontrolu a aktualizáciu',
|
||||
};
|
||||
|
||||
export default sk;
|
||||
|
||||
@@ -5,8 +5,8 @@ const sv: Translation = {
|
||||
RETRY: 'Försök igen',
|
||||
LOADING: 'Laddar',
|
||||
IS_REQUIRED: '{0} Krävs',
|
||||
SIGN_IN: 'Logga In',
|
||||
SIGN_OUT: 'Logga Ut',
|
||||
SIGN_IN: 'Logga in',
|
||||
SIGN_OUT: 'Logga ut',
|
||||
USERNAME: 'Användarnamn',
|
||||
PASSWORD: 'Lösenord',
|
||||
SU_PASSWORD: 'su Lösenord',
|
||||
@@ -29,7 +29,7 @@ const sv: Translation = {
|
||||
FAVORITES: "Favoriter",
|
||||
DEVICE_DETAILS: 'Enhetsdetaljer',
|
||||
ID_OF: '{0}-ID',
|
||||
DEVICE: 'Enhets',
|
||||
DEVICE: 'Enhet',
|
||||
PRODUCT: 'Produkt',
|
||||
VERSION: 'Version',
|
||||
BRAND: 'Fabrikat',
|
||||
@@ -37,28 +37,28 @@ const sv: Translation = {
|
||||
VALUE: '{{värde|Värde}}',
|
||||
DEVICES: 'Enheter',
|
||||
SENSORS: 'Sensorer',
|
||||
RUN_COMMAND: 'Kör Kommando',
|
||||
CHANGE_VALUE: 'Ändra Värde',
|
||||
RUN_COMMAND: 'Kör kommando',
|
||||
CHANGE_VALUE: 'Ändra värde',
|
||||
CANCEL: 'Avbryt',
|
||||
RESET: 'Nollställ',
|
||||
APPLY_CHANGES: 'Apply Changes ({0})', // TODO translate
|
||||
UPDATE: 'Update', // TODO translate
|
||||
EXECUTE: 'Execute', // TODO translate
|
||||
APPLY_CHANGES: 'Utför ändringar ({0})',
|
||||
UPDATE: 'Uppdatera',
|
||||
EXECUTE: 'Utför',
|
||||
REMOVE: 'Ta bort',
|
||||
PROBLEM_UPDATING: 'Problem vid uppdatering',
|
||||
PROBLEM_LOADING: 'Problem vid hämtning',
|
||||
ANALOG_SENSOR: 'Analog Sensor',
|
||||
ANALOG_SENSORS: 'Analoga Sensorer',
|
||||
ANALOG_SENSOR: 'analog sensor',
|
||||
ANALOG_SENSORS: 'Analoga sensorer',
|
||||
SETTINGS: 'Inställningar',
|
||||
UPDATED_OF: '{0} Uppdaterad',
|
||||
UPDATE_OF: '{0} Uppdatera',
|
||||
REMOVED_OF: '{0} Raderad',
|
||||
DELETION_OF: '{0} Radering',
|
||||
OFFSET: 'Kompensering',
|
||||
OFFSET: 'Offset',
|
||||
FACTOR: 'Faktor',
|
||||
FREQ: 'Frekvens',
|
||||
DUTY_CYCLE: 'Duty Cycle',
|
||||
UNIT: 'UoM',
|
||||
DUTY_CYCLE: 'Pulskvot',
|
||||
UNIT: 'Måttenhet',
|
||||
STARTVALUE: 'Startvärde',
|
||||
WARN_GPIO: 'Varning: Var försiktig vid aktivering av GPIO!',
|
||||
EDIT: 'Ändra',
|
||||
@@ -71,9 +71,9 @@ const sv: Translation = {
|
||||
CONNECTED: 'Ansluten',
|
||||
TX_ISSUES: 'Sändfel - Prova ett annat TX-läge',
|
||||
DISCONNECTED: 'Nedkopplad',
|
||||
EMS_SCAN: 'Är du säker att du vill initiera en full genomsökning av EMS-bussen?',
|
||||
DATA_TRAFFIC: 'Data Traffic', // TODO translate
|
||||
EMS_DEVICE: 'EMS Enhet',
|
||||
EMS_SCAN: 'Är du säker på att du vill starta en full genomsökning av EMS-bussen?',
|
||||
DATA_TRAFFIC: 'Datatrafik',
|
||||
EMS_DEVICE: 'EMS enhet',
|
||||
SUCCESS: 'Lyckades',
|
||||
FAIL: 'Misslyckades',
|
||||
QUALITY: 'Kvalitet',
|
||||
@@ -92,8 +92,8 @@ const sv: Translation = {
|
||||
NUM_SECONDS: '{num} sekund{{er}}',
|
||||
NUM_HOURS: '{num} timmar',
|
||||
NUM_MINUTES: '{num} minut{{er}}',
|
||||
APPLICATION: 'Apliká',
|
||||
CUSTOMIZATIONS: 'Anpassningr',
|
||||
APPLICATION: 'Applikation',
|
||||
CUSTOMIZATIONS: 'Anpassningar',
|
||||
APPLICATION_RESTARTING: 'EMS-ESP startar om',
|
||||
BOARD_PROFILE: 'Hårdvarutyp',
|
||||
CUSTOM: 'Anpassa',
|
||||
@@ -105,34 +105,34 @@ const sv: Translation = {
|
||||
TX_MODE: 'EMS Tx-läge',
|
||||
HARDWARE: 'Hårdvara',
|
||||
EMS_BUS: '{{BUSS|EMS-BUSS}}',
|
||||
GENERAL_OPTIONS: 'Allmänna Inställningar',
|
||||
GENERAL_OPTIONS: 'Allmänna inställningar',
|
||||
LANGUAGE_ENTITIES: 'Språk (för entiteter)',
|
||||
HIDE_LED: 'Inaktivera LED',
|
||||
ENABLE_TELNET: 'Aktivera Telnet',
|
||||
ENABLE_ANALOG: 'Aktivera Analoga Sensorer',
|
||||
CONVERT_FAHRENHEIT: 'Konvertera temperaturer till Fahrenheit',
|
||||
BYPASS_TOKEN: 'Inaktivera Token-autensiering för API-anrop',
|
||||
READONLY: 'Aktivera read-only (blockerar alla utgående skrivkommandon mot EMS-bussen)',
|
||||
BYPASS_TOKEN: 'Inaktivera Token-autentisiering för API-anrop',
|
||||
READONLY: 'Aktivera skrivskydd (blockerar alla utgående skrivkommandon mot EMS-bussen)',
|
||||
UNDERCLOCK_CPU: 'Nedklocka Processorhastighet',
|
||||
REMOTE_TIMEOUT: 'Remote timeout',
|
||||
REMOTE_TIMEOUT_EN: 'Disable remote on missing room temperature', // TODO translate
|
||||
HEATINGOFF: 'Start boiler with forced heating off', // TODO translate
|
||||
MIN_DURATION: 'Wait time',
|
||||
REMOTE_TIMEOUT_EN: 'Deaktivera remote vid missad rumstemperatur',
|
||||
HEATINGOFF: 'Starta värmepump/panna med forcerad värme avstängd',
|
||||
MIN_DURATION: 'Väntetid',
|
||||
ENABLE_SHOWER_TIMER: 'Aktivera Dusch-timer',
|
||||
ENABLE_SHOWER_ALERT: 'Aktivera Dusch-varning',
|
||||
TRIGGER_TIME: 'Aktiveringstid',
|
||||
COLD_SHOT_DURATION: 'Längd på kalldusch',
|
||||
FORMATTING_OPTIONS: 'Formatteringsalternativ',
|
||||
FORMATTING_OPTIONS: 'Formateringsalternativ',
|
||||
BOOLEAN_FORMAT_DASHBOARD: 'Bool-format Kontrollpanel',
|
||||
BOOLEAN_FORMAT_API: 'Bool-format API/MQTT',
|
||||
ENUM_FORMAT: 'Enum-format API/MQTT',
|
||||
INDEX: 'Index',
|
||||
ENABLE_PARASITE: 'Aktivera 1-wire parasitström',
|
||||
LOGGING: 'Loggning',
|
||||
LOG_HEX: 'Logga EMS-telegram i hexadecimal',
|
||||
LOG_HEX: 'Logga EMS-telegram i hexadecimalformat',
|
||||
ENABLE_SYSLOG: 'Aktivera Syslog',
|
||||
LOG_LEVEL: 'Loggnivå',
|
||||
MARK_INTERVAL: 'Markerings-interval',
|
||||
MARK_INTERVAL: 'Markeringsintervall',
|
||||
SECONDS: 'sekunder',
|
||||
MINUTES: 'minuter',
|
||||
HOURS: 'timmar',
|
||||
@@ -140,25 +140,25 @@ const sv: Translation = {
|
||||
RESTART_TEXT: 'EMS-ESP kräver en omstart för att applicera förändrade systeminställningar',
|
||||
RESTART_CONFIRM: 'Är du säker på att du vill starta om EMS-ESP?',
|
||||
COMMAND: 'Kommando',
|
||||
CUSTOMIZATIONS_RESTART: 'Alla anpassningr har raderats. Startar om...',
|
||||
CUSTOMIZATIONS_FULL: 'Antal valda enheter för högt. Vänligen spara i mindre antal åt gången.',
|
||||
CUSTOMIZATIONS_SAVED: 'Anpassningar sparade',
|
||||
CUSTOMIZATIONS_HELP_1: 'Välj en enhet och anpassa underenheter med hjälp av alternativen',
|
||||
CUSTOMIZATIONS_RESTART: 'Alla anpassningar har raderats. Startar om...',
|
||||
CUSTOMIZATIONS_FULL: 'För många valda enheter. Vänligen spara ett mindre antal åt gången.',
|
||||
CUSTOMIZATIONS_SAVED: 'Anpassningarna är sparade',
|
||||
CUSTOMIZATIONS_HELP_1: 'Välj en enhet och anpassa entiteter med hjälp av alternativen',
|
||||
CUSTOMIZATIONS_HELP_2: 'Favorit',
|
||||
CUSTOMIZATIONS_HELP_3: 'Inaktivera skrivningar',
|
||||
CUSTOMIZATIONS_HELP_3: 'Skrivskyddad',
|
||||
CUSTOMIZATIONS_HELP_4: 'Exkludera från MQTT & API',
|
||||
CUSTOMIZATIONS_HELP_5: 'dölj från enheter',
|
||||
CUSTOMIZATIONS_HELP_6: 'remove from memory',
|
||||
CUSTOMIZATIONS_HELP_5: 'Dölj under Enheter',
|
||||
CUSTOMIZATIONS_HELP_6: 'Ta bort',
|
||||
SELECT_DEVICE: 'Välj en enhet',
|
||||
SET_ALL: 'ställ in alla',
|
||||
OPTIONS: 'Alternativ',
|
||||
NAME: 'Namn',
|
||||
CUSTOMIZATIONS_RESET: 'Är du säker på att du vill ta bort alla anpassningar inklusive inställningar för Temperatur och Analoga sensorer?',
|
||||
CUSTOMIZATIONS_RESET: 'Är du säker på att du vill ta bort alla anpassningar, inklusive inställningar för Temperatursensorer och Analoga sensorer?',
|
||||
SUPPORT_INFORMATION: 'Supportinformation',
|
||||
HELP_INFORMATION_1: 'Besök Wikin för instruktioner för hur du kan konfigurera EMS-ESP',
|
||||
HELP_INFORMATION_1: 'Besök Wikin för instruktioner om hur du kan konfigurera EMS-ESP',
|
||||
HELP_INFORMATION_2: 'För community-support besök vår Discord-server',
|
||||
HELP_INFORMATION_3: 'Önska en ny funktion eller rapportera en bugg',
|
||||
HELP_INFORMATION_4: 'Bifoga din systeminformation för snabbare hantering när du rapporterar ett problem',
|
||||
HELP_INFORMATION_4: 'Bifoga din supportinformation för snabbare hantering när du rapporterar ett problem',
|
||||
UPLOAD: 'Uppladdning',
|
||||
DOWNLOAD: '{{N|n|n}}edladdning',
|
||||
INSTALL: 'Installera',
|
||||
@@ -168,90 +168,90 @@ const sv: Translation = {
|
||||
SYSTEM: 'System',
|
||||
LOG_OF: '{0} Logg',
|
||||
STATUS_OF: '{0} Status',
|
||||
DOWNLOAD_UPLOAD: 'Nedladdning/Upp',
|
||||
DOWNLOAD_UPLOAD: 'Nedladdning/Uppladdning',
|
||||
CLOSE: 'Stäng',
|
||||
USE: 'Använd',
|
||||
FACTORY_RESET: 'Fabriksåterställning',
|
||||
SYSTEM_FACTORY_TEXT: 'Enheten har blivit fabriksåterställd och startar nu om',
|
||||
SYSTEM_FACTORY_TEXT_DIALOG: 'Är du säker att du vill fabriksåterställa enheten?',
|
||||
AVAILABLE_VERSION: 'Latest Available Versions', // TODO translate
|
||||
STABLE: 'Stable', // TODO translate
|
||||
SYSTEM_FACTORY_TEXT_DIALOG: 'Är du säker på att du vill fabriksåterställa enheten?',
|
||||
AVAILABLE_VERSION: 'Senaste tillgängliga versioner',
|
||||
STABLE: 'Stabil',
|
||||
DEVELOPMENT: 'Utveckling',
|
||||
UPTIME: 'Systemets Upptid',
|
||||
FREE_MEMORY: 'Ledigt Memory',
|
||||
UPTIME: 'Systemets upptid',
|
||||
FREE_MEMORY: 'Ledigt minne',
|
||||
PSRAM: 'PSRAM (Storlek / Ledigt)',
|
||||
FLASH: 'Flashminne (Storlek , Hastighet)',
|
||||
APPSIZE: 'Applikationer (Partition: Använt / Ledigt)',
|
||||
FILESYSTEM: 'Filsystem (Använt / Ledigt)',
|
||||
BUFFER_SIZE: 'Max Bufferstorlek',
|
||||
COMPACT: 'Komprimera',
|
||||
DOWNLOAD_SETTINGS_TEXT: 'Create a backup of your configuration and settings', // TODO translate
|
||||
UPLOAD_TEXT: 'Upload a new firmware file (.bin) or a backup file (.json)', // TODO translate
|
||||
UPLOAD_DROP_TEXT: 'Släpp fil eller klicka här',
|
||||
ERROR: 'Okänt Fel, var god försök igen',
|
||||
BUFFER_SIZE: 'Max bufferstorlek',
|
||||
COMPACT: 'Komprimerad',
|
||||
DOWNLOAD_SETTINGS_TEXT: 'Skapa en säkerhetskopia av din konfiguration och inställningar',
|
||||
UPLOAD_TEXT: 'Ladda upp en ny firmwarefil (.bin) eller en säkerhetskopiafil (.json)',
|
||||
UPLOAD_DROP_TEXT: 'Drop a firmware .bin file or click here', // TODO translate
|
||||
ERROR: 'Okänt fel, var god försök igen',
|
||||
TIME_SET: 'Ställ in tid',
|
||||
MANAGE_USERS: 'Användare',
|
||||
IS_ADMIN: 'Admin',
|
||||
USER_WARNING: 'Du måste ha minst en admin konfigurerad',
|
||||
IS_ADMIN: 'Administratör',
|
||||
USER_WARNING: 'Du måste ha minst en administratör konfigurerad',
|
||||
ADD: 'Lägg till',
|
||||
ACCESS_TOKEN_FOR: 'Access Token för',
|
||||
ACCESS_TOKEN_TEXT: 'Nedan Token används med REST API-anrop som kräver auktorisering. Den kan skickas med antingen som en Bearer token i Authorization-headern eller i access_token URL query-parametern.',
|
||||
GENERATING_TOKEN: 'Genererar token',
|
||||
USER: 'Användare',
|
||||
MODIFY: 'Ändra',
|
||||
SU_TEXT: 'SU-användarens (super user) lösenord används för att signera autensierings-tokens samt för att aktivera administratörsprivilegier i Console-läge',
|
||||
SU_TEXT: 'SU-användarens (super user) lösenord används för att signera autentisierings-tokens samt för att aktivera administratörsprivilegier i Console-läge',
|
||||
NOT_ENABLED: 'Ej aktiv',
|
||||
ERRORS_OF: '{0} fel',
|
||||
DISCONNECT_REASON: 'Anledning till nedkoppling',
|
||||
ENABLE_MQTT: 'Aktivera MQTT',
|
||||
BROKER: 'Broker',
|
||||
CLIENT: 'Client',
|
||||
BASE_TOPIC: 'Base',
|
||||
CLIENT: 'Klient',
|
||||
BASE_TOPIC: 'Bas-topic',
|
||||
OPTIONAL: 'valfritt',
|
||||
FORMATTING: 'Formatering',
|
||||
MQTT_FORMAT: 'Topic/Payload Format',
|
||||
MQTT_NEST_1: 'Nestlat i en topic.',
|
||||
MQTT_FORMAT: 'Topic/Payload format',
|
||||
MQTT_NEST_1: 'Nästlat i en topic.',
|
||||
MQTT_NEST_2: 'Som individuella topics',
|
||||
MQTT_RESPONSE: 'Publish-kommando som ett `response` topic',
|
||||
MQTT_PUBLISH_TEXT_1: 'Publicera single value topics vid värdeförändring',
|
||||
MQTT_PUBLISH_TEXT_2: 'Publicera till kommando-topics (ioBroker)',
|
||||
MQTT_PUBLISH_TEXT_3: 'Aktivera MQTT Discovery',
|
||||
MQTT_PUBLISH_TEXT_4: 'Prefix för Discovery topics',
|
||||
MQTT_PUBLISH_TEXT_5: 'Discovery type', // TODO translate
|
||||
MQTT_PUBLISH_TEXT_4: 'Prefix för Discovery topics',
|
||||
MQTT_PUBLISH_TEXT_5: 'Discoverytyp',
|
||||
MQTT_PUBLISH_INTERVALS: 'Publiceringsintervall',
|
||||
MQTT_INT_BOILER: 'Värmepump/panna',
|
||||
MQTT_INT_THERMOSTATS: 'Termostater',
|
||||
MQTT_INT_SOLAR: 'Solpaneler',
|
||||
MQTT_INT_MIXER: 'Blandningsventiler',
|
||||
MQTT_INT_WATER: 'Water Modules', // TODO translate
|
||||
MQTT_INT_WATER: 'Varmvattenmoduler',
|
||||
MQTT_QUEUE: 'MQTT-kö',
|
||||
DEFAULT: 'Standard',
|
||||
MQTT_ENTITY_FORMAT: 'Entitets-ID format',
|
||||
MQTT_ENTITY_FORMAT_0: 'Singel-instans, långt namn(v3.4)',
|
||||
MQTT_ENTITY_FORMAT_1: 'Singel-instans, kort name',
|
||||
MQTT_ENTITY_FORMAT_2: 'Multi-instans, kort name',
|
||||
MQTT_ENTITY_FORMAT_1: 'Singel-instans, kort namn',
|
||||
MQTT_ENTITY_FORMAT_2: 'Multi-instans, kort namn',
|
||||
MQTT_CLEAN_SESSION: 'Använd "Clean Session"-flaggan',
|
||||
MQTT_RETAIN_FLAG: 'Använd "Always Retain"-flaggan',
|
||||
INACTIVE: 'Inaktiv',
|
||||
ACTIVE: 'Aktiv',
|
||||
UNKNOWN: 'Okänt',
|
||||
SET_TIME: 'Ställ in klockan',
|
||||
SET_TIME_TEXT: 'Ange lokal datum och tid nedan för att ställa in klockan',
|
||||
SET_TIME_TEXT: 'Ange lokalt datum och tid nedan för att ställa in klockan',
|
||||
LOCAL_TIME: 'Tid (lokal)',
|
||||
UTC_TIME: 'Tid (UTC)',
|
||||
ENABLE_NTP: 'Aktivera NTP',
|
||||
NTP_SERVER: 'NTP-server',
|
||||
TIME_ZONE: 'Tidszon',
|
||||
ACCESS_POINT: 'Accesspunkt',
|
||||
AP_PROVIDE: 'Aktivera Accesspunkt',
|
||||
AP_PROVIDE: 'Aktivera accesspunkt',
|
||||
AP_PROVIDE_TEXT_1: 'alltid',
|
||||
AP_PROVIDE_TEXT_2: 'när WiFi är nedkopplat',
|
||||
AP_PROVIDE_TEXT_3: 'aldrig',
|
||||
AP_PREFERRED_CHANNEL: 'Kanal',
|
||||
AP_HIDE_SSID: 'Göm SSID',
|
||||
AP_CLIENTS: 'AP-klienter',
|
||||
AP_MAX_CLIENTS: 'Max Klienter',
|
||||
AP_LOCAL_IP: 'Lokalt IP',
|
||||
AP_MAX_CLIENTS: 'Max antal klienter',
|
||||
AP_LOCAL_IP: 'Lokal IP-adress',
|
||||
NETWORK_SCAN: 'Sök efter WiFi-nätverk',
|
||||
IDLE: 'Vilande',
|
||||
LOST: 'Förlorad',
|
||||
@@ -260,90 +260,99 @@ const sv: Translation = {
|
||||
NETWORK_SCANNER: 'Hittade nätverk',
|
||||
NETWORK_NO_WIFI: 'Inga WiFi-nätverk hittades',
|
||||
NETWORK_BLANK_SSID: 'lämna blankt för att inaktivera WiFi',
|
||||
NETWORK_BLANK_BSSID: 'leave blank to use only SSID', // TODO translate
|
||||
TX_POWER: 'Tx Effekt',
|
||||
NETWORK_BLANK_BSSID: 'lämna blankt för att bara använda SSID',
|
||||
TX_POWER: 'Tx effekt',
|
||||
HOSTNAME: 'Värdnamn',
|
||||
NETWORK_DISABLE_SLEEP: 'Inaktivera sömnläge',
|
||||
NETWORK_DISABLE_SLEEP: 'Inaktivera sovläge',
|
||||
NETWORK_LOW_BAND: 'Använd lägre bandbredd',
|
||||
NETWORK_USE_DNS: 'Aktivera mDNS-tjänsten',
|
||||
NETWORK_ENABLE_CORS: 'Aktivera CORS',
|
||||
NETWORK_CORS_ORIGIN: 'CORS origin',
|
||||
NETWORK_FIXED_IP: 'Använd statiskt IP',
|
||||
NETWORK_FIXED_IP: 'Använd statisk IP-adress',
|
||||
NETWORK_GATEWAY: 'Gateway',
|
||||
NETWORK_SUBNET: 'Subnätmask',
|
||||
NETWORK_DNS: 'DNS-Server',
|
||||
ADDRESS_OF: '{0} Adress',
|
||||
ADMINISTRATOR: 'Administrator',
|
||||
ADMINISTRATOR: 'Administratör',
|
||||
GUEST: 'Gäst',
|
||||
NEW: 'Ny',
|
||||
NEW: 'ny',
|
||||
NEW_NAME_OF: 'Byt namn {0}',
|
||||
ENTITY: 'Entitet',
|
||||
MIN: 'min',
|
||||
MAX: 'max',
|
||||
BLOCK_NAVIGATE_1: 'You have unsaved changes', // TODO translate
|
||||
BLOCK_NAVIGATE_2: 'If you navigate to a different page, your unsaved changes will be lost. Are you sure you want to leave this page?', // TODO translate
|
||||
STAY: 'Stay', // TODO translate
|
||||
LEAVE: 'Leave', // TODO translate
|
||||
SCHEDULER: 'Scheduler', // TODO translate
|
||||
SCHEDULER_HELP_1: 'Automate commands by adding scheduled events below. Set a unique Name to enable/disable activation via API/MQTT', // TODO translate
|
||||
SCHEDULER_HELP_2: 'Use 00:00 to trigger once on start-up', // TODO translate
|
||||
SCHEDULE: 'Schedule', // TODO translate
|
||||
TIME: 'Time', // TODO translate
|
||||
TIMER: 'Timer', // TODO translate
|
||||
ONCHANGE: 'På förändring',
|
||||
CONDITION: 'Skick',
|
||||
BLOCK_NAVIGATE_1: 'Du har osparade ändringar',
|
||||
BLOCK_NAVIGATE_2: 'Om du navigerar till en annan sida, kommer dina osparade ändringar förloras. Är du säker på att du vill lämna den här sidan?',
|
||||
STAY: 'Stanna',
|
||||
LEAVE: 'Lämna',
|
||||
SCHEDULER: 'Schemaläggning',
|
||||
SCHEDULER_HELP_1: 'Automatisera kommandon genom att lägga till schemahändelser nedan. Ange ett unikt namn för att aktivera/avaktivera aktivering via API/MQTT',
|
||||
SCHEDULER_HELP_2: 'Använd 00:00 för att trigga en gång vid uppstart',
|
||||
SCHEDULE: 'schema',
|
||||
TIME: 'Tid',
|
||||
TIMER: 'Timer',
|
||||
ONCHANGE: 'Vid förändring',
|
||||
CONDITION: 'Villkor',
|
||||
IMMEDIATE: 'Omedelbar',
|
||||
SCHEDULE_UPDATED: 'Schedule updated', // TODO translate
|
||||
SCHEDULE_TIMER_1: 'on startup', // TODO translate
|
||||
SCHEDULE_TIMER_2: 'every minute', // TODO translate
|
||||
SCHEDULE_TIMER_3: 'every hour', // TODO translate
|
||||
CUSTOM_ENTITIES: 'Custom Entities', // TODO translate
|
||||
ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus', // TODO translate
|
||||
ENTITIES_UPDATED: 'Entities Updated', // TODO translate
|
||||
WRITEABLE: 'Writeable', // TODO translate
|
||||
SHOWING: 'Showing', // TODO translate
|
||||
SEARCH: 'Search', // TODO translate
|
||||
CERT: 'TLS root certificate (leave blank for insecure)', // TODO translate
|
||||
SCHEDULE_UPDATED: 'Schema uppdaterat',
|
||||
SCHEDULE_TIMER_1: 'vid uppstart',
|
||||
SCHEDULE_TIMER_2: 'varje minut',
|
||||
SCHEDULE_TIMER_3: 'varje timme',
|
||||
CUSTOM_ENTITIES: 'Anpassade entiteter',
|
||||
ENTITIES_HELP_1: 'Hämta anpassade entiteter från EMS bussen',
|
||||
ENTITIES_UPDATED: 'Entiteter uppdaterade',
|
||||
WRITEABLE: 'Skrivbara',
|
||||
SHOWING: 'Visar',
|
||||
SEARCH: 'Sök',
|
||||
CERT: 'TLS rotcertifikat (lämna blankt för ingen säkerhet)',
|
||||
ENABLE_TLS: 'Aktivera TLS',
|
||||
ON: 'On', // TODO translate
|
||||
OFF: 'Off', // TODO translate
|
||||
POLARITY: 'Polarity', // TODO translate
|
||||
ACTIVEHIGH: 'Active High', // TODO translate
|
||||
ACTIVELOW: 'Active Low', // TODO translate
|
||||
UNCHANGED: 'Unchanged', // TODO translate
|
||||
ALWAYS: 'Always', // TODO translate
|
||||
ACTIVITY: 'Activity', // TODO translate
|
||||
CONFIGURE: 'Configure {0}', // TODO translate
|
||||
SYSTEM_MEMORY: 'System Memory', // TODO translate
|
||||
APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate
|
||||
SECURITY_1: 'Add or remove users', // TODO translate
|
||||
DOWNLOAD_UPLOAD_1: 'Download and Upload Settings and Firmware', // TODO translate
|
||||
MODULES: 'Module', // TODO translate
|
||||
ON: 'På',
|
||||
OFF: 'Av',
|
||||
POLARITY: 'Polaritet',
|
||||
ACTIVEHIGH: 'Aktivt hög',
|
||||
ACTIVELOW: 'Aktivt låg',
|
||||
UNCHANGED: 'Oändrad',
|
||||
ALWAYS: 'Alltid',
|
||||
ACTIVITY: 'Aktivitet',
|
||||
CONFIGURE: 'Konfigurera {0}',
|
||||
SYSTEM_MEMORY: 'Systemminne',
|
||||
APPLICATION_SETTINGS_1: 'Ändra EMS-ESP Applikationsinställningar',
|
||||
SECURITY_1: 'Lägg till eller ta bort användare',
|
||||
DOWNLOAD_UPLOAD_1: 'Ladda ner eller ladda upp inställningar och firmware',
|
||||
MODULES: 'Moduler',
|
||||
MODULES_1: 'Aktivera eller avaktivera externa moduler',
|
||||
MODULES_UPDATED: 'Modules updated', // TODO translate
|
||||
MODULES_DESCRIPTION: 'Click on the Module to activate or de-activate EMS-ESP library modules', // TODO translate
|
||||
MODULES_NONE: 'No external modules detected', // TODO translate
|
||||
RENAME: 'Rename', // TODO translate
|
||||
MODULES_UPDATED: 'Moduler updaterade',
|
||||
MODULES_DESCRIPTION: 'Klicka på modulen för att aktivera eller deaktivera EMS-ESP moduler',
|
||||
MODULES_NONE: 'Inga externa moduler upptäckta',
|
||||
RENAME: 'Byt namn',
|
||||
ENABLE_MODBUS: 'Aktivera Modbus',
|
||||
VIEW_LOG: 'View log to diagnose issues', // TODO translate
|
||||
UPLOAD_DRAG: 'drag and drop a file here or click to select one', // TODO translate
|
||||
SERVICES: 'Services', // TODO translate
|
||||
ALLVALUES: 'All Values', // TODO translate
|
||||
SPECIAL_FUNCTIONS: 'Special Functions', // TODO translate
|
||||
WAIT_FIRMWARE: 'Firmware is uploading and installing', // TODO translate
|
||||
INSTALL_VERSION: 'This will install version {0}. Are you sure?', // TODO translate
|
||||
SWITCH_DEV: 'switch to the development version', // TODO translate
|
||||
UPGRADE_AVAILABLE: 'There is a firmware upgrade available!', // TODO translate
|
||||
LATEST_VERSION: 'You are using the latest firmware version.', // TODO translate
|
||||
PLEASE_WAIT: 'Please wait', // TODO translate
|
||||
RESTARTING_PRE: 'Initializing', // TODO translate
|
||||
RESTARTING_POST: 'Preparing', // TODO translate
|
||||
AUTO_SCROLL: 'Auto Scroll', // TODO translate
|
||||
DASHBOARD: 'Dashboard', // TODO translate
|
||||
NO_DATA: 'No data available', // TODO translate
|
||||
DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorite using the Customizations module', // TODO translate
|
||||
DEVELOPER_MODE: 'Developer Mode', // TODO translate
|
||||
UPGRADE: 'Upgrade' // TODO translate
|
||||
VIEW_LOG: 'Titta i loggen för att felsöka problem',
|
||||
UPLOAD_DRAG: 'dra och släpp en fil här eller klicka för att välja en',
|
||||
SERVICES: 'Tjänster',
|
||||
ALLVALUES: 'Alla värden',
|
||||
SPECIAL_FUNCTIONS: 'Specialfunktioner',
|
||||
WAIT_FIRMWARE: 'Firmware laddas upp och installeras',
|
||||
INSTALL_VERSION: 'Det här kommer installera version {0}. Är du säker?',
|
||||
UPGRADE_AVAILABLE: 'Det finns en tillgänglig firmwareupgradering!',
|
||||
LATEST_VERSION: 'Du använder den senaste firmwareversionen.',
|
||||
PLEASE_WAIT: 'Var god vänta',
|
||||
RESTARTING_PRE: 'Initialiserar',
|
||||
RESTARTING_POST: 'Förbereder',
|
||||
AUTO_SCROLL: 'Autoskrolla',
|
||||
DASHBOARD: 'Kontrollpanel',
|
||||
DEVELOPER_MODE: 'Utvecklarläge',
|
||||
BYTES: 'Bytes', // TODO translate
|
||||
BITMASK: 'Bit Mask',// TODO translate
|
||||
DUPLICATE: 'Dublett',
|
||||
UPGRADE: 'Uppgradera',
|
||||
DASHBOARD_1: 'All EMS entities that are active and marked as Favorite, plus all Custom Entities, Schedules and external Sensors data are displayed below.', // TODO translate
|
||||
NO_DATA_1: 'No favorite EMS entities found yet. Use the', // TODO translate
|
||||
NO_DATA_2: 'module to mark them.', // TODO translate
|
||||
NO_DATA_3: 'To see all available entities go to', // TODO translate
|
||||
THIS_VERSION: 'This Version', // TODO translate
|
||||
PLATFORM: 'Platform', // TODO translate
|
||||
RELEASE_TYPE: 'Release Type', // TODO translate
|
||||
REINSTALL: 'Re-install', // TODO translate
|
||||
INTERNET_CONNECTION_REQUIRED: 'Internetanslutning krävs för automatisk version kontroll och uppdatering',
|
||||
};
|
||||
|
||||
export default sv;
|
||||
|
||||
@@ -187,7 +187,7 @@ const tr: Translation = {
|
||||
COMPACT: 'Sıkışık',
|
||||
DOWNLOAD_SETTINGS_TEXT: 'Create a backup of your configuration and settings', // TODO translate
|
||||
UPLOAD_TEXT: 'Upload a new firmware file (.bin) or a backup file (.json)', // TODO translate
|
||||
UPLOAD_DROP_TEXT: 'Buraya tıklayın yada dosyayı sürükleyip bırakın',
|
||||
UPLOAD_DROP_TEXT: 'Drop a firmware .bin file or click here', // TODO translate
|
||||
ERROR: 'Beklenemedik hata, lütfen tekrar deneyin.',
|
||||
TIME_SET: 'Zaman ayarı',
|
||||
MANAGE_USERS: 'Kullanıcıları yönet',
|
||||
@@ -332,7 +332,6 @@ const tr: Translation = {
|
||||
SPECIAL_FUNCTIONS: 'Special Functions', // TODO translate
|
||||
WAIT_FIRMWARE: 'Firmware is uploading and installing', // TODO translate
|
||||
INSTALL_VERSION: 'This will install version {0}. Are you sure?', // TODO translate
|
||||
SWITCH_DEV: 'switch to the development version', // TODO translate
|
||||
UPGRADE_AVAILABLE: 'There is a firmware upgrade available!', // TODO translate
|
||||
LATEST_VERSION: 'You are using the latest firmware version.', // TODO translate
|
||||
PLEASE_WAIT: 'Please wait', // TODO translate
|
||||
@@ -340,10 +339,20 @@ const tr: Translation = {
|
||||
RESTARTING_POST: 'Preparing', // TODO translate
|
||||
AUTO_SCROLL: 'Auto Scroll', // TODO translate
|
||||
DASHBOARD: 'Dashboard', // TODO translate
|
||||
NO_DATA: 'No data available', // TODO translate
|
||||
DASHBOARD_1: 'Customize your dashboard by marking EMS entities as Favorite using the Customizations module', // TODO translate
|
||||
DEVELOPER_MODE: 'Developer Mode', // TODO translate
|
||||
UPGRADE: 'Upgrade' // TODO translate
|
||||
BYTES: 'Bytes', // TODO translate
|
||||
BITMASK: 'Bit Mask',// TODO translate
|
||||
DUPLICATE: 'Duplicate', // TODO translate
|
||||
UPGRADE: 'Upgrade', // TODO translate
|
||||
DASHBOARD_1: 'All EMS entities that are active and marked as Favorite, plus all Custom Entities, Schedules and external Sensors data are displayed below.', // TODO translate
|
||||
NO_DATA_1: 'No favorite EMS entities found yet. Use the', // TODO translate
|
||||
NO_DATA_2: 'module to mark them.', // TODO translate
|
||||
NO_DATA_3: 'To see all available entities go to', // TODO translate
|
||||
THIS_VERSION: 'This Version', // TODO translate
|
||||
PLATFORM: 'Platform', // TODO translate
|
||||
RELEASE_TYPE: 'Release Type', // TODO translate
|
||||
REINSTALL: 'Re-install', // TODO translate
|
||||
INTERNET_CONNECTION_REQUIRED: 'Internet connection required for automatic version checking and upgrading', // TODO translate
|
||||
};
|
||||
|
||||
export default tr;
|
||||
|
||||
@@ -2,6 +2,15 @@ import type { busConnectionStatus } from 'app/main/types';
|
||||
|
||||
import type { NetworkConnectionStatus } from './network';
|
||||
|
||||
export enum SystemStatusCodes {
|
||||
SYSTEM_STATUS_NORMAL = 0,
|
||||
SYSTEM_STATUS_PENDING_UPLOAD = 1,
|
||||
SYSTEM_STATUS_UPLOADING = 100,
|
||||
SYSTEM_STATUS_ERROR_UPLOAD = 3,
|
||||
SYSTEM_STATUS_PENDING_RESTART = 4,
|
||||
SYSTEM_STATUS_RESTART_REQUESTED = 5
|
||||
}
|
||||
|
||||
export interface SystemStatus {
|
||||
emsesp_version: string;
|
||||
bus_status: busConnectionStatus;
|
||||
@@ -11,6 +20,7 @@ export interface SystemStatus {
|
||||
num_sensors: number;
|
||||
num_analogs: number;
|
||||
ntp_status: number;
|
||||
ntp_time?: string;
|
||||
mqtt_status: boolean;
|
||||
ap_status: boolean;
|
||||
network_status: NetworkConnectionStatus;
|
||||
@@ -40,7 +50,7 @@ export interface SystemStatus {
|
||||
model: string;
|
||||
has_loader: boolean;
|
||||
has_partition: boolean;
|
||||
status: string;
|
||||
status: number; // SystemStatusCodes which matches SYSTEM_STATUS in System.h
|
||||
temperature?: number;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
const DEFAULT_DELAY = 3000;
|
||||
|
||||
// adapted from https://www.joshwcomeau.com/snippets/react-hooks/use-interval/
|
||||
export const useInterval = (callback: () => void, delay: number) => {
|
||||
export const useInterval = (callback: () => void, delay: number = DEFAULT_DELAY) => {
|
||||
const intervalRef = useRef<number | null>(null);
|
||||
const savedCallback = useRef<() => void>(callback);
|
||||
|
||||
@@ -11,14 +13,12 @@ export const useInterval = (callback: () => void, delay: number) => {
|
||||
|
||||
useEffect(() => {
|
||||
const tick = () => savedCallback.current();
|
||||
if (typeof delay === 'number') {
|
||||
intervalRef.current = window.setInterval(tick, delay);
|
||||
return () => {
|
||||
if (intervalRef.current !== null) {
|
||||
window.clearInterval(intervalRef.current);
|
||||
}
|
||||
};
|
||||
}
|
||||
intervalRef.current = window.setInterval(tick, delay);
|
||||
return () => {
|
||||
if (intervalRef.current !== null) {
|
||||
window.clearInterval(intervalRef.current);
|
||||
}
|
||||
};
|
||||
}, [delay]);
|
||||
|
||||
return intervalRef;
|
||||
|
||||
1942
interface/yarn.lock
1942
interface/yarn.lock
File diff suppressed because it is too large
Load Diff
@@ -1,7 +0,0 @@
|
||||
Version 7.2.1
|
||||
|
||||
From https://github.com/bblanchon/ArduinoJson/releases
|
||||
|
||||
MIT License (MIT)
|
||||
|
||||
Copyright © 2014-2024, Benoit BLANCHON
|
||||
@@ -1,165 +0,0 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
||||
@@ -1,14 +0,0 @@
|
||||
# AsyncTCP
|
||||
|
||||

|
||||
A fork of the [AsyncTCP](https://github.com/me-no-dev/AsyncTCP) library by [@me-no-dev](https://github.com/me-no-dev) for [ESPHome](https://esphome.io).
|
||||
|
||||
### Async TCP Library for ESP32 Arduino
|
||||
|
||||
This is a fully asynchronous TCP library, aimed at enabling trouble-free, multi-connection network environment for Espressif's ESP32 MCUs.
|
||||
|
||||
This library is the base for [ESPAsyncWebServer](https://github.com/me-no-dev/ESPAsyncWebServer)
|
||||
|
||||
## AsyncClient and AsyncServer
|
||||
|
||||
The base classes on which everything else is built. They expose all possible scenarios, but are really raw and require more skills to use.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,276 +0,0 @@
|
||||
/*
|
||||
Asynchronous TCP library for Espressif MCUs
|
||||
|
||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
||||
This file is part of the esp8266 core for Arduino environment.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef ASYNCTCP_H_
|
||||
#define ASYNCTCP_H_
|
||||
|
||||
#include "IPAddress.h"
|
||||
#if ESP_IDF_VERSION_MAJOR < 5
|
||||
#include "IPv6Address.h"
|
||||
#endif
|
||||
#include <functional>
|
||||
#include "lwip/ip_addr.h"
|
||||
#include "lwip/ip6_addr.h"
|
||||
|
||||
#ifndef LIBRETINY
|
||||
#include "sdkconfig.h"
|
||||
extern "C" {
|
||||
#include "freertos/semphr.h"
|
||||
#include "lwip/pbuf.h"
|
||||
}
|
||||
#else
|
||||
extern "C" {
|
||||
#include <semphr.h>
|
||||
#include <lwip/pbuf.h>
|
||||
}
|
||||
#define CONFIG_ASYNC_TCP_RUNNING_CORE -1 //any available core
|
||||
#define CONFIG_ASYNC_TCP_USE_WDT 0
|
||||
#endif
|
||||
|
||||
//If core is not defined, then we are running in Arduino or PIO
|
||||
#ifndef CONFIG_ASYNC_TCP_RUNNING_CORE
|
||||
#define CONFIG_ASYNC_TCP_RUNNING_CORE -1 //any available core
|
||||
// Note default was 1 and previously set to 0 for EMS-ESP
|
||||
#define CONFIG_ASYNC_TCP_USE_WDT 1 //if enabled, adds between 33us and 200us per event
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_ASYNC_TCP_TASK_PRIORITY
|
||||
#define CONFIG_ASYNC_TCP_TASK_PRIORITY 5
|
||||
#endif
|
||||
|
||||
// EMS-ESP: stack usage measured: ESP32: ~2.3K, ESP32S3: ~3.5k
|
||||
#ifndef CONFIG_ASYNC_TCP_STACK_SIZE
|
||||
#define CONFIG_ASYNC_TCP_STACK_SIZE 5120
|
||||
#endif
|
||||
|
||||
|
||||
// EMS-ESP: maybe enlarge queue to 64 or 128 see https://github.com/emsesp/EMS-ESP32/issues/177
|
||||
#ifndef CONFIG_ASYNC_TCP_QUEUE
|
||||
#define CONFIG_ASYNC_TCP_QUEUE 32
|
||||
#endif
|
||||
|
||||
class AsyncClient;
|
||||
|
||||
#define ASYNC_MAX_ACK_TIME 5000
|
||||
#define ASYNC_WRITE_FLAG_COPY 0x01 //will allocate new buffer to hold the data while sending (else will hold reference to the data given)
|
||||
#define ASYNC_WRITE_FLAG_MORE 0x02 //will not send PSH flag, meaning that there should be more data to be sent before the application should react.
|
||||
|
||||
typedef std::function<void(void *, AsyncClient *)> AcConnectHandler;
|
||||
typedef std::function<void(void *, AsyncClient *, size_t len, uint32_t time)> AcAckHandler;
|
||||
typedef std::function<void(void *, AsyncClient *, int8_t error)> AcErrorHandler;
|
||||
typedef std::function<void(void *, AsyncClient *, void * data, size_t len)> AcDataHandler;
|
||||
typedef std::function<void(void *, AsyncClient *, struct pbuf * pb)> AcPacketHandler;
|
||||
typedef std::function<void(void *, AsyncClient *, uint32_t time)> AcTimeoutHandler;
|
||||
|
||||
struct tcp_pcb;
|
||||
struct ip_addr;
|
||||
|
||||
class AsyncClient {
|
||||
public:
|
||||
AsyncClient(tcp_pcb * pcb = 0);
|
||||
~AsyncClient();
|
||||
|
||||
AsyncClient & operator=(const AsyncClient & other);
|
||||
AsyncClient & operator+=(const AsyncClient & other);
|
||||
|
||||
bool operator==(const AsyncClient & other);
|
||||
|
||||
bool operator!=(const AsyncClient & other) {
|
||||
return !(*this == other);
|
||||
}
|
||||
bool connect(IPAddress ip, uint16_t port);
|
||||
#if ESP_IDF_VERSION_MAJOR < 5
|
||||
bool connect(IPv6Address ip, uint16_t port);
|
||||
#endif
|
||||
bool connect(const char * host, uint16_t port);
|
||||
void close(bool now = false);
|
||||
void stop();
|
||||
int8_t abort();
|
||||
bool free();
|
||||
|
||||
bool canSend(); //ack is not pending
|
||||
size_t space(); //space available in the TCP window
|
||||
size_t add(const char * data, size_t size, uint8_t apiflags = ASYNC_WRITE_FLAG_COPY); //add for sending
|
||||
bool send(); //send all data added with the method above
|
||||
|
||||
//write equals add()+send()
|
||||
size_t write(const char * data);
|
||||
size_t write(const char * data, size_t size, uint8_t apiflags = ASYNC_WRITE_FLAG_COPY); //only when canSend() == true
|
||||
|
||||
uint8_t state();
|
||||
bool connecting();
|
||||
bool connected();
|
||||
bool disconnecting();
|
||||
bool disconnected();
|
||||
bool freeable(); //disconnected or disconnecting
|
||||
|
||||
uint16_t getMss();
|
||||
|
||||
uint32_t getRxTimeout();
|
||||
void setRxTimeout(uint32_t timeout); //no RX data timeout for the connection in seconds
|
||||
|
||||
uint32_t getAckTimeout();
|
||||
void setAckTimeout(uint32_t timeout); //no ACK timeout for the last sent packet in milliseconds
|
||||
|
||||
void setNoDelay(bool nodelay);
|
||||
bool getNoDelay();
|
||||
|
||||
uint32_t getRemoteAddress();
|
||||
uint16_t getRemotePort();
|
||||
uint32_t getLocalAddress();
|
||||
uint16_t getLocalPort();
|
||||
#if LWIP_IPV6
|
||||
ip6_addr_t getRemoteAddress6();
|
||||
ip6_addr_t getLocalAddress6();
|
||||
#if ESP_IDF_VERSION_MAJOR < 5
|
||||
IPv6Address remoteIP6();
|
||||
IPv6Address localIP6();
|
||||
#else
|
||||
IPAddress remoteIP6();
|
||||
IPAddress localIP6();
|
||||
#endif
|
||||
#endif
|
||||
|
||||
//compatibility
|
||||
IPAddress remoteIP();
|
||||
uint16_t remotePort();
|
||||
IPAddress localIP();
|
||||
uint16_t localPort();
|
||||
|
||||
void onConnect(AcConnectHandler cb, void * arg = 0); //on successful connect
|
||||
void onDisconnect(AcConnectHandler cb, void * arg = 0); //disconnected
|
||||
void onAck(AcAckHandler cb, void * arg = 0); //ack received
|
||||
void onError(AcErrorHandler cb, void * arg = 0); //unsuccessful connect or error
|
||||
void onData(AcDataHandler cb, void * arg = 0); //data received (called if onPacket is not used)
|
||||
void onPacket(AcPacketHandler cb, void * arg = 0); //data received
|
||||
void onTimeout(AcTimeoutHandler cb, void * arg = 0); //ack timeout
|
||||
void onPoll(AcConnectHandler cb, void * arg = 0); //every 125ms when connected
|
||||
|
||||
void ackPacket(struct pbuf * pb); //ack pbuf from onPacket
|
||||
size_t ack(size_t len); //ack data that you have not acked using the method below
|
||||
void ackLater() {
|
||||
_ack_pcb = false;
|
||||
} //will not ack the current packet. Call from onData
|
||||
|
||||
const char * errorToString(int8_t error);
|
||||
const char * stateToString();
|
||||
|
||||
//Do not use any of the functions below!
|
||||
static int8_t _s_poll(void * arg, struct tcp_pcb * tpcb);
|
||||
static int8_t _s_recv(void * arg, struct tcp_pcb * tpcb, struct pbuf * pb, int8_t err);
|
||||
static int8_t _s_fin(void * arg, struct tcp_pcb * tpcb, int8_t err);
|
||||
static int8_t _s_lwip_fin(void * arg, struct tcp_pcb * tpcb, int8_t err);
|
||||
static void _s_error(void * arg, int8_t err);
|
||||
static int8_t _s_sent(void * arg, struct tcp_pcb * tpcb, uint16_t len);
|
||||
static int8_t _s_connected(void * arg, void * tpcb, int8_t err);
|
||||
static void _s_dns_found(const char * name, struct ip_addr * ipaddr, void * arg);
|
||||
|
||||
int8_t _recv(tcp_pcb * pcb, pbuf * pb, int8_t err);
|
||||
tcp_pcb * pcb() {
|
||||
return _pcb;
|
||||
}
|
||||
|
||||
protected:
|
||||
bool _connect(ip_addr_t addr, uint16_t port);
|
||||
|
||||
tcp_pcb * _pcb;
|
||||
int8_t _closed_slot;
|
||||
|
||||
AcConnectHandler _connect_cb;
|
||||
void * _connect_cb_arg;
|
||||
AcConnectHandler _discard_cb;
|
||||
void * _discard_cb_arg;
|
||||
AcAckHandler _sent_cb;
|
||||
void * _sent_cb_arg;
|
||||
AcErrorHandler _error_cb;
|
||||
void * _error_cb_arg;
|
||||
AcDataHandler _recv_cb;
|
||||
void * _recv_cb_arg;
|
||||
AcPacketHandler _pb_cb;
|
||||
void * _pb_cb_arg;
|
||||
AcTimeoutHandler _timeout_cb;
|
||||
void * _timeout_cb_arg;
|
||||
AcConnectHandler _poll_cb;
|
||||
void * _poll_cb_arg;
|
||||
|
||||
bool _ack_pcb;
|
||||
uint32_t _tx_last_packet;
|
||||
uint32_t _rx_ack_len;
|
||||
uint32_t _rx_last_packet;
|
||||
uint32_t _rx_timeout;
|
||||
uint32_t _rx_last_ack;
|
||||
uint32_t _ack_timeout;
|
||||
uint16_t _connect_port;
|
||||
|
||||
int8_t _close();
|
||||
void _free_closed_slot();
|
||||
void _allocate_closed_slot();
|
||||
int8_t _connected(void * pcb, int8_t err);
|
||||
void _error(int8_t err);
|
||||
int8_t _poll(tcp_pcb * pcb);
|
||||
int8_t _sent(tcp_pcb * pcb, uint16_t len);
|
||||
int8_t _fin(tcp_pcb * pcb, int8_t err);
|
||||
int8_t _lwip_fin(tcp_pcb * pcb, int8_t err);
|
||||
void _dns_found(struct ip_addr * ipaddr);
|
||||
|
||||
public:
|
||||
AsyncClient * prev;
|
||||
AsyncClient * next;
|
||||
};
|
||||
|
||||
class AsyncServer {
|
||||
public:
|
||||
AsyncServer(IPAddress addr, uint16_t port);
|
||||
#if ESP_IDF_VERSION_MAJOR < 5
|
||||
AsyncServer(IPv6Address addr, uint16_t port);
|
||||
#endif
|
||||
AsyncServer(uint16_t port);
|
||||
~AsyncServer();
|
||||
void onClient(AcConnectHandler cb, void * arg);
|
||||
void begin();
|
||||
void end();
|
||||
void setNoDelay(bool nodelay);
|
||||
bool getNoDelay();
|
||||
uint8_t status();
|
||||
|
||||
//Do not use any of the functions below!
|
||||
static int8_t _s_accept(void * arg, tcp_pcb * newpcb, int8_t err);
|
||||
static int8_t _s_accepted(void * arg, AsyncClient * client);
|
||||
|
||||
protected:
|
||||
uint16_t _port;
|
||||
bool _bind4 = false;
|
||||
bool _bind6 = false;
|
||||
IPAddress _addr;
|
||||
#if ESP_IDF_VERSION_MAJOR < 5
|
||||
IPv6Address _addr6;
|
||||
#endif
|
||||
bool _noDelay;
|
||||
tcp_pcb * _pcb;
|
||||
AcConnectHandler _connect_cb;
|
||||
void * _connect_cb_arg;
|
||||
|
||||
int8_t _accept(tcp_pcb * newpcb, int8_t err);
|
||||
int8_t _accepted(AsyncClient * client);
|
||||
};
|
||||
|
||||
|
||||
#endif /* ASYNCTCP_H_ */
|
||||
@@ -1,165 +0,0 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
||||
@@ -1,75 +0,0 @@
|
||||
# ESP Async WebServer
|
||||
|
||||
[](https://opensource.org/license/lgpl-3-0/)
|
||||
[](https://github.com/mathieucarbou/ESPAsyncWebServer/actions/workflows/ci.yml)
|
||||
[](https://registry.platformio.org/libraries/mathieucarbou/ESP%20Async%20WebServer)
|
||||
|
||||
Async Web Server for ESP31B
|
||||
|
||||
This is using <https://github.com/mathieucarbou/ESPAsyncWebServer>
|
||||
|
||||
This fork is based on <https://github.com/yubox-node-org/ESPAsyncWebServer> and includes all the concurrency fixes.
|
||||
|
||||
## Changes
|
||||
|
||||
- SPIFFSEditor is removed
|
||||
- Arduino Json 7 compatibility
|
||||
- Deployed in PlatformIO registry and Arduino IDE library manager
|
||||
- CI
|
||||
- Only supports ESP32
|
||||
- Resurrected `AsyncWebSocketMessageBuffer` and `makeBuffer()` in order to make the fork API-compatible with the original library from me-no-dev regarding WebSocket.
|
||||
|
||||
## Documentation
|
||||
|
||||
Usage and API stays the same as the original library.
|
||||
Please look at the original libraries for more examples and documentation.
|
||||
|
||||
[https://github.com/yubox-node-org/ESPAsyncWebServer](https://github.com/yubox-node-org/ESPAsyncWebServer)
|
||||
|
||||
## `AsyncWebSocketMessageBuffer` and `makeBuffer()`
|
||||
|
||||
The fork from `yubox-node-org` introduces some breaking API changes compared to the original library, especially regarding the use of `std::shared_ptr<std::vector<uint8_t>>` for WebSocket.
|
||||
|
||||
This fork is compatible with the original library from `me-no-dev` regarding WebSocket, and wraps the optimizations done by `yubox-node-org` in the `AsyncWebSocketMessageBuffer` class.
|
||||
So you have the choice of which API to use.
|
||||
I strongly suggest to use the optimized API from `yubox-node-org` as it is much more efficient.
|
||||
|
||||
Here is an example for serializing a Json document in a websocket message buffer. This code is compatible with any forks, but not optimized:
|
||||
|
||||
```cpp
|
||||
void send(JsonDocument& doc) {
|
||||
const size_t len = measureJson(doc);
|
||||
|
||||
// original API from me-no-dev
|
||||
AsyncWebSocketMessageBuffer* buffer = _ws->makeBuffer(len);
|
||||
assert(buffer); // up to you to keep or remove this
|
||||
serializeJson(doc, buffer->get(), len);
|
||||
_ws->textAll(buffer);
|
||||
}
|
||||
```
|
||||
|
||||
Here is an example for serializing a Json document in a more optimized way, and compatible with both forks:
|
||||
|
||||
```cpp
|
||||
void send(JsonDocument& doc) {
|
||||
const size_t len = measureJson(doc);
|
||||
|
||||
#if defined(ASYNCWEBSERVER_FORK_mathieucarbou)
|
||||
|
||||
// this fork (originally from yubox-node-org), uses another API with shared pointer that better support concurrent use cases then the original project
|
||||
auto buffer = std::make_shared<std::vector<uint8_t>>(len);
|
||||
assert(buffer); // up to you to keep or remove this
|
||||
serializeJson(doc, buffer->data(), len);
|
||||
_ws->textAll(std::move(buffer));
|
||||
|
||||
#else
|
||||
|
||||
// original API from me-no-dev
|
||||
AsyncWebSocketMessageBuffer* buffer = _ws->makeBuffer(len);
|
||||
assert(buffer); // up to you to keep or remove this
|
||||
serializeJson(doc, buffer->get(), len);
|
||||
_ws->textAll(buffer);
|
||||
|
||||
#endif
|
||||
}
|
||||
```
|
||||
@@ -1,405 +0,0 @@
|
||||
/*
|
||||
Asynchronous WebServer library for Espressif MCUs
|
||||
|
||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
#include "Arduino.h"
|
||||
#include "AsyncEventSource.h"
|
||||
|
||||
static String generateEventMessage(const char *message, const char *event, uint32_t id, uint32_t reconnect){
|
||||
String ev = "";
|
||||
|
||||
if(reconnect){
|
||||
ev += "retry: ";
|
||||
ev += String(reconnect);
|
||||
ev += "\r\n";
|
||||
}
|
||||
|
||||
if(id){
|
||||
ev += "id: ";
|
||||
ev += String(id);
|
||||
ev += "\r\n";
|
||||
}
|
||||
|
||||
if(event != NULL){
|
||||
ev += "event: ";
|
||||
ev += String(event);
|
||||
ev += "\r\n";
|
||||
}
|
||||
|
||||
if(message != NULL){
|
||||
size_t messageLen = strlen(message);
|
||||
char * lineStart = (char *)message;
|
||||
char * lineEnd;
|
||||
do {
|
||||
char * nextN = strchr(lineStart, '\n');
|
||||
char * nextR = strchr(lineStart, '\r');
|
||||
if(nextN == NULL && nextR == NULL){
|
||||
size_t llen = ((char *)message + messageLen) - lineStart;
|
||||
char * ldata = (char *)malloc(llen+1);
|
||||
if(ldata != NULL){
|
||||
memcpy(ldata, lineStart, llen);
|
||||
ldata[llen] = 0;
|
||||
ev += "data: ";
|
||||
ev += ldata;
|
||||
ev += "\r\n\r\n";
|
||||
free(ldata);
|
||||
}
|
||||
lineStart = (char *)message + messageLen;
|
||||
} else {
|
||||
char * nextLine = NULL;
|
||||
if(nextN != NULL && nextR != NULL){
|
||||
if(nextR < nextN){
|
||||
lineEnd = nextR;
|
||||
if(nextN == (nextR + 1))
|
||||
nextLine = nextN + 1;
|
||||
else
|
||||
nextLine = nextR + 1;
|
||||
} else {
|
||||
lineEnd = nextN;
|
||||
if(nextR == (nextN + 1))
|
||||
nextLine = nextR + 1;
|
||||
else
|
||||
nextLine = nextN + 1;
|
||||
}
|
||||
} else if(nextN != NULL){
|
||||
lineEnd = nextN;
|
||||
nextLine = nextN + 1;
|
||||
} else {
|
||||
lineEnd = nextR;
|
||||
nextLine = nextR + 1;
|
||||
}
|
||||
|
||||
size_t llen = lineEnd - lineStart;
|
||||
char * ldata = (char *)malloc(llen+1);
|
||||
if(ldata != NULL){
|
||||
memcpy(ldata, lineStart, llen);
|
||||
ldata[llen] = 0;
|
||||
ev += "data: ";
|
||||
ev += ldata;
|
||||
ev += "\r\n";
|
||||
free(ldata);
|
||||
}
|
||||
lineStart = nextLine;
|
||||
if(lineStart == ((char *)message + messageLen))
|
||||
ev += "\r\n";
|
||||
}
|
||||
} while(lineStart < ((char *)message + messageLen));
|
||||
}
|
||||
|
||||
return ev;
|
||||
}
|
||||
|
||||
// Message
|
||||
|
||||
AsyncEventSourceMessage::AsyncEventSourceMessage(const char * data, size_t len)
|
||||
: _data(nullptr), _len(len), _sent(0), _acked(0)
|
||||
{
|
||||
_data = (uint8_t*)malloc(_len+1);
|
||||
if(_data == nullptr){
|
||||
_len = 0;
|
||||
} else {
|
||||
memcpy(_data, data, len);
|
||||
_data[_len] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
AsyncEventSourceMessage::~AsyncEventSourceMessage() {
|
||||
if(_data != NULL)
|
||||
free(_data);
|
||||
}
|
||||
|
||||
size_t AsyncEventSourceMessage::ack(size_t len) {
|
||||
// If the whole message is now acked...
|
||||
if(_acked + len > _len){
|
||||
// Return the number of extra bytes acked (they will be carried on to the next message)
|
||||
const size_t extra = _acked + len - _len;
|
||||
_acked = _len;
|
||||
return extra;
|
||||
}
|
||||
// Return that no extra bytes left.
|
||||
_acked += len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t AsyncEventSourceMessage::write_buffer(AsyncClient *client) {
|
||||
if (!client->canSend())
|
||||
return 0;
|
||||
const size_t len = _len - _sent;
|
||||
if(client->space() < len){
|
||||
return 0;
|
||||
}
|
||||
size_t sent = client->add((const char *)_data, len);
|
||||
_sent += sent;
|
||||
return sent;
|
||||
}
|
||||
|
||||
size_t AsyncEventSourceMessage::send(AsyncClient *client) {
|
||||
size_t sent = write_buffer(client);
|
||||
client->send();
|
||||
return sent;
|
||||
}
|
||||
|
||||
// Client
|
||||
|
||||
AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server)
|
||||
: _messageQueue(LinkedList<AsyncEventSourceMessage *>([](AsyncEventSourceMessage *m){ delete m; }))
|
||||
{
|
||||
_client = request->client();
|
||||
_server = server;
|
||||
_lastId = 0;
|
||||
if(request->hasHeader("Last-Event-ID"))
|
||||
_lastId = atoi(request->getHeader("Last-Event-ID")->value().c_str());
|
||||
|
||||
_client->setRxTimeout(0);
|
||||
_client->onError(NULL, NULL);
|
||||
_client->onAck([](void *r, AsyncClient* c, size_t len, uint32_t time){ (void)c; ((AsyncEventSourceClient*)(r))->_onAck(len, time); }, this);
|
||||
_client->onPoll([](void *r, AsyncClient* c){ (void)c; ((AsyncEventSourceClient*)(r))->_onPoll(); }, this);
|
||||
_client->onData(NULL, NULL);
|
||||
_client->onTimeout([this](void *r, AsyncClient* c __attribute__((unused)), uint32_t time){ ((AsyncEventSourceClient*)(r))->_onTimeout(time); }, this);
|
||||
_client->onDisconnect([this](void *r, AsyncClient* c){ ((AsyncEventSourceClient*)(r))->_onDisconnect(); delete c; }, this);
|
||||
|
||||
_server->_addClient(this);
|
||||
delete request;
|
||||
|
||||
_client->setNoDelay(true);
|
||||
}
|
||||
|
||||
AsyncEventSourceClient::~AsyncEventSourceClient(){
|
||||
_messageQueue.free();
|
||||
close();
|
||||
}
|
||||
|
||||
void AsyncEventSourceClient::_queueMessage(AsyncEventSourceMessage *dataMessage){
|
||||
if(dataMessage == NULL)
|
||||
return;
|
||||
if(!connected()){
|
||||
delete dataMessage;
|
||||
return;
|
||||
}
|
||||
|
||||
if(_messageQueue.length() >= SSE_MAX_QUEUED_MESSAGES){
|
||||
ets_printf("AsyncEventSourceClient: ERROR: Queue is full, communications too slow, dropping event");
|
||||
delete dataMessage;
|
||||
} else {
|
||||
_messageQueue.add(dataMessage);
|
||||
}
|
||||
if(_client->canSend())
|
||||
_runQueue();
|
||||
}
|
||||
|
||||
void AsyncEventSourceClient::_onAck(size_t len, uint32_t time){
|
||||
_runQueue();
|
||||
}
|
||||
|
||||
void AsyncEventSourceClient::_onPoll(){
|
||||
if(!_messageQueue.isEmpty()){
|
||||
_runQueue();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void AsyncEventSourceClient::_onTimeout(uint32_t time __attribute__((unused))){
|
||||
_client->close(true);
|
||||
}
|
||||
|
||||
void AsyncEventSourceClient::_onDisconnect(){
|
||||
_client = NULL;
|
||||
_server->_handleDisconnect(this);
|
||||
}
|
||||
|
||||
void AsyncEventSourceClient::close(){
|
||||
if(_client != NULL)
|
||||
_client->close();
|
||||
}
|
||||
|
||||
void AsyncEventSourceClient::write(const char * message, size_t len){
|
||||
_queueMessage(new AsyncEventSourceMessage(message, len));
|
||||
}
|
||||
|
||||
void AsyncEventSourceClient::send(const char *message, const char *event, uint32_t id, uint32_t reconnect){
|
||||
String ev = generateEventMessage(message, event, id, reconnect);
|
||||
_queueMessage(new AsyncEventSourceMessage(ev.c_str(), ev.length()));
|
||||
}
|
||||
|
||||
void AsyncEventSourceClient::_runQueue(){
|
||||
#if defined(ESP32)
|
||||
if(!this->_messageQueue_mutex.try_lock()) {
|
||||
return;
|
||||
}
|
||||
#else
|
||||
if(this->_messageQueue_processing){
|
||||
return;
|
||||
}
|
||||
this->_messageQueue_processing = true;
|
||||
#endif // ESP32
|
||||
|
||||
size_t total_bytes_written = 0;
|
||||
for(auto i = _messageQueue.begin(); i != _messageQueue.end(); ++i)
|
||||
{
|
||||
if(!(*i)->sent()) {
|
||||
size_t bytes_written = (*i)->write_buffer(_client);
|
||||
total_bytes_written += bytes_written;
|
||||
if(bytes_written == 0)
|
||||
break;
|
||||
// todo: there is a further optimization to write a partial event to squeeze the last few bytes into the outgoing tcp send buffer, in
|
||||
// fact all of this code is already set up to do so, it's only write_buffer that needs to be updated to allow it instead of
|
||||
// returning zero when the full event won't fit into what's left of the buffer
|
||||
// todo: windows is taking 40-50ms to send an ack back while it waits for more data which won't come since this code must wait for ack first
|
||||
// due to system resource limitations - if the dashboard javascript just sends a single byte back per event received (which this
|
||||
// code would of course throw away as meaningless) then windows (or whatever other host runs the webbrower) will piggyback an ack
|
||||
// onto that outgoing packet for us, reducing roundtrip ack latency and potentially as much as trippling throughput again
|
||||
// (measured: ESP-01: 20ms to send another packet after ack received, windows: 40-50ms to ack after receiving a packet)
|
||||
}
|
||||
}
|
||||
if(total_bytes_written > 0)
|
||||
_client->send();
|
||||
|
||||
size_t len = total_bytes_written;
|
||||
while(len && !_messageQueue.isEmpty()){
|
||||
len = _messageQueue.front()->ack(len);
|
||||
if(_messageQueue.front()->finished()){
|
||||
_messageQueue.remove(_messageQueue.front());
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(ESP32)
|
||||
this->_messageQueue_mutex.unlock();
|
||||
#else
|
||||
this->_messageQueue_processing = false;
|
||||
#endif // ESP32
|
||||
}
|
||||
|
||||
|
||||
// Handler
|
||||
|
||||
AsyncEventSource::AsyncEventSource(const String& url)
|
||||
: _url(url)
|
||||
, _clients(LinkedList<AsyncEventSourceClient *>([](AsyncEventSourceClient *c){ delete c; }))
|
||||
, _connectcb(NULL)
|
||||
{}
|
||||
|
||||
AsyncEventSource::~AsyncEventSource(){
|
||||
close();
|
||||
}
|
||||
|
||||
void AsyncEventSource::onConnect(ArEventHandlerFunction cb){
|
||||
_connectcb = cb;
|
||||
}
|
||||
|
||||
void AsyncEventSource::_addClient(AsyncEventSourceClient * client){
|
||||
/*char * temp = (char *)malloc(2054);
|
||||
if(temp != NULL){
|
||||
memset(temp+1,' ',2048);
|
||||
temp[0] = ':';
|
||||
temp[2049] = '\r';
|
||||
temp[2050] = '\n';
|
||||
temp[2051] = '\r';
|
||||
temp[2052] = '\n';
|
||||
temp[2053] = 0;
|
||||
client->write((const char *)temp, 2053);
|
||||
free(temp);
|
||||
}*/
|
||||
|
||||
_clients.add(client);
|
||||
if(_connectcb)
|
||||
_connectcb(client);
|
||||
}
|
||||
|
||||
void AsyncEventSource::_handleDisconnect(AsyncEventSourceClient * client){
|
||||
_clients.remove(client);
|
||||
}
|
||||
|
||||
void AsyncEventSource::close(){
|
||||
for(const auto &c: _clients){
|
||||
if(c->connected())
|
||||
c->close();
|
||||
}
|
||||
}
|
||||
|
||||
// pmb fix
|
||||
size_t AsyncEventSource::avgPacketsWaiting() const {
|
||||
if(_clients.isEmpty())
|
||||
return 0;
|
||||
|
||||
size_t aql=0;
|
||||
uint32_t nConnectedClients=0;
|
||||
|
||||
for(const auto &c: _clients){
|
||||
if(c->connected()) {
|
||||
aql+=c->packetsWaiting();
|
||||
++nConnectedClients;
|
||||
}
|
||||
}
|
||||
// return aql / nConnectedClients;
|
||||
return ((aql) + (nConnectedClients/2))/(nConnectedClients); // round up
|
||||
}
|
||||
|
||||
void AsyncEventSource::send(const char *message, const char *event, uint32_t id, uint32_t reconnect){
|
||||
|
||||
|
||||
String ev = generateEventMessage(message, event, id, reconnect);
|
||||
for(const auto &c: _clients){
|
||||
if(c->connected()) {
|
||||
c->write(ev.c_str(), ev.length());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t AsyncEventSource::count() const {
|
||||
return _clients.count_if([](AsyncEventSourceClient *c){
|
||||
return c->connected();
|
||||
});
|
||||
}
|
||||
|
||||
bool AsyncEventSource::canHandle(AsyncWebServerRequest *request){
|
||||
if(request->method() != HTTP_GET || !request->url().equals(_url)) {
|
||||
return false;
|
||||
}
|
||||
request->addInterestingHeader("Last-Event-ID");
|
||||
return true;
|
||||
}
|
||||
|
||||
void AsyncEventSource::handleRequest(AsyncWebServerRequest *request){
|
||||
if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str()))
|
||||
return request->requestAuthentication();
|
||||
request->send(new AsyncEventSourceResponse(this));
|
||||
}
|
||||
|
||||
// Response
|
||||
|
||||
AsyncEventSourceResponse::AsyncEventSourceResponse(AsyncEventSource *server){
|
||||
_server = server;
|
||||
_code = 200;
|
||||
_contentType = "text/event-stream";
|
||||
_sendContentLength = false;
|
||||
addHeader("Cache-Control", "no-cache");
|
||||
addHeader("Connection","keep-alive");
|
||||
}
|
||||
|
||||
void AsyncEventSourceResponse::_respond(AsyncWebServerRequest *request){
|
||||
String out = _assembleHead(request->version());
|
||||
request->client()->write(out.c_str(), _headLength);
|
||||
_state = RESPONSE_WAIT_ACK;
|
||||
}
|
||||
|
||||
size_t AsyncEventSourceResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time __attribute__((unused))){
|
||||
if(len){
|
||||
new AsyncEventSourceClient(request, _server);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
/*
|
||||
Asynchronous WebServer library for Espressif MCUs
|
||||
|
||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
#ifndef ASYNCEVENTSOURCE_H_
|
||||
#define ASYNCEVENTSOURCE_H_
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <Arduino.h>
|
||||
#if defined(ESP32) || defined(LIBRETINY)
|
||||
#include <AsyncTCP.h>
|
||||
#else
|
||||
#include <ESPAsyncTCP.h>
|
||||
#endif
|
||||
|
||||
#if defined(ESP32)
|
||||
#include <mutex>
|
||||
#endif // ESP32
|
||||
|
||||
#ifndef SSE_MAX_QUEUED_MESSAGES
|
||||
#define SSE_MAX_QUEUED_MESSAGES 32
|
||||
#endif
|
||||
|
||||
#include <ESPAsyncWebServer.h>
|
||||
|
||||
#include "AsyncWebSynchronization.h"
|
||||
|
||||
#ifdef ESP8266
|
||||
#include <Hash.h>
|
||||
#ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library
|
||||
#include <../src/Hash.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(ESP32) || defined(LIBRETINY)
|
||||
#define DEFAULT_MAX_SSE_CLIENTS 8
|
||||
#else
|
||||
#define DEFAULT_MAX_SSE_CLIENTS 4
|
||||
#endif
|
||||
|
||||
class AsyncEventSource;
|
||||
class AsyncEventSourceResponse;
|
||||
class AsyncEventSourceClient;
|
||||
typedef std::function<void(AsyncEventSourceClient *client)> ArEventHandlerFunction;
|
||||
|
||||
class AsyncEventSourceMessage {
|
||||
private:
|
||||
uint8_t * _data;
|
||||
size_t _len;
|
||||
size_t _sent;
|
||||
//size_t _ack;
|
||||
size_t _acked;
|
||||
public:
|
||||
AsyncEventSourceMessage(const char * data, size_t len);
|
||||
~AsyncEventSourceMessage();
|
||||
size_t ack(size_t len);
|
||||
size_t write_buffer(AsyncClient *client);
|
||||
size_t send(AsyncClient *client);
|
||||
bool finished(){ return _acked == _len; }
|
||||
bool sent() { return _sent == _len; }
|
||||
};
|
||||
|
||||
class AsyncEventSourceClient {
|
||||
private:
|
||||
AsyncClient *_client;
|
||||
AsyncEventSource *_server;
|
||||
uint32_t _lastId;
|
||||
#if defined(ESP32)
|
||||
std::mutex _messageQueue_mutex;
|
||||
#else
|
||||
bool _messageQueue_processing{false};
|
||||
#endif // ESP32
|
||||
LinkedList<AsyncEventSourceMessage *> _messageQueue;
|
||||
void _queueMessage(AsyncEventSourceMessage *dataMessage);
|
||||
void _runQueue();
|
||||
|
||||
public:
|
||||
|
||||
AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server);
|
||||
~AsyncEventSourceClient();
|
||||
|
||||
AsyncClient* client(){ return _client; }
|
||||
void close();
|
||||
void write(const char * message, size_t len);
|
||||
void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0);
|
||||
bool connected() const { return (_client != NULL) && _client->connected(); }
|
||||
uint32_t lastId() const { return _lastId; }
|
||||
size_t packetsWaiting() const { return _messageQueue.length(); }
|
||||
|
||||
//system callbacks (do not call)
|
||||
void _onAck(size_t len, uint32_t time);
|
||||
void _onPoll();
|
||||
void _onTimeout(uint32_t time);
|
||||
void _onDisconnect();
|
||||
};
|
||||
|
||||
class AsyncEventSource: public AsyncWebHandler {
|
||||
private:
|
||||
String _url;
|
||||
LinkedList<AsyncEventSourceClient *> _clients;
|
||||
ArEventHandlerFunction _connectcb;
|
||||
public:
|
||||
AsyncEventSource(const String& url);
|
||||
~AsyncEventSource();
|
||||
|
||||
const char * url() const { return _url.c_str(); }
|
||||
void close();
|
||||
void onConnect(ArEventHandlerFunction cb);
|
||||
void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0);
|
||||
size_t count() const; //number clinets connected
|
||||
size_t avgPacketsWaiting() const;
|
||||
|
||||
//system callbacks (do not call)
|
||||
void _addClient(AsyncEventSourceClient * client);
|
||||
void _handleDisconnect(AsyncEventSourceClient * client);
|
||||
virtual bool canHandle(AsyncWebServerRequest *request) override final;
|
||||
virtual void handleRequest(AsyncWebServerRequest *request) override final;
|
||||
};
|
||||
|
||||
class AsyncEventSourceResponse: public AsyncWebServerResponse {
|
||||
private:
|
||||
String _content;
|
||||
AsyncEventSource *_server;
|
||||
public:
|
||||
AsyncEventSourceResponse(AsyncEventSource *server);
|
||||
void _respond(AsyncWebServerRequest *request);
|
||||
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time);
|
||||
bool _sourceValid() const { return true; }
|
||||
};
|
||||
|
||||
|
||||
#endif /* ASYNCEVENTSOURCE_H_ */
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user